【SwiftUI】GeometryReader内で@AppStorageが再描画されない場合の対処法

SwiftUI

GeometryReader下で@AppStorageでマークした変数を使用して描画を行なっていても、
変数の変化により再描画されない場合があります。

こういった場合はGeometryReaderを別のViewに分ける事で解決します。

@Stateや@SceneStorageで作って置いて、後から@AppStorageに変えると気づき難いので注意しましょう。

再現するコード

今回はXcode14.2、iOS16.2で実行しています。

struct ContentView: View {
    
    @State private var stateFlag = true
    @AppStorage("AppStorage") private var appStorageFlag = true
    
    var body: some View {
        GeometryReader { geometry in
            VStack {
                HStack {
                    Button("StateFlag"){
                        stateFlag.toggle()
                    }
                    Text(stateFlag ? "True" : "False")
                }.padding()
                
                HStack {
                    Button("AppStorageFlag (Issue)"){
                        appStorageFlag.toggle()
                    }
                    Text(appStorageFlag ? "True" : "False")
                }.padding()
                
                ChildView().padding()
            }
        }.padding()
    }
}


struct ChildView:View{
    
    @AppStorage("AppStorage") private var appStorageFlag = true
    
    var body: some View{
        VStack {
            HStack {
                Button("AppStorageFlag in Child"){
                    appStorageFlag.toggle()
                }
                Text(appStorageFlag ? "True" : "False")
            }
        }
    }
}

解説

この問題は@AppStorageとGeometryReaderが同一Viewに存在していると発生します。
この場合に正常に変更を検知せず再描画されません。
1度目の変更のみ再描画されて2度目以降は再描画されなくなっています。

@Stateの方は問題なく再描画されます。※SceneStorageも問題なく動きます。
また@AppStorageも値自体は変わっているのでこの時に表示が変わります。

解決法としてはChildViewのように@AppStorageをGeometryReaderと別のViewにしてしまえば問題ありません。
なお、この時ChildViewで@AppStorageを変更しても、親のContentView側では変更を検知できていないので再描画されません。

ChildViewでGeometryReaderの値を使用したい場合はGeometryProxyを渡してください。

GeometryReader { geometry in
    ChildView(geometry:geometry)
}

struct ChildView:View{
    
    let geometry:GeometryProxy
    @AppStorage("AppStorage") private var appStorageFlag = true

}

コメント

タイトルとURLをコピーしました