SwiftUI,为啥部分视图没有刷新? @State 变量未更新
Posted
技术标签:
【中文标题】SwiftUI,为啥部分视图没有刷新? @State 变量未更新【英文标题】:SwiftUI, why a part of the view did not refresh? @State variable not updatingSwiftUI,为什么部分视图没有刷新? @State 变量未更新 【发布时间】:2020-04-08 01:15:04 【问题描述】:这是我在 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 可能是正确的答案,但这不起作用。 这个例子可以工作(除了未更新的切换)
提前致谢!
【问题讨论】:
【参考方案1】:这是一种可能的方法,它为设备存储添加了显式视图模型,并加入了所有视图以在该存储中进行精确修改(避免应对设备值)。
使用 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()
【讨论】:
这是个好主意!但是在 ToggleSection 中,我需要计算 Toggle 的“isOn”状态。在这里我没有报告这一点,但实际上在 ToggleSection 中有另一个@State var toggleDisabled: [Bool]
计算是否可以启用或禁用切换(用户无法与之交互)。使用这种方法无法将计算绑定传递给 Toggle。
好的,我解决了(例如使用简单的计算值)Toggle(isOn: Binding( get: return !self.dev.isCellularOn, set: (newvalue) in ), label: Text("Cellular") )
。谢谢你的回答!以上是关于SwiftUI,为啥部分视图没有刷新? @State 变量未更新的主要内容,如果未能解决你的问题,请参考以下文章
为啥我的视图在 swiftUI 中首次出现在屏幕上时没有转换
为啥即使发布者发出一个值,我的视图也没有在 SwiftUI 中呈现
SwiftUI 如何快速识别视图(View)界面的刷新是由哪个状态的改变导致的?