Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Repeating Action Continuously In SwiftUI

Tags:

swift

swiftui

How can I make an element such as a text field scale up and then down continuously?

I have this:

struct ContentView : View {
    @State var size:Double = 0.5

    var body: some View { 
        ZStack {
            Text("Hello!")
                 .padding()
                 .scaleEffect(size)
        }
    }
}

I know I need to increase size and then decrease it in some sort of loop but the following cannot be done in SwiftUI:

while true {

  self.size += 0.8
  sleep(0.2)
  self.size -= 0.8

}
like image 615
Isaac Avatar asked Jun 08 '19 18:06

Isaac


4 Answers

A possible solution is to use a (repeating, auto-reversing) animation:

struct ContentView : View {
    @State var size: CGFloat = 0.5
    
    var repeatingAnimation: Animation {
        Animation
            .easeInOut(duration: 2) //.easeIn, .easyOut, .linear, etc...
            .repeatForever()
    }

    var body: some View {
        Text("Hello!")
            .padding()
            .scaleEffect(size)
            .onAppear() {
                withAnimation(self.repeatingAnimation) { self.size = 1.3 }
        }
    }
}
like image 122
Martin R Avatar answered Sep 25 '22 07:09

Martin R


Animation.basic is deprecated. Basic animations are now named after their curve types: like linear, etc:

var foreverAnimation: Animation {
        Animation.linear(duration: 0.3)
        .repeatForever()
 }

Source: https://forums.swift.org/t/swiftui-animation-basic-duration-curve-deprecated/27076

like image 31
PaFi Avatar answered Sep 27 '22 07:09

PaFi


The best way is to create separate animation struct and configure all the options you need(this way your code will be more compact).

To make it more clear and logical use @State property isAnimating. You will be able to stop your animation and resume again and understand when it is in progress.

    @State private var isAnimating = false

    var foreverAnimation: Animation {
        Animation.linear(duration: 0.3)
        .repeatForever()
    }

    var body: some View {

        Text("Hello")
            .scaleEffect(isAnimating ? 1.5 : 1)
            .animation(foreverAnimation)
            .onAppear {
                self.isAnimating = true
        }
}
like image 42
DenFav Avatar answered Sep 24 '22 07:09

DenFav


Using a repeating animation on a view has weird behaviour when used inside if statements.

If you want to do:

if something {
    BlinkingView()
}

use a transition with an animation modifier, otherwise the view stays on the screen even after something is set to false.

I made this extension to show a view that repeats change from one state to the next and back:

extension AnyTransition {
    static func repeating<T: ViewModifier>(from: T, to: T, duration: Double = 1) -> AnyTransition {
       .asymmetric(
            insertion: AnyTransition
                .modifier(active: from, identity: to)
                .animation(Animation.easeInOut(duration: duration).repeatForever())
                .combined(with: .opacity), 
            removal: .opacity
        )
    }
}

This makes the view appear and disappear with AnyTransition.opacity and while it is shown it switches between the from and to state with a delay of duration.

Example usage:

struct Opacity: ViewModifier {
    private let opacity: Double
    init(_ opacity: Double) {
        self.opacity = opacity
    }

    func body(content: Content) -> some View {
        content.opacity(opacity)
    }
}

struct ContentView: View {
    @State var showBlinkingView: Bool = false

    var body: some View {
        VStack {
            if showBlinkingView {
                Text("I am blinking")
                    .transition(.repeating(from: Opacity(0.3), to: Opacity(0.7)))
            }
            Spacer()
            Button(action: {
                self.showBlinkingView.toggle()
            }, label: {
                Text("Toggle blinking view")
            })
        }.padding(.vertical, 50)
    }
}

Edit:

When the show condition is true on appear, the transition doesn't start. To fix this I do toggle the condition on appear of the superview (The VStack in my example):

.onAppear {
    if self.showBlinkingView {
        self.showBlinkingView.toggle()
        DispatchQueue.main.async {
            self.showBlinkingView.toggle()
        }
    }
}
like image 30
Casper Zandbergen Avatar answered Sep 28 '22 07:09

Casper Zandbergen