swiftUI 动画:使 class 符合 VectorArithmetic 以使用可动画数据
swiftUI animation: making class to conform VectorArithmetic for animatableData usage
我尝试制作 mi 自己的结构,以便在 AnimatableModifier 的 animatableData 属性 中使用它。一切顺利。然后我尝试制作一个符合 VectorArithmetic 的 class 但我失败了。
还好,动画是运行,就是里面没有插值。整个动画显示了最后一个位置,我不明白为什么。看起来 SwiftUI 无法存储更改并认为动画开始之前和动画结束之后的位置相等。
完整代码如下:
import SwiftUI
struct SimpleBorderMove: View{
var body: some View{
SimpleView()
.frame(height: 300)
}
}
struct SimpleView: View{
@State var position: CGFloat = 0
@State var height: CGFloat = 0
var body: some View{
VStack{
ZStack{
Rectangle()
.fill(Color.gray)
BorderView(position: position, height: height)
}
HStack{
VStack{
HStack{
Slider(value: self.$position, in: 0...1)
Button(action: {
withAnimation(.linear(duration: 1)){
self.position = 0
}
}){
Text("X")
}
}
HStack{
Slider(value: self.$height, in: 0...1)
Button(action: {
withAnimation(.linear(duration: 1)){
self.height = 0
}
}){
Text("X")
}
}
}
Button(action: {
withAnimation(.linear(duration: 1)){
self.position = CGFloat.random(in: 0..<1)
print("new position is \(self.position)")
self.height = CGFloat.random(in: 0..<1)
print("new height is \(self.height)")
}
}){
Text("Randomize")
}.background(Color.gray)
}
}
}
}
struct BorderView: View{
public var animatableData: CGFloat {
get {
print("Reding position: \(position)")
return self.position
}
set {
self.position = newValue
print("setting position: \(position)")
}
}
var position: CGFloat
var height: CGFloat
let borderWidth: CGFloat
init(position: CGFloat, borderWidth: CGFloat = 10, height: CGFloat = 1){
self.position = position
self.borderWidth = borderWidth
self.height = height
print("BorderView init")
}
var body: some View{
Rectangle()
.fill(Color.green)
.frame(width: self.borderWidth)
.twoParameterBorder(position: position, height: height)
}
}
struct BorderPosition: AnimatableModifier {//ViewModifier
var position: CGFloat
let startDate: Date = Date()
public var animatableData: CGFloat {
get {
print("animation: reading position: \(position) at time \(Date().timeIntervalSince(startDate))")
return position
}
set {
print("animation: setting position: \(newValue) at time \(Date().timeIntervalSince(startDate))")
position = newValue
}
}
init(position: CGFloat){
self.position = position
print("modifier init with position \(position)")
}
func body(content: Content) -> some View {
GeometryReader{geometry in
content
.animation(nil)
.offset(x: self.getXOffset(inSize: geometry.size), y: 0)
}
}
func getXOffset(inSize: CGSize) -> CGFloat{
let p = position
let offset = -inSize.width / 2 + inSize.width * p
print("at position \(p) offset is \(offset)")
return offset
}
}
extension View{
func borderIn(position: CGFloat) -> some View{
self.modifier(BorderPosition(position: position))
}
}
extension View{
func twoParameterBorder(position: CGFloat, height: CGFloat) -> some View{
self.modifier(TwoParameterBorder(position: position, height: height))
}
}
struct TwoParameterBorder: AnimatableModifier {
var height: CGFloat
var position: CGFloat
let startDate: Date = Date()
public var animatableData: MyAnimatableVector{
get {
print("animation read position: \(position), height: \(height) at time: \(Date().timeIntervalSince(startDate))")
return MyAnimatableVector(position: position, height: height)
}
set {
self.position = newValue.position
print("animating position at \(position) at time: \(Date().timeIntervalSince(startDate))")
self.height = newValue.height
print("animating height at \(height) at time: \(Date().timeIntervalSince(startDate))")
}
}
init(position: CGFloat, height: CGFloat){
self.position = position
self.height = height
}
init(height: CGFloat, position: CGFloat){
self.position = position
self.height = height
}
func body(content: Content) -> some View {
GeometryReader{geometry in
content
.animation(nil)
.offset(x: -geometry.size.width / 2 + geometry.size.width * self.position, y: 0)
.frame(height: self.height * (geometry.size.height - 20) + 20)
}
}
}
final class MyAnimatableVector: VectorArithmetic{
//struct MyAnimatableVector: VectorArithmetic{
var position: CGFloat
var height: CGFloat
static func - (lhs: MyAnimatableVector, rhs: MyAnimatableVector) -> Self {
var new = Self.init()
new.position = lhs.position - rhs.position
new.height = lhs.height - rhs.height
print("\(lhs.position) - \(rhs.position) = \(new.position)")
print("\(lhs.height) - \(rhs.height) = \(new.height)")
return new
}
static func -= (lhs: inout MyAnimatableVector, rhs: MyAnimatableVector) {
lhs = lhs - rhs
}
static func + (lhs: MyAnimatableVector, rhs: MyAnimatableVector) -> Self {
var new = Self.init()
new.position = lhs.position + rhs.position
new.height = lhs.height + rhs.height
print("\(lhs.position) + \(rhs.position) = \(new.position)")
print("\(lhs.height) + \(rhs.height) = \(new.height)")
return new
}
//
static func += (lhs: inout MyAnimatableVector, rhs: MyAnimatableVector) {
lhs = lhs + rhs
}
//mutating
func scale(by rhs: Double) {
let newPosition = self.position * CGFloat(rhs)
let newHeight = self.height * CGFloat(rhs)
print("\(position) * \(rhs) = \(newPosition)")
print("\(height) * \(rhs) = \(newHeight)")
self.position = newPosition
self.height = newHeight
}
var magnitudeSquared: Double{
get{
let result = Double(self.position * self.position) + Double(self.height * self.height)
return result
}
}
static var zero: Self{
get{Self.init()}
}
static func == (lhs: MyAnimatableVector, rhs: MyAnimatableVector) -> Bool {
let result = lhs.position == rhs.position && lhs.height == rhs.height
return result
}
init(position: CGFloat, height: CGFloat){
self.position = position
self.height = height
}
required init(){
self.position = 0
self.height = 0
}
}
struct SimpleBorderMove_Previews: PreviewProvider {
static var previews: some View {
SimpleBorderMove()
.frame(height: 300)
}
}
与结构完全相同的实现效果很好:
//final class MyAnimatableVector: VectorArithmetic{
struct MyAnimatableVector: VectorArithmetic{
var position: CGFloat
var height: CGFloat
static func - (lhs: MyAnimatableVector, rhs: MyAnimatableVector) -> Self {
var new = Self.init()
new.position = lhs.position - rhs.position
new.height = lhs.height - rhs.height
print("\(lhs.position) - \(rhs.position) = \(new.position)")
print("\(lhs.height) - \(rhs.height) = \(new.height)")
return new
}
static func -= (lhs: inout MyAnimatableVector, rhs: MyAnimatableVector) {
lhs = lhs - rhs
}
static func + (lhs: MyAnimatableVector, rhs: MyAnimatableVector) -> Self {
var new = Self.init()
new.position = lhs.position + rhs.position
new.height = lhs.height + rhs.height
print("\(lhs.position) + \(rhs.position) = \(new.position)")
print("\(lhs.height) + \(rhs.height) = \(new.height)")
return new
}
//
static func += (lhs: inout MyAnimatableVector, rhs: MyAnimatableVector) {
lhs = lhs + rhs
}
mutating
func scale(by rhs: Double) {
let newPosition = self.position * CGFloat(rhs)
let newHeight = self.height * CGFloat(rhs)
print("\(position) * \(rhs) = \(newPosition)")
print("\(height) * \(rhs) = \(newHeight)")
self.position = newPosition
self.height = newHeight
}
var magnitudeSquared: Double{
get{
let result = Double(self.position * self.position) + Double(self.height * self.height)
return result
}
}
static var zero: Self{
get{Self.init()}
}
static func == (lhs: MyAnimatableVector, rhs: MyAnimatableVector) -> Bool {
let result = lhs.position == rhs.position && lhs.height == rhs.height
return result
}
init(position: CGFloat, height: CGFloat){
self.position = position
self.height = height
}
// required
init(){
self.position = 0
self.height = 0
}
}
我对 class 做错了什么?
你对 class 没有做错 - 只是 Apple 只支持结构的这个功能。
我尝试制作 mi 自己的结构,以便在 AnimatableModifier 的 animatableData 属性 中使用它。一切顺利。然后我尝试制作一个符合 VectorArithmetic 的 class 但我失败了。
还好,动画是运行,就是里面没有插值。整个动画显示了最后一个位置,我不明白为什么。看起来 SwiftUI 无法存储更改并认为动画开始之前和动画结束之后的位置相等。
完整代码如下:
import SwiftUI
struct SimpleBorderMove: View{
var body: some View{
SimpleView()
.frame(height: 300)
}
}
struct SimpleView: View{
@State var position: CGFloat = 0
@State var height: CGFloat = 0
var body: some View{
VStack{
ZStack{
Rectangle()
.fill(Color.gray)
BorderView(position: position, height: height)
}
HStack{
VStack{
HStack{
Slider(value: self.$position, in: 0...1)
Button(action: {
withAnimation(.linear(duration: 1)){
self.position = 0
}
}){
Text("X")
}
}
HStack{
Slider(value: self.$height, in: 0...1)
Button(action: {
withAnimation(.linear(duration: 1)){
self.height = 0
}
}){
Text("X")
}
}
}
Button(action: {
withAnimation(.linear(duration: 1)){
self.position = CGFloat.random(in: 0..<1)
print("new position is \(self.position)")
self.height = CGFloat.random(in: 0..<1)
print("new height is \(self.height)")
}
}){
Text("Randomize")
}.background(Color.gray)
}
}
}
}
struct BorderView: View{
public var animatableData: CGFloat {
get {
print("Reding position: \(position)")
return self.position
}
set {
self.position = newValue
print("setting position: \(position)")
}
}
var position: CGFloat
var height: CGFloat
let borderWidth: CGFloat
init(position: CGFloat, borderWidth: CGFloat = 10, height: CGFloat = 1){
self.position = position
self.borderWidth = borderWidth
self.height = height
print("BorderView init")
}
var body: some View{
Rectangle()
.fill(Color.green)
.frame(width: self.borderWidth)
.twoParameterBorder(position: position, height: height)
}
}
struct BorderPosition: AnimatableModifier {//ViewModifier
var position: CGFloat
let startDate: Date = Date()
public var animatableData: CGFloat {
get {
print("animation: reading position: \(position) at time \(Date().timeIntervalSince(startDate))")
return position
}
set {
print("animation: setting position: \(newValue) at time \(Date().timeIntervalSince(startDate))")
position = newValue
}
}
init(position: CGFloat){
self.position = position
print("modifier init with position \(position)")
}
func body(content: Content) -> some View {
GeometryReader{geometry in
content
.animation(nil)
.offset(x: self.getXOffset(inSize: geometry.size), y: 0)
}
}
func getXOffset(inSize: CGSize) -> CGFloat{
let p = position
let offset = -inSize.width / 2 + inSize.width * p
print("at position \(p) offset is \(offset)")
return offset
}
}
extension View{
func borderIn(position: CGFloat) -> some View{
self.modifier(BorderPosition(position: position))
}
}
extension View{
func twoParameterBorder(position: CGFloat, height: CGFloat) -> some View{
self.modifier(TwoParameterBorder(position: position, height: height))
}
}
struct TwoParameterBorder: AnimatableModifier {
var height: CGFloat
var position: CGFloat
let startDate: Date = Date()
public var animatableData: MyAnimatableVector{
get {
print("animation read position: \(position), height: \(height) at time: \(Date().timeIntervalSince(startDate))")
return MyAnimatableVector(position: position, height: height)
}
set {
self.position = newValue.position
print("animating position at \(position) at time: \(Date().timeIntervalSince(startDate))")
self.height = newValue.height
print("animating height at \(height) at time: \(Date().timeIntervalSince(startDate))")
}
}
init(position: CGFloat, height: CGFloat){
self.position = position
self.height = height
}
init(height: CGFloat, position: CGFloat){
self.position = position
self.height = height
}
func body(content: Content) -> some View {
GeometryReader{geometry in
content
.animation(nil)
.offset(x: -geometry.size.width / 2 + geometry.size.width * self.position, y: 0)
.frame(height: self.height * (geometry.size.height - 20) + 20)
}
}
}
final class MyAnimatableVector: VectorArithmetic{
//struct MyAnimatableVector: VectorArithmetic{
var position: CGFloat
var height: CGFloat
static func - (lhs: MyAnimatableVector, rhs: MyAnimatableVector) -> Self {
var new = Self.init()
new.position = lhs.position - rhs.position
new.height = lhs.height - rhs.height
print("\(lhs.position) - \(rhs.position) = \(new.position)")
print("\(lhs.height) - \(rhs.height) = \(new.height)")
return new
}
static func -= (lhs: inout MyAnimatableVector, rhs: MyAnimatableVector) {
lhs = lhs - rhs
}
static func + (lhs: MyAnimatableVector, rhs: MyAnimatableVector) -> Self {
var new = Self.init()
new.position = lhs.position + rhs.position
new.height = lhs.height + rhs.height
print("\(lhs.position) + \(rhs.position) = \(new.position)")
print("\(lhs.height) + \(rhs.height) = \(new.height)")
return new
}
//
static func += (lhs: inout MyAnimatableVector, rhs: MyAnimatableVector) {
lhs = lhs + rhs
}
//mutating
func scale(by rhs: Double) {
let newPosition = self.position * CGFloat(rhs)
let newHeight = self.height * CGFloat(rhs)
print("\(position) * \(rhs) = \(newPosition)")
print("\(height) * \(rhs) = \(newHeight)")
self.position = newPosition
self.height = newHeight
}
var magnitudeSquared: Double{
get{
let result = Double(self.position * self.position) + Double(self.height * self.height)
return result
}
}
static var zero: Self{
get{Self.init()}
}
static func == (lhs: MyAnimatableVector, rhs: MyAnimatableVector) -> Bool {
let result = lhs.position == rhs.position && lhs.height == rhs.height
return result
}
init(position: CGFloat, height: CGFloat){
self.position = position
self.height = height
}
required init(){
self.position = 0
self.height = 0
}
}
struct SimpleBorderMove_Previews: PreviewProvider {
static var previews: some View {
SimpleBorderMove()
.frame(height: 300)
}
}
与结构完全相同的实现效果很好:
//final class MyAnimatableVector: VectorArithmetic{
struct MyAnimatableVector: VectorArithmetic{
var position: CGFloat
var height: CGFloat
static func - (lhs: MyAnimatableVector, rhs: MyAnimatableVector) -> Self {
var new = Self.init()
new.position = lhs.position - rhs.position
new.height = lhs.height - rhs.height
print("\(lhs.position) - \(rhs.position) = \(new.position)")
print("\(lhs.height) - \(rhs.height) = \(new.height)")
return new
}
static func -= (lhs: inout MyAnimatableVector, rhs: MyAnimatableVector) {
lhs = lhs - rhs
}
static func + (lhs: MyAnimatableVector, rhs: MyAnimatableVector) -> Self {
var new = Self.init()
new.position = lhs.position + rhs.position
new.height = lhs.height + rhs.height
print("\(lhs.position) + \(rhs.position) = \(new.position)")
print("\(lhs.height) + \(rhs.height) = \(new.height)")
return new
}
//
static func += (lhs: inout MyAnimatableVector, rhs: MyAnimatableVector) {
lhs = lhs + rhs
}
mutating
func scale(by rhs: Double) {
let newPosition = self.position * CGFloat(rhs)
let newHeight = self.height * CGFloat(rhs)
print("\(position) * \(rhs) = \(newPosition)")
print("\(height) * \(rhs) = \(newHeight)")
self.position = newPosition
self.height = newHeight
}
var magnitudeSquared: Double{
get{
let result = Double(self.position * self.position) + Double(self.height * self.height)
return result
}
}
static var zero: Self{
get{Self.init()}
}
static func == (lhs: MyAnimatableVector, rhs: MyAnimatableVector) -> Bool {
let result = lhs.position == rhs.position && lhs.height == rhs.height
return result
}
init(position: CGFloat, height: CGFloat){
self.position = position
self.height = height
}
// required
init(){
self.position = 0
self.height = 0
}
}
我对 class 做错了什么?
你对 class 没有做错 - 只是 Apple 只支持结构的这个功能。