Home > Back-end >  Save button center position and load it in view controller Loaded
Save button center position and load it in view controller Loaded

Time:10-14

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.

  • Related