Swift中旋转/捏合缩放后按钮无法保持视图右侧的解决方案咨询
问题描述
这是一款图片编辑APP,支持通过捏合手势缩放图片、旋转手势旋转图片,还能拖拽边角调整图片尺寸。图片右侧设有两个按钮:触发图片旋转的rotate按钮、删除图片的delete按钮。目前执行捏合缩放或旋转操作后,按钮无法保持在图片右侧,布局约束失效。
效果对比
- 异常效果:

- 正常效果:

相关代码
视图初始化代码
private let imageView: UIImageView = { let imageView = UIImageView() let bundlePath = Bundle.main.path(forResource: "lockscreen", ofType: "jpeg") imageView.translatesAutoresizingMaskIntoConstraints = false imageView.image = UIImage(contentsOfFile: bundlePath ?? "") imageView.isUserInteractionEnabled = true imageView.contentMode = .scaleToFill imageView.clipsToBounds = false return imageView }() private let rotateButton: UIButton = { let button = UIButton() button.translatesAutoresizingMaskIntoConstraints = false let newSize = CGSize(width: 25, height: 25) let image = UIImage(systemName: "rotate.left") let resizedImage = image?.withConfiguration(UIImage.SymbolConfiguration(pointSize: newSize.width, weight: .medium)) button.setImage(resizedImage, for: .normal) button.tintColor = .systemPink button.isHidden = true return button }() private let deleteButton: UIButton = { let button = UIButton() button.translatesAutoresizingMaskIntoConstraints = false let newSize = CGSize(width: 25, height: 25) let image = UIImage(systemName: "trash") let resizedImage = image?.withConfiguration(UIImage.SymbolConfiguration(pointSize: newSize.width, weight: .medium)) button.setImage(resizedImage, for: .normal) button.tintColor = .systemPink button.isHidden = true return button }() private var topLeftCircleView = CircleView() private var topRightCircleView = CircleView() private var bottomLeftCircleView = CircleView() private var bottomRightCircleView = CircleView() private var borderView = UIView() private var imageViewTopConstraint: NSLayoutConstraint! private var imageViewBottomConstraint: NSLayoutConstraint! private var imageViewLeftConstraint: NSLayoutConstraint! private var imageViewRightConstraint: NSLayoutConstraint! private var rotateButtonBottomAnchorConstraint: NSLayoutConstraint! private var rotateButtonTrailingAnchorConstraint: NSLayoutConstraint! private var deleteButtonTopAnchorConstraint: NSLayoutConstraint! private var deleteButtonTrailingAnchorConstraint: NSLayoutConstraint! private var isResizing: Bool = false { didSet { [topLeftCircleView, topRightCircleView, bottomLeftCircleView, bottomRightCircleView, rotateButton, deleteButton].forEach { $0.isHidden = !isResizing } borderView.layer.borderWidth = isResizing ? 2.0 : 0.0 } } private var originalImageFrame: CGRect = .zero private var resizeRect = ResizeRect() private var proxyFactor: CGFloat = 10.0 private var rotationGesture: UIRotationGestureRecognizer! override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white setupView() addConstraintsForItems() createCircles() addTapGestureRecognizer() addPinchGestureRecognizer() addRotationGesture() addDeletePhotoButton() }
约束配置代码
private func addConstraintsForItems() { imageViewTopConstraint = imageView.topAnchor.constraint(equalTo: view.topAnchor, constant: 180) imageViewBottomConstraint = view.bottomAnchor.constraint(equalTo: imageView.bottomAnchor, constant: 180) imageViewLeftConstraint = imageView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 60) imageViewRightConstraint = view.trailingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 70) NSLayoutConstraint.activate([imageViewTopConstraint, imageViewBottomConstraint, imageViewLeftConstraint, imageViewRightConstraint]) let borderViewTopConstraint = borderView.topAnchor.constraint(equalTo: imageView.topAnchor) let borderViewLeftConstraint = borderView.leadingAnchor.constraint(equalTo: imageView.leadingAnchor) let borderViewRightConstraint = borderView.trailingAnchor.constraint(equalTo: imageView.trailingAnchor) let borderViewBottomConstraint = borderView.bottomAnchor.constraint(equalTo: imageView.bottomAnchor) NSLayoutConstraint.activate([borderViewTopConstraint, borderViewLeftConstraint, borderViewRightConstraint, borderViewBottomConstraint]) rotateButtonBottomAnchorConstraint = rotateButton.centerYAnchor.constraint(equalTo: imageView.centerYAnchor, constant: -20) rotateButtonTrailingAnchorConstraint = rotateButton.trailingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 40) NSLayoutConstraint.activate([rotateButtonBottomAnchorConstraint, rotateButtonTrailingAnchorConstraint]) deleteButtonTopAnchorConstraint = deleteButton.centerYAnchor.constraint(equalTo: imageView.centerYAnchor, constant: 20) deleteButtonTrailingAnchorConstraint = deleteButton.trailingAnchor.constraint(equalTo: imageView.trailingAnchor, constant: 40) NSLayoutConstraint.activate([deleteButtonTopAnchorConstraint, deleteButtonTrailingAnchorConstraint]) }
缩放与旋转处理代码
@objc private func handlePincheGesture(_ gestureRecognizer: UIPinchGestureRecognizer) { if gestureRecognizer.state == .began { isResizing = false } gestureRecognizer.view?.transform = (gestureRecognizer.view?.transform)!.scaledBy(x: gestureRecognizer.scale, y: gestureRecognizer.scale) gestureRecognizer.scale = 1.0 if gestureRecognizer.state == .ended { isResizing = true } } @objc private func handleRotationGesture(_ gestureRecognizer: UIRotationGestureRecognizer) { if gestureRecognizer.state == .began { isResizing = false } if let view = gestureRecognizer.view { view.transform = view.transform.rotated(by: gestureRecognizer.rotation) gestureRecognizer.rotation = 0 } if gestureRecognizer.state == .ended { isResizing = true } }
解决方案
问题根源
直接修改imageView的transform属性实现缩放和旋转时,Auto Layout的约束是基于视图的原始frame计算的,不会跟随transform变化自动更新位置。按钮约束绑定在imageView上,自然会出现位置偏移。
修复步骤
1. 引入容器视图包裹imageView
创建containerView作为imageView的父视图,所有缩放、旋转操作作用在containerView上,imageView保持原始约束,按钮约束绑定到containerView。
添加容器视图初始化:
private let containerView: UIView = { let view = UIView() view.translatesAutoresizingMaskIntoConstraints = false view.isUserInteractionEnabled = true return view }()
在setupView()中调整视图层级:
private func setupView() { view.addSubview(containerView) containerView.addSubview(imageView) // 其他视图(borderView、按钮、边角圆圈)仍添加到主视图 view.addSubview(borderView) view.addSubview(rotateButton) view.addSubview(deleteButton) view.addSubview(topLeftCircleView) view.addSubview(topRightCircleView) view.addSubview(bottomLeftCircleView) view.addSubview(bottomRightCircleView) }
2. 调整约束配置
修改addConstraintsForItems(),将imageView约束绑定到containerView,按钮和borderView约束也绑定到containerView:
private func addConstraintsForItems() { // 设置containerView初始位置(替换原imageView的约束) let containerTop = containerView.topAnchor.constraint(equalTo: view.topAnchor, constant: 180) let containerBottom = view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: 180) let containerLeft = containerView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 60) let containerRight = view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: 70) NSLayoutConstraint.activate([containerTop, containerBottom, containerLeft, containerRight]) // imageView绑定到containerView边缘 NSLayoutConstraint.activate([ imageView.topAnchor.constraint(equalTo: containerView.topAnchor), imageView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), imageView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), imageView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor) ]) // borderView绑定到containerView NSLayoutConstraint.activate([ borderView.topAnchor.constraint(equalTo: containerView.topAnchor), borderView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), borderView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), borderView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor) ]) // 按钮绑定到containerView rotateButtonBottomAnchorConstraint = rotateButton.centerYAnchor.constraint(equalTo: containerView.centerYAnchor, constant: -20) rotateButtonTrailingAnchorConstraint = rotateButton.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: 40) NSLayoutConstraint.activate([rotateButtonBottomAnchorConstraint, rotateButtonTrailingAnchorConstraint]) deleteButtonTopAnchorConstraint = deleteButton.centerYAnchor.constraint(equalTo: containerView.centerYAnchor, constant: 20) deleteButtonTrailingAnchorConstraint = deleteButton.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: 40) NSLayoutConstraint.activate([deleteButtonTopAnchorConstraint, deleteButtonTrailingAnchorConstraint]) }
3. 修改手势处理代码
将缩放和旋转操作对象改为containerView:
@objc private func handlePincheGesture(_ gestureRecognizer: UIPinchGestureRecognizer) { if gestureRecognizer.state == .began { isResizing = false } containerView.transform = containerView.transform.scaledBy(x: gestureRecognizer.scale, y: gestureRecognizer.scale) gestureRecognizer.scale = 1.0 if gestureRecognizer.state == .ended { isResizing = true } } @objc private func handleRotationGesture(_ gestureRecognizer: UIRotationGestureRecognizer) { if gestureRecognizer.state == .began { isResizing = false } containerView.transform = containerView.transform.rotated(by: gestureRecognizer.rotation) gestureRecognizer.rotation = 0 if gestureRecognizer.state == .ended { isResizing = true } }
4. 更新手势添加逻辑
确保手势添加到containerView上:
private func addPinchGestureRecognizer() { let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(handlePincheGesture)) containerView.addGestureRecognizer(pinchGesture) } private func addRotationGesture() { rotationGesture = UIRotationGestureRecognizer(target: self, action: #selector(handleRotationGesture)) containerView.addGestureRecognizer(rotationGesture) }
原理说明
通过容器视图隔离transform操作,Auto Layout约束基于容器视图的原始布局计算,按钮和边框的约束始终绑定到容器视图,能保持相对位置不变。容器视图的transform变化仅影响内部的imageView,不会破坏外部约束关系。
内容的提问来源于stack exchange,提问作者Stefan
相关产品推荐
相关产品推荐

