swiftuiswiftui-scrollviewappstoragescrollviewreader

@AppStorage var inside ScrollViewReader does not update view


I have an issue with @AppStorage and ScrollViewReader that seems like a bug.

My @AppStorage variable fontSize is supposed to adjust the text size inside a ScrollViewReader. However, when its value changes, it does not update the view.

When I remove ScrollViewReader or change @AppStorage to @State, it will update.

What's wrong and how can I fix it?

import SwiftUI
struct ContentView: View {
    @AppStorage("fontSize") var fontSize: Int = 12
    // @State var fontSize: Int = 12
    
    var body: some View {
        VStack {
            Text("Hello, bigger world!")
                .font(.system(size: CGFloat(fontSize)))
            ScrollViewReader { proxy in
                ScrollView {
                    Text("Hello, bigger world!")
                    // @AppStorage var inside ScrollViewReader does not update?
                        .font(.system(size: CGFloat(fontSize)))
                }
            }
            Button("toggle font size") {
                fontSize += 2
            }
            .keyboardShortcut("+")
            Text("\(fontSize)")
        }
        .padding()
        .fixedSize()
        .onAppear {
            DispatchQueue.main.async {
                fontSize = 12
            }
        }
    }
}

macOS 12.6.9, Xcode 14.2 (SDK macOS 13.1)


Solution

  • I found my own solution, analogous to this solution for GeometryReader. I split the ScrollView to a separate view:

    import SwiftUI
    struct ContentView: View {
        @AppStorage("fontSize") var fontSize: Int = 12
        
        var body: some View {
            VStack {
                Text("Hello, bigger world!")
                    .font(.system(size: CGFloat(fontSize)))
                ScrollViewReader { proxy in
                    ScrollViewContainer(fontSize: $fontSize, proxy: proxy)
                }
                Button("toggle font size") {
                    fontSize += 2
                }
                .keyboardShortcut("+")
                Text("\(fontSize)")
            }
            .padding()
            .fixedSize()
            .onAppear {
                DispatchQueue.main.async {
                    fontSize = 12
                }
            }
        }
    }
    
    /// A Container view to make @AppStorage value binding work (SwiftUI bug)
    struct ScrollViewContainer: View {
        @Binding var fontSize: Int
        var proxy: ScrollViewProxy
        
        var body: some View {
            ScrollView {
                Text("Hello, bigger world!")
                    .font(.system(size: CGFloat(fontSize)))
            }
        }
    }
    

    Using proxy with .scrollTo(ID) in this new view also works, which is the whole point of having ScrollViewReader.