IOS 14 将 ObservedObject 添加到 var 导致视图不出现在 iPhone
IOS 14 adding ObservedObject to a var causes views not to appear on iPhone
我正在研究斯坦福在线 CS139p 中的示例。抽取记忆游戏的卡片。我将 viewModel 设为 ObservableObject,发布代表记忆游戏的 var,以及 运行 应用程序。一切都如我所料发生。然后,我将 @ObservedObject 属性 处理器添加到视图控制器中的 viewModel var。当我 运行 应用程序时,我不再看到抽取卡片。
问题 1:发生了什么事?
问题 2:我该如何调试它?我尝试设置一个断点并遍历,但我无法确定出了什么问题。我在谷歌上搜索了一下,发现了一些可以尝试的东西,但最终无法让它发挥作用。这个例子在 1 年前对我有用,当时我第一次尝试它。
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)
}
}
MemoryGame.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
}
}
创建一个对象 @ObservedObject
每当观察到的对象发生变化时,视图 re-render 本身就会创建,这是通过其 @Published
属性确定的(也可以直接通过 objectWilLChange
,但它不适用于此处)。
您有一个 @Published
属性,即:
@Published private var model: MemoryGame<String> = ...
(重要的是它是私有的,对象仍然通知观察视图)
因此,每当 model
更新时,视图 re-renders.
而且,事实上,模型总是在计算 属性 cards
中更新,视图在其 ForEach
:
中访问
var cards: Array<MemoryGame<String>.Card> {
model.cards.shuffle() // this mutates the model
return model.cards
}
所以,基本上,作为视图 re-computes 它的主体,它访问它的视图模型,导致它变异为 side-effect,状态已经失效,它需要另一个 re-computation - 在无限循环中。
将 side-effect 放在 getter 中通常是一种不好的做法。
解决方法是从 getter 中删除 model.cards.shuffle()
并将其放在其他有意义的地方。
我正在研究斯坦福在线 CS139p 中的示例。抽取记忆游戏的卡片。我将 viewModel 设为 ObservableObject,发布代表记忆游戏的 var,以及 运行 应用程序。一切都如我所料发生。然后,我将 @ObservedObject 属性 处理器添加到视图控制器中的 viewModel var。当我 运行 应用程序时,我不再看到抽取卡片。
问题 1:发生了什么事?
问题 2:我该如何调试它?我尝试设置一个断点并遍历,但我无法确定出了什么问题。我在谷歌上搜索了一下,发现了一些可以尝试的东西,但最终无法让它发挥作用。这个例子在 1 年前对我有用,当时我第一次尝试它。
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)
}
}
MemoryGame.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
}
}
创建一个对象 @ObservedObject
每当观察到的对象发生变化时,视图 re-render 本身就会创建,这是通过其 @Published
属性确定的(也可以直接通过 objectWilLChange
,但它不适用于此处)。
您有一个 @Published
属性,即:
@Published private var model: MemoryGame<String> = ...
(重要的是它是私有的,对象仍然通知观察视图)
因此,每当 model
更新时,视图 re-renders.
而且,事实上,模型总是在计算 属性 cards
中更新,视图在其 ForEach
:
var cards: Array<MemoryGame<String>.Card> {
model.cards.shuffle() // this mutates the model
return model.cards
}
所以,基本上,作为视图 re-computes 它的主体,它访问它的视图模型,导致它变异为 side-effect,状态已经失效,它需要另一个 re-computation - 在无限循环中。
将 side-effect 放在 getter 中通常是一种不好的做法。
解决方法是从 getter 中删除 model.cards.shuffle()
并将其放在其他有意义的地方。