4

Here is a simple example. You can create new SwiftUI iOS project and copy it to ContentView file.

import SwiftUI

struct Settings {
    static let onOff = "onOff"
}

struct ContentView: View {
    @AppStorage(wrappedValue: false, Settings.onOff) var onOff
    
    var body: some View {
        NavigationView {
            GeometryReader { reader in // < Comment out this line
                List {
                    Section (header:
                                VStack {
                                    HStack {
                                        Spacer()
                                        VStack {
                                            Text("THIS SHOULD BE FULL-WIDTH")
                                            Text("It is thanks to GeometryReader")
                                        }
                                        Spacer()
                                    }
                                    .padding()
                                    .background(Color.yellow)
                                    
                                    HStack {
                                        Text("This should update from AppStorage: ")
                                        Spacer()
                                        Text(onOff == true ? "ON" : "OFF")
                                    }
                                    .padding()
                                }
                                .frame(width: reader.size.width) // < Comment out this line
                                .textCase(nil)
                                .font(.body)
                    ) {
                        Toggle(isOn: $onOff) {
                            Text("ON / OFF")
                        }
                    }
                    
                }
                .listStyle(GroupedListStyle())
            } // < Comment out this line
            
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

I have 3 elements:

  1. Text with yellow background - I need it full-width and I use GeometryReader to do it.
  2. Text. Last word should switch ON/OFF based on toggle value. This is just for testing purposes to check if AppStorage works correctly.
  3. Toggle - switches onOff variable and saves it to AppStorage (UserDefaults).

AppStorage works perfectly only without GeometryReader. Please comment out 3 tagged lines to check it out.

Is it a bug? Or something is wrong with my AppStorage code? Or maybe GeometryReader part is wrong? If I could set yellow part full-width, I could drop GeometryReader completely.

mallow
  • 2,368
  • 2
  • 22
  • 63

2 Answers2

3

One solution that works in my testing is to factor out the GeometryReader's content, including the @AppStorage:

struct ContentView: View {
    var body: some View {
        NavigationView {
            GeometryReader { proxy in
                _ContentView(width: proxy.size.width)
            }
        }
    }
}

struct _ContentView: View {
    var width: CGFloat
    @AppStorage(wrappedValue: false, Settings.onOff) var onOff

    var body: some View {
        List {
            Section(header: header) {
                Toggle(isOn: $onOff) {
                    Text("ON / OFF")
                }
            }

        }
        .listStyle(GroupedListStyle())
    }

    var header: some View {
        VStack {
            VStack {
                Text("THIS SHOULD BE FULL-WIDTH")
                Text("It is thanks to GeometryReader")
            }
            .padding()
            .frame(width: width)
            .background(Color.yellow)

            HStack {
                Text("This should update from AppStorage: ")
                Spacer()
                Text(onOff == true ? "ON" : "OFF")
            }
            .padding()
        }
        .textCase(nil)
        .font(.body)
    }
}
rob mayoff
  • 375,296
  • 67
  • 796
  • 848
  • Thanks, Rob! It works :) So it seems like a bug. GeometryReader or AppStorage has some problems with how data flow work in SwiftUI. Your solutions forces the view to refresh properly. I will file this bug to Apple. With your solution. Thanks a lot – mallow Sep 07 '21 at 11:47
2

Yes, this is definitely a bug. Looks like AppStorage doesn't behave completely same as State for some reason, and change doesn't trigger updates inside GeometryReader. Example can be reduced to this:

struct ContentView: View {
    @AppStorage("onOff") var onOff = false

    var body: some View {
        VStack {
            Text("Text updating: " + (onOff == true ? "ON" : "OFF"))
            GeometryReader { reader in
                Toggle(isOn: $onOff) {
                    Text("Text not updating: " + (onOff == true ? "ON" : "OFF"))
                }
            }
        }
    }
}

This is not even fixed in the latest iOS 15 beta. You can try to hack with some duplicate state variable, but instead I suggest you switching to custom AppStorage, it works fine in this circumstances.

Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
  • Thank you, Philip. I didn't know about custom AppStorage. Thanks for link. So far I am trying to write a code without any dependencies, so I think I will your other suggestion - using another state variable. But right now Rob's solution works as well. – mallow Sep 07 '21 at 11:50
  • @mallow sure I understand. I prefer not using some huge dependencies, but in this case the solution source code is only around 60 lines of code(the rest is just boilerplate initializers for different types), which if fine to me. And I can copy and modify it easily, if I need, unlike the system one, and don't need to hack it with moving `GeometryReader` out of the view. But it's up to you. – Phil Dukhov Sep 07 '21 at 13:04
  • In my case it was just easier to move GeometryReader, because I have it in just 1 place. And apart from this AppStorage works good for me. But it’s great you posted this link, because in other project your solution could work better. Also I still don’t have a lot of experience with external libraries and Swift packages :) – mallow Sep 07 '21 at 13:08