首页 > 解决方案 > IOS 14 将 ObservedObject 添加到 var 导致视图不会出现在 iPhone 上

问题描述

我正在研究斯坦福在线 CS139p 中的示例。记忆游戏的卡片被绘制出来。我将 viewModel 设为 ObservableObject,发布代表记忆游戏的 var,然后运行应用程序。一切都按我的预期发生。然后,我将 @ObservedObject 属性处理器添加到 View Controller 中的 viewModel var。当我运行应用程序时,我不再看到卡片被抽出。

问题1:发生了什么?

问题2:我将如何调试这个?我尝试设置断点并遍历,但我无法确定出了什么问题。我用谷歌搜索并找到了一些可以尝试的东西,但最终无法让它发挥作用。一年前,当我第一次尝试时,这个例子对我有用。

MemorizeApp.swift

import SwiftUI

@main
struct MemorizeApp: App {
    var body: some Scene {
        WindowGroup {
            let game = EmojiMemoryGame()
            EmojiMemoryGameView(viewModel: game)
        }
    }
}

EmojiMemoryGameView.swift

import SwiftUI

// uncomment the below line and then comment the line below it to see the condition that does not work. 


struct EmojiMemoryGameView: View {
//    @ObservedObject var viewModel: EmojiMemoryGame
    var viewModel:EmojiMemoryGame
    var body: some View {
        HStack {
            ForEach(viewModel.cards) { card in
                CardView(card: card).onTapGesture {
                    viewModel.choose(card: card)
                }
        }
        }
        .foregroundColor(Color.orange)
        .padding()
        .font(viewModel.cards.count == 10 ? Font.title : Font.largeTitle)
        
    }
}


struct CardView: View {
    var card: MemoryGame<String>.Card
    var body: some View {
        ZStack {
            
            if card.isFaceUp {
                RoundedRectangle(cornerRadius: 10.0).fill(Color.white)
                RoundedRectangle(cornerRadius: 10.0).stroke(lineWidth: 3.0)
                Text(card.content)
            } else {
                RoundedRectangle(cornerRadius: 10.0).fill()
            }
        }
        .aspectRatio(2/3, contentMode: .fit)
    }
}



struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        EmojiMemoryGameView(viewModel: EmojiMemoryGame())
    }
}

EmojiMemoryGame.swift


import SwiftUI

func createCardContent(pairIndex: Int) -> String {
    return ""
}
// This is the viewModel
class EmojiMemoryGame: ObservableObject {
    
    @Published private var model: MemoryGame<String> = EmojiMemoryGame.createMemoryGame()
    
    static func createMemoryGame() -> MemoryGame<String> {
        let emojies = ["", "", "", "", "", "", "", "", "", "", "", ""]
        return MemoryGame<String>(numberOfPairsOfCards: Int.random(in: 2...5)) { pairIndex in
            return emojies.randomElement()!
            //return emojies[pairIndex]
        }
    }
    
    
    // MARK: Access to the Model
    
    var cards: Array<MemoryGame<String>.Card> {
        model.cards.shuffle()
        return model.cards
    }
    
    // MARK: Intent(s)
    
    func choose(card: MemoryGame<String>.Card) {
        model.choose(card: card)
    }

}

记忆游戏.swift

import Foundation

struct MemoryGame<CardContent> {
    
    var cards: Array<Card>
    
    mutating func choose(card: Card) {
        print("Card Choosen: \(card)")
        let chosenIndex: Int = index(of: card)
        cards[chosenIndex].isFaceUp = !cards[chosenIndex].isFaceUp
        
    }
    
    func index(of card: Card) -> Int {
        for index in 0..<cards.count {
            if self.cards[index].id == card.id {
                return index
            }
        }
        return 0 // TODO: bogus!
    }
    
    init(numberOfPairsOfCards: Int, cardContentFactory: (Int) -> CardContent) {
        cards = Array<Card>()
        for pairIndex in 0..<numberOfPairsOfCards {
            let content = cardContentFactory(pairIndex)
            cards.append(Card(content: content, id: pairIndex*2))
            cards.append(Card(content: content, id: pairIndex*2+1))
        }
    }
    
    struct Card: Identifiable {
        
        
        var isFaceUp: Bool = true
        var isMatched: Bool = false
        var content: CardContent
        var id: Int
    }
}

标签: swiftuiios14observableobjectobservedobject

解决方案


每当观察到的对象发生变化时,创建一个对象@ObservedObject会使视图重新呈现自身,这是通过其@Published属性确定的(也可以直接通过objectWilLChange,但在这里不适用)。

您只有一个@Published属性,即:

@Published private var model: MemoryGame<String> = ...

(不管它是私有的,对象仍然通知观察视图)

因此,无论何时model更新,视图都会重新渲染。

而且,事实上,模型总是在计算属性中更新,cards视图在其 中访问该属性ForEach

var cards: Array<MemoryGame<String>.Card> {
    model.cards.shuffle() // this mutates the model
    return model.cards
}

所以,基本上,当视图重新计算它的主体时,它访问它的视图模型,这会导致它作为副作用发生变异,状态已经失效,它需要另一个重新计算——在一个无限循环中。


在吸气剂内部产生副作用通常是一种不好的做法。

解决方法是model.cards.shuffle()从吸气剂内部移除并将其放在有意义的其他地方。


推荐阅读