【问题标题】:How to modify/reset @State when other variables are updated in SwiftUI在 SwiftUI 中更新其他变量时如何修改/重置@State
【发布时间】:2019-12-16 22:57:24
【问题描述】:

我想在每次“重新分配”@ObservedObject 时重置一些 @State。如何实现?


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 但这在这里不起作用。

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

【问题讨论】:

  • 我很难理解你的目标。你能把Selection的代码贴出来吗?
  • but I'm not looking to modify state in response to objectWillChange but rather when the object itself is switched out 是一样的还是我遗漏了什么?
  • @Lu_No 不一样。我已经更新了我的代码以明确这一点。
  • @kontiki 这只是一个占位符。可以是任何东西。

标签: ios macos swiftui


【解决方案1】:

目前(Beta 5),最好的方法可能是使用构造函数加上通用ObservableObject 来处理我想在数据更改时重置的项目。这允许保留一些@State,同时重置一些。

在下面的示例中,每次selection 更改时都会重置tapCount,而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
            }
        }
    }
}

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

【讨论】:

  • 你可以使用self._tapCount = State&lt;Int&gt;(initialValue: 0)init中设置状态,只是没有帮助。
  • self._tapCount = State(initialValue: 0) 也可以使用
【解决方案2】:

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

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

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

【讨论】:

  • 好主意。但希望它不使用DispatchQueue
  • 您也可以为此创建一个一次性的计时器发布器,并在.onReceive(timer){} 中设置isDisabled。遗憾的是,如果没有更短的时间框架,我就无法重绘它。
【解决方案3】:

我相信这就是你想要的:

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 不喜欢你的代码,所以我需要做一些其他的更改,而不是仅仅将计数移到父级中

【讨论】:

  • 这又存在要求父母知道孩子内部状态的问题。
猜你喜欢
  • 1970-01-01
  • 2020-04-10
  • 2021-08-06
  • 1970-01-01
  • 2022-12-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-11-10
相关资源
最近更新 更多