How to create a wave path Swift
No, there isn't a built in method to build a path from a function, but you can easily write one of your own. In Swift 3:
/// Build path within rectangle
///
/// Given a `function` that converts values between zero and one to another values between zero and one, this method will create `UIBezierPath` within `rect` using that `function`.
///
/// - parameter rect: The `CGRect` of points on the screen.
///
/// - parameter count: How many points should be rendered. Defaults to `rect.size.width`.
///
/// - parameter function: A closure that will be passed an floating point number between zero and one and should return a return value between zero and one as well.
private func path(in rect: CGRect, count: Int? = nil, function: (CGFloat) -> (CGFloat)) -> UIBezierPath {
let numberOfPoints = count ?? Int(rect.size.width)
let path = UIBezierPath()
path.move(to: convert(point: CGPoint(x: 0, y: function(0)), in: rect))
for i in 1 ..< numberOfPoints {
let x = CGFloat(i) / CGFloat(numberOfPoints - 1)
path.addLine(to: convert(point: CGPoint(x: x, y: function(x)), in: rect))
}
return path
}
/// Convert point with x and y values between 0 and 1 within the `CGRect`.
///
/// - parameter point: A `CGPoint` value with x and y values between 0 and 1.
/// - parameter rect: The `CGRect` within which that point should be converted.
private func convert(point: CGPoint, in rect: CGRect) -> CGPoint {
return CGPoint(
x: rect.origin.x + point.x * rect.size.width,
y: rect.origin.y + rect.size.height - point.y * rect.size.height
)
}
So, let's pass a function that does one sine curve as it progresses across the width
of the of the rect
:
func sinePath(in rect: CGRect, count: Int? = nil) -> UIBezierPath {
// note, since sine returns values between -1 and 1, let's add 1 and divide by two to get it between 0 and 1
return path(in: rect, count: count) { (sin($0 * .pi * 2.0) + 1.0) / 2.0 }
}
Note, the above assumed that you wanted traverse from left to right, building the path defined by the function. You could also do more of a parametric rendition:
/// Build path within rectangle
///
/// Given a `function` that converts values between zero and one to another values between zero and one, this method will create `UIBezierPath` within `rect` using that `function`.
///
/// - parameter rect: The `CGRect` of points on the screen.
///
/// - parameter count: How many points should be rendered. Defaults to `rect.size.width` or `rect.size.width`, whichever is larger.
///
/// - parameter function: A closure that will be passed an floating point number between zero and one and should return a `CGPoint` with `x` and `y` values between 0 and 1.
private func parametricPath(in rect: CGRect, count: Int? = nil, function: (CGFloat) -> (CGPoint)) -> UIBezierPath {
let numberOfPoints = count ?? max(Int(rect.size.width), Int(rect.size.height))
let path = UIBezierPath()
let result = function(0)
path.move(to: convert(point: CGPoint(x: result.x, y: result.y), in: rect))
for i in 1 ..< numberOfPoints {
let t = CGFloat(i) / CGFloat(numberOfPoints - 1)
let result = function(t)
path.addLine(to: convert(point: CGPoint(x: result.x, y: result.y), in: rect))
}
return path
}
Then you can modify the x
coordinate using sine curve, and just increment y
:
func verticalSinePath(in rect: CGRect, count: Int? = nil) -> UIBezierPath {
// note, since sine returns values between -1 and 1, let's add 1 and divide by two to get it between 0 and 1
return parametricPath(in: rect, count: count) { CGPoint(
x: (sin($0 * .pi * 2.0) + 1.0) / 2.0,
y: $0
) }
}
The virtue of this is that you could also now define any sort of path you want, e.g. a spiral:
func spiralPath(in rect: CGRect, count: Int? = nil) -> UIBezierPath {
return parametricPath(in: rect, count: count) { t in
let r = 1.0 - sin(t * .pi / 2.0)
return CGPoint(
x: (r * sin(t * 10.0 * .pi * 2.0) + 1.0) / 2.0,
y: (r * cos(t * 10.0 * .pi * 2.0) + 1.0) / 2.0
)
}
}
Here are the Swift 2 renditions of the above:
/// Build path within rectangle
///
/// Given a `function` that converts values between zero and one to another values between zero and one, this method will create `UIBezierPath` within `rect` using that `function`.
///
/// - parameter rect: The `CGRect` of points on the screen.
///
/// - parameter count: How many points should be rendered. Defaults to `rect.size.width`.
///
/// - parameter function: A closure that will be passed an floating point number between zero and one and should return a return value between zero and one as well.
private func path(in rect: CGRect, count: Int? = nil, function: (CGFloat) -> (CGFloat)) -> UIBezierPath {
let numberOfPoints = count ?? Int(rect.size.width)
let path = UIBezierPath()
path.moveToPoint(convert(point: CGPoint(x: 0, y: function(0)), rect: rect))
for i in 1 ..< numberOfPoints {
let x = CGFloat(i) / CGFloat(numberOfPoints - 1)
path.addLineToPoint(convert(point: CGPoint(x: x, y: function(x)), rect: rect))
}
return path
}
/// Convert point with x and y values between 0 and 1 within the `CGRect`.
///
/// - parameter point: A `CGPoint` value with x and y values between 0 and 1.
/// - parameter rect: The `CGRect` within which that point should be converted.
private func convert(point point: CGPoint, rect: CGRect) -> CGPoint {
return CGPoint(
x: rect.origin.x + point.x * rect.size.width,
y: rect.origin.y + rect.size.height - point.y * rect.size.height
)
}
func sinePath(in rect: CGRect, count: Int? = nil) -> UIBezierPath {
// note, since sine returns values between -1 and 1, let's add 1 and divide by two to get it between 0 and 1
return path(in: rect, count: count) { (sin($0 * CGFloat(M_PI * 2.0)) + 1.0) / 2.0 }
}
/// Build path within rectangle
///
/// Given a `function` that converts values between zero and one to another values between zero and one, this method will create `UIBezierPath` within `rect` using that `function`.
///
/// - parameter rect: The `CGRect` of points on the screen.
///
/// - parameter count: How many points should be rendered. Defaults to `rect.size.width`.
///
/// - parameter function: A closure that will be passed an floating point number between zero and one and should return a `CGPoint` with `x` and `y` values between 0 and 1.
private func parametricPath(in rect: CGRect, count: Int? = nil, function: (CGFloat) -> (CGPoint)) -> UIBezierPath {
let numberOfPoints = count ?? max(Int(rect.size.width), Int(rect.size.height))
let path = UIBezierPath()
let result = function(0)
path.moveToPoint(convert(point: CGPoint(x: result.x, y: result.y), rect: rect))
for i in 1 ..< numberOfPoints {
let t = CGFloat(i) / CGFloat(numberOfPoints - 1)
let result = function(t)
path.addLineToPoint(convert(point: CGPoint(x: result.x, y: result.y), rect: rect))
}
return path
}
func verticalSinePath(in rect: CGRect, count: Int? = nil) -> UIBezierPath {
// note, since sine returns values between -1 and 1, let's add 1 and divide by two to get it between 0 and 1
return parametricPath(in: rect, count: count) { CGPoint(
x: (sin($0 * CGFloat(M_PI * 2.0)) + 1.0) / 2.0,
y: $0
) }
}
func spiralPath(in rect: CGRect, count: Int? = nil) -> UIBezierPath {
return parametricPath(in: rect, count: count) { t in
let r = 1.0 - sin(t * CGFloat(M_PI_2))
return CGPoint(
x: (r * sin(t * 10.0 * CGFloat(M_PI * 2.0)) + 1.0) / 2.0,
y: (r * cos(t * 10.0 * CGFloat(M_PI * 2.0)) + 1.0) / 2.0
)
}
}
Adding a small UIView to an SKScene
LearnCocos2D definitely deserves credit for this, so if he ever posts an answer I will accept it. Essentially, I was failing to set the frame for the navigation controller's view. Here is my method for adding the smaller view to the scene:
override func didMoveToView(view: SKView) {
anchorPoint = CGPoint(x: 0.5, y: 0.5)
self.name = "Bugs Scene"
backgroundColor = bugsColor
let smallerRect = CGRectMake(400, 24, 600, 720)
let navRect = CGRectMake(0, 0, 600, 720)
let bugsTableView: BugsTVC = BugsTVC()
nav = UINavigationController(rootViewController: bugsTableView)
nav.view.frame = navRect
smallerView = UIView(frame: smallerRect)
smallerView.backgroundColor = UIColor.clearColor()
bugsTableView.view.frame = smallerRect
smallerView.frame = smallerRect
smallerView.addSubview(nav.view)
self.view.addSubview(smallerView)
}
It could probably be refined some but this is working for me. Now if I can only figure out how to remove the view when I transition to another scene!
Detecting collision on only one side of a rectangular physics body - Swift3
You could achieve that by detecting collisions with your rectangle and then deciding whether the collision was with the side of your interest or not. Here is a discussion about how to do that. Good luck!
iOS: How to animate an image along a sine curve?
To animate along a path, you first need to define that path. If you really need a true sine curve, we can show you how to do that, but it's probably easiest to define something that approximates a sine curve using two quadratic bezier curves:
CGFloat width = ...
CGFloat height = ...
CGPoint startPoint = ...
CGPoint point = startPoint;
CGPoint controlPoint = CGPointMake(point.x + width / 4.0, point.y - height / 4.0);
CGPoint nextPoint = CGPointMake(point.x + width / 2.0, point.y);
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:point];
[path addQuadCurveToPoint:nextPoint controlPoint:controlPoint];
point = nextPoint;
controlPoint = CGPointMake(point.x + width / 4.0, point.y + height / 4.0);
nextPoint = CGPointMake(point.x + width / 2.0, point.y);
[path addQuadCurveToPoint:nextPoint controlPoint:controlPoint];
That renders path like so:
Obviously, change startPoint
, width
, and height
to be whatever you want. Or repeat that process of adding more bezier paths if you need more iterations.
Anyway, having defined a path, rather than rendering the path itself, you can create a CAKeyframeAnimation
that animates the position
of the UIView
along that path:
CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"position"];
animation.fillMode = kCAFillModeForwards;
animation.removedOnCompletion = NO;
animation.duration = 1.0;
animation.path = path.CGPath;
[view.layer addAnimation:animation forKey:@"myPathAnimation"];
Related Topics
How to Change/Modify The Displayed Title of an Nspopupbutton
Swift Ensembles Set Up & Ubiquitycontaineridentifier
How to Check for Self with Contains() in Swift
How to Get Window Reference (Cgwindow, Nswindow or Windowref) from Cgwindowid in Swift
Allocating Ekeventstore Throws Warnings
Issue with Optional Core Data Relationship Using Nspersistentcloudkitcontainer
Skipnext Skipprevious Google Cast Greyed Out
Why Is a Firestore Listener Returning .Added Twice When a Single Document Is Added
Auto Height Does Not Work in UIview in Swift 3
Difference Between Orientation and Rotation in Scnnode
Swift Spritekit Get Visible Frame Size
Is There an Object Class in Swift
Swiftui Navigationview Starting Inside Itself
Allow Line Editing When Reading Input from The Command Line
How to Resolve Rctpromiseresolveblock After Bftask
Access Environment Variable Inside Global Function - Swiftui + Coredata