0

I have a button which add a new object of Type CircleInfo to an array called circleArray! this array will be used to supply data to work for a ForEach loop, All is working except one big issue that I noticed!

The Issue: When I add new object to array, the action of updating array make ForEach to update itself as well, and it brings app and Memory to knee with a little more detailed View than a simple Circle, because it starts Re-Initializing Initialized-View again! For example we got 10 Circle in Screen, as soon as I add new Circle, Foreach will go render all rendered 10 circle plus the new one! Maybe you say it is the way of working SwiftUI which draw screen with small changes, But I am telling with more and more data it goes eat so much memory and CPU!

My Goal: I want to find a new way of adding new object to screen, more likely adding overly or something like that which those not need ForEach! because as soon as we change items of array or range of that array, it will go draw all again!

enter image description here

struct CircleInfo: Identifiable {

    let id: UUID = UUID()
    let circleColor: Color = Color(red: Double.random(in: 0...1), green: Double.random(in: 0...1), blue: Double.random(in: 0...1))

}

    struct CircleView: View {

    let circleInfo: CircleInfo
    let index: Int

    init(circleInfo: CircleInfo, index: Int) {

        self.circleInfo = circleInfo
        self.index = index
        
        print("initializing circle:", index)
    }

    var body: some View {

        Circle()
            .fill(circleInfo.circleColor)
            .frame(width: 200, height: 200, alignment: .center)
        
    }
    
}

    struct ContentView: View {

    @State var circleArray: [CircleInfo] = [CircleInfo]()

    var body: some View {

        GeometryReader { geometry in

            Color
                .yellow
                .ignoresSafeArea()

            ForEach(Array(circleArray.enumerated()), id:\.element.id) { (index, item) in

                CircleView(circleInfo: item, index: index)
                    .position(x: geometry.size.width/2, y: 20*CGFloat(index) + 100)
                
            }
 
        }
        .overlay(button, alignment: Alignment.bottom)
  
    }

    var button: some View {

        Button("add Circle") { circleArray.append(CircleInfo()) }
  
    }
 
}

1 Answer 1

1

Easiest way to achieve your goal is to have an array of CircleView instead of CircleInfo.

First make CircleView identifiable:

struct CircleView: View, Identifiable {

    let id: UUID = UUID()
…

Then use a CircleView array in ForEach


struct ContentView: View {

    @State var circleArray: [CircleView] = [CircleView]()

    var body: some View {

        GeometryReader { geometry in

            Color
                .yellow
                .ignoresSafeArea()

                ForEach(circleArray) { item in
                    item
                        .position(x: geometry.size.width/2, y: 20*CGFloat(item.index) + 100)

            }

        }
        .overlay(button, alignment: Alignment.bottom)

    }

    var button: some View {

        Button("add Circle") { circleArray.append(CircleView(circleInfo: CircleInfo(), index: circleArray.count)) }

    }

}

You'll definitely use the CPU less.

Sign up to request clarification or add additional context in comments.

4 Comments

I liked your way, did you faced this issue before or you just find the answer?
@swiftPunk Never faced the issue before but I have written a lot of SwiftUI code since it came out, so it seemed relatively natural to go that way to not have the initializer recalled every time a CircleView was added.
We are both adding new Item to the array, I am adding CircleInfo to array, but you are adding CircleView! why we get deferent results? I mean, we both updating an array for a ForEach, why such a big deference?
ForEach gets a closure as trailing argument. When @State variable is changed it causes SwiftUI to redraw ContentView which in turn makes ForEach loop through circleArray and execute the closure with every item of circleArray. In your version the closure instantiates a new CircleView, in mine that role is delegated to the button and CircleView instances are cached in circleArray

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.