Animating Text in Swift UI

前端 未结 2 1024
说谎
说谎 2021-02-08 04:16

How would it be possible to animate Text or TextField views from Swift UI?

By animation I mean, that when the text changes it will \"count up\

相关标签:
2条回答
  • 2021-02-08 04:55

    There is a pure way of animating text in SwiftUI. Here's an implementation of your progress indicator using the AnimatableModifier protocol in SwiftUI:

    I've written an extensive article documenting the use of AnimatableModifier (and its bugs). It includes the progress indicator too. You can read it here: https://swiftui-lab.com/swiftui-animations-part3/

    struct ContentView: View {
        @State private var percent: CGFloat = 0
    
        var body: some View {
            VStack {
                Spacer()
                Color.clear.overlay(Indicator(pct: self.percent))
    
                Spacer()
                HStack(spacing: 10) {
                    MyButton(label: "0%", font: .headline) { withAnimation(.easeInOut(duration: 1.0)) { self.percent = 0 } }
    
                    MyButton(label: "27%", font: .headline) { withAnimation(.easeInOut(duration: 1.0)) { self.percent = 0.27 } }
    
                    MyButton(label: "100%", font: .headline) { withAnimation(.easeInOut(duration: 1.0)) { self.percent = 1.0 } }
                }
            }.navigationBarTitle("Example 10")
        }
    }
    
    struct Indicator: View {
        var pct: CGFloat
    
        var body: some View {
            return Circle()
                .fill(LinearGradient(gradient: Gradient(colors: [.blue, .purple]), startPoint: .topLeading, endPoint: .bottomTrailing))
                .frame(width: 150, height: 150)
                .modifier(PercentageIndicator(pct: self.pct))
        }
    }
    
    struct PercentageIndicator: AnimatableModifier {
        var pct: CGFloat = 0
    
        var animatableData: CGFloat {
            get { pct }
            set { pct = newValue }
        }
    
        func body(content: Content) -> some View {
            content
                .overlay(ArcShape(pct: pct).foregroundColor(.red))
                .overlay(LabelView(pct: pct))
        }
    
        struct ArcShape: Shape {
            let pct: CGFloat
    
            func path(in rect: CGRect) -> Path {
    
                var p = Path()
    
                p.addArc(center: CGPoint(x: rect.width / 2.0, y:rect.height / 2.0),
                         radius: rect.height / 2.0 + 5.0,
                         startAngle: .degrees(0),
                         endAngle: .degrees(360.0 * Double(pct)), clockwise: false)
    
                return p.strokedPath(.init(lineWidth: 10, dash: [6, 3], dashPhase: 10))
            }
        }
    
        struct LabelView: View {
            let pct: CGFloat
    
            var body: some View {
                Text("\(Int(pct * 100)) %")
                    .font(.largeTitle)
                    .fontWeight(.bold)
                    .foregroundColor(.white)
            }
        }
    }
    
    0 讨论(0)
  • 2021-02-08 05:12

    You can use a CADisplayLink in a BindableObject to create a timer that updates your text during the animation. Gist

    
    class CADisplayLinkBinding: NSObject, BindableObject {
    
        let didChange = PassthroughSubject<CADisplayLinkBinding, Never>()
        private(set) var progress: Double = 0.0
    
        private(set) var startTime: CFTimeInterval = 0.0
        private(set) var duration: CFTimeInterval = 0.0
        private(set) lazy var displayLink: CADisplayLink = {
            let link = CADisplayLink(target: self, selector: #selector(tick))
            link.add(to: .main, forMode: .common)
            link.isPaused = true
            return link
        }()
    
        func run(for duration: CFTimeInterval) {
            let now = CACurrentMediaTime()
            self.progress = 0.0
            self.startTime = now
            self.duration = duration
            self.displayLink.isPaused = false
        }
    
        @objc private func tick() {
            let elapsed = CACurrentMediaTime() - self.startTime
            self.progress = min(1.0, elapsed / self.duration)
            self.displayLink.isPaused = self.progress >= 1.0
                self.didChange.send(self)
        }
    
        deinit {
            self.displayLink.invalidate()
        }
    
    }
    

    And then to use it:

    @ObjectBinding var displayLink = CADisplayLinkBinding()
    
    var body: some View {
        Text("\(Int(self.displayLink.progress*100))")
            .onAppear {
                self.displayLink.run(for: 10.0)
        }
    }
    
    0 讨论(0)
提交回复
热议问题