自定义UIButton代理触发后子VC视图位置异常问题求助
问题描述
我在自定义UIButton和父子视图控制器间通过Protocol&Delegate实现交互,结构为父VC包含子VC和自定义按钮。需求是点击按钮时,修改子VC的view frame原点并切换按钮图片。但实际测试时,只要修改按钮图片,子VC视图会先按updateView方法更新位置,随后立刻复位到初始位置;如果不修改按钮图片,功能完全正常。
问题根源
- AutoLayout约束重复激活:父VC的
viewDidLayoutSubviews方法里反复调用applyConstraints(),每次都会重新激活子VC的初始底部约束。修改按钮图片会触发视图布局更新,导致viewDidLayoutSubviews执行,强制将子VC视图拉回初始位置。 - 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
相关产品推荐
相关产品推荐

