maustinstar / liquid

Create a playful backsplash in SwiftUI.
MIT License
798 stars 29 forks source link

Is there a way to make the animation continue indefinitely? #3

Closed jeffgrann closed 3 years ago

jeffgrann commented 3 years ago

The animation only lasts for a few seconds. Is there a way to keep it going?

maustinstar commented 3 years ago

Hi @jeffgrann thanks for trying out Liquid!

Line 20 of Sources/Liquid/PrivateViews/LiquidCircleView.swift establishes a Combine trigger from a repeating timer on the main run loop that regularly animates the view.

The problem you describe is not expected behavior. Can you share a snippet of your code that replicates the problem?

jeffgrann commented 3 years ago

Hi @maustinstar thanks for creating Liquid!

After some investigation, it looks like this only happens when Liquid is within a TabView. I'm not sure if the PageTabViewStyle is necessary to cause the issue since I haven't tried it without it. When I run the following code in a simulator or an actual device, Liquid on the first page animates for a few seconds and then freezes. The same thing happens after moving to other pages too, but not always. In the supplied video, it happened on every page. I'm using Xcode 12.2 (12B45b).

struct ContentView: View {
    var body: some View {
        TabView {
            Liquid(samples: 6, period: 6)
                .frame(width: 250, height: 175)
                .foregroundColor(Color.blue)

            Liquid(samples: 6, period: 6)
                .frame(width: 250, height: 175)
                .foregroundColor(Color.red)

            Liquid(samples: 6, period: 6)
                .frame(width: 250, height: 175)
                .foregroundColor(Color.yellow)
        }
        .tabViewStyle(PageTabViewStyle())
        .indexViewStyle(PageIndexViewStyle(backgroundDisplayMode: .always))
    }
}

https://user-images.githubusercontent.com/2565267/103177662-65585680-484a-11eb-8021-fb4630600613.mp4

jeffgrann commented 3 years ago

Any movement on this one?

daisuke-t-jp commented 3 years ago

I had the same experience.

Animation stops because onDisappear is called when the View in TabView is displayed.

In iOS 14.4 and earlier, onAppear and onDisappear are called multiple times when viewing a View in TabView.

In iOS 14.5, onAppear and onDisappear are now called only once.

So by changing LiquidCircleView.swift as follows, I confirmed that the animation can be resumed even when switching tabs. (IOS 14.5 only)


//
//  LiquidCircleView.swift
//  
//
//  Created by Michael Verges on 8/17/20.
//

import SwiftUI
import Combine

struct LiquidCircleView: View {
    @State var samples: Int
    @State var radians: AnimatableArray
    @State var trigger: Timer.TimerPublisher?
    @State var cancellable: Cancellable?
    let period: TimeInterval

    init(samples: Int, period: TimeInterval) {
        self._samples = .init(initialValue: samples)
        self._radians = .init(initialValue: AnimatableArray(LiquidCircleView.generateRadial(samples)))
        self.period = period

        startTimer()
    }

    var body: some View {
        LiquidCircle(radians: radians)
            .animation(.linear(duration: period))
            .onAppear {
                self.radians = AnimatableArray(LiquidCircleView.generateRadial(self.samples))

                self.startTimer()
            }
            .onDisappear {
                self.stopTimer()
            }
    }

    static func generateRadial(_ count: Int = 6) -> [Double] {

        var radians: [Double] = []
        let offset = Double.random(in: 0...(.pi / Double(count)))
        for i in 0..<count {
            let min = Double(i) / Double(count) * 2 * .pi
            let max = Double(i + 1) / Double(count) * 2 * .pi
            radians.append(Double.random(in: min...max) + offset)
        }

        return radians
    }

    private func startTimer() {
        guard self.cancellable == nil else {
            return
        }

        self.cancellable = Timer.publish(every: period, on: .main, in: .common)
            .autoconnect()
            .sink { _ in
                self.radians = AnimatableArray(LiquidCircleView.generateRadial(self.samples))
            }
    }

    private func stopTimer() {
        self.cancellable?.cancel()
        self.cancellable = nil
    }

}
maustinstar commented 3 years ago

That's awesome @daisuke-t-jp! Thank you for the investigation here. Would you like to open a pull request for this? If not, I can paste it in myself.

grgar commented 3 years ago

Fixed in #5 please close

jeffgrann commented 3 years ago

Could you release a 0.0.2 with this fix?

maustinstar commented 3 years ago

@jeffgrann tagged a new release

jeffgrann commented 3 years ago

Thanks. 😊