如何使用平移手势识别器更改不同视图的布局约束?
How can I change layout constraints for different views using a pan gesture recognizer?
我有一个功能,可以在每次按下按钮时向主视图添加一个新的 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
。
- 创建一个名为
MovableView
的 UIView
子类,它具有 horizontalConstraint
和 verticalConstraint
的 NSLayoutConstraint?
属性。
- 在设置约束之前设置这些属性
isActive = true
。
当您需要移动 MovableView
时,请为 horizontalConstraint
和 verticalConstraint
更新 constant
属性。
moveableView.horizontalConstraint?.constant = newXConstant
moveableView.verticalConstraint?.constant = newYConstant
或者,您可以在创建、分配和激活新约束之前设置 horizontalConstraint?.isActive = false
和 verticalConstraint?.isActive = false
。
moveableView.horizontalConstraint?.isActive = false
moveableView.verticalConstraint?.isActive = false
moveableView.horizontalConstraint = ...
moveableView.horizontalConstraint?.isActive = true
moveableView.verticalConstraint = ...
moveableView.verticalConstraint?.isActive = true
有关我 4 年前编写的可拖动视图的实现,请参阅 draggable views。它已经过时并且需要更新,但您可能会从中得到一些想法。另请阅读下面的评论,@Matt 解释说您可以跳过对可拖动视图使用约束。可以向屏幕添加不参与 Auto Layout 的视图,即使屏幕的其余部分参与也是如此。
我修复了你的代码:
- 您没有提供
MovableView
的实现,但约束属性似乎是 static
而不是成员属性。您需要将它们作为成员属性以支持多个可移动视图。
- 你
startingConstantPosX
和 startingConstantPosY
交换了导致跳跃的原因。
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)
}
}
我有一个功能,可以在每次按下按钮时向主视图添加一个新的 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
。
- 创建一个名为
MovableView
的UIView
子类,它具有horizontalConstraint
和verticalConstraint
的NSLayoutConstraint?
属性。 - 在设置约束之前设置这些属性
isActive = true
。 当您需要移动
MovableView
时,请为horizontalConstraint
和verticalConstraint
更新constant
属性。moveableView.horizontalConstraint?.constant = newXConstant moveableView.verticalConstraint?.constant = newYConstant
或者,您可以在创建、分配和激活新约束之前设置
horizontalConstraint?.isActive = false
和verticalConstraint?.isActive = false
。moveableView.horizontalConstraint?.isActive = false moveableView.verticalConstraint?.isActive = false moveableView.horizontalConstraint = ... moveableView.horizontalConstraint?.isActive = true moveableView.verticalConstraint = ... moveableView.verticalConstraint?.isActive = true
有关我 4 年前编写的可拖动视图的实现,请参阅 draggable views。它已经过时并且需要更新,但您可能会从中得到一些想法。另请阅读下面的评论,@Matt 解释说您可以跳过对可拖动视图使用约束。可以向屏幕添加不参与 Auto Layout 的视图,即使屏幕的其余部分参与也是如此。
我修复了你的代码:
- 您没有提供
MovableView
的实现,但约束属性似乎是static
而不是成员属性。您需要将它们作为成员属性以支持多个可移动视图。 - 你
startingConstantPosX
和startingConstantPosY
交换了导致跳跃的原因。
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)
}
}