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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 05:30:53