SwiftUI @State 数组未更新在 init() 期间附加的值

Posted

技术标签:

【中文标题】SwiftUI @State 数组未更新在 init() 期间附加的值【英文标题】:SwiftUI @State array not updating a value appended during init() 【发布时间】:2021-02-08 21:45:15 【问题描述】:

我是一个初级程序员所以...

在 iMac Big Sur 11.2 上使用 Xcode 12.4,该项目是“多平台应用程序”

我正在尝试构建要在网格中移动的图像数组。我将“My Image.png”复制到我的 Xcode 项目中并很好地使用它,但无论我如何尝试将它放入一个数组中,它都会返回一个 nil 数组。我在另一台电脑上试过这个,它似乎工作。 Xcode 中的哪些内容会影响这一点? (或者我只是想象它在另一台机器上工作?:-p)

代码:

@State var stringURL: URL = Bundle.main.url(forResource: "My Image", withExtension: "png")!
Image(nsImage: NSImage(byReferencing: stringURL))

@State var arrayURLs = [URL]()
arrayURLs.append(Bundle.main.url(forResource: "My Image", withExtension: "png")!)
Image(nsImage: NSImage(byReferencing: arrayURLs[0]))

前 2 行有效,但后 3 行在同一个应用程序中失败。我收到运行时错误“索引超出范围”,我认为这是因为捆绑调用返回 nil。

在上下文中基本相同。我在这个项目的最开始,从未超过第一行......

import SwiftUI

struct ContentView: View 
@State var pieceImageURLs = [URL]()


init() 
    self.pieceImageURLs.append(Bundle.main.url(forResource: "Red Warrior", withExtension: "png")!)


var body: some View 
    HStack 
         Image(nsImage: NSImage(byReferencing: pieceImageURLs[0]))
    




    import SwiftUI

struct ContentView: View 
    @State var stringURL: URL = Bundle.main.url(forResource: "My Image", withExtension: "png")!
    @State var arrayURLs = [URL]()
init() 
        stringURL = Bundle.main.url(forResource: "My Image", withExtension: "png")!
        arrayURLs.append(Bundle.main.url(forResource: "My Image", withExtension: "png")!)
    

    var body: some View 
        HStack 
            Image(nsImage: NSImage(byReferencing: stringURL))
            Image(nsImage: NSImage(byReferencing: arrayURLs[0]))
        
        Button(action: 
            arrayURLs.append(Bundle.main.url(forResource: "My Image", withExtension: "png")!)
            Image(nsImage: NSImage(byReferencing: arrayURLs[0]))
        ) 
            Text("add image")
        
    

'''

或者像这样一起使用数组的图像行失败

【问题讨论】:

您能在您的代码上下文中向我们展示吗?即,这些行在实际文件中显然不相邻存在,因为您不能声明相邻的属性和逻辑。可能有助于诊断。 【参考方案1】:

这是一个有趣的行为,我自己也不会预测到这种行为。看起来 SwiftUI 真的不喜欢你在视图的初始化阶段设置 @State 变量。有几种方法可以解决这个问题。

选项 1

您不能设置新值,但可以在您的 init 中将其显式初始化为 State

struct ContentView: View 
    @State var pieceImageURLs = [URL]()
    
    init() 
        if let url = Bundle.main.url(forResource: "Frame 1-2", withExtension: "png") 
            _pieceImageURLs = State(initialValue: [url]) // <--- Here
        
    
    
    var body: some View 
        HStack 
            if let imgUrl = pieceImageURLs.first, let nsImage = NSImage(byReferencing: imgUrl) 
                Image(nsImage: nsImage)
             else 
                Text("HI")
            
        
        .frame(minWidth: 300, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity)
    


选项 2

不要为此使用@State,因为它无论如何都是静态的(至少在您的示例中)——您只是在 init 中设置一个值。

struct ContentView: View 
    var pieceImageURLs = [URL]()  // <--- Here
    
    init() 
        if let url = Bundle.main.url(forResource: "Frame 1-2", withExtension: "png") 
            pieceImageURLs.append(url)
        
    
    
    var body: some View 
        HStack 
            if let imgUrl = pieceImageURLs.first, let nsImage = NSImage(byReferencing: imgUrl) 
                Image(nsImage: nsImage)
             else 
                Text("HI")
            
        
        .frame(minWidth: 300, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity)
    

选项 3

使用状态,但设置在onAppear:

struct ContentView: View 
    @State var pieceImageURLs = [URL]()
    
    var body: some View 
        HStack 
            if let imgUrl = pieceImageURLs.first, let nsImage = NSImage(byReferencing: imgUrl) 
                Image(nsImage: nsImage)
             else 
                Text("HI")
            
        
        .onAppear   // <--- Here
            if let url = Bundle.main.url(forResource: "Frame 1-2", withExtension: "png") 
                pieceImageURLs.append(url)
            
        
        .frame(minWidth: 300, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity)
    

选项 4

在视图模型中执行初始化 (ObservableObject)

class ViewModel : ObservableObject   // <--- Here
    @Published var pieceImageURLs = [URL]()
    
    init() 
        if let url = Bundle.main.url(forResource: "Frame 1-2", withExtension: "png") 
            pieceImageURLs.append(url)
        
    


struct ContentView: View 
    @ObservedObject var viewModel = ViewModel()  // <--- Here
    
    var body: some View 
        HStack 
            if let imgUrl = viewModel.pieceImageURLs.first, let nsImage = NSImage(byReferencing: imgUrl) 
                Image(nsImage: nsImage)
             else 
                Text("HI")
            
        
        .frame(minWidth: 300, maxWidth: .infinity, minHeight: 300, maxHeight: .infinity)
    

【讨论】:

非常感谢。我要花很长时间才能弄清楚这一点。试图建立一个类似于国际象棋的棋盘游戏。对教程或示例有什么建议吗? @DouglasCurran -- 如果这对您有用并回答了您的问题,请考虑接受答案。对于这种特殊情况,我没有任何具体的教程可以建议。一般来说,我总是建议尽可能多地阅读hackingwithswift.com 谢谢,我一直在通过 hackingwithswift 挖掘自己的方式。 option 1 & 2 产生编译错误:“Escaping Closure captures mutating 'self' parameter”,这是一个架构问题,不要在视图渲染周期中改变视图状态,而是更改视图的数据在渲染周期之外建立模型并让视图的重新渲染反映该更改,这就是为什么 - 选项 3 和 4 是首选的任何一种方式,这意味着在第一次视图渲染时,数据将丢失/nil/或部分,而您必须处理它(添加一些 if 语句来检查数组是否为空,或者添加一些在对象初始化后将运行的空数据)

以上是关于SwiftUI @State 数组未更新在 init() 期间附加的值的主要内容,如果未能解决你的问题,请参考以下文章

SwiftUI @State 变量未更新

在 SwiftUI 中使用 @State 属性包装器未更新视图

SwiftUI,为啥部分视图没有刷新? @State 变量未更新

更新@State后未调用SwiftUI UIViewControllerRepresentable.updateUIViewController

在 SwiftUI 中从数据模型中搜索数组

SwiftUI:如何在函数计算 @State 值时同时更新视图?