How can I remove already rendered unneeded Views instead of rendering needed Views?

Viewed 366

I am working on a CustomForEach which would act and work like a normal ForEach in SwiftUI, this CustomForEach has it own early days and it has some issues for use for me, which makes me to learn more about SwiftUI and challenge me to try to solve the issues, one of this issues is finding a way to destroy the unneeded Views instated of rendering all needed Views!

Currently when I update lowerBound the CustomForEach starts rendering for new range which is understandable. But the new range need less Views than before and that is not understandable to rendering them again for already rendered Views.

Goal: I want find a way to stop rendering all needed Views because they are already exist and there is no need to rendering again, and just removing the unneeded Views. And also I do not want start an another expensive calculation inside CustomForEach for finding out if the Views already exist!

struct TextView: View {
    let string: String

    var body: some View {            
        print("rendering " + string)
        return HStack {
            Text(string)
            Circle().fill(Color.red).frame(width: 5, height: 5, alignment: .center)
        }
    }
}

struct CustomForEachView<Content: View>: View {
    private let id: Int
    let range: ClosedRange<Int>
    let content: (Int) -> Content
    
    init(range: ClosedRange<Int>, @ViewBuilder content: @escaping (Int) -> Content) {
        self.id = range.lowerBound
        self.range = range
        self.content = content
    }
    
    // The issue is rendering all existed Views when lower Bound get updated, even we do not need to render new View in updating lower Bound!
    
    var body: some View {
        content(range.lowerBound)
        if let suffixRange = suffix(of: range) { 
            CustomForEachView(range: suffixRange, content: content)
        }
    }
    
    private func suffix(of range: ClosedRange<Int>) -> ClosedRange<Int>? {
        return (range.count > 1) ? (range.lowerBound + 1)...range.upperBound : nil
    }
}

struct ContentView: View {
    @State private var lowerBound: Int = -2
    @State private var upperBound: Int = 2
    
    var body: some View {
        HStack {
            CustomForEachView(range: lowerBound...upperBound) { item in
                TextView(string: item.description)                    
            }
        }
        
        HStack {
            Button("add lowerBound") { lowerBound += 1 }
            Spacer()
            Button("add upperBound") { upperBound += 1 }
        }
        .padding()
    }
}
1 Answers

First of all, one thing important thing to understand is that a SwiftUI.View struct is not a view instance that is rendered on the screen. It's merely a description of the desired view hierarchy. The SwiftUI.View instances are going to be recreated and torn down a lot by the framework anyway.

The SwiftUI framework takes care of the actual rendering. It might use UIViews for this, or it might not. That's an implementation detail you shouldn't need to worry about in most cases.


That said, you might be able to help the framework by adding explicit ids to the views by using the id modifier. That way SwiftUI can use that to keep track of which view is which.

But, I'm not sure if that would actually help. Just an idea.

Related