Swift UI is data-driven, not event-driven
Data-driven versus event-driven,
Example: Add a popUp view
Data-driven, there is already a popUp at the top, which controls its display and hide with data
Event-driven, is to create a popUp and add it to the top
Event-driven, possible repeated creation and addition,
Data-driven, safer
Effect 1, Expand and fold
@state modified property, as the driver data source, source of truth,
The @state modifier property changes and the View refreshes
@State var showMoon: String? = nil
func toggleMoons(_ name: String) -> Bool {
return name == showMoon
}
Copy the code
There are three scenarios in the following demonstration,
- Initialization, the grid is folded up, click on one to expand
- There’s a grid that expands, click on it, it collapses
- I have A grid A expanded, I click on grid B, grid A collapses, and then grid B expands
The above data structure, neatly solved,
Init, showMoon = nil, not equal to the name of any of the grid attributes, all decanted by default
In the other two cases, the logic is similar
Button(action: {
withAnimation(.easeInOut) {
self.showMoon = self.toggleMoons(planet.name) ? nil : planet.name
}
}) {
Image(systemName: "moon.circle.fill")
}
if self.toggleMoons(planet.name) {
MoonList(planet: planet)
}
Copy the code
Effect 2, custom Transition animation
Asymmetric animation,
Inserted,
Removed animation,
It can be different
extension AnyTransition { static var customTransition: AnyTransition { let insertion = AnyTransition.move(edge: .top).combined(with:.scale(scale: 0.2, Anchor:.toptrailing)).combined(with: .opacity) let removal = AnyTransition.move(edge: .top).combined(with: .opacity) return .asymmetric(insertion: insertion, removal: removal) } }Copy the code
Transition animation, you can also combine effects more freely
combined
Effect 3, star tracks on the cover
Orbit animation, requires a Geometrically animated Archive
3.1 Geometrically Yeffect
GeometryEffect protocol, native method
func effectValue(size: CGSize) -> ProjectionTransform
Copy the code
This method provides the computed coordinates of the animation point at a point in time
3.1.1, GeometryEffect protocol, inherited from Animatable
Animatable comes with attributes,
var animatableData: Self.AnimatableData
Copy the code
Animation time, animation property animatableData,
Between the starting value and the final value
3.1.2. OrbitEffect
You give me a region, and you draw a circle inside that region
AnimatableData interferes with Angle Angle, which works
Struct OrbitEffect: GeometryEffect {// GeometryEffect}, arbitrary let initialAngle = CGFloat. Random (in: 0.. < 2 * .pi) var angle: CGFloat = 0 let radius: CGFloat var animatableData: CGFloat { get { return angle } set { angle = newValue } } func effectValue(size: Let pt = CGPoint(x: cos(Angle + initialAngle) * radius, y: sin(angle + initialAngle) * radius) let translation = CGAffineTransform(translationX: pt.x, y: pt.y) return ProjectionTransform(translation) } }Copy the code
3.1.3, self.animationFlag ? 2 * .pi : 0
Determines the from & to of the animation
func makeOrbitEffect(diameter: CGFloat) -> some GeometryEffect { return OrbitEffect(angle: self.animationFlag ? 2 *. PI: 0, radius: diameter / 2.0)}Copy the code
3.2, error call demonstration
Refresh as it appears, call the animation
OnAppear, refresh the @state modifier
In this case, when you go to the screen, you get onAppear, and you call the track animation
After expansion, the track animation will appear dislocation, obvious bug
@State private var animationFlag = false func makeSystem(_ geometry: GeometryProxy) -> some View {let planetSize = geometry. Size. Height * 0.25 let moonSize = geometry radiusIncrement = (geometry.size.height - planetSize - moonSize) / CGFloat(moons.count) let range = 1 ... moons.count return ZStack { // ... ForEach(range, id: \.self) { index in // individual "moon" circles self.moon(planetSize: planetSize, moonSize: moonSize, radiusIncrement: radiusIncrement, index: CGFloat(index)) .modifier(self.makeOrbitEffect( diameter: planetSize + radiusIncrement * CGFloat(index) )) .animation(Animation .linear(duration: Double.random(in: 10 ... 100)) .repeatForever(autoreverses: false) ) } } .onAppear { self.animationFlag.toggle() } } func makeOrbitEffect(diameter: CGFloat) -> some GeometryEffect { return OrbitEffect(angle: self.animationFlag ? 2 *. PI: 0, radius: diameter / 2.0)}Copy the code
3.3. Correct invocation demonstration
The code above makes sense, but when it runs wrong,
Because he’s modifying the @state property in the method,
The method for
@inlinable public func onAppear(perform action: (() -> Void)? = nil) -> some View
Copy the code
Does not have an escape effect, outside the scope of the function, can still be executed,
If it is changed to the following, OK
@State private var animationFlag = false var body: some View { GeometryReader { geometry in self.makeSystem(geometry) { animationFlag.toggle() } } } func makeSystem(_ geometry: GeometryProxy, action completionHandler: @escaping () -> Void) -> some View {let planetSize = geometry. Size. Height * 0.25 let moonSize = geometry 0.1 let radiusIncrement = (geometry. Size. Height-planetsize-moonsize)/CGFloat(honey.count) let range = 1... moons.count return ZStack { ForEach(range, id: \.self) { index in // individual "moon" circles self.moon(moonSize: moonSize) .modifier(self.makeOrbitEffect( diameter: planetSize + radiusIncrement * CGFloat(index) )) .onAppear { withAnimation(Animation .linear(duration: Double.random(in: 10 ... 100)) .repeatForever(autoreverses: false)) { completionHandler() } } } } }Copy the code
3.3.1 Debug print of SwiftUI
The code inside the View can only be declarative,
Add View or conditional statement (if else)
extension View {
func Print(_ vars: Any...) -> some View {
for v in vars { print(v) }
return EmptyView()
}
}
Copy the code
3.3.2 SwiftUI uses func instead of Custom View
When the Model property calculation is mixed with the View creation,
Use func to create a View,
Use custom View to write UI declaratively
Declarative UI, code structure is highly consistent with UI