Xcode 9 Swift 4:实现可拖拽调整位置的视图布局需求
解决iOS多行彩色圆形拖拽调整位置的Swift方案
Hey there! 作为有多年编程经验的开发者,刚转iOS遇到这种布局拖拽问题确实会有点懵——毕竟Android里直接调margin的思路,在iOS里得换个更贴合的玩法。我来给你梳理个完全符合需求的实现方案,保证拖拽平滑、右侧视图跟随,还能严格限制间距不小于0:
1. 先定义数据模型
首先我们需要一个模型来存储每个圆形的属性,尤其是控制间距的offset:
class CircleModel { var offset: CGFloat // 相对于左侧兄弟/父视图的间距 var color: UIColor var size: CGSize // 可选,如果你需要不同大小的圆形 init(offset: CGFloat, color: UIColor, size: CGSize = CGSize(width: 50, height: 50)) { self.offset = offset self.color = color self.size = size } }
2. 自定义容器视图处理布局与拖拽
我们用一个自定义容器视图来管理所有圆形,这样布局和拖拽逻辑都能封装在一起,代码更清晰:
class CircleContainerView: UIView { private var circleViews: [UIView] = [] private var circleModels: [CircleModel] = [] private var currentDraggingIndex: Int? private var initialTouchOffset: CGFloat = 0 // 记录手指与拖拽视图的偏移,避免跳变 // 初始化布局:根据模型数组创建并摆放所有圆形 func setup(with models: [CircleModel]) { circleModels = models // 清理旧视图 circleViews.forEach { $0.removeFromSuperview() } circleViews.removeAll() var currentX: CGFloat = 0 var currentY: CGFloat = (bounds.height - models.first?.size.height ?? 50)/2 // 垂直居中 for model in models { currentX += model.offset // 处理换行:如果当前x位置超出容器宽度,就换到下一行 if currentX + model.size.width > bounds.width { currentX = model.offset currentY += model.size.height + 10 // 行间距10,可调整 } let circle = UIView(frame: CGRect(x: currentX, y: currentY, width: model.size.width, height: model.size.height)) circle.backgroundColor = model.color circle.layer.cornerRadius = model.size.width/2 addSubview(circle) circleViews.append(circle) // 添加拖拽手势 let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:))) circle.addGestureRecognizer(pan) currentX += model.size.width } } // 核心:拖拽手势处理逻辑 @objc private func handlePan(_ gesture: UIPanGestureRecognizer) { guard let draggingView = gesture.view, let index = circleViews.firstIndex(of: draggingView) else { return } let translation = gesture.translation(in: self) switch gesture.state { case .began: currentDraggingIndex = index // 记录手指相对于拖拽视图左侧的偏移,避免拖拽时视图突然跳到手指位置 initialTouchOffset = translation.x - draggingView.frame.origin.x case .changed: // 计算拖拽后的目标X位置 var targetX = translation.x - initialTouchOffset // 限制左间距不能小于0: if index > 0 { // 非第一个圆形,不能超过左侧圆形的右侧边缘 let leftCircleRightEdge = circleViews[index-1].frame.maxX targetX = max(targetX, leftCircleRightEdge) } else { // 第一个圆形,不能超出父视图左侧(可根据你的需求调整起始位置) targetX = max(targetX, 0) } // 计算位置变化量,避免无意义的刷新 let deltaX = targetX - draggingView.frame.origin.x guard deltaX != 0 else { return } // 更新当前拖拽视图的位置 draggingView.frame.origin.x = targetX // 更新对应模型的offset值 if index > 0 { circleModels[index].offset = targetX - circleViews[index-1].frame.maxX } else { circleModels[index].offset = targetX } // 让右侧所有圆形跟随移动,保持相对间距 for i in index+1..<circleViews.count { let prevCircle = circleViews[i-1] let currentCircle = circleViews[i] currentCircle.frame.origin.x = prevCircle.frame.maxX + circleModels[i].offset // 如果是多行场景,这里还要检查是否需要换行,不过基础逻辑和单行一致 if currentCircle.frame.maxX > bounds.width { // 可选:如果拖拽导致右侧圆形超出容器,自动换行(根据你的需求决定是否需要) currentCircle.frame.origin.x = circleModels[i].offset currentCircle.frame.origin.y = prevCircle.frame.maxY + 10 } } case .ended, .cancelled: currentDraggingIndex = nil default: break } } }
3. 在ViewController中使用容器视图
最后在你的控制器里初始化容器视图,传入模拟的服务器数据即可:
class ViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .white // 创建容器视图,设置合适的frame let container = CircleContainerView(frame: CGRect(x: 20, y: 100, width: view.bounds.width - 40, height: 300)) container.backgroundColor = .lightGray view.addSubview(container) // 模拟服务器返回的圆形数据 let circleModels = [ CircleModel(offset: 20, color: .systemRed), CircleModel(offset: 30, color: .systemGreen), CircleModel(offset: 15, color: .systemBlue), CircleModel(offset: 25, color: .systemYellow), CircleModel(offset: 10, color: .systemPurple), CircleModel(offset: 35, color: .systemOrange) ] container.setup(with: circleModels) } }
关键逻辑说明
- 平滑拖拽:通过记录
initialTouchOffset避免拖拽时视图跳变,保证跟随手指实时移动;直接操作frame比AutoLayout约束更流畅,适合实时交互场景。 - 间距限制:拖拽时判断当前圆形是否是第一个,分别限制其不能超出父视图左侧或左侧兄弟视图的右侧边缘,确保间距≥0。
- 右侧视图跟随:遍历当前拖拽圆形的所有右侧兄弟,用前一个视图的
maxX加上当前模型的offset来更新位置,保持相对间距不变。 - 多行适配:初始化布局时判断是否超出容器宽度,自动换行;拖拽时也可根据需求处理右侧圆形的换行逻辑。
内容的提问来源于stack exchange,提问作者allwiine
相关产品推荐
相关产品推荐

