如何在 SwiftUI 视图中执行非视图代码

How to execute non-view code inside a SwiftUI view

我一遍又一遍地为此苦苦挣扎,所以我想我错过了什么。我需要做数学运算、进行设置、分配一个值或响应某些用户操作的许多简单操作中的任何一个,例如此处显示的示例,而 SwiftUI 想要一个我不需要视图的视图。必须有办法绕过 ViewBuilder 的规则。我通过创建一个不必要的视图并在视图的 init() 中执行我需要的代码来解决这个问题,但这看起来非常尴尬。

import SwiftUI

struct ContentView: View
{
    @State var showStuff = false

    var body: some View
    {
        VStack
        {
            Toggle(isOn: $showStuff)
            {
                Text("Label")
            }
            if showStuff
            {
                UserDefaults.standard.set(true, forKey: "Something")
            }
        }
    }
}

你不能做你想做的事,因为实际上 body 中的每个视图块都是一个 ViewBuidler.buildBlock 函数参数。 IE。您在函数 参数 space 中。我希望你不会期望像

这样的表达

foo(Toggle(), if showStuff { ... } )

会起作用(假设 foofunc foo(args: View...)。但这是您在 body 中尝试做的事情。

因此 SwiftUI 中的表达式必须在 ViewBuilder 块之外(ViewBuilder 本身支持视图的一些例外情况)。

这是针对您的情况的解决方案:

SwiftUI 2.0

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

    var body: some View {
        VStack {
            Toggle(isOn: $showStuff) {
                Text("Label")
            }
        }
    }
}

SwiftUI 1.0

在已经解决的地方找到SwiftUI toggle switches

注:View.body(不包括一些动作修饰符)等同于UIView.draw(_ rect:)...你不把UserDefaults存入draw(_rect:),你呢?

在 SwiftUI 2.0 中,有一个新的 ViewModifier onChange(of:perform:),它允许您对值的变化做出反应。

但是你可以用一个巧妙的技巧来创建类似的东西(我忘记了我在哪里看到的,所以不幸的是我不能留下正确的归属),通过用 onChange 扩展 Binding方法:

extension Binding {
   func onChange(perform action: @escaping (Value, Value) -> Void) -> Self {
      .init(
         get: { self.wrappedValue },
         set: { newValue in
            let oldValue = self.wrappedValue
            DispatchQueue.main.async { action(newValue, oldValue) }
            self.wrappedValue = newValue
         })
   }
}

你可以这样使用它:

Toggle(isOn: $showStuff.onChange(perform: { (new, old) in
  if new {
     UserDefaults.standard.set(true, forKey: "Something")
  }
}))

方式 1(最佳):

struct ExecuteCode : View {
    init( _ codeToExec: () -> () ) {
        codeToExec()
    }
    
    var body: some View {
        return EmptyView()
    }
}

用法:

HStack {
    ExecuteCode { 
        print("SomeView1 was re-drawn!") 
    }

    SomeView1()
}

方式二:

(我的第一种方法更好 - 你在这里只能编写简单的代码)

带有 let _ = 的代码在 View 内部工作!

HStack {
    let _ = print("SomeView1 was re-drawn!")

    SomeView1()
}

方式三:

(我的第一种方法更好-代码结构太难了)

HStack {
    // here is the magic
    { () -> SomeView1() in
        // here is code to execute
        print("SomeView1 was re-drawn!")
       
        
        // here is the magic
        return SomeView1()
    }
}

视图实际上是所谓的Function Builders,视图主体的内容用作buildBlock函数的参数,如@Asperi所述.

如果必须 运行 在此上下文中编写代码,则另一种解决方案是使用 returns 所需视图的闭包:

VStack {
    // ... some views ...
    { () -> Text in
      // ... any code ...
      return Text("some view") }()
    // ... some views ...
}