这是我想要做的:

  1. 有一个更改本地状态变量的SwiftUI视图
  2. 点击按钮,将该变量传递给我的应用程序的其他部分

然而,由于某些原因,即使我更新了状态变量,它在传递到下一个视图时也不会更新.

以下是显示问题的一些示例代码:

struct NumberView: View {
    @State var number: Int = 1

    @State private var showNumber = false

    var body: some View {
        NavigationStack {
            VStack(spacing: 40) {
//              Text("\(number)")

                Button {
                    number = 99
                    print(number)
                } label: {
                    Text("Change Number")
                }

                Button {
                    showNumber = true
                } label: {
                    Text("Show Number")
                }
            }
            .fullScreenCover(isPresented: $showNumber) {
                SomeView(number: number)
            }
        }
    }
}

struct SomeView: View {
    let number: Int

    var body: some View {
        Text("\(number)")
    }
}

如果你点击"更改号码",它会将本地状态更新为99.但是,当我创建另一个视图并将其作为参数传递时,它显示1而不是99.到底怎么回事?

以下是一些需要注意的事项:

  • 如果你取消对Text("\(number)")的注释,它会起作用.但这不应该是必要的国际海事组织.
  • 如果您让SomeView使用绑定,它也是有效的.但对于我的应用程序来说,这是行不通的.我的实际用例是" Select 游戏选项"视图.然后,我将创建一个非SwiftUI游戏视图,并希望将这些选项作为参数传递.所以,我不能仅仅因为这个错误就让绑定一直到我的游戏代码.我只想捕获用户输入的内容,并使用该数据创建一个参数对象.
  • 如果你把它改成navigationDestination分,而不是fullScreenCover分,它也是有效的.\(ツ)/对此一无所知...

推荐答案

视图是一个 struct ,因此它的属性是不可变的,所以视图不能更改它自己的属性.这就是为什么要从视图体内部更改名为number的属性需要使用101属性包装器对该属性进行注释.多亏了SWIFT和SwiftUI,透明的读写回调让所看到的值发生了变化.因此,在调用fullScreenCover()时,不能将number作为参数SomeView()传递,而是传递对number的引用,以便系统地调用回调:106.由于不再传递整数来构造struct SomeView,因此此 struct 中名为number的属性的类型不能再是整数,而必须是对整数(即绑定)的引用:为此使用@Binding批注.

因此,将SomeView(number: number)替换为SomeView(number: $number),将let number: Int替换为@Binding var number: Int即可完成这项工作.

以下是正确的源代码:

import SwiftUI

struct NumberView: View {
    @State var number: Int = 1

    @State private var showNumber = false

    var body: some View {
        NavigationStack {
            VStack(spacing: 40) {
//              Text("\(number)")

                Button {
                    number = 99
                    print(number)
                } label: {
                    Text("Change Number")
                }

                Button {
                    showNumber = true
                } label: {
                    Text("Show Number")
                }
            }
            .fullScreenCover(isPresented: $showNumber) {
                SomeView(number: $number)
            }
        }
    }
}

struct SomeView: View {
    @Binding var number: Int

    var body: some View {
        Text("\(number)")
    }
}

毕竟,为了获得有效的源代码,他们的一个小诀窍到目前为止还没有得到解释:如果你简单地将源代码中的Text("Change Number")替换为Text("Change Number \(number)"),而不使用$引用或@Binding关键字,你将看到问题也自动解决了!不需要在SomeView中使用@binding!这是因为SwiftUI在构建视图树时进行了优化.如果它知道显示的视图发生了更改(不仅是它的属性),它将使用更新的@State值计算该视图.将number添加到按钮标签使SwiftUI跟踪number状态属性的更改,并且现在它更新其缓存值以显示文本按钮标签,因此这个新值将被正确地用于创建SomeView.所有这一切可能会被认为是奇怪的事情,但这仅仅是因为SwiftUI的优化.苹果没有完全解释它是如何实现优化构建视图树的,在WWDC活动期间给出了一些信息,但源代码没有开放.因此,您需要严格遵循基于@State@Binding的设计模式,以确保整个工作正常进行.

话虽如此,有人可能会说,Apple说,如果子视图只想访问值:share the state with any child views that also need access, either directly for read-only access, or as a binding for read-write access(https://developer.apple.com/documentation/swiftui/state),那么您不必使用@Binding将值传递给子视图.这是对的,但苹果在同一篇文章中说,你需要place [state] in the highest view in the view hierarchy that needs access to the value.对于苹果来说,需要访问一个值意味着你需要它来显示视图,而不仅仅是进行其他对屏幕没有影响的计算.正是这种解释允许Apple在需要更新NumberView时优化状态属性的计算,例如在计算Text("Change Number \(number)")行的内容时.你可能会发现这真的很棘手.但有一种方法可以理解这一点:取您编写的初始代码,删除var number: Int = 1前面的@State.要编译它,您需要将此行从 struct 内部移到外部,例如,在源文件的第一行,恰好在导入声明之后.你会看到它起作用了!这是因为您不需要此值即可显示NumberView.因此,将值设置得更高是完全合法的,即构建名为SomeView的视图.请注意,这里您不想更新SomeView,因此没有边框效果.但如果你必须更新SomeView,它就不会起作用.

下面是最后一个技巧的代码:

import SwiftUI

// number is declared outside the views!
var number: Int = 1

struct NumberView: View {
    // no more state variable named number!
    // No more modification: the following code is exactly yours!

    @State private var showNumber = false
    
    var body: some View {
        NavigationStack {
            VStack(spacing: 40) {
//              Text("\(number)")

                Button {
                    number = 99
                    print(number)
                } label: {
                    Text("Change Number")
                }

                Button {
                    showNumber = true
                } label: {
                    Text("Show Number")
                }
            }
            .fullScreenCover(isPresented: $showNumber) {
                SomeView(number: number)
            }
        }
    }
}

struct SomeView: View {
    let number: Int
    var body: some View {
        Text("\(number)")
    }
}

This is why you should definitely follow the 100 and 101 design pattern, taking into account that if you declare a state in a view that does not use it to display its content, you should declare this state as a @Binding in child views even if those children do not need to make changes to this state.使用@State的最好方法是在需要它来显示某些东西的最高视图中声明它:不要忘记必须在这个变量的视图中声明@State;创建一个拥有变量但不必使用它来显示其内容的视图是一种反模式.

Ios相关问答推荐

底部导航栏在iOS上浮动

快速相机放大手势不能正常工作

SwiftUI Divider过大了其父视图

无法下载并安装带有Xcode 15.0的iOS 17.0模拟器运行时

使用 Objective-C 创建 iOS 框架

如何使用 Rxswift 处理程序最喜欢的按钮

NavigationLink 只能在文本部分点击

无法在 CollectionView 中禁用部分弹跳 - 组合布局

如何在 SwiftUI 中将选项卡放在滚动视图中?

如何从 Locale Swift 获取货币符号

在 Swift 中映射 MySQL 数据

具有多个目标的应用程序的动态内容

Swiftui为什么在点击第二行之前背景不起作用

从远程通知启动时,iOS 应用程序加载错误

使用swift将图像转换为A4大小的pdf

未找到签名证书​​iOS Distribution

如何识别导航堆栈中的前一个视图控制器

xcode 5.1:libCordova.a 架构问题

openURL:在 iOS 10 中已弃用

iOS13状态栏背景 colored颜色 与大文本模式下的导航栏不同