如何在 SwiftUI 中使用 inout 而不是 Binding?

Posted

技术标签:

【中文标题】如何在 SwiftUI 中使用 inout 而不是 Binding?【英文标题】:How can I use inout instead of Binding in SwiftUI? 【发布时间】:2021-04-04 09:32:06 【问题描述】:

我想知道我是否可以通过 inout 而不是 Binding 使我的视图函数绑定自身,是否有可能为我们提供正确/更好的语法?知道我收到以下编译时错误:

Modifying state during view update, this will cause undefined behavior.

这对我来说是可以理解的,我想也许我在这项工作中使用了错误的语法。

PS:我完全了解 Binding 及其用例,如果我们也可以使用 inout,这个问题尝试找到答案。


func TextView(inPutValue: inout Bool) -> some View 

    return Text("Hello")
        .onTapGesture  inPutValue.toggle() 
    


用例:

struct ContentView: View 

    @State private var isOn: Bool = true

    var body: some View 
        
        TextView(inPutValue: &isOn)
 
    
    



更新:

import SwiftUI

struct ContentView: View 

    @State private var value: Int = Int()  didSet  print(value.description)  

    var body: some View 

        Button("update State via inout")  inoutFunction(incomingValue: &value) 
        
    



func inoutFunction(incomingValue: inout Int)  incomingValue += 1 

【问题讨论】:

【参考方案1】:

您不能在这里使用inout 的原因是因为inout 具有copy-in-copy-out 行为。该值作为副本传递给函数,一旦函数返回,修改后的副本就会被复制回来。您不应该将其视为传递引用,而是认为可以通过这种方式实现优化。

现在知道了,Swift 编译器禁止你在转义闭包中使用 inout 参数是很有意义的,例如在 onTapGesture 中使用 inPutValue。毕竟,修改后的inPutValue 仅在TextView 返回时才被复制回来,而不是在有人点击它时,因此您在onTapGesture 中对其进行的任何修改对于TextView 的调用者来说根本不可见。另见this answer。

因此,一旦TextView 返回,该值就会被复制回调用者。如错误消息所述,不允许在计算 body(即“视图更新期间”)时直接修改 @State

现在我们来看看Button的情况。请注意,这一次,对inoutFunction(incomingValue: &value) 的调用发生在按钮的单击处理程序中,而不是在body 中——这意味着当按下按钮时将写入value。这是允许的。

您可以通过添加闭包参数使您的TextViewButton 的初始化程序具有相似的形式:

func TextView(update: @escaping () -> Void) -> some View 

    return Text("Hello")
        .onTapGesture(perform: update)
    

注意这里根本没有inout,就像Button.init中没有inout一样。

然后您可以使用inout 参数编写您的函数:

func inoutFunction(_ b: inout Bool) 
    b.toggle()

并使用它:

@State private var isOn = true

var body: some View 
    TextView  inoutFunction(incomingValue: &isOn) 

请注意,您根本不需要 inout

TextView  isOn.toggle() 

【讨论】:

哇!非常感谢,我就像您看到我的观点一样,为什么我期望我的代码可以工作,因为 onTapGesture 正在更改正文中未用作渲染参考的值,这就是我这样做的原因应该可以工作,因为我刚刚将一个状态传递给 TextView,这在正文中根本没有任何关系,但是在你解释之后,这一切现在对我来说都可以理解了。 这不是改变游戏规则吗?通过这种方式,我们可以直接进行修改,而无需在两者之间使用 getter 和 setter,你不这么认为吗? 我不确定我是否理解。你在说什么“方式”? @swiftPunk 你的回答我的意思是,我们也可以用普通的绑定方式! @swiftPunk 通过绑定,您可以抽象出toggle 调用,即调用者不需要每次都编写isOn.toggle() 调用。调用者只是传递$isOn,被调用者知道如何处理(应该设置什么值)。

以上是关于如何在 SwiftUI 中使用 inout 而不是 Binding?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 PostgreSQL 13 中使用 INOUT 参数调用存储过程(不是函数)

如何创建一个带有输入的闭包结构参数,在 SwiftUI 中返回“some View”而不是“AnyView”?

如何显示 SwiftUI 视图而不是 UIViewController?

如何让 SwiftUI 应用程序使用 Firebase Functions 模拟器而不是生产环境?

SwiftUI如何在更新已发布数组中的对象而不是数组本身时更新视图

如何让 SwiftUI SidebarMenu 每次都显示相同的 DetailView 而不是创建一个新的(在 macOS 上)