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

自定义UIButton代理触发后子VC视图位置异常问题求助

问题描述

我在自定义UIButton和父子视图控制器间通过Protocol&Delegate实现交互,结构为父VC包含子VC和自定义按钮。需求是点击按钮时,修改子VC的view frame原点并切换按钮图片。但实际测试时,只要修改按钮图片,子VC视图会先按updateView方法更新位置,随后立刻复位到初始位置;如果不修改按钮图片,功能完全正常。

问题根源
  1. AutoLayout约束重复激活:父VC的viewDidLayoutSubviews方法里反复调用applyConstraints(),每次都会重新激活子VC的初始底部约束。修改按钮图片会触发视图布局更新,导致viewDidLayoutSubviews执行,强制将子VC视图拉回初始位置。
  2. Frame与AutoLayout混用冲突:子VC的view开启了AutoLayout(translatesAutoresizingMaskIntoConstraints = false),但你直接修改frame来调整位置。AutoLayout会在布局周期内优先遵循约束规则,覆盖手动修改的frame值。
修复方案

方案一:用约束控制位置(推荐,符合AutoLayout最佳实践)

  • 给子VC的底部约束添加引用,通过修改约束的constant值控制位置,替代直接修改frame
  • 移除viewDidLayoutSubviews里的applyConstraints()调用,避免重复激活约束

方案二:关闭AutoLayout对子视图的控制

  • 将子VC的view.translatesAutoresizingMaskIntoConstraints设为true,让视图使用 autoresizing mask 而非AutoLayout约束。但这种方式仅适合简单布局,不推荐在复杂界面中使用。
修改后的完整代码

Parent View Controller

class ParentViewController: UIViewController {
    
    // 保存子VC底部约束的引用
    private var childVCBottomConstraint: NSLayoutConstraint!
    
    let childVC: ChildViewController = {
        let childVC = ChildViewController()
        childVC.view.translatesAutoresizingMaskIntoConstraints = false
        return childVC
    }()
    
    let customButton: CustomUIButton = {
        let button = CustomUIButton(frame: .zero)
        button.translatesAutoresizingMaskIntoConstraints = false
        return button
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        
        setupCustomButton()
        setupChildVC()
        applyConstraints() // 仅在viewDidLoad中激活一次约束
    }
    
    private func setupCustomButton() {
        view.addSubview(customButton)
        customButton.delegate = self
    }
    
    private func setupChildVC() {
        addChild(childVC)
        view.addSubview(childVC.view)
        childVC.didMove(toParent: self)
    }
    
    private func applyConstraints() {
        let childVCConstraints = [
            childVC.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            childVC.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            childVC.view.heightAnchor.constraint(equalToConstant: 65)
        ]
        // 单独保存底部约束,方便后续修改
        childVCBottomConstraint = childVC.view.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -200)
        
        let customButtonConstraints = [
            customButton.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            customButton.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -50),
            customButton.heightAnchor.constraint(equalToConstant: 70),
            customButton.widthAnchor.constraint(equalToConstant: 70)
        ]
        
        NSLayoutConstraint.activate(childVCConstraints)
        NSLayoutConstraint.activate(customButtonConstraints)
        childVCBottomConstraint.isActive = true
    }
}

extension ParentViewController: CustomUIButtonDelegate {
    func customUIButtonDidTap() {
        // 通过修改约束constant更新位置
        childVC.updateView(with: &childVCBottomConstraint)
    }
}

Child View Controller

class ChildViewController: UIViewController {
    
    var isHidden: Bool = false

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBlue
    }
    
    // 修改方法,接收底部约束引用
    func updateView(with bottomConstraint: inout NSLayoutConstraint) {
        if isHidden {
            bottomConstraint.constant = -200
            isHidden = false
        } else {
            bottomConstraint.constant = 0 // 对应原逻辑中frame下移200的效果
            isHidden = true
        }
        // 触发布局动画
        UIView.animate(withDuration: 0.3) {
            self.view.superview?.layoutIfNeeded()
        }
    }
}

Custom UI Button

protocol CustomUIButtonDelegate: AnyObject {
    func customUIButtonDidTap()
}

class CustomUIButton: UIButton {

    weak var delegate: CustomUIButtonDelegate?
    
    var isDoing: Bool = false
    
    let image1: UIImage = {
        let image = UIImage(systemName: "chevron.right") ?? UIImage()
        return image
    }()
    
    let image2: UIImage = {
        let image = UIImage(systemName: "chevron.left") ?? UIImage()
        return image
    }()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        setTitle("", for: .normal)
        backgroundColor = .systemRed
        layer.cornerRadius = 35
        layer.shadowColor = UIColor.systemCyan.cgColor
        layer.shadowOpacity = 0.8
        layer.shadowOffset = CGSize(width: 0, height: 0)
        
        addTarget(self, action: #selector(didButtonTapped), for: .touchUpInside)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    @objc func didButtonTapped() {
        isDoing.toggle()
        setImage(isDoing ? image1 : image2, for: .normal)
        delegate?.customUIButtonDidTap()
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 17:47:29