Swift中实现数组元素依次显示及修复仅显示最后元素问题
问题修复:文本数组成员依次显示数秒却仅显示最后一个
我尝试让文本数组的每个成员依次显示数秒,但编写的代码全程只显示最后一个成员。调整DispatchQueue.main.asyncAfter的位置后问题仍未解决,该如何修复?
原代码
import SwiftUI class ViewController: UIViewController { var connectedView: UIView? var emotionView: UIView? var labelsButton = UIButton(type: .roundedRect) let labels = ["Apple", "Baby", "Cat", "Dog", "Evening", "Friend", "Go", "Happy", "Independent", "Joyful"] override func viewDidLoad() { super.viewDidLoad() layoutForConnected() } @objc func didTouchlabelButton(_ sender: UIButton?) { sender?.setTitle("Labels", for: .normal) layoutForCharacter() } func layoutForConnected() { if connectedView == nil { connectedView = UIView(frame: view.bounds) connectedView?.backgroundColor = UIColor.white labelsButton.frame = CGRect(x: 70, y: 475, width: 180, height: 60) labelsButton.setTitle("Labels", for: .normal) labelsButton.addTarget(self,action: #selector(didTouchlabelButton),for: .touchUpInside) labelsButton.tintColor = UIColor.black labelsButton.backgroundColor = UIColor.init(hue: 0.563, saturation: 1.0, brightness: 0.93, alpha: 1.0) labelsButton.layer.cornerRadius = 20 connectedView?.addSubview(labelsButton) } if let connectedView = connectedView { self.view.addSubview(connectedView) } } var displayTime = 5.0 var index = 0 func layoutForCharacter() { // set up frame and background color emotionView = UIView(frame: view.bounds) emotionView?.backgroundColor = UIColor.init(hue: 0.563, saturation: 1.0, brightness: 0.93, alpha: 1.0) let labelLabel = UILabel(frame: CGRect(x: 0, y: 400 , width: view.frame.size.width, height: 20)) labelLabel.textAlignment = .center // Display each label for displayTime seconds index = 0 view?.addSubview(labelLabel) labelLabel.text = labels[index] while index < 9 { labelLabel.text = labels[index] // Displays "Joyful", i.e. index = 9 DispatchQueue.main.asyncAfter(deadline: .now() + displayTime * Double(index)) { [self] in labelLabel.text = labels[index] } self.index += 1 } DispatchQueue.main.asyncAfter(deadline: .now() + displayTime * Double(index)) { [self] in self.layoutForConnected() } } }
问题根源
- 闭包捕获引用而非值:循环中
DispatchQueue.main.asyncAfter的闭包捕获的是index变量的引用,而非当前循环迭代的具体值。当所有闭包执行时,循环已经跑完,index已经变成9,所以所有闭包都设置了数组最后一个元素。 - 循环内直接覆盖文本:循环里直接执行
labelLabel.text = labels[index]会立刻把文本设置为当前循环的index对应值,导致初始显示的内容被快速覆盖,还没等延迟执行就已经到最后一个元素。 - emotionView未添加到视图:创建的
emotionView没有添加到当前视图层级,导致背景色无法显示。
修复方案
方案1:用局部变量保存当前index值
在循环内部创建局部变量保存当前迭代的index,让闭包捕获这个局部变量的值:
func layoutForCharacter() { // 视图基础设置 emotionView = UIView(frame: view.bounds) emotionView?.backgroundColor = UIColor.init(hue: 0.563, saturation: 1.0, brightness: 0.93, alpha: 1.0) view.addSubview(emotionView!) // 将emotionView添加到视图层级 let labelLabel = UILabel(frame: CGRect(x: 0, y: 400 , width: view.frame.size.width, height: 20)) labelLabel.textAlignment = .center view.addSubview(labelLabel) index = 0 labelLabel.text = labels[index] while index < labels.count { let currentIndex = index // 保存当前index到局部变量,避免闭包捕获引用 DispatchQueue.main.asyncAfter(deadline: .now() + displayTime * Double(currentIndex)) { [weak self] in guard let self = self else { return } labelLabel.text = self.labels[currentIndex] } index += 1 } DispatchQueue.main.asyncAfter(deadline: .now() + displayTime * Double(index)) { [weak self] in self?.layoutForConnected() } }
方案2:改用递归方式(逻辑更清晰)
递归方式可以彻底避免闭包捕获问题,流程更直观:
func layoutForCharacter() { // 视图基础设置 emotionView = UIView(frame: view.bounds) emotionView?.backgroundColor = UIColor.init(hue: 0.563, saturation: 1.0, brightness: 0.93, alpha: 1.0) view.addSubview(emotionView!) let labelLabel = UILabel(frame: CGRect(x: 0, y: 400 , width: view.frame.size.width, height: 20)) labelLabel.textAlignment = .center view.addSubview(labelLabel) index = 0 // 定义递归函数,负责依次显示标签 func displayNextLabel() { if index < labels.count { labelLabel.text = labels[index] index += 1 DispatchQueue.main.asyncAfter(deadline: .now() + displayTime) { [weak self] in self?.displayNextLabel() } } else { // 所有标签显示完毕,回到初始界面 layoutForConnected() } } // 启动递归流程 displayNextLabel() }
关键改进点
- 将
emotionView添加到view上,确保背景色正常显示 - 用局部变量或递归避免闭包捕获引用导致的index值错误
- 移除循环内直接设置
labelLabel.text的代码,避免初始显示被快速覆盖 - 使用
[weak self]避免循环引用
内容的提问来源于stack exchange,提问作者MarkB
相关产品推荐
相关产品推荐

