Skip to content

Commit a942d70

Browse files
committed
Fixed some access permission issues
- Added convenience inits for Path classes to remove the need to pass in a PathState manually - Added setupPerformanceMode convenience method to Path classes (which call PathState's method internally)
1 parent c880a70 commit a942d70

15 files changed

+175
-100
lines changed

Examples/MotionExamples/Classes/PathMotionContiguousViewController.swift

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ class PathMotionContiguousViewController: UIViewController, ButtonsViewDelegate
2626
return view
2727
}()
2828

29+
lazy var label: UILabel = {
30+
let label = UILabel()
31+
label.font = .systemFont(ofSize: 12.0)
32+
label.isUserInteractionEnabled = false
33+
label.text = "contiguousEdges edge behavior allows motions to seamlessly travel beyond one edge of the path to the other."
34+
label.numberOfLines = 4
35+
return label
36+
}()
2937

3038
lazy var pathView: PathView = {
3139
return PathView()
@@ -64,11 +72,9 @@ class PathMotionContiguousViewController: UIViewController, ButtonsViewDelegate
6472
let rect: CGRect = CGRect(x: 0, y: 0, width: 320, height: 320).insetBy(dx: lineWidth, dy: lineWidth)
6573
let radius: CGFloat = rect.width * 0.25
6674
let rectPath = UIBezierPath(roundedRect: rect, cornerRadius: radius)
67-
let pathState = PathState(path: rectPath.cgPath)
68-
self.pathState = pathState
6975
pathView.path = rectPath
7076

71-
motion = PathMotion(path: pathState, duration: 2.5, endPosition: 1.0, easing: EasingElastic.easeInOut(), edgeBehavior: .contiguousEdges)
77+
motion = PathMotion(path: rectPath.cgPath, duration: 2, endPosition: 1.0, easing: EasingElastic.easeInOut(), edgeBehavior: .contiguousEdges)
7278
.repeats()
7379
.reverses(withEasing: EasingBack.easeInOut())
7480

@@ -97,7 +103,13 @@ class PathMotionContiguousViewController: UIViewController, ButtonsViewDelegate
97103
} else {
98104
top_anchor = margins.bottomAnchor
99105
}
100-
106+
107+
self.view.addSubview(label)
108+
label.translatesAutoresizingMaskIntoConstraints = false
109+
label.topAnchor.constraint(equalTo: top_anchor, constant: 30.0).isActive = true
110+
label.leadingAnchor.constraint(equalTo: margins.leadingAnchor, constant: 30).isActive = true
111+
label.trailingAnchor.constraint(equalTo: margins.trailingAnchor, constant: -30).isActive = true
112+
101113
view.addSubview(buttonsView)
102114
buttonsView.delegate = self
103115
buttonsView.translatesAutoresizingMaskIntoConstraints = false
@@ -106,7 +118,7 @@ class PathMotionContiguousViewController: UIViewController, ButtonsViewDelegate
106118

107119
self.view.addSubview(pathView)
108120
pathView.translatesAutoresizingMaskIntoConstraints = false
109-
pathView.topAnchor.constraint(equalTo: top_anchor, constant: 50.0).isActive = true
121+
pathView.topAnchor.constraint(equalTo: label.bottomAnchor, constant: 30.0).isActive = true
110122
pathView.centerXAnchor.constraint(equalTo: self.view.centerXAnchor, constant: 0.0).isActive = true
111123
pathView.widthAnchor.constraint(equalToConstant: 320.0).isActive = true
112124
pathView.heightAnchor.constraint(equalToConstant: 320.0).isActive = true

Examples/MotionExamples/Classes/PathMotionViewController.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,9 @@ class PathMotionViewController: UIViewController, ButtonsViewDelegate {
6161
let path = UIBezierPath(arcCenter: CGPoint(x: 20, y: 20), radius: 200, startAngle: 0.087, endAngle: 1.66, clockwise: true)
6262
path.addQuadCurve(to: CGPoint(x: 20, y: 50), controlPoint: CGPoint(x: 100, y: 45))
6363

64-
let pathState = PathState(path: path.cgPath)
65-
self.pathState = pathState
6664
pathView.path = path
6765

68-
motion = PathMotion(path: pathState,
66+
motion = PathMotion(path: path.cgPath,
6967
duration: 2.0,
7068
easing: EasingQuadratic.easeInOut())
7169
.reverses(withEasing: EasingQuartic.easeInOut())

Examples/MotionExamples/Classes/PathPhysicsMotionViewController.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,10 @@ class PathPhysicsMotionViewController: UIViewController, ButtonsViewDelegate {
6060
let path = UIBezierPath(arcCenter: CGPoint(x: 20, y: 20), radius: 200, startAngle: 0.087, endAngle: 1.66, clockwise: true)
6161
path.addQuadCurve(to: CGPoint(x: 20, y: 50), controlPoint: CGPoint(x: 100, y: 45))
6262

63-
let pathState = PathState(path: path.cgPath)
64-
self.pathState = pathState
6563
pathView.path = path
6664

6765
let config = PhysicsConfiguration(velocity: 800, friction: 0.4, restitution: 0.7)
68-
motion = PathPhysicsMotion(path: pathState, configuration: config)
66+
motion = PathPhysicsMotion(path: path.cgPath, configuration: config)
6967
motion?.updated({ [weak pathView, weak view, weak motionView] (motion, currentPoint) in
7068
if let view, let adjustedPoint = pathView?.convert(currentPoint, to: view) {
7169
motionView?.center = adjustedPoint

Guides/MoveableClasses.md

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,7 @@ let motion = PhysicsMotion(target: view,
135135
Here's a basic example. We've supplied the `PathMotion` with a basic CGPath via a `PathState` object, an easing equation, and we've told it to reverse back to the beginning once it travels to the end. Notice though how we've defined a `startPosition` and `endPosition`. This tells the `PathMotion` it should start the animation at a point 10% from the beginning of the path and end the animation at a point 80% along the path's length. If you leave these parameters out it will travel the full distance.
136136
```swift
137137
let path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 100, height: 100))
138-
let pathState = PathState(path: path.cgPath)
139-
motion = PathMotion(path: pathState,
138+
motion = PathMotion(path: path.cgPath,
140139
duration: 1.0,
141140
startPosition: 0.1,
142141
endPosition: 0.8,
@@ -148,8 +147,7 @@ motion.start()
148147
In this example note that we've added an `edgeBehavior` parameter to the `PathMotion` initializer. There are two types of edge behaviors – `stopAtEdges` (the default), which simply stops motion once the animated point gets to either specified edge point, and `contiguousEdges`, which tells the `PathMotion` that the path's starting and ending edges should be treated as connected, contiguous points. If the animated point travels beyond the path's edge, as can happen with some easing equation classes like `EasingElastic` and `EasingBack`, the motion will continue in the current direction at the beginning of the other edge. This behavior type is useful with closed paths like polygonal shapes to create a seamless animation.
149148
```swift
150149
let path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 100, height: 100))
151-
let pathState = PathState(path: path.cgPath)
152-
motion = PathMotion(path: pathState,
150+
motion = PathMotion(path: path.cgPath,
153151
duration: 1.0,
154152
easing: EasingElastic.easeInOut(),
155153
edgeBehavior: .contiguousEdges)
@@ -160,8 +158,7 @@ motion = PathMotion(path: pathState,
160158
The `startPosition` and `endPosition` values can also be flipped. Doing so will make the point travel along the path from the end of the path towards the beginning in its forward motion. In this example, the motion point will start at 80% along the path and travel "backwards" to 20%. If you added a `reverses` option, then at the end of the motion it would reverse along the path back to 80%.
161159
```swift
162160
let path = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 100, height: 100))
163-
let pathState = PathState(path: path.cgPath)
164-
motion = PathMotion(path: pathState,
161+
motion = PathMotion(path: path.cgPath,
165162
duration: 1.0,
166163
startPosition: 0.8,
167164
endPosition: 0.2,
@@ -170,10 +167,10 @@ motion.start()
170167
```
171168

172169
> [!IMPORTANT]
173-
> Note that because it is mathematically complex to find all points on a CGPath, large, complex paths can present performance challenges for `PathMotion` to animate along. Fortunately, `PathMotion` comes with a performance mode. When this mode is activated, `PathState` generates a lookup table which it uses to find points on the path in O(n) time. While this increases setup time, it significantly improves performance while the motion is running, for instance from 10% down to 1% CPU usage. To use performance mode, call the async method `setupPerformanceMode()` on the `PathState` instance before starting the `PathMotion`. This method will run the lookup table generation code on a background queue and return when complete. For most reasonably large paths this should take under a second.
170+
> Note that because it is mathematically complex to find all points on a CGPath, large, complex paths can present performance challenges for `PathMotion` to animate along. Fortunately, `PathMotion` comes with a performance mode. When this mode is activated, `PathState` generates a lookup table which it uses to find points on the path in O(n) time. While this increases setup time, it significantly improves performance while the motion is running, for instance from 10% down to 1% CPU usage. To use performance mode, call the async method `setupPerformanceMode()` before starting the `PathMotion`, as shown in the below example. This method will run the lookup table generation code on a background queue and return when complete. For most reasonably large paths this should take under a second.
174171
```swift
175172
Task {
176-
await pathState?.setupPerformanceMode()
173+
await motion?.setupPerformanceMode()
177174
motion?.start()
178175
}
179176
```
@@ -185,9 +182,8 @@ Task {
185182
Here's a basic example. We've supplied the `PathPhysicsMotion` with a basic CGPath via a `PathState` object, an easing equation, and we've told it to reverse back to the beginning once it travels to the end. Notice though how we've defined a `startPosition` and `endPosition`. This tells the `PathPhysicsMotion` it should start the animation at a point 10% from the beginning of the path and end the animation at a point 80% along the path's length. If you leave these parameters out it will have the full length of the path to travel along.
186183
```swift
187184
let path = UIBezierPath(arcCenter: CGPoint(x: 20, y: 20), radius: 100, startAngle: 0.087, endAngle: 1.66, clockwise: true)
188-
let pathState = PathState(path: path.cgPath)
189185
let config = PhysicsConfiguration(velocity: 500, friction: 0.4)
190-
motion = PathPhysicsMotion(path: pathState,
186+
motion = PathPhysicsMotion(path: path.cgPath,
191187
configuration: config,
192188
startPosition: 0.1,
193189
endPosition: 0.8)
@@ -197,19 +193,17 @@ motion.start()
197193
In this second example we're going to turn on collision detection and add a `restitution` value. Technically, collisions are turned on any time the `stopAtEdges` edge behavior is chosen, but adding a `restitution` value will cause a change in the motion. This value enables the object to collide and "bounce" off the starting and ending points when a point reaches them. The `restitution` value determines the elasticity of the object, which in effect determines how much velocity the object retains after colliding with an edge. A `restitution` value of 0.0 (the default if no value is provided) results in no bouncing at all, while a value of 1.0 results in no energy being lost in the collision. By default the collision points are at the start and end of the path, but if you specify your own start and end points those will be used instead.
198194
```swift
199195
let path = UIBezierPath(arcCenter: CGPoint(x: 20, y: 20), radius: 100, startAngle: 0.087, endAngle: 1.66, clockwise: true)
200-
let pathState = PathState(path: path.cgPath)
201196
let config = PhysicsConfiguration(velocity: 600, friction: 0.4, restitution: 0.8)
202-
motion = PathPhysicsMotion(path: pathState,
197+
motion = PathPhysicsMotion(path: path.cgPath,
203198
configuration: config)
204199
motion.start()
205200
```
206201

207202
Although `PathPhysicsMotion` uses a physics simulation instead of specifying discrete ending values, we can still apply `repeats` and `reverses` options. Repeating and reversing act in the same way as `Motion` and interacts with `MoveableCollection` classes as you would expect.
208203
```swift
209204
let path = UIBezierPath(arcCenter: CGPoint(x: 20, y: 20), radius: 100, startAngle: 0.087, endAngle: 1.66, clockwise: true)
210-
let pathState = PathState(path: path.cgPath)
211205
let config = PhysicsConfiguration(velocity: 500, friction: 0.4)
212-
motion = PathPhysicsMotion(path: pathState,
206+
motion = PathPhysicsMotion(path: path.cgPath,
213207
configuration: config)
214208
.reverses()
215209
.repeats(1)
@@ -219,20 +213,19 @@ motion = PathPhysicsMotion(path: pathState,
219213
The `startPosition` and `endPosition` values can also be flipped. Doing so will make the point travel along the path from the end of the path towards the beginning in its forward motion. Be aware that if you do this, the velocity should also be a negative value in order for the point to travel in the correct direction. In this example, the motion point will start at 80% along the path and travel "backwards" to 20%.
220214
```swift
221215
let path = UIBezierPath(arcCenter: CGPoint(x: 20, y: 20), radius: 100, startAngle: 0.087, endAngle: 1.66, clockwise: true)
222-
let pathState = PathState(path: path.cgPath)
223216
let config = PhysicsConfiguration(velocity: -600, friction: 0.4, restitution: 0.8)
224-
motion = PathPhysicsMotion(path: pathState,
217+
motion = PathPhysicsMotion(path: path.cgPath,
225218
configuration: config,
226219
startPosition: 0.8,
227220
endPosition: 0.2)
228221
motion.start()
229222
```
230223

231224
> [!IMPORTANT]
232-
> Note that because it is mathematically complex to find all points on a CGPath, large, complex paths can present performance challenges for `PathPhysicsMotion` to animate along. Fortunately, `PathPhysicsMotion` comes with a performance mode. When this mode is activated, the `PathState` object generates a lookup table which it uses to find points on the path in O(n) time. While this increases setup time, it significantly improves performance while the motion is running, for instance from 10% down to 1% CPU usage. To use performance mode, call the async method `setupPerformanceMode()` on the `PathState` instance before starting the `PathPhysicsMotion`. This method will run the lookup table generation code on a background queue and return when complete. For most reasonably large paths this should take under a second.
225+
> Note that because it is mathematically complex to find all points on a CGPath, large, complex paths can present performance challenges for `PathPhysicsMotion` to animate along. Fortunately, `PathPhysicsMotion` comes with a performance mode. When this mode is activated, the `PathState` object generates a lookup table which it uses to find points on the path in O(n) time. While this increases setup time, it significantly improves performance while the motion is running, for instance from 10% down to 1% CPU usage. To use performance mode, call the async method `setupPerformanceMode()` before starting the `PathPhysicsMotion`, as shown in the below example. This method will run the lookup table generation code on a background queue and return when complete. For most reasonably large paths this should take under a second.
233226
```swift
234227
Task {
235-
await pathState?.setupPerformanceMode()
228+
await motion?.setupPerformanceMode()
236229
motion?.start()
237230
}
238231
```

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version: 5.5
1+
// swift-tools-version: 5.10
22

33
// Package.swift
44
// MotionMachine

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
![MotionMachine logo](Guides/mmlogo.png)
22

3-
![swift](https://img.shields.io/badge/Swift-5.5%20%7C%206.0-005AA5.svg)
3+
![swift](https://img.shields.io/badge/Swift-5.10%20%7C%206.0-005AA5.svg)
44
![platforms](https://img.shields.io/badge/platforms-iOS%20%7C%20tvOS-005AA5.svg) ![license](https://img.shields.io/badge/license-MIT-005AA5.svg)
55

66
MotionMachine provides a modular, powerful, and generic platform for manipulating values, whether that be animating UI elements or interpolating property values in your own classes. It offers sensible default functionality that abstracts most of the hard work away, allowing you to focus on your work. While it is type-agnostic, MotionMachine does support most major UIKit types out of the box and provides syntactic sugar to easily manipulate them. But it's also easy to dive in and modify for your own needs, whether that be custom motion classes, supporting custom value types, or new easing equations.
@@ -166,7 +166,7 @@ You can add MotionMachine to an Xcode project by adding it as a Swift package de
166166
## Compatibility
167167

168168
MotionMachine currently requires:
169-
* Swift 5.5 or above
169+
* Swift 5.10 or above
170170
* Xcode 16+
171171
* iOS 13.0 or later, tvOS 13.0 or later
172172

@@ -182,7 +182,7 @@ MotionMachine currently requires:
182182

183183
## Credits
184184

185-
MotionMachine was created by [Brett Walker](https://twitter.com/petsound). It is loosely based on the author's Objective-C library [PMTween](https://github.com/poetmountain/PMTween).
185+
MotionMachine was created by [Brett Walker](https://bsky.app/profile/petsound.bsky.social). It is loosely based on the author's Objective-C library [PMTween](https://github.com/poetmountain/PMTween).
186186

187187

188188
## License

0 commit comments

Comments
 (0)