Edit: I've since figured it out. Here's the correct solution, as far as I know. It compiles without errors, runs without problems, and gives the correct result. So, unless someone sees a problem I'm not seeing, I'm now happy to consider this question resolved.
The problem with the previous implementation is on line 23: by creating a local mutable copy of self and operating on *that*, self itself doesn't get changed.
import Foundation
import CoreGraphics
import Accelerate
extension CGPoint: AccelerateMutableBuffer {
		public typealias Element = Double
		public var count: Int { 2 }
		public func withUnsafeBufferPointer<R>(_ body: (UnsafeBufferPointer<Double>) throws -> R) rethrows -> R {
				try Swift.withUnsafeBytes(of: self) { urbp in
						try body(urbp.bindMemory(to: Double.self))
				}
		}
		public mutating func withUnsafeMutableBufferPointer <R> (
				_ body: (inout UnsafeMutableBufferPointer<Double>) throws -> R
		) rethrows -> R {
				try Swift.withUnsafeMutableBytes(of: &self) { umrbp in
						var umbp = umrbp.bindMemory(to: Double.self)
						return try body(&umbp)
				}
		}
}
let p1 = CGPoint(x: 1.5, y: 3.5)
let p2 = CGPoint(x: 0.5, y: 2.5)
var p: CGPoint = .zero
vDSP.add(p1, p2, result: &p)
p // (x: 2, y: 6)	✅
I understand the error message now. The Element type of the type conforming to Accelerate[Mutable]Buffer must be a Double. I can live with that since CGFloat is a Double on a 64-bit platform. Still, vDSP is not returning the correct result. Again, what am I doing incorrectly?
Many thanks.
import Foundation
import CoreGraphics
import Accelerate
extension CGPoint: AccelerateMutableBuffer {
public typealias Element = Double
public var count: Int { 2 }
public func withUnsafeBufferPointer <R> (
_ body: (UnsafeBufferPointer<Element>) throws -> R
) rethrows -> R {
var varself = self
let ubp = withUnsafeBytes(of: &varself) { urbp in
urbp.bindMemory(to: Element.self)
}
return try body(ubp)
}
public mutating func withUnsafeMutableBufferPointer <R> (
_ body: (inout UnsafeMutableBufferPointer<Element>) throws -> R
) rethrows -> R {
var varself = self
var umbp = withUnsafeMutableBytes(of: &varself) { umrbp in
umrbp.bindMemory(to: Element.self)
}
return try body(&umbp)
}
}
let p1 = CGPoint(x: 1.5, y: 3.5)
let p2 = CGPoint(x: 0.5, y: 2.5)
var p: CGPoint = .zero
vDSP.add(p1, p2, result: &p)
p	 //	result is (0.0, 0.0), not the expected (2.0, 6.0)
Doh! 🤦🏻♂️ I accidentally clicked on that checkmark button and now I can't uncheck it. This question is not yet resolved!
Follow-up...
After some more digging, and reading, and trial and error, I managed to get the implementation below, which I think is correct. However, now Xcode gives me a vDSP error that I don't understand, since CGPoint is now conforming to the types that vDSP expects its arguments and return values to have. What am I doing incorrectly?
(The code below is in a Playground)
import Foundation
import CoreGraphics
import Accelerate
extension CGPoint: AccelerateMutableBuffer {
public var count: Int { 2 }
public func withUnsafeBufferPointer<R>(
_ body: (UnsafeBufferPointer<CGFloat>) throws -> R
) rethrows -> R {
var varself = self
let ubp = withUnsafeBytes(of: &varself) { urbp in
urbp.bindMemory(to: CGFloat.self)
}
return try body(ubp)
}
public mutating func withUnsafeMutableBufferPointer<R>(
_ body: (inout UnsafeMutableBufferPointer<CGFloat>) throws -> R
) rethrows -> R {
var varself = self
var ubp = withUnsafeMutableBytes(of: &varself) { urbp in
urbp.bindMemory(to: CGFloat.self)
}
return try body(&ubp)
}
}
let p1 = CGPoint(x: 1.5, y: -3.5)
let p2 = CGPoint(x: -0.5, y: 2.5)
var p: CGPoint = .zero
vDSP.add(p1, p2, result: &p) // Error: "No exact matches in call to static method 'add(_:_:result:)'"
p
Post not yet marked as solved
I nearly got a working implementation (well, in theory) but I got stuck because of a private API.import SwiftUI
struct ContentView: View {
var body: some View {
CircleView()
.animating(\.s, // QQQ Not sure why the generic parameter Value could not be inferred
from: 0.2, to: 0.8, startingAt: 0.3,
animation: .basic(duration: 0.8, curve: .easeInEaseOut),
isActive: true)
}
}
struct CircleView: View {
@AutoAnimated private var s: CGFloat
var body: some View {
Circle()
.fill(Color.red)
.scaleEffect(s)
}
}
#if DEBUG
struct ContentView_Previews : PreviewProvider {
static var previews: some View {
ContentView()
}
}
#endif
// =========================================== //
// QQQ There should be an extension to ViewModifier that does work
// nearly identical to that of the extension to View below, so that
// we can call '.animating(...)' on the result of another view modifier.
public extension ViewModifier {}
// ==================== //
public extension View {
func animating<Value: BinaryFloatingPoint>(_ propertyKeyPath: WritableKeyPath<Body, Value>,
from fromValue: Value = 0,
to toValue: Value = 1,
startingAt startValue: Value,
animation: Animation = .default,
isActive: Bool = true) -> some View
where Body == AutoAnimationViewModifier<Value>.Body {
return AutoAnimationViewModifier<Value>()
.animating(propertyKeyPath,
from: fromValue,
to: toValue,
startingAt: startValue,
animation: animation,
isActive: isActive)
.body(content: self.body) // QQQ Don't know how to fix the type mismatch here,
// since it involves a type private to SwiftUI
}
}
// ==================== //
public struct AutoAnimationViewModifier<Value: BinaryFloatingPoint>: ViewModifier {
public init() {}
public func animating(_ propertyKeyPath: WritableKeyPath<Body, Value>,
from fromValue: Value = 0,
to toValue: Value = 1,
startingAt startValue: Value = 0,
animation: Animation = .default,
isActive: Bool = true) -> AutoAnimationViewModifier {
.init(propertyKeyPath,
from: fromValue,
to: toValue,
startingAt: startValue,
animation: animation,
isActive: isActive)
}
public func body(content: Content) -> some View {
content // QQQ Not sure how to apply the animation since the whole point
// is not to have to explicitly trigger model changes
}
private var animatedPropertyKeyPath: WritableKeyPath<Body, Value>!
private var from: Value = 0
private var to: Value = 1
private var startingAt: Value = 0
private var animation: Animation = .default
private var isActive: Bool = true
private init(_ propertyKeyPath: WritableKeyPath<Body, Value>! = nil,
from fromValue: Value = 0,
to toValue: Value = 1,
startingAt startValue: Value,
animation: Animation = .default,
isActive: Bool = true) {
self.animatedPropertyKeyPath = propertyKeyPath
self.from = fromValue
self.to = toValue
let low = min(fromValue, toValue)
let high = max(fromValue, toValue)
self.startingAt = min(max(low, startingAt), high)
self.animation = animation
self.isActive = isActive
}
}
// ==================== //
@propertyWrapper
public struct AutoAnimated<Value: BinaryFloatingPoint> {
@State private var storage: Value = 0
public init() {}
public var value: Value {
storage
}
}
Post not yet marked as solved
For reference, I’ve logged a suggestion with Apple’s issue tracker. The issue number is FB6145347.
Post not yet marked as solved
Upon further reflection, I think the idea I suggested above (assuming it makes sense and that there’s no simpler way to do it already) could be enhanced by adding an ‘isActive’ boolean to the view modifier function ‘.animating(...)’ so that we can programmatically start or stop the animation.Moreover, since the animation is entirely under SwiftUI’s control, maybe those exposed properties should be marked with a property wrapper that gives SwiftUI read-write access but gives the view that exposes them read-only access.So, something like the following:struct SomeView {
@AutoAnimated private var x: CGFloat // the compiler ensures that x is read-only to us but read-write to SwiftUI
// ....
}
struct EnclosingView {
@State private var xAnimActive = true // this view can set and change this value to control the animation life cycle
SomeView()
.animating(\.x, from: /* some value */, to: /* some value */, startingAt: /* some value */,
animation: /* some animation here */,
active: xAnimActive)
}