mardi 29 octobre 2019

SwiftUI View struct without reloading

I would like to create a starry background view in SwiftUI that has its stars located randomly using Double.random(), but does not reinitialise them and move them when the parent view reloads its var body.

struct ContentView: View {
    @State private var showButton = true

    var body: some View {
        ZStack {
            BackgroundView()
            if showButton {
                Button("Tap me"){
                    self.showButton = false
                }
            }
        }
    }
}

I define my background view as such.

struct BackgroundView: View {
    var body: some View {
        ZStack {
            GeometryReader { geometry in
                Color.black
                ForEach(0..<self.getStarAmount(using: geometry), id: \.self){ _ in
                    Star(using: geometry)
                }
                LinearGradient(gradient: Gradient(colors: [.purple, .clear]), startPoint: .bottom, endPoint: .top)
                    .opacity(0.7)
            }
        }
    }

    func getStarAmount(using geometry: GeometryProxy) -> Int {
        return Int(geometry.size.width*geometry.size.height/100)
    }
}

A Star is defined as

struct Star: View {
    let pos: CGPoint
    @State private var opacity = Double.random(in: 0.05..<0.4)

    init(using geometry: GeometryProxy) {
        self.pos = CGPoint(x: Double.random(in: 0..<Double(geometry.size.width)), y: Double.random(in: 0..<Double(geometry.size.height)))
    }



    var body: some View {
        Circle()
            .foregroundColor(.white)
            .frame(width: 2, height: 2)
            .scaleEffect(CGFloat(Double.random(in: 0.25...1)))
            .position(pos)
            .opacity(self.opacity)
            .onAppear(){
                withAnimation(Animation.linear(duration: 2).delay(Double.random(in: 0..<6)).repeatForever()){
                    self.opacity = self.opacity+0.5
                }
            }
    }
}

As one can see, a Star heavily relies on random values, for both its animation (to create a 'random' twinkling effect) as well as its position. When the parent view of the BackgroundView, ContentView in this example, gets redrawn however, all Stars get reinitialised, their position values change and they move across the screen. How can this best be prevented?

I have tried several approaches to prevent the positions from being reinitialised. I can create a struct StarCollection as a static let of BackgroundView, but this is quite cumbersome. What is the best way to go about having a View dependent on random values (positions), only determine those positions once?


Furthermore, the rendering is quite slow. I have attempted to call .drawingGroup() on the ForEach, but this then seems to interfere with the animation's opacity interpolation. Is there any viable way to speed up the creation / re-rendering of a view with many Circle() elements?




Aucun commentaire:

Enregistrer un commentaire