Home > Enterprise >  How to Calculate and Publish Arrays in SwiftUI
How to Calculate and Publish Arrays in SwiftUI

Time:04-05

In SwiftUI, if I declare an array variable with the property wrapper @Published, and then calculate each element of that array within a for() loop, will the variable be published each time I compute a new element? If so, is there a way to tell the compiler to not publish the variable until every element is computed?

I have an app that computes a spectrum for each successive frame of audio data. The app then publishes that spectrum[] to several Views that render fancy graphics. I want each View to re-draw only once for each successive frame of audio data - not once for each computed element of the spectrum[] array.

Here's some simplified code to illustrate the problem:

class ArrayGenerator: ObservableObject {
    @Published var spectrum = [Float](repeating: 0.0, count: 1000) 

    DispatchQueue.main.async { [self] in
        for bin in 0 ..< 1000 {
            spectrum[bin] = (userGain   userSlope * Float(bin)) * amplitudes[bin]
        }
    }
}

Since this code changes the variable 1,000 times, I believe it publishes it 1,000 times. But I want it to be published only once - when the for() loop is completed. How can I accomplish this?

CodePudding user response:

Your assumption that it will publish 1000 changes/renders is incorrect. All of those iterations of the for loop will be done in one iteration of the main run loop. Take this simple example:

class ArrayGenerator: ObservableObject {
    @Published var spectrum = [Float](repeating: 0.0, count: 1000)

    func run() {
        DispatchQueue.main.async { [self] in
            for bin in 0 ..< 1000 {
                spectrum[bin] = Float(bin)
            }
        }
    }
}

struct ContentView: View {
    
    @StateObject private var generator = ArrayGenerator()
    
    var body: some View {
        let _ = print("Rendering...")
        Text(generator.spectrum.map { String($0) }.joined())
            .onAppear {
                generator.run()
            }
    }
}

Which prints:

Rendering...
Rendering...

It renders once for the first appearance, then the onAppear runs, and then an additional render after the loop is done.

So, your code already does what you expect.

CodePudding user response:

Do your work on a copy and then set it once

  • Related