在SwiftUI中更新其他变量时如何修改/重置@State

时间:2019-08-09 08:13:30

标签: ios macos swiftui

每次@State被“重新分配”时,我想重置一些@ObservedObject。如何做到这一点?


class Selection: ObservableObject {
    let id = UUID()
}

struct ContentView: View {
  @State var selectedIndex: Int = 0
  var selections: [Selection] = [Selection(), Selection(), Selection()]
  var body: some View {
        VStack {
            // Assume the user can select something so selectedIndex changes all the time
            // SwiftUI will just keep updating the same presentation layer with new data
            Button("Next") {
                self.selectedIndex += 1
                if self.selectedIndex >= self.selections.count {
                    self.selectedIndex = 0
                }
            }
            SelectionDisplayer(selection: self.selections[self.selectedIndex])
        }
  }
}

struct SelectionDisplayer: View {
    @ObservedObject var selection: Selection {
        didSet { // Wish there were something *like* this
            print("will never happen")
            self.tabIndex = 0
        }
    }

  @State var tapCount: Int = 0 // Reset this to 0 when `selection` changes from one object to another

  var body: some View {
        Text(self.selection.id.description)
            .onReceive(self.selection.objectWillChange) {
                print("will never happen")
        }
        Button("Tap Count: \(self.tapCount)") {
            self.tapCount += 1
        }
    }
}

我知道onReceive,但我不是想响应objectWillChange来修改状态,而是当对象本身被关闭时。在具有引用类型的UIKit中,我将使用didSet,但这在这里不起作用。

我确实尝试为此使用PreferenceKeygist),但这似乎太过分了。

3 个答案:

答案 0 :(得分:1)

为我完成此操作的唯一方法是通过暂时隐藏子视图来强制父视图重画该子视图。这是一种hack,但是替代方法是传入$tapCount,因为情况更糟,因为这样父级不仅必须知道必须重绘它,而且还必须知道其内部状态。

这可能可以重构为它自己的视图,从而使其不那么肮脏。

import Foundation
import SwiftUI
import Combine

class Selection {
    let id = UUID()
}

struct RedirectStateChangeView: View {
    @State var selectedIndex: Int = 0
    @State var isDisabled = false
    var selections: [Selection] = [Selection(), Selection(), Selection()]
    var body: some View {
        VStack {
            // Assume the user can select something so selectedIndex changes all the time
            // SwiftUI will just keep updating the same presentation layer with new data
            Button("Next") {
                self.selectedIndex += 1
                if self.selectedIndex >= self.selections.count {
                    self.selectedIndex = 0
                }
                self.isDisabled = true
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.005) {
                    self.isDisabled = false
                }
            }
            if !isDisabled {
                SelectionDisplayer(selection: selections[selectedIndex])
            }
        }
    }
}

struct SelectionDisplayer: View {
    var selection: Selection
    @State var tapCount: Int = 0 // Reset this to 0 when `selection` changes from one object to another

    var body: some View {
        VStack{
            Text(selection.id.description)
            Button("Tap Count: \(self.tapCount)") {
                self.tapCount += 1
            }
        }
    }
}

答案 1 :(得分:1)

当前(测试版5),最好的方法可能是对数据更改时要重置的项目使用构造函数和通用ObservableObject。这样可以保留一些@State,而有些则被重置。

在下面的示例中,tapCount每次更改都会重置selection,而allTimeTaps不会更改。

class StateHolder<Value>: ObservableObject {
    @Published var value: Value
    init(value: Value) {
        self.value = value
    }
}

struct ContentView: View {

  @State var selectedIndex: Int = 0
  var selections: [Selection] = [Selection(), Selection(), Selection()]
  var body: some View {
        VStack {
            // Assume the user can select something so selectedIndex changes all the time
            // SwiftUI will just keep updating the same presentation though
            Button("Next") {
                self.selectedIndex += 1
                if self.selectedIndex >= self.selections.count {
                    self.selectedIndex = 0
                }
            }
            SelectionDisplayer(selection: self.selections[self.selectedIndex])
        }
  }
}

struct SelectionDisplayer: View {

    struct SelectionState {
        var tapCount: Int = 0
    }

    @ObservedObject var selection: Selection
    @ObservedObject var stateHolder: StateHolder<SelectionState>
    @State var allTimeTaps: Int = 0

    init(selection: Selection) {
        let state = SelectionState()
        self.stateHolder = StateHolder(value: state)
        self.selection = selection
    }

  var body: some View {
        VStack {
            Text(self.selection.id.description)
            Text("All Time Taps: \(self.allTimeTaps)")
            Text("Tap Count: \(self.stateHolder.value.tapCount)")
            Button("Tap") {
                self.stateHolder.value.tapCount += 1
                self.allTimeTaps += 1
            }
        }
    }
}

在寻找解决方案时,我很感兴趣地发现您无法在@State中初始化init变量。编译器会抱怨在访问self之前尚未设置所有属性。

答案 2 :(得分:0)

这就是您想要的,我相信:

class Selection: ObservableObject { let id = UUID() }

struct ContentView: View {
  @State var selectedIndex: Int = 0
  var selections: [Selection] = [Selection(), Selection(), Selection()]
  @State var count = 0

  var body: some View {
        VStack {
            Button("Next") {
                self.count = 0
                self.selectedIndex += 1
                if self.selectedIndex >= self.selections.count {
                    self.selectedIndex = 0
                }
            }
            SelectionDisplayer(selection: self.selections[self.selectedIndex], count: $count)
        }
  }
}

struct SelectionDisplayer: View {
    @ObservedObject var selection: Selection
    @Binding var count: Int

    var body: some View {
        VStack {
            Text("\(self.selection.id)")
            Button("Tap Count: \(self.count)") { self.count += 1 }
        }
    }
}

我的Xcode不喜欢您的代码,因此我需要做一些其他更改,而不仅仅是将计数移到父级