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

在viewWillAppear中设置带CAShapeLayer的UIButton默认选中时shadowLayer为空

解决CustomButton在viewWillAppear中默认选中时shadowLayer为nil的问题

这个问题的核心原因确实和你推测的一致:viewWillAppear执行阶段,按钮的layoutSubviews方法还没被系统调用,shadowLayer还未完成初始化,此时调用修改shadowLayer的方法自然会触发nil报错。下面给你几个实用的解决办法:

方案1:提前强制按钮完成布局(最简单直接)

在调用sendActions(for: .touchUpInside)之前,先调用按钮的layoutIfNeeded()方法,强制系统执行layoutSubviews,确保shadowLayer被初始化:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated) // 别忘了调用父类方法,这是UIViewController生命周期的规范
    self.navigationItem.title = navigationTitle
    self.view.makeToastActivity(.center)
    
    // 关键:强制按钮完成布局,触发layoutSubviews初始化shadowLayer
    routineButton.layoutIfNeeded()
    
    self.routineButton.sendActions(for: .touchUpInside) // 默认选中routineButton
    loadEvaluationList(userId: 8, evaluationType: "RT") {
        self.view.hideToastActivity()
    }
}

方案2:调整shadowLayer的初始化时机(更健壮)

把shadowLayer的初始化逻辑从layoutSubviews中拆分出来,放到didMoveToWindow方法中——这个方法会在视图被添加到窗口时调用,此时按钮的bounds已经确定,能保证初始化的正确性;同时保留layoutSubviews中更新路径的逻辑,应对按钮尺寸变化的场景:

class CustomButton: UIButton {
    var shadowLayer: CAShapeLayer!
    
    override func didMoveToWindow() {
        super.didMoveToWindow()
        setupShadowLayer()
    }
    
    private func setupShadowLayer() {
        guard shadowLayer == nil else { return } // 避免重复初始化
        shadowLayer = CAShapeLayer()
        updateShadowPath()
        shadowLayer.fillColor = UIColor.white.cgColor
        shadowLayer.shadowColor = UIColor.darkGray.cgColor
        shadowLayer.shadowOffset = CGSize(width: 2.0, height: 2.0)
        shadowLayer.shadowOpacity = 0.8
        shadowLayer.shadowRadius = 2
        layer.insertSublayer(shadowLayer, at: 0)
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        updateShadowPath() // 尺寸变化时更新阴影路径
    }
    
    private func updateShadowPath() {
        shadowLayer?.path = UIBezierPath(roundedRect: bounds, cornerRadius: 12).cgPath
        shadowLayer?.shadowPath = shadowLayer?.path
    }
}

这种方式能让阴影层的初始化时机更可控,同时保证布局变化时阴影始终匹配按钮的圆角和尺寸。

方案3:在状态切换方法中做nil保护(兜底方案)

如果不想修改按钮的初始化逻辑,可以在setSelectedButton和setUnselected方法中增加nil判断,一旦发现shadowLayer未初始化,先触发布局再执行后续操作:

// 设置按钮为选中状态
func setSelectedButton(sender:CustomButton){
    guard let shadowLayer = sender.shadowLayer else {
        // 强制触发布局初始化shadowLayer
        sender.layoutIfNeeded()
        // 递归调用一次,确保shadowLayer存在后再修改
        setSelectedButton(sender: sender)
        return
    }
    shadowLayer.fillColor = UIColor(named: "headerColor")?.cgColor
    sender.setTitleColor(UIColor.white, for: .normal)
}

// 设置按钮为未选中状态
func setUnselected(sender:CustomButton){
    guard let shadowLayer = sender.shadowLayer else {
        sender.layoutIfNeeded()
        setUnselected(sender: sender)
        return
    }
    shadowLayer.fillColor = UIColor.white.cgColor
    sender.setTitleColor(.black, for: .normal)
}

这个方案属于兜底处理,适合快速修复现有代码,但不如前两种方案优雅。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 09:39:43