Swift:更新UILabel导致子视图无法变更的问题求助
我正在跟着斯坦福大学的iOS 11课程学习Swift,目前在做一个叫“Set”的卡牌游戏项目。我在ViewController里创建了cardView容器来显示场上的所有卡牌,每张卡对应一个子视图,游戏目标是找出3张构成“Set”的卡牌。
我做了一个提示按钮,点击后会把构成Set的3张卡牌的边框改成红色来提示解法,但每次点击分数会减少。我的问题是:只要更新scoreLabel的文本,那些卡牌的边框就不会显示;删掉更新分数的代码,边框就能正常显示,但看不到分数变化。我通过断点确认,哪怕更新分数的代码放在方法最后,只要保留它,卡牌子视图的边框就不会变更,没法同时实现分数更新和提示功能。
我的IBAction代码如下:
@IBAction private func hintButtonPressed(_ sender: UIButton) { for subview in cardView.subviews { subview.layer.borderWidth = 0 } game.hint() if !game.selectedCards.isEmpty { for index in game.indicesOfSelectedCards { cardView.subviews[index].layer.borderWidth = 4 cardView.subviews[index].layer.cornerRadius = 8 cardView.subviews[index].layer.borderColor = UIColor.red.cgColor print("cardView.subview[\(index)] changed borderColor.") } game.resetSelectedCards() } else { print("There are no sets on the field") } scoreLabel.text = "Score: \(game.score)" }
这个问题大概率是UI更新的时机冲突或者视图重绘覆盖了边框设置导致的,下面是几个可行的解决思路:
1. 强制所有UI操作在主线程执行
虽然IBAction默认在主线程,但有时候游戏逻辑的异步操作(比如game.hint())可能间接导致UI更新不在主线程,或者当前runloop的优先级问题让边框设置被分数更新覆盖。把所有UI相关的代码包裹在DispatchQueue.main.async里,确保它们按预期执行:
@IBAction private func hintButtonPressed(_ sender: UIButton) { DispatchQueue.main.async { [weak self] in guard let self = self else { return } // 重置所有卡牌边框 for subview in self.cardView.subviews { subview.layer.borderWidth = 0 } // 调用游戏的提示逻辑 self.game.hint() if !self.game.selectedCards.isEmpty { for index in self.game.indicesOfSelectedCards { // 加个索引越界判断,避免崩溃 guard index < self.cardView.subviews.count else { continue } let targetCard = self.cardView.subviews[index] targetCard.layer.borderWidth = 4 targetCard.layer.cornerRadius = 8 targetCard.layer.borderColor = UIColor.red.cgColor print("cardView.subview[\(index)] changed borderColor.") } self.game.resetSelectedCards() } else { print("There are no sets on the field") } // 最后更新分数 self.scoreLabel.text = "Score: \(self.game.score)" } }
2. 检查是否有其他代码重置了卡牌边框
如果你的卡牌是自定义的UIView子类,或者在ViewController的layoutSubviews、updateViewConstraints等布局方法里,有重置borderWidth的代码,那么更新分数Label时触发的视图重绘会覆盖你设置的边框。
比如如果自定义卡牌视图里有这样的代码:
override func layoutSubviews() { super.layoutSubviews() layer.borderWidth = 0 // 这里会覆盖提示状态的边框 }
你可以给自定义卡牌视图加一个isHinted属性,用来控制边框状态:
class CardView: UIView { var isHinted: Bool = false { didSet { updateBorderStyle() } } private func updateBorderStyle() { layer.borderWidth = isHinted ? 4 : 0 layer.borderColor = isHinted ? UIColor.red.cgColor : UIColor.clear.cgColor layer.cornerRadius = 8 } override func layoutSubviews() { super.layoutSubviews() updateBorderStyle() // 用统一的方法设置边框,而不是写死0 } }
然后在提示按钮的代码里,更新对应卡牌的isHinted属性:
// 替换原来设置layer的代码 if let targetCard = self.cardView.subviews[index] as? CardView { targetCard.isHinted = true } // 同时重置其他卡牌的isHinted for subview in self.cardView.subviews { if let card = subview as? CardView { card.isHinted = false } }
3. 调整代码执行顺序
有时候UI更新的顺序会影响结果,你可以尝试先更新分数,再设置卡牌边框,看看是否能解决冲突:
@IBAction private func hintButtonPressed(_ sender: UIButton) { for subview in cardView.subviews { subview.layer.borderWidth = 0 } game.hint() // 先更新分数 scoreLabel.text = "Score: \(game.score)" if !game.selectedCards.isEmpty { for index in game.indicesOfSelectedCards { cardView.subviews[index].layer.borderWidth = 4 cardView.subviews[index].layer.cornerRadius = 8 cardView.subviews[index].layer.borderColor = UIColor.red.cgColor print("cardView.subview[\(index)] changed borderColor.") } game.resetSelectedCards() } else { print("There are no sets on the field") } }
内容的提问来源于stack exchange,提问作者Toan Nguyen

