You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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()
        }
    }
}

问题根源

  1. 闭包捕获引用而非值:循环中DispatchQueue.main.asyncAfter的闭包捕获的是index变量的引用,而非当前循环迭代的具体值。当所有闭包执行时,循环已经跑完,index已经变成9,所以所有闭包都设置了数组最后一个元素。
  2. 循环内直接覆盖文本:循环里直接执行labelLabel.text = labels[index]会立刻把文本设置为当前循环的index对应值,导致初始显示的内容被快速覆盖,还没等延迟执行就已经到最后一个元素。
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 02:24:57