SwiftUI - AppStorage不能与GeometryReader一起使用

5

以下是一个简单的例子。您可以创建新的SwiftUI iOS项目并将其复制到ContentView文件中。

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()
    }
}

我有3个元素:

  1. 黄色背景文字 - 我需要它全宽并使用GeometryReader来实现。
  2. 普通文本。最后一个词应该根据切换值ON/OFF切换。这只是为了测试AppStorage是否正确工作。
  3. 开关按钮 - 切换onOff变量并将其保存到AppStorage(UserDefaults)中。

AppStorage在没有GeometryReader时完美工作。请将标记的3行注释掉以进行检查。

这是一个bug吗?还是我的AppStorage代码有问题?或者GeometryReader部分有问题?如果我能将黄色部分全屏幕显示,我就可以完全放弃GeometryReader。


1
这个漏洞在Xcode 14/iOS 16中一直存在!感谢这个帖子,它对我很有帮助。 - alex.bour
我他妈的浪费了我的时间。 - MJ Studio
我认为ScrollViewReader有同样的bug。 - undefined
2个回答

4
在我的测试中有效的一种解决方案是将GeometryReader的内容分解出来,包括@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! 它可用了 :) 看来这是个 bug。GeometryReader 或 AppStorage 在 SwiftUI 中数据流的处理方式上存在一些问题。你的解决方案可以强制刷新视图。我会向 Apple 报告这个 bug,使用你的解决方案。非常感谢! - mallow
我用你的方法解决了一个类似的问题,使用ScrollViewReader。谢谢! - undefined

3

是的,这绝对是一个bug。看起来AppStorageState的行为不完全相同,因此更改不会在GeometryReader内触发更新。可以将示例简化为以下内容:

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"))
                }
            }
        }
    }
}

即使在最新的iOS 15测试版中,这个问题仍然没有被解决。你可以尝试使用一些重复状态变量来进行黑客攻击,但我建议你改用自定义AppStorage,在这种情况下它可以正常工作。


谢谢你,Philip。我不知道自定义AppStorage。感谢提供链接。到目前为止,我正在尝试编写没有任何依赖关系的代码,所以我想我会使用你的另一个建议-使用另一个状态变量。但是现在Rob的解决方案也可以工作。 - mallow
@mallow 确定我理解了。我更喜欢不使用一些庞大的依赖项,但在这种情况下,解决方案源代码只有约60行代码(其余部分只是针对不同类型的样板初始化器),这对我来说很好。如果需要,我可以轻松地复制和修改它,而不像系统那样需要将GeometryReader移出视图进行黑客攻击。但这取决于你。 - Phil Dukhov
在我的情况下,将GeometryReader移动起来更容易,因为我只在一个地方使用它。除此之外,AppStorage对我来说很有效。但是你提供的链接很好,因为在其他项目中,你的解决方案可能更适用。此外,我仍然没有很多关于外部库和Swift包的经验 :) - mallow

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接