4

I have a UILabel object which is being animated, moving up and down. I am coding in Xcode 8.3 with Swift 3. A user can pan this object and drag around to one location to get some points. I am handling the pan/drag using touchesXXX gesture. When I tap and let it go immediately, however, this object jumps vertically above its location for some reason and I am unable to figure this out why for a week now...

When I enabled some debugging, I can only see touchesBegan was invoked (touchesMoved was not called as expected, neither touchesEnded which appears unexpected to me). If I disable animation on the object manually, it works as expected and the object is able to be panned effortlessly and there is obviously no object jumps.

Here is an extract of the relevant code:

override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    let touch = touches.first
    let touchLocation = touch!.location(in: self.view)

    if self.optOneLbl.layer.presentation()!.hitTest(touchLocation) != nil {
        //pauseLayer(self.optOneLbl.layer)      <<<<< comment #1
        //optOneLbl.translatesAutoresizingMaskIntoConstraints = true      <<<<< comment #2
        optOneLbl.center = touchLocation
    }
}

override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
    let touch = touches.first
    let touchLocation = touch!.location(in: self.view)
    var sender: UILabel

    if self.optOneLbl.layer.presentation()!.hitTest(touchLocation) != nil {
        self.optOneLbl.center = touchLocation
        sender = self.optOneLbl
    }

    // identify which letter was overlapped
    var overlappedView : UILabel
    if (sender.frame.contains(letter1.center)) {
        ...
    }
    else {
        return
    }
}

override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    super.touchesEnded(touches, with: event)
}

After reading responses to other SO questions, I thought disabling the label animation programmatically when touched as per above comment #1 in touchesBegan might help, but the issue persisted. Also, I thought may be Auto Layout is causing this weird jump. So, I enabled translatesAutoresizingMaskIntoConstraints as per comment #2, but it too didn't help.

Anyone is able to see where I am handling this incorrectly? Thank you for reading!

EDIT:

As per @agibson007 request, I am adding the animation code extract for reference:

UIView.animate(withDuration: 12.0, delay: 0.0, options: [ .allowUserInteraction, .curveLinear, .autoreverse, .repeat ], animations: {
    self.optOneLbl.center = CGPoint(x: self.optOneLbl.center.x, y: screenSize.midY*1.1)
})
Heelara
  • 861
  • 9
  • 17
  • Should the item be animating after touchesBegan. Maybe try removing it and readding after dragging is complete – agibson007 Aug 14 '17 at 03:14
  • Actually let me see the animation code. Is it repeating. You have to set the new value of the layer. Remove it. Drag. Then readd the animation – agibson007 Aug 14 '17 at 03:16
  • take a look in this answer https://stackoverflow.com/questions/45392104/drag-uibutton-without-it-shifting-to-center-swift-3/45392805#45392805 maybe can help you let me know – Reinier Melian Aug 14 '17 at 03:16
  • @agibson007, yes animation is repeating. I have edited my original post to include the animation code. May I please seek clarification about this "You have to set the new value of the layer. Remove it."? Thanks – Heelara Aug 14 '17 at 04:22

2 Answers2

2

You need to remove/reset the animation after you change the location of the label. The animation does not know you updated the values and is trying to stay in the same range of byValue from the beginning. Here is a working example of updating the animation.

import UIKit

class ViewController: UIViewController {

    var optOneLbl = UILabel()


    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.


        optOneLbl = UILabel(frame: CGRect(x: 20, y: 50, width: self.view.bounds.width - 40, height: 40))
        optOneLbl.textAlignment = .center
        optOneLbl.text = "I Will Be Moving Up and Down"
        optOneLbl.textColor = .white
        optOneLbl.backgroundColor = .blue
        self.view.addSubview(optOneLbl)

        //starts it in the beginnning with a y
        fireAnimation(toY:  self.view.bounds.midY*1.1)


    }

    func fireAnimation(toY:CGFloat) {
        UIView.animate(withDuration: 12.0, delay: 0.0, options: [ .allowUserInteraction, .curveLinear, .autoreverse, .repeat ], animations: {
            self.optOneLbl.center = CGPoint(x: self.optOneLbl.center.x, y:toY)
        })
    }



    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first
        let touchLocation = touch!.location(in: self.view)

        if self.optOneLbl.layer.presentation()!.hitTest(touchLocation) != nil {
            //re
            optOneLbl.layer.removeAllAnimations()
            optOneLbl.center = touchLocation
        }
    }

    override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
        let touch = touches.first
        let touchLocation = touch!.location(in: self.view)
        var sender: UILabel

        if self.optOneLbl.layer.presentation()!.hitTest(touchLocation) != nil {
            self.optOneLbl.center = touchLocation
            sender = self.optOneLbl
        }
    }

    override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
        super.touchesEnded(touches, with: event)
        //restart animation after finished and change the Y if you want.  
        // you could change duration or whatever
        fireAnimation(toY: self.view.bounds.height - optOneLbl.bounds.height)
    }

}
agibson007
  • 4,173
  • 2
  • 19
  • 24
  • Wow!! Thanks very much for this quick working code. Adding `removeAllAnimations()` resolved this issue. I thought pausing the animation was doing the same thing, but now I know it wasn't. – Heelara Aug 14 '17 at 11:48
  • @Heelara Glad it helped. Think of animations as a separate movie going on over top of your real model. That process will help you in the future. – agibson007 Aug 14 '17 at 12:02
0

You can achieve the same using UIPanGestureRecognizer. Add pan gesture to your label and move the label on pan as

@IBAction func handlePan(_ gestureRecognizer: UIPanGestureRecognizer) {
    if gestureRecognizer.state == .began || gestureRecognizer.state == .changed {

        let translation = gestureRecognizer.translation(in: self.view)
        yourLabel!.center = CGPoint(x: yourLabel!.center.x + translation.x, y: yourLabel!.center.y + translation.y)
        gestureRecognizer.setTranslation(CGPoint.zero, in: self.view)
    }
}

You can add gesturerecognizer to your UILabel from storyboard or programmatically in viewDidLoad as

let gestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan))
yourLabel.addGestureRecognizer(gestureRecognizer)

Using Pan gesture as advantage that you dont have to follow up touchesXX method. To why your view is moving up, I think you might be resetting your label frame some where else too. There are also chances for gesture conflicting with touches. As both the cases are not evident in your code provided we need more code to confirm the same.

luckyShubhra
  • 2,731
  • 1
  • 12
  • 19
  • Thanks for your suggestion. FYI, I was using `UIPanGestureRecognizer` previously. But due to the issues highlighted in [https://stackoverflow.com/questions/8346100/uibutton-cant-be-touched-while-animated-with-uiview-animatewithduration](https://stackoverflow.com/questions/8346100/), I resorted to the current approach which now has this new challenge. – Heelara Aug 14 '17 at 04:17