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

iOS从推送页面返回时viewWillAppear中按钮标签初始透明问题

问题:UIButton标签在viewWillAppear过渡动画中初始透明的原因与解决

问题描述

我有一个包含2个图标和UILabel的自定义UIButton,在.selected状态下有不同背景色。点击按钮后切换为选中状态并推送新ViewController,返回时想模仿UITableViewCell的选中过渡动画,用UIView.transition(with: button, duration: 0.3, options: .transitionCrossDissolve) { button.isSelected = false }实现背景色过渡。这段代码放在viewDidAppear中功能正常但时机偏晚,移到viewWillAppear后,按钮上的UILabel初始为透明,只有标签会从透明动画到正常颜色,图标显示正常。

演示代码

import UIKit

class ViewController: UIViewController {
    var myButton: MyButton?

    override func viewDidLoad() {
        super.viewDidLoad()
        myButton = MyButton()
        view.addSubview(myButton!)
        myButton?.frame.origin.x = 200
        myButton?.frame.origin.y = 200
        myButton?.addAction(.init { [weak self] _ in
            self?.myButton?.isSelected = true
            self?.navigationController?.pushViewController(ViewController2(), animated: true)
        }, for: .touchUpInside)
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        UIView.transition(with: myButton!, duration: 3, options: .transitionCrossDissolve) { self.myButton!.isSelected = false }
    }
    
//    override func viewDidAppear(_ animated: Bool) {
//        super.viewDidAppear(animated)
//        UIView.transition(with: myButton!, duration: 3, options: .transitionCrossDissolve) { self.myButton!.isSelected = false }
//    }

}
class ViewController2: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
    }
    
}

class MyButton: UIButton {
    
    let label = UILabel("Hahahaha", font: .systemFont(ofSize: 14), textColor: .black)

    convenience init() {
        self.init(frame: .init(x: 0, y: 0, width: 200, height: 200))
        
        setBackgroundColor(color: .white, forState: .normal)
        setBackgroundColor(color: .gray, forState: .highlighted)
        setBackgroundColor(color: .gray, forState: .selected)

        addSubview(label)
        label.frame.origin = .init(x: 40, y: 40)
    }
}

extension UIButton {
    func setBackgroundColor(color: UIColor, forState: UIControl.State) {
        self.clipsToBounds = true
        UIGraphicsBeginImageContext(CGSize(width: 1, height: 1))
        if let context = UIGraphicsGetCurrentContext() {
            context.setFillColor(color.cgColor)
            context.fill(CGRect(x: 0, y: 0, width: 1, height: 1))
            let colorImage = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
            self.setBackgroundImage(colorImage, for: forState)
        }
    }
}

extension UILabel {
    convenience init(_ text: String = "", font: UIFont? = nil, textColor: UIColor? = .black) {
        self.init()
        self.text = text
        self.font = font
        sizeToFit()
        self.textColor = textColor
    }
}

原因分析

  • viewWillAppear阶段,视图控制器的视图还未完成完全的布局和渲染,UILabel的初始绘制状态可能还未被系统确认。
  • 使用.transitionCrossDissolve过渡时,系统会对目标视图(整个UIButton)的当前状态和最终状态做快照对比。在viewWillAppear时,UILabel尚未完成首次渲染,快照中它的状态是透明的,导致动画过程中出现从透明到正常颜色的过渡。
  • viewDidAppear时视图已经完成渲染,快照能正确捕获UILabel的正常状态,所以动画表现正常。

解决方法

方法1:强制UILabel提前完成渲染

在自定义MyButton的初始化中,添加代码让label提前完成布局和绘制:

convenience init() {
    self.init(frame: .init(x: 0, y: 0, width: 200, height: 200))
    
    // 原有背景色设置代码
    
    addSubview(label)
    label.frame.origin = .init(x: 40, y: 40)
    // 强制label完成布局与绘制
    label.layoutIfNeeded()
    label.layer.displayIfNeeded()
}

方法2:延迟动画到视图渲染完成后执行

在viewWillAppear中用DispatchQueue.main.async包裹动画代码,让动画在当前runloop结束后执行,此时视图已完成初始渲染:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    DispatchQueue.main.async {
        UIView.transition(with: self.myButton!, duration: 3, options: .transitionCrossDissolve) {
            self.myButton!.isSelected = false
        }
    }
}

方法3:改用属性动画替代整体视图过渡

避免对整个按钮做过渡,直接对按钮状态切换做属性动画,依赖UIButton状态变化时的属性响应:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    UIView.animate(withDuration: 0.3) {
        self.myButton?.isSelected = false
    }
}

当前的setBackgroundColor方法已经通过背景图片实现状态切换,属性动画会自动处理背景色的平滑过渡。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 15:45:38