如何使用平移手势识别器更改不同视图的布局约束



我有一个函数,每次按下按钮都会向主视图添加一个新的UIView。我希望这些视图中的每一个都是可拖动的。目前,我正在为每个视图添加一个平移手势识别器,它可以更改每个视图的布局约束。

我的问题是,在移动每个视图之前,我没有正确禁用它的原始约束,所以新旧约束会发生冲突。

这是我的代码:

import UIKit
class ViewController5: UIViewController, UINavigationControllerDelegate, UIGestureRecognizerDelegate {
var textPosXConstraint = NSLayoutConstraint()
var textPosYConstraint = NSLayoutConstraint()
var numberOfViews = 0
var startingConstantPosX: CGFloat  = 0.0
var startingConstantPosY: CGFloat  = 100.0
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "edit views"
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "add", style: .plain, target: self, action: #selector(addView))
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Clear", style: .plain, target: self, action: #selector(clearThis))
}
@objc func addView() {
let view1 = UIView()
view1.backgroundColor = UIColor.yellow
view1.frame = CGRect()
view.addSubview(view1)
view1.translatesAutoresizingMaskIntoConstraints = false
view1.widthAnchor.constraint(equalToConstant: 80).isActive = true
view1.heightAnchor.constraint(equalToConstant: 80).isActive = true
textPosXConstraint = view1.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0)
textPosXConstraint.isActive = true
textPosYConstraint = view1.centerYAnchor.constraint(equalTo: view.topAnchor, constant: 100)
textPosYConstraint.isActive = true
numberOfViews+=1
view1.tag = numberOfViews
//add pan gesture
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
panGestureRecognizer.delegate = self
view1.addGestureRecognizer(panGestureRecognizer)
}
@objc func clearThis() {
for view in view.subviews {
view.removeFromSuperview()
}
numberOfViews = 0
}
@objc func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
guard let currentView = gestureRecognizer.view else { return }
if gestureRecognizer.state == .began {
print("view being moved is: (currentView.tag)")
startingConstantPosY = textPosYConstraint.constant
startingConstantPosX = textPosXConstraint.constant
} else if gestureRecognizer.state == .changed {
let translation = gestureRecognizer.translation(in: self.view)
let newXConstant = self.startingConstantPosX + translation.x
let newYConstant = self.startingConstantPosY + translation.y
currentView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: startingConstantPosX).isActive = false
currentView.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: newXConstant).isActive = true
}
}
}

理想情况下,它可以工作,这样用户就可以添加他们想要的任意多的视图,并移动每个视图。

谢谢你的帮助!

您需要保留对为视图创建的位置约束的引用,以便在创建新约束之前更改其constant值或设置isActive = false

  1. 创建一个名为MovableViewUIView子类,该子类具有horizontalConstraintverticalConstraintNSLayoutConstraint?属性
  2. 在设置约束的isActive = true之前,在创建约束时设置这些特性
  3. 当需要移动MovableView时,请更新horizontalConstraintverticalConstraintconstant属性。

    moveableView.horizontalConstraint?.constant = newXConstant
    moveableView.verticalConstraint?.constant = newYConstant
    

    或者,可以在创建、指定和激活新约束之前设置horizontalConstraint?.isActive = falseverticalConstraint?.isActive = false

    moveableView.horizontalConstraint?.isActive = false
    moveableView.verticalConstraint?.isActive = false
    moveableView.horizontalConstraint = ...
    moveableView.horizontalConstraint?.isActive = true
    moveableView.verticalConstraint = ...
    moveableView.verticalConstraint?.isActive = true
    

有关我4年前写的可拖动视图的实现,请参阅可拖动视图。它已经过时,需要更新,但你可能会从中得到一些想法。还可以阅读下面的评论,@Matt解释说,你可以跳过对可拖动视图的限制。可以向屏幕添加不参与自动布局的视图,即使屏幕的其他部分参与。


我修复了你的代码:

  • 您没有提供MovableView的实现,但约束属性似乎是static,而不是成员属性。您需要将它们作为成员属性,以便支持多个可移动视图
  • 您交换了startingConstantPosXstartingConstantPosY,这就是导致跳跃的原因

class MovableView: UIView {
var horizontalConstraint: NSLayoutConstraint?
var verticalConstraint: NSLayoutConstraint?
}
class ViewController: UIViewController, UINavigationControllerDelegate, UIGestureRecognizerDelegate {
var numberOfViews = 0
var startingConstantPosX: CGFloat  = 0.0
var startingConstantPosY: CGFloat  = 100.0
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "edit views"
navigationItem.rightBarButtonItem = UIBarButtonItem(title: "add", style: .plain, target: self, action: #selector(addView))
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Clear", style: .plain, target: self, action: #selector(clearThis))
}
@objc func addView() {
let view1 = MovableView()
view1.backgroundColor = UIColor.yellow
view1.frame = CGRect()
view.addSubview(view1)
view1.translatesAutoresizingMaskIntoConstraints = false
view1.widthAnchor.constraint(equalToConstant: 80).isActive = true
view1.heightAnchor.constraint(equalToConstant: 80).isActive = true
view1.horizontalConstraint = view1.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0)
view1.horizontalConstraint?.isActive = true
view1.verticalConstraint = view1.centerYAnchor.constraint(equalTo: view.topAnchor, constant: 100)
view1.verticalConstraint?.isActive = true
numberOfViews+=1
view1.tag = numberOfViews
//add pan gesture
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
panGestureRecognizer.delegate = self
view1.addGestureRecognizer(panGestureRecognizer)
}
@objc func clearThis() {
for view in view.subviews {
view.removeFromSuperview()
}
numberOfViews = 0
}

@objc func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
guard let currentView = gestureRecognizer.view as? MovableView else { return }
if gestureRecognizer.state == .began {
print("view being moved is: (currentView.tag)")
startingConstantPosX = currentView.horizontalConstraint?.constant ?? 0
startingConstantPosY = currentView.verticalConstraint?.constant ?? 0
} else if gestureRecognizer.state == .changed {
let translation = gestureRecognizer.translation(in: self.view)
let newXConstant = startingConstantPosX + translation.x
let newYConstant = startingConstantPosY + translation.y
currentView.horizontalConstraint?.constant = newXConstant
currentView.verticalConstraint?.constant = newYConstant
}
}
}

如果每次调用handlePan时都将translation设置回.zero,则无需跟踪起始位置即可简化代码:

@objc func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
guard let currentView = gestureRecognizer.view as? MovableView else { return }
if gestureRecognizer.state == .began {
print("view being moved is: (currentView.tag)")
} else if gestureRecognizer.state == .changed {
let translation = gestureRecognizer.translation(in: self.view)
currentView.horizontalConstraint?.constant += translation.x
currentView.verticalConstraint?.constant += translation.y
gestureRecognizer.setTranslation(.zero, in: self.view)
}
}

最新更新