swift - SwiftUI,为什么部分视图没有刷新?@State 变量未更新
问题描述
这是我在我的 SwiftUI 代码中遇到的问题的再现。假设有一个设备列表,并且每个我都有一些连接信息(启用或未启用)。
使用 SwiftUI 中的经典列表,每个设备都会显示一个 DeviceView。为方便起见,我想在顶部添加几个箭头来在设备之间切换,而无需每次都返回列表(理解,就像 iOS 中的邮件应用程序)。因此,我没有从列表中调用 DeviceView,而是在调用存储当前设备信息的 MotherDeviceView 之前。当我点击顶部的箭头时, MotherDeviceView 再次调用 DeviceView 并更新视图。
struct Device: Identifiable {
var id = Int()
var deviceName = String()
var isWifiOn = Bool()
var isCellularOn = Bool()
var isBluetoothOn = Bool()
}
let devices: [Device] = [
Device(id: 0, deviceName: "iPhone", isWifiOn: true, isCellularOn: false, isBluetoothOn: true),
Device(id: 1, deviceName: "iPod", isWifiOn: false, isCellularOn: false, isBluetoothOn: true),
Device(id: 2, deviceName: "iPad", isWifiOn: false, isCellularOn: true, isBluetoothOn: false)
]
// main view, with list of devices
struct DeviceTab: View {
var body: some View {
NavigationView {
List(devices) { device in
NavigationLink(destination: MotherDeviceView(device: device)){
Text(device.deviceName)
}
}.navigationBarTitle(Text("Devices"))
}
}
}
// the list call this view and pass the device to DeviceView.
// Using this view is possible to go to the other device using the arrows on the top
// without returning to the list
struct MotherDeviceView: View {
@State var device: Device
var body: some View {
VStack{
DeviceView(device: $device)
}
.navigationBarItems(trailing: arrows)
}
private var arrows: some View {
HStack{
Button(action: { self.previous() },
label: { Image(systemName: "chevron.up") } )
.padding(.horizontal, 10)
Button(action: { self.next() },
label: { Image(systemName: "chevron.down") } )
}
}
func previous() {
if device.id == 0 { return }
device = devices[device.id - 1]
}
func next() {
if device.id == 2 { return }
device = devices[device.id + 1]
}
}
// DeviceView
struct DeviceView: View {
@Binding var device: Device
var body: some View {
VStack{
Spacer()
Text("This is the device number: " + String(device.id))
Spacer()
ToggleSection(dev: $device)
Spacer()
}.navigationBarTitle(device.deviceName)
}
}
// Toggle part of DeviceView
struct ToggleSection: View {
@Binding var dev: Device
@State var toggleOn: [Bool] = [false, false, false]
var body: some View {
VStack{
Text(dev.deviceName)
.fontWeight(.bold)
Toggle(isOn: $toggleOn[0], label: { Text("Wifi") } )
.padding()
Toggle(isOn: $toggleOn[1], label: { Text("Cellular") } )
.padding()
Toggle(isOn: $toggleOn[2], label: { Text("Bluetooth") } )
.padding()
}
.onAppear{ self.initialConfigToggle() }
// with onAppear is okay, but when I use the top arrows, this section does not update
}
private func initialConfigToggle() {
self.toggleOn[0] = self.dev.isWifiOn
self.toggleOn[1] = self.dev.isCellularOn
self.toggleOn[2] = self.dev.isBluetoothOn
}
}
但是DeviceView中有一个部分ToggleSection不会更新,我不知道如何解决这个问题。也许强制更新?但我认为这不是正确的答案(但我什至不知道如何强制更新)。
我认为如果在 ToggleSection 中有 @Binding 而不是 @State 可能是正确的答案,但这不起作用。这个例子可以工作(除了未更新的切换)
提前致谢!
解决方案
这是一种可能的方法,它为设备存储添加了显式视图模型并加入了所有视图,以便在该存储中精确地进行修改(避免应对设备值)。
使用 Xcode 11.4 / iOS 13.4 测试
修改后的代码:
import SwiftUI
import Combine
struct Device: Identifiable {
var id = Int()
var deviceName = String()
var isWifiOn = Bool()
var isCellularOn = Bool()
var isBluetoothOn = Bool()
}
class DeviceStorage: ObservableObject {
@Published var devices: [Device] = [
Device(id: 0, deviceName: "iPhone", isWifiOn: true, isCellularOn: false, isBluetoothOn: true),
Device(id: 1, deviceName: "iPod", isWifiOn: false, isCellularOn: false, isBluetoothOn: true),
Device(id: 2, deviceName: "iPad", isWifiOn: false, isCellularOn: true, isBluetoothOn: false)
]
}
// main view, with list of devices
struct DeviceTab: View {
@ObservedObject var vm = DeviceStorage() // for demo, can be injected via init
var body: some View {
NavigationView {
List(Array(vm.devices.enumerated()), id: \.element.id) { i, device in
NavigationLink(destination: MotherDeviceView(vm: self.vm, selectedDevice: i)) {
Text(device.deviceName)
}
}.navigationBarTitle(Text("Devices"))
}
}
}
struct MotherDeviceView: View {
@ObservedObject var vm: DeviceStorage // reference type, so have same
@State private var selected: Int // selection index
init(vm: DeviceStorage, selectedDevice: Int) {
self.vm = vm
self._selected = State<Int>(initialValue: selectedDevice)
}
var body: some View {
VStack{
DeviceView(device: $vm.devices[selected]) // bind to selected
}
.navigationBarItems(trailing: arrows)
}
private var arrows: some View {
HStack{
Button(action: { self.previous() },
label: { Image(systemName: "chevron.up") } )
.padding(.horizontal, 10)
Button(action: { self.next() },
label: { Image(systemName: "chevron.down") } )
}
}
func previous() {
if vm.devices[selected].id == 0 { return }
selected -= 1 // change selection
}
func next() {
if vm.devices[selected].id == 2 { return }
selected += 1 // change selection
}
}
// DeviceView
struct DeviceView: View {
@Binding var device: Device
var body: some View {
VStack{
Spacer()
Text("This is the device number: " + String(device.id))
Spacer()
ToggleSection(dev: $device)
Spacer()
}.navigationBarTitle(device.deviceName)
}
}
// Toggle part of DeviceView
struct ToggleSection: View {
@Binding var dev: Device
var body: some View {
VStack{
Text(dev.deviceName)
.fontWeight(.bold)
// all below bound directly to model !!!
Toggle(isOn: $dev.isWifiOn, label: { Text("Wifi") } )
.padding()
Toggle(isOn: $dev.isCellularOn, label: { Text("Cellular") } )
.padding()
Toggle(isOn: $dev.isBluetoothOn, label: { Text("Bluetooth") } )
.padding()
}
}
}
推荐阅读
- swift - UITableView 或 UILabel 在使用委托和协议观察者方法时发现 NIL,因为它在 viewDidLoad 之前调用
- javascript - 试图创造一个弹跳的波纹圆圈效果
- android - 错误:对于 FCM 通知没有回退可绘制的自适应图标,可能会导致 Android Oreo 上的不可逆转的崩溃
- ruby-on-rails - 谷歌日历 API:“你需要有这个日历的作者权限。”
- python - 从 for 循环到数据框的结果,然后到 csv
- javascript - 点击离开或页面内锚链接后如何删除汉堡菜单
- r - 在正文中创建一个带有 ggplot 的函数并在每次调用时生成两个绘图输出
- reactjs - 单击模态本身时处理外部单击关闭。在模式之外的任何地方单击时基本上不应该关闭
- react-native - React-Native Flatlist 错误:对象作为 React 子项无效
- html - 如何将徽标和导航水平居中在同一行上?