Hi guys sorry for my English, I´m working on an app that is capable of moving a bunch of buttons, each of them, on-screen with UIPanGestureRecognizer
, saving the center position (x,y) and tag in Core Data
.
When the app starts from zero and I load position from Core Data
:
if let c = ButtonEntity.getButton(tag: self.tag)
{
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5)
{
self.center = CGPoint(x: CGFloat( c.xPoint ?? 0), y: CGFloat( c.yPoint ?? 0))
}
This is from a custom UIButton
Class and the buttons are placed by default in a certain position with auto layout
The problem is that in some cases some buttons return to their auto layout
positions instead of coreData
center position assigned on viewDidLoad
is there any solution or another way to do this? thanks
To help understand why we cannot mix constraints with .center
(frame) changes...
Here are two almost identical view controllers.
Each adds a button, and sets the button's .centerXAnchor
and .centerYAnchor
to the view's .centerXAnchor
and .centerYAnchor
.
We add a pan gesture to the button so we can drag it around.
We also implement touchesBegan(...)
where all we do is change the button title. Doing so will trigger an auto-layout pass.
In the pan func in the first example, we use a typical "get the pan location and update the .center
property:
@objc func pan(_ sender: UIPanGestureRecognizer) {
guard let v = sender.view, let sv = v.superview else { return }
if sender.state == .changed {
let pt: CGPoint = sender.location(in: sv)
// update the button's .center property (changes the frame)
v.center = pt
}
}
This works fine, until we tap anywhere off the button. At that point, we change the button title and auto-layout moves the button back to its center X and Y constraints.
In the second example, we add X and Y constraints as var / properties to the controller:
// btn center constraints
// we will modify the .constants when panning
var xConstraint: NSLayoutConstraint!
var yConstraint: NSLayoutConstraint!
set them up in viewDidLoad()
, and then move the button in the pan gesture like this:
@objc func pan(_ sender: UIPanGestureRecognizer) {
guard let v = sender.view, let sv = v.superview else { return }
if sender.state == .changed {
let pt: CGPoint = sender.location(in: sv)
let xOff = pt.x - sv.center.x
let yOff = pt.y - sv.center.y
// update the .constant values for the btn center x/y
xConstraint.constant = xOff
yConstraint.constant = yOff
}
}
Now, tapping anywhere else will change the button title, but it will stay where it is because we've changed the .constant
values of our X and Y constraints.
Set .center
-- problems
class DemoVC: UIViewController {
let btnToMove = UIButton()
var tapCounter: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
btnToMove.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(btnToMove)
btnToMove.setTitle("Test", for: [])
btnToMove.backgroundColor = .red
NSLayoutConstraint.activate([
btnToMove.centerXAnchor.constraint(equalTo: view.centerXAnchor),
btnToMove.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
let p = UIPanGestureRecognizer(target: self, action: #selector(pan(_:)))
btnToMove.addGestureRecognizer(p)
}
@objc func pan(_ sender: UIPanGestureRecognizer) {
guard let v = sender.view, let sv = v.superview else { return }
if sender.state == .changed {
let pt: CGPoint = sender.location(in: sv)
// update the button's .center property (changes the frame)
v.center = pt
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// update the button title - which will trigger an auto-layout pass
tapCounter += 1
btnToMove.setTitle("Test \(tapCounter)", for: [])
}
}
Update constraint .constant
values -- no problems
class DemoVC: UIViewController {
let btnToMove = UIButton()
var tapCounter: Int = 0
// btn center constraints
// we will modify the .constants when panning
var xConstraint: NSLayoutConstraint!
var yConstraint: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
btnToMove.setTitle("Test", for: [])
btnToMove.backgroundColor = .red
btnToMove.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(btnToMove)
xConstraint = btnToMove.centerXAnchor.constraint(equalTo: view.centerXAnchor, constant: 0.0)
yConstraint = btnToMove.centerYAnchor.constraint(equalTo: view.centerYAnchor, constant: 0.0)
NSLayoutConstraint.activate([
xConstraint, yConstraint,
])
let p = UIPanGestureRecognizer(target: self, action: #selector(pan(_:)))
btnToMove.addGestureRecognizer(p)
}
@objc func pan(_ sender: UIPanGestureRecognizer) {
guard let v = sender.view, let sv = v.superview else { return }
if sender.state == .changed {
let pt: CGPoint = sender.location(in: sv)
let xOff = pt.x - sv.center.x
let yOff = pt.y - sv.center.y
// update the .constant values for the btn center x/y
xConstraint.constant = xOff
yConstraint.constant = yOff
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// update the button title - which will trigger an auto-layout pass
tapCounter += 1
btnToMove.setTitle("Test \(tapCounter)", for: [])
}
}