Home > other >  True delay Animation effect in SwiftUI
True delay Animation effect in SwiftUI

Time:09-28

I am working on a custom delay Animation which I started my version 0.0.0 with asyncAfter and then updated my code to Version 1.0.0 using sleep, I would like to know if there is a native and easy way for such a delay Animation instead my custom way, see the deference in gif or code.

PS: If you see any bad coding or upgradable code, please let me know.

struct ContentView: View {
    
    @State private var array: [Int] = [Int]()
    
    var body: some View {
        
        VStack(spacing: 10.0) {
            
            Button("remove all") { array.removeAll() }.foregroundColor(.red)
            
            Button("Way 1") { array  = Array(repeating: 0, count: 5) }
            
            Button("Way 2(Custom Code)") {
                
                DispatchQueue.global(qos: .background).async {
                    
                    for index in 0...5  {
                        
                        usleep(50_000)
                        
                        DispatchQueue.main.async { array.append(index) }
                        
                    }
                    
                }
                
            }
            
            ForEach(array.indices, id:\.self) { index in
                
                Image(systemName: "arrowtriangle.up.fill").scaledToFit().frame(width: 25, height: 25)
                
            }
            
        }
        .animation(.default, value: array)
        
    }
    
}

enter image description here

CodePudding user response:

You could use Combine Publishers to manage the appending/delays. There are multiple ways to accomplish this -- here's one using a Timer Publisher that is connected with a simple [Int] that get zipped together:

class ViewModel : ObservableObject {
    @Published var array: [Int] = [Int]()
    
    private var cancellable : AnyCancellable?
    
    func run() {
        cancellable = Publishers.Zip(Array(0...5).publisher,
                Timer.publish(every: 0.5, on: .main, in: .default).autoconnect()
            )
            .map { $0.0 }
            .sink(receiveValue: { newValue in
                self.array.append(newValue)
            })
    }
    
    func cancel() {
        cancellable?.cancel()
    }
    
    func removeAll() {
        array.removeAll()
    }
}

struct ContentView: View {

    @StateObject private var viewModel = ViewModel()

    var body: some View {

        VStack(spacing: 10.0) {

            Button("remove all") { viewModel.removeAll() }.foregroundColor(.red)

            Button("Add arrows") {

                viewModel.run()

            }
            
            Button("Cancel") {
                viewModel.cancel()
            }

            ForEach(viewModel.array.indices, id:\.self) { index in

                Image(systemName: "arrowtriangle.up.fill").scaledToFit().frame(width: 25, height: 25)

            }

        }
        .animation(.default, value: viewModel.array)
    }
}

This approach lets you avoid sleep, but also allows you do other useful things like cancel mid-session.

CodePudding user response:

you could try something simple, like this (especially without using usleep(50_000)):

struct ContentView: View {
    @State private var array: [Int] = [Int]()
    
    var body: some View {
        VStack(spacing: 10.0) {
            Button("remove all") { array.removeAll() }.foregroundColor(.red)
            Button("Way 1") { array  = Array(repeating: 0, count: 5) }
            Button("Way 2(Custom Code)") {
                for index in 0...5  {
                    array.append(index)
                }
            }
            ForEach(array.indices, id:\.self) { index in
                Image(systemName: "arrowtriangle.up.fill").scaledToFit().frame(width: 25, height: 25)
            }
        }
        .animation(.linear(duration: 0.5), value: array) // <-- here
    }
}
  • Related