Body needlessly recomputed for Views that have @AppStorage and @SceneStorage properties

Originator:indiekiduk
Number:rdar://FB8893705 Date Originated:12/11/2020
Status:Open Resolved:No
Product:SwiftUI Product Version:14.2
Classification:Bug Reproducible:Always
 
Please describe the issue:
I'm reporting unexpected behaviour where a View's body is being recomputed seemingly unnecessarily.  I've included a sample project demonstrating the problem. The parent View has a button that causes a state change, its body is recomputed and child Views are reinitialised. I expected that the body of these child Views will only be recomputed if a property has changed. But in the case of a View that is using@ @AppStorage or @SceneStorage properties is recomputed regardless. This does not happen for a child View that has @State property. 

I did some debugging to the reason why and it seems that with @State the binding location does not change, but with @AppStorage and @SceneStorage the binding location is different. Which is why I think SwiftUI is detecting these Views as different from the previous time they were created and why their bodies are being recomputed.

This issue affects me because I'm trying to design my Views to be as efficient as possible and only be recomputed when really required.

Please list the steps you took to reproduce the issue:
Please run the sample project on the iOS 14.2 Simulator and tap the button. In the console you will see debug output showing when each View's body is recomputed.

What did you expect to happen?
I expected the body not to be recomputed for all Views 1 & 2 like it isn't for View 3.

What actually happened?
As you can see View1 and View2 are recomputed. 

Sample code:

import SwiftUI

struct ContentView: View {
    
    @State private var counter = 0
    
    var body: some View {
        print("ContentView body computed")
        return VStack {
            Text("Hello, world! \(counter)")
            Button("Increment") {
                counter += 1
                
            }
            View1()
            View2()
            View3()
        }
        .padding()
    }
}

struct View1: View {
    @AppStorage("test") private var test = false
    
    var body: some View {
        print("View1 body computed")
        return Text("View1")
    }
}

struct View2: View {
    @SceneStorage("test") private var test = false
    
    var body: some View {
        print("View2 body computed")
        return Text("View2")
    }
}


struct View3: View {
    @State private var test = false
    
    var body: some View {
        print("View3 body computed")
        return Text("View3")
    }
}

Comments


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!