Search code examples
iosswiftswift2constraintsanimatewithduration

Moving a button in swift using animate with duration with constraints and detecting a touch during it


I want to move a button from point A to point B. Point A: leadingConstraint = 120, topConstraint = 400. Point B: leadingConstraint = 120, topConstraint = 200. For my game purpose, I can't use frames. I also want to be able to detect a touch on it while its moving (I have no idea how to do this). I have this code where I'm attempting to move the button (using animate with duration) but whats happening is that its starting from the top left corner (small size) instead of starting from point A. It then goes to point B (normal size). Here is the code:

@IBAction func Start(sender: UIButton) { 
    var topAnchorforButton: NSLayoutConstraint!
    let button = UIButton()
    button.setTitle("button", forState: .Normal)
    button.setTitleColor(UIColor.blackColor(), forState: .Normal)
    button.addTarget(self, action: "buttonPressed:", forControlEvents: UIControlEvents.TouchUpInside)
    self.view.addSubview(button)
    button.translatesAutoresizingMaskIntoConstraints = false
    topAnchorforButton = button.topAnchor.constraintEqualToAnchor(view.topAnchor, constant: 400)
    NSLayoutConstraint.activateConstraints([
    button.widthAnchor.constraintEqualToConstant(75),
    button.heightAnchor.constraintEqualToConstant(75),
    button.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor, constant: 120),
    topAnchorforButton
        ])
    [UIView.animateWithDuration(5.0, delay: 0.0, options: [.CurveLinear, .AllowUserInteraction], animations: {
    topAnchorforButton.constant = 200                         
    self.view.layoutIfNeeded()
        }, completion: nil)]    
   }
func buttonPressed(sender: UIButton) {
    print("hi")
}

Please help. I also don't want to use CADisplayLink. Thanks in advance... Anton


Solution

  • There are 3 issues in your question:

    1. The button must start from point A instead of the top left corner.
    2. The button must have a normal size instead of a small size.
    3. The button can be able to click while its moving.

    For (1) and (2), you must move code to add button inside the viewDidLoad() and also need to keep instances for button and topAnchorForButton.

    Firstly, you declare two properties for button and topAnchorForButton.

    var button: UIButton!
    var topAnchorForButton: NSLayoutConstraint!
    

    Next, you add button and set its position inside the viewDidLoad().

    override func viewDidLoad() {
        super.viewDidLoad()
    
        button = UIButton()
        button.setTitle("button", forState: .Normal)
        button.setTitleColor(UIColor.blackColor(), forState: .Normal)
        button.addTarget(self, action: "buttonPressed:", forControlEvents: UIControlEvents.TouchUpInside)
        self.view.addSubview(button)
        button.translatesAutoresizingMaskIntoConstraints = false
    
        topAnchorForButton = button.topAnchor.constraintEqualToAnchor(view.topAnchor, constant: 400)
        NSLayoutConstraint.activateConstraints([
            topAnchorForButton,
            button.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor, constant: 120),
        ])
    }
    
    func buttonPressed(sender: UIButton) {
        print("hi")
    }
    
    @IBAction func start(sender: AnyObject) {
        topAnchorForButton.constant = 200
    
        [UIView.animateWithDuration(5.0, delay: 0.0, options: [.CurveLinear, .AllowUserInteraction], animations: {
            self.view.layoutIfNeeded()
            }, completion: nil)]
    }
    

    For (3), to hit a moving button, you need to do hit testing on the button's .layer.presentationLayer property.

    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        guard let touch = (touches as NSSet).anyObject() as? UITouch else {
            return
        }
    
        let touchLocation = touch.locationInView(self.view)
        if button.layer.presentationLayer()?.hitTest(touchLocation) != nil {
            self.buttonPressed(button)
        }
    }
    

    If you want to create a new button every time the start button is pressed, you put the code to add and set its position inside the start: method. You don't need to keep instances for button and topAnchorForButton anymore. You can use tag to retrieve the button.

    @IBAction func start(sender: AnyObject) {
        // Remove the previous button
        if let previousButton = self.view.viewWithTag(100) as? UIButton {
            previousButton.removeFromSuperview()
        }
    
        let button = UIButton()
        button.tag = 100 // Set a tag so you can retrieve the button
        button.setTitle("button", forState: .Normal)
        button.setTitleColor(UIColor.blackColor(), forState: .Normal)
        button.addTarget(self, action: "buttonPressed:", forControlEvents: UIControlEvents.TouchUpInside)
        self.view.addSubview(button)
        button.translatesAutoresizingMaskIntoConstraints = false
    
        let topAnchorForButton = button.topAnchor.constraintEqualToAnchor(view.topAnchor, constant: 400)
        NSLayoutConstraint.activateConstraints([
            topAnchorForButton,
            button.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor, constant: 120),
            ])
    
        self.view.layoutIfNeeded()
    
        [UIView.animateWithDuration(5.0, delay: 0.0, options: [.CurveLinear, .AllowUserInteraction], animations: {
            topAnchorForButton.constant = 200
            self.view.layoutIfNeeded()
            }, completion: nil)]
    }
    

    Update the code for touchesBegan:withEvent event:

    override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
        guard let touch = (touches as NSSet).anyObject() as? UITouch else {
            return
        }
    
        guard let button = self.view.viewWithTag(100) as? UIButton else {
            return
        }
    
        let touchLocation = touch.locationInView(self.view)
        if button.layer.presentationLayer()?.hitTest(touchLocation) != nil {
            self.buttonPressed(button)
        }
    }
    

    Note that if you don't like to use tag to retrieve the button, you can declare a property for the button.