Swift iOS通过pan手势拖拽调整UITextView/UIView宽度及旋转适配问题
解决方案
核心问题根源
你当前实现的失效原因主要有三个:
- 全局持有单个
myView变量,无法支持多视图批量添加和独立手势管理 - 当UIView应用了旋转等非默认
transform后,直接修改frame属性的计算结果不符合预期:此时frame代表视图在父视图中的外观包围盒,不是视图内部坐标系的真实尺寸和位置 - 左右圆点的拖拽计算基于全局屏幕坐标系,未适配视图旋转后的自身方向,导致拖拽方向和调整逻辑和预期不符
具体优化方案
将可调整视图封装为独立自定义类,所有手势逻辑、子视图布局都在类内部实现,避免全局变量依赖,调整尺寸时通过修改bounds和center属性替代frame,适配旋转场景。
完整实现代码
1. 自定义可调整视图类
import UIKit class ResizableCustomView: UIView { // 内部子视图 private let leftDotView = UIView() private let rightDotView = UIView() private let txtView = UITextView() // 最小宽度限制,避免拖拽太窄 private let minWidth: CGFloat = 100 override init(frame: CGRect) { super.init(frame: frame) setupSubviews() setupGestures() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func setupSubviews() { backgroundColor = .yellow // 左红点配置 leftDotView.backgroundColor = .red leftDotView.translatesAutoresizingMaskIntoConstraints = false addSubview(leftDotView) NSLayoutConstraint.activate([ leftDotView.leadingAnchor.constraint(equalTo: leadingAnchor), leftDotView.topAnchor.constraint(equalTo: topAnchor, constant: 30), leftDotView.widthAnchor.constraint(equalToConstant: 10), leftDotView.heightAnchor.constraint(equalToConstant: 40) ]) // 右红点配置 rightDotView.backgroundColor = .red rightDotView.translatesAutoresizingMaskIntoConstraints = false addSubview(rightDotView) NSLayoutConstraint.activate([ rightDotView.trailingAnchor.constraint(equalTo: trailingAnchor), rightDotView.topAnchor.constraint(equalTo: topAnchor, constant: 30), rightDotView.widthAnchor.constraint(equalToConstant: 10), rightDotView.heightAnchor.constraint(equalToConstant: 40) ]) // 文本视图配置 txtView.text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry." txtView.font = .systemFont(ofSize: 15) txtView.isUserInteractionEnabled = false txtView.backgroundColor = .clear txtView.translatesAutoresizingMaskIntoConstraints = false addSubview(txtView) NSLayoutConstraint.activate([ txtView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 10), txtView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -10), txtView.topAnchor.constraint(equalTo: topAnchor, constant: 10), txtView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -10) ]) } private func setupGestures() { isUserInteractionEnabled = true // 视图整体拖拽手势 let panGesture = UIPanGestureRecognizer(target: self, action: #selector(draggedView(_:))) panGesture.delegate = self addGestureRecognizer(panGesture) // 旋转手势 let rotateGesture = UIRotationGestureRecognizer(target: self, action: #selector(handleRotate(recognizer:))) rotateGesture.delegate = self addGestureRecognizer(rotateGesture) // 左红点拖拽手势 let leftPan = UIPanGestureRecognizer(target: self, action: #selector(leftDotMoveGesture(_:))) leftPan.delegate = self leftDotView.addGestureRecognizer(leftPan) // 右红点拖拽手势 let rightPan = UIPanGestureRecognizer(target: self, action: #selector(rightDotMoveGesture(_:))) rightPan.delegate = self rightDotView.addGestureRecognizer(rightPan) } // 整体拖拽逻辑 @objc private func draggedView(_ sender: UIPanGestureRecognizer) { guard let superview = superview else { return } superview.bringSubviewToFront(self) let translation = sender.translation(in: superview) center = CGPoint(x: center.x + translation.x, y: center.y + translation.y) sender.setTranslation(.zero, in: superview) } // 旋转逻辑 @objc private func handleRotate(recognizer: UIRotationGestureRecognizer) { transform = transform.rotated(by: recognizer.rotation) recognizer.rotation = 0 } // 左红点拖拽调整宽度 @objc private func leftDotMoveGesture(_ recognizer: UIPanGestureRecognizer) { guard let superview = superview else { return } let translation = recognizer.translation(in: superview) // 将全局平移转换为视图自身坐标系的平移,适配旋转方向 let convertedTranslation = convert(translation, from: superview) let deltaX = convertedTranslation.x var newWidth = bounds.width - deltaX // 限制最小宽度 guard newWidth >= minWidth else { return } bounds.size.width = newWidth // 调整center位置,保证右边缘不动 let globalDeltaX = translation.x center = CGPoint(x: center.x + globalDeltaX / 2, y: center.y + translation.y / 2) recognizer.setTranslation(.zero, in: superview) } // 右红点拖拽调整宽度 @objc private func rightDotMoveGesture(_ recognizer: UIPanGestureRecognizer) { guard let superview = superview else { return } let translation = recognizer.translation(in: superview) let convertedTranslation = convert(translation, from: superview) let deltaX = convertedTranslation.x var newWidth = bounds.width + deltaX guard newWidth >= minWidth else { return } bounds.size.width = newWidth let globalDeltaX = translation.x center = CGPoint(x: center.x + globalDeltaX / 2, y: center.y + translation.y / 2) recognizer.setTranslation(.zero, in: superview) } } extension ResizableCustomView: UIGestureRecognizerDelegate { func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } }
2. 视图控制器调用代码
需要添加多少个视图就调用多少次addCustomView即可:
func addCustomView() { let customView = ResizableCustomView(frame: CGRect(x: 100, y: 100, width: 200, height: 100)) view.addSubview(customView) }
效果说明
- 无论视图旋转到任何角度,拖拽左右红点都可以沿视图自身水平方向调整宽度
- 文本内容会随宽度变化自动换行重排
- 支持同时添加任意数量的同类视图,每个视图独立处理自己的拖拽、旋转、调整逻辑,互不干扰
内容的提问来源于stack exchange,提问作者AtulParmar
相关产品推荐
相关产品推荐

