英文:
iOS UIkit ViewDidLayout Calls On Handle Drag
问题 {#heading}
应用程序有两个不同的部分。一个是矩形视图(Rectangle view),另一个是按钮(Button)。矩形视图可以通过拖动手势(pan touch gesture)进行拖动。按钮可以通过轻击手势(tap gestures)更改自己的图像。当用户轻击按钮时,它会更改自己的图像。但是,当我轻击按钮时,矩形视图会移动到初始位置(即视图加载时的位置)。
import UIKit
final class SampleViewController: UIViewController {
private var isTapped: Bool = false
let button: UIButton = {
let button = UIButton()
let image = UIImageView(image: UIImage(systemName: "play"))
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = .yellow
return button
}()
let littleView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.borderColor = UIColor.red.cgColor
view.layer.borderWidth = 4
view.isUserInteractionEnabled = true
view.backgroundColor = .red
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(button)
self.view.addSubview(littleView)
littleView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:))))
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
NSLayoutConstraint.activate([
button.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor),
button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
littleView.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 1/5),
littleView.widthAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 1/3),
])
}
override func viewDidLayoutSubviews() {
//littleView.frame = CGRect(x: 0, y: 0, width: littleView.frame.width, height: littleView.frame.height)
}
override func viewWillLayoutSubviews() {
//littleView.frame = CGRect(x: 0, y: 0, width: littleView.frame.width, height: littleView.frame.height)
}
@objc private func buttonTapped() {
button.setImage(UIImage(systemName: isTapped ? "play.slash" : "play"), for: .normal)
self.view.layoutIfNeeded()
isTapped.toggle()
print("Button tapped")
}
@objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
guard let viewToMove = gesture.view else { return }
if gesture.state == .began || gesture.state == .changed {
let translation = gesture.translation(in: view)
let newX = max(0, min(view.frame.width - viewToMove.frame.width, viewToMove.frame.origin.x + translation.x))
let newY = max(0, min((view.frame.height - viewToMove.frame.height), viewToMove.frame.origin.y + translation.y))
viewToMove.frame.origin = CGPoint(x: newX, y: newY)
gesture.setTranslation(CGPoint.zero, in: view)
}
}
}
我检查了日志,并发现在轻击按钮时会调用viewDidLayout
方法。如何处理此问题?有人可以帮助我吗?非常感谢。
英文:
App has 2 different parts. Rectangle view and button. Rectangle view can drag with pan touch gesture. Button can change own image with tap gestures. When user taps the button, it changes own image. But when I tap the button, rectangle view moves to the at the beginning position.(view did load position).
import UIKit
final class SampleViewController: UIViewController {
private var isTapped: Bool = false
let button: UIButton = {
let button = UIButton()
let image = UIImageView(image: UIImage(systemName: "play"))
button.translatesAutoresizingMaskIntoConstraints = false
button.backgroundColor = .yellow
return button
}()
let littleView: UIView = {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.layer.borderColor = UIColor.red.cgColor
view.layer.borderWidth = 4
view.isUserInteractionEnabled = true
view.backgroundColor = .red
return view
}()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(button)
self.view.addSubview(littleView)
littleView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:))))
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
NSLayoutConstraint.activate([
button.bottomAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.bottomAnchor),
button.centerXAnchor.constraint(equalTo: self.view.centerXAnchor),
littleView.heightAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 1/5),
littleView.widthAnchor.constraint(equalTo: self.view.heightAnchor, multiplier: 1/3),
])
}
override func viewDidLayoutSubviews() {
//littleView.frame = CGRect(x: 0, y: 0, width: littleView.frame.width, height: littleView.frame.height)
}
override func viewWillLayoutSubviews() {
//littleView.frame = CGRect(x: 0, y: 0, width: littleView.frame.width, height: littleView.frame.height)
}
@objc private func buttonTapped() {
button.setImage(UIImage(systemName: isTapped ? "play.slash" : "play"), for: .normal)
self.view.layoutIfNeeded()
isTapped.toggle()
print("Button tapped")
}
@objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
guard let viewToMove = gesture.view else { return }
if gesture.state == .began || gesture.state == .changed {
let translation = gesture.translation(in: view)
let newX = max(0, min(view.frame.width - viewToMove.frame.width, viewToMove.frame.origin.x + translation.x))
let newY = max(0, min((view.frame.height - viewToMove.frame.height), viewToMove.frame.origin.y + translation.y))
viewToMove.frame.origin = CGPoint(x: newX, y: newY)
gesture.setTranslation(CGPoint.zero, in: view)
}
}
}
I checked logs and I see viewDidLayout methods calling when I tap the button. How can I handle this problem, is there anyone help me ? Thanks a lot.
答案1 {#1}
得分: 0
你不能在明确的框架设置中"混合匹配"约束条件。
两个选项...
1 - 在 littleView
上不使用任何约束条件:
littleView.translatesAutoresizingMaskIntoConstraints = true
littleView.frame = .init(x: 0.0, y: 0.0, width: 160.0, height: 120.0)
2 - 使用约束条件,但跟踪顶部和前导约束条件,并在拖动时更新它们的常量值:
// 将这些添加为类属性
var lvTop: NSLayoutConstraint!
var lvLeading: NSLayoutConstraint!
// 在 `viewDidLoad()` 中设置 widthAnchor 和 heightAnchor 后,添加以下代码:
lvTop = littleView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0.0)
lvLeading = littleView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0)
lvTop.isActive = true
lvLeading.isActive = true
// 将您的 `handlePan` 更改为:
@objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
guard let viewToMove = gesture.view else { return }
if gesture.state == .began || gesture.state == .changed {
let translation = gesture.translation(in: view)
lvTop.constant += translation.y
lvLeading.constant += translation.x
gesture.setTranslation(CGPoint.zero, in: view)
}
}
英文:
You cannot "mix-and-match" constraints with explicit frame settings.
Two options...
1 - don't use any constraints on littleView
:
littleView.translatesAutoresizingMaskIntoConstraints = true
littleView.frame = .init(x: 0.0, y: 0.0, width: 160.0, height: 120.0)
2 - use constraints, but track the top and leading constraints and update their constants when dragging:
// add these as class properties
var lvTop: NSLayoutConstraint!
var lvLeading: NSLayoutConstraint!
after setting widthAnchor and heightAnchor in viewDidLoad()
, add these lines:
lvTop = littleView.topAnchor.constraint(equalTo: view.topAnchor, constant: 0.0)
lvLeading = littleView.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0)
lvTop.isActive = true
lvLeading.isActive = true
change your handlePan
to this:
@objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
guard let viewToMove = gesture.view else { return }
if gesture.state == .began || gesture.state == .changed {
let translation = gesture.translation(in: view)
lvTop.constant += translation.y
lvLeading.constant += translation.x
gesture.setTranslation(CGPoint.zero, in: view)
}
}