Inconsistency in how Group's onAppear and onDisappear are called

Something that I noticed today and didn't expect it is that if a Group is not the root view of the screen, its onAppear is called per each child, while for a Group that is the root, the method is called once, regardless of the number of its children. For example:

struct ContentView: View {
    var body: some View {
        Group { // Group is the root of the screen, onAppear is called once
            Group {  // non-root view, onAppear is called once per each child
                Color(.red)
                Color(.yellow)
            }
            .onAppear { print(".:. onAppear2") }
            .onDisappear { print(".:. onDisappear2") }
            
            Color(.blue)
            Color(.purple)
        }
        .onAppear { print(".:. onAppear1") }
        .onDisappear { print(".:. onDisappear1") }
    }
}
/* Output:
.:. onAppear1
.:. onAppear2
.:. onAppear2
*/

It becomes more interesting when you learn that if the combination of the children of a non-root Group changes while the view is being displayed, onDisappear is called for each child that gets removed and onAppear is called when a new child gets added. For example, in the next sample code, within the non-root Group, there is a condition (counter.counter == nil) that at the beginning is true, so the Group has 3 children and onAppear is called 3 times. After 1 second, the condition becomes false and the first child gets removed, and interestingly onDisappear of Group is called!

class Counter: ObservableObject {
    @Published var counter: Int? = nil

    init() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            self.counter = 1
        }
    }
}
struct IceCreamListView: View {
    @ObservedObject private var counter = Counter()

    var body: some View {
        Group {
            Group {
                if counter.counter == nil {
                    Color(.red)
                }
                Color(.red)
                Color(.yellow)
            }
            .onAppear { print(".:. onAppear2") }
            .onDisappear { print(".:. onDisappear2") }
            
            Color(.blue)
            Color(.purple)
        }
        .onAppear { print(".:. onAppear1") }
        .onDisappear { print(".:. onDisappear1") }
    }
}
/* Output:
.:. onAppear1
.:. onAppear2
.:. onAppear2
.:. onAppear2
.:. onDisappear2
*/

I have tested these pieces of code on:
Xcode Version 11.4.1 (11E503a)
Xcode Version 11.5 (11E608c)

1 Like