Hi guys sorry for my english, i´m working in app that is capable of move a bunch of buttons, each of them, on screen with UIPanGestureRecognizer, save 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 positions 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 ViewLoaded
is there any solution or another way to do this ? thanks
CodePudding user response:
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: [])
}
}
CodePudding user response:
It would be batter to create and add UIButtons
programmatically and assign it's initial position at initialisation with UIButton.frame
property. set it's translatesAutoresizingMaskIntoConstraints = false and changing it's position will work perfectly.
check this for programmatically uibuttons : Swift add show action to button programmatically
Have you set translatesAutoresizingMaskIntoConstraints = false for your button before changing it's position ? It should also work with buttons created with auto layout.