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 : 0Determines 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

GitHub repo