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

UIKit中如何实现占位符动态切换动画?现有代码效果不符求修正

问题描述

希望实现占位符文字逐个动态切换的动画效果(如参考示例所示):
目标占位符逐个切换动画

当前代码尝试通过上下移动标签实现动画,但实际效果与预期差距较大:
当前不符的动画效果

原实现代码如下:

import UIKit

class ViewController: UIViewController, UISearchBarDelegate {
    
    var placeholderIndex = 0
    var placeholderTexts = ["Text 1", "Text 2", "Text 3"]
    var searchBar: UISearchBar!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        searchBar = UISearchBar()
        searchBar.placeholder = "Search...." // Set initial placeholder
        searchBar.searchBarStyle = .prominent
        searchBar.sizeToFit()
        searchBar.isTranslucent = true
        searchBar.delegate = self
        searchBar.backgroundImage = UIImage()
        searchBar.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(searchBar)
        
        Timer.scheduledTimer(timeInterval: 3.0, target: self, selector: #selector(updatePlaceholder), userInfo: nil, repeats: true)
        
        NSLayoutConstraint.activate([
            searchBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
            searchBar.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            searchBar.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            searchBar.heightAnchor.constraint(equalToConstant: 70)
        ])
    }
    
    
    @objc func updatePlaceholder() {
        let nextPlaceholderLabel = UILabel(frame: CGRect(x: 0, y: -searchBar.frame.height, width: searchBar.frame.width, height: searchBar.frame.height))
        nextPlaceholderLabel.text = placeholderTexts[placeholderIndex]
        nextPlaceholderLabel.textAlignment = .center
        nextPlaceholderLabel.font = searchBar.searchTextField.font
        nextPlaceholderLabel.textColor = searchBar.searchTextField.textColor
        searchBar.addSubview(nextPlaceholderLabel)

        UIView.animate(withDuration: 0.5, animations: {
            nextPlaceholderLabel.frame.origin.y = 0
            nextPlaceholderLabel.alpha = 1.0
            self.searchBar.layoutIfNeeded()
        }) { _ in
            self.searchBar.placeholder = self.placeholderTexts[self.placeholderIndex]
            nextPlaceholderLabel.removeFromSuperview()
        }

        placeholderIndex = (placeholderIndex + 1) % placeholderTexts.count
    }
}
修正方案

原代码的核心问题是采用了整体标签上下移动的逻辑,与目标的逐个字符替换动画不匹配。我们需要通过逐字符构建/删除文本的方式实现效果,修改后的代码如下:

import UIKit

class ViewController: UIViewController, UISearchBarDelegate {
    
    var placeholderIndex = 0
    var placeholderTexts = ["Text 1", "Text 2", "Text 3"]
    var searchBar: UISearchBar!
    // 用于控制动画过程中的文本构建状态
    var currentTextBuilder = ""
    var currentTextIndex = 0
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        searchBar = UISearchBar()
        searchBar.placeholder = placeholderTexts.first ?? ""
        searchBar.searchBarStyle = .prominent
        searchBar.sizeToFit()
        searchBar.isTranslucent = true
        searchBar.delegate = self
        searchBar.backgroundImage = UIImage()
        searchBar.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(searchBar)
        
        // 启动第一轮动画序列
        DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
            self.startCharacterReplacementAnimation()
        }
        
        NSLayoutConstraint.activate([
            searchBar.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20),
            searchBar.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20),
            searchBar.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20),
            searchBar.heightAnchor.constraint(equalToConstant: 70)
        ])
    }
    
    /// 启动逐个字符替换的完整动画序列
    func startCharacterReplacementAnimation() {
        let targetText = placeholderTexts[placeholderIndex]
        let currentText = searchBar.placeholder ?? ""
        
        // 先执行文本清空动画,再执行文本构建动画
        if !currentText.isEmpty {
            clearTextWithAnimation {
                self.buildTextWithAnimation(targetText: targetText)
            }
        } else {
            buildTextWithAnimation(targetText: targetText)
        }
        
        // 更新索引,准备下一轮动画
        placeholderIndex = (placeholderIndex + 1) % placeholderTexts.count
        
        // 3秒后启动下一轮动画循环
        DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
            self.startCharacterReplacementAnimation()
        }
    }
    
    /// 逐个删除现有文本的动画逻辑
    func clearTextWithAnimation(completion: @escaping () -> Void) {
        guard var currentText = searchBar.placeholder, !currentText.isEmpty else {
            completion()
            return
        }
        
        let timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] timer in
            guard let self = self else { return }
            currentText.removeLast()
            self.searchBar.placeholder = currentText
            
            if currentText.isEmpty {
                timer.invalidate()
                completion()
            }
        }
        timer.fire()
    }
    
    /// 逐个添加目标文本的动画逻辑
    func buildTextWithAnimation(targetText: String) {
        currentTextBuilder = ""
        currentTextIndex = 0
        
        let timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true) { [weak self] timer in
            guard let self = self else { return }
            if self.currentTextIndex < targetText.count {
                let index = targetText.index(targetText.startIndex, offsetBy: self.currentTextIndex)
                self.currentTextBuilder.append(targetText[index])
                self.searchBar.placeholder = self.currentTextBuilder
                self.currentTextIndex += 1
            } else {
                timer.invalidate()
            }
        }
        timer.fire()
    }
}

关键修改说明

  • 替换原有的整体标签移动逻辑,改为逐字符删除+逐字符添加的分步动画,完全匹配目标效果
  • 使用定时器控制每个字符的动画间隔(0.1秒),保证动画流畅自然
  • 采用异步延迟调度替代重复Timer,避免动画重叠问题
  • 拆分清空和构建两个动画阶段,实现完整的占位符切换流程

内容的提问来源于stack exchange,提问作者Swarup Panda

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 04:25:04