为何通过NotificationCenter调整UILabel尺寸需延迟才能正常更新?
解决动态类型变更时UILabel Frame更新异常的问题
这个坑我之前踩过!你遇到的问题核心原因其实是:系统发送UIContentSizeCategoryDidChangeNotification的时机,早于动态字体尺寸的实际更新完成时间。直接在通知回调里主线程更新frame,拿到的还是旧字体的尺寸,自然计算出来的结果不对;加0.1秒延迟本质上是“等系统把字体更新的活儿干完”,但这属于hack式的临时方案,不够可靠。
下面给你几个更规范、稳定的解决思路:
1. 优先用AutoLayout让系统自动处理(最推荐)
UIKit本身就设计了动态类型的自动适配能力,完全没必要手动计算frame。只要给UILabel设置动态字体+开启自适应开关,配合AutoLayout约束,系统会自动帮你调整label的尺寸:
let dynamicLabel = UILabel() // 设置动态字体 dynamicLabel.font = UIFont.preferredFont(forTextStyle: .body) // 开启动态类型适配 dynamicLabel.adjustsFontForContentSizeCategory = true // 多行的话记得设置numberOfLines dynamicLabel.numberOfLines = 0 // 添加AutoLayout约束(示例:左右边距20,顶部距导航栏下方) dynamicLabel.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ dynamicLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 20), dynamicLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -20), dynamicLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 20) ])
这种方式完全不用监听通知,系统会自动处理所有动态类型变更后的布局调整,是UIKit官方推荐的最佳实践。
2. 必须手动计算frame?监听UILabel的字体变化
如果因为特殊场景必须手动设置frame,别依赖通知,而是直接监听UILabel的font属性变化——这是字体真正更新完成的信号。
用KVO实现(兼容iOS旧版本)
class YourViewController: UIViewController { private var customLabel = UILabel() private var fontObservation: NSKeyValueObservation? override func viewDidLoad() { super.viewDidLoad() // 初始化label并设置动态字体 customLabel.font = UIFont.preferredFont(forTextStyle: .title1) customLabel.adjustsFontForContentSizeCategory = true view.addSubview(customLabel) // 监听font属性变化 fontObservation = customLabel.observe(\.font, options: [.new]) { [weak self] label, _ in guard let self = self else { return } // 此时font已经是更新后的动态字体,计算frame let fitSize = label.sizeThatFits(CGSize(width: self.view.bounds.width - 40, height: .greatestFiniteMagnitude)) label.frame = CGRect(x: 20, y: 100, width: fitSize.width, height: fitSize.height) } } }
用Combine实现(iOS 13+)
import Combine class YourViewController: UIViewController { private var customLabel = UILabel() private var cancellables = Set<AnyCancellable>() override func viewDidLoad() { super.viewDidLoad() customLabel.font = UIFont.preferredFont(forTextStyle: .title1) customLabel.adjustsFontForContentSizeCategory = true view.addSubview(customLabel) // 订阅font属性变化 customLabel.publisher(for: \.font) .sink { [weak self] _ in guard let self = self else { return } let fitSize = self.customLabel.sizeThatFits(CGSize(width: self.view.bounds.width - 40, height: .greatestFiniteMagnitude)) self.customLabel.frame = CGRect(x: 20, y: 100, width: fitSize.width, height: fitSize.height) } .store(in: &cancellables) } }
3. 一定要用通知?等RunLoop完成当前周期
如果坚持要基于UIContentSizeCategoryDidChangeNotification处理,别用固定延迟,而是等待当前RunLoop周期结束,确保系统完成字体更新:
class YourViewController: UIViewController { private var customLabel = UILabel() override func viewDidLoad() { super.viewDidLoad() // 初始化label... NotificationCenter.default.addObserver(self, selector: #selector(handleContentSizeChange), name: UIContentSizeCategory.didChangeNotification, object: nil) } @objc private func handleContentSizeChange(_ notification: Notification) { // 等待当前RunLoop周期结束,确保字体更新完成 DispatchQueue.main.async { [weak self] in guard let self = self else { return } let fitSize = self.customLabel.sizeThatFits(CGSize(width: self.view.bounds.width - 40, height: .greatestFiniteMagnitude)) self.customLabel.frame = CGRect(x: 20, y: 100, width: fitSize.width, height: fitSize.height) } } }
这种方式比固定延迟更灵活,但还是不如前两种方案可靠。
内容的提问来源于stack exchange,提问作者elfanek
相关产品推荐
相关产品推荐

