Search code examples
uipangesturerecognizer

How to fix a new textField position after panGesture?


I need some help with a litter issue in my code. I have a view with two textField one on the top and another on the bottom, both over a imageView. I applied a Pan Gesture in both textfields to allow change their position after the user load an image. But when I show a activityView to share the new image, the textFields return the previous location. I'd like to fix them in the new location after panGesture. What can I do? Or What am I forgetting to do?

   This is my Pan Gesture Action
@IBAction func didPanTfTop(_ sender: UIPanGestureRecognizer) {

    let translation = sender.translation(in: self.view)

    if sender.state == .began {
        print("Gesture began")
        textFieldOriginalCenter = tfTop.center
    } else if sender.state == .changed {
        print("Gesture is changed")
        tfTop.center = CGPoint(x: textFieldOriginalCenter.x + translation.x, y: textFieldOriginalCenter.y + translation.y)
    } else if sender.state == .ended {
        print("Gesture ended")

    }
}

And this is my activityView methods...

@IBAction func share(_ sender: UIBarButtonItem) {
    Feedback.share.hapticFeedback()
    let memeImage = generateMemedImage()
    let activityView = UIActivityViewController(activityItems: [memeImage], applicationActivities: nil)
    activityView.completionWithItemsHandler = { (activityType: UIActivity.ActivityType?, completed: Bool, returnedItems: [Any]?, error: Error?) -> Void in

        if completed {
            self.save()
            print("Image has saved")
        }
    }
    present(activityView, animated: true, completion: nil)
}

Solution

  • Generally, this behavior of seeing views snapping back to their original locations is a result of a combination of (a) using auto-layout constraints; and (b) doing something that triggers the constraints to be reapplied. This process of constraints being reapplied can sometimes be triggered by the most innocuous of UI action. Bottom line, rather than worrying about what triggered the constraints from being re-applied, it’s best to just make sure that your views will stay where you want them to, even after this happens.

    One solution is to update the transform of the views rather than the center, e.g.:

    var originalTransform: CGAffineTransform!
    
    @IBAction func handlePan(_ gesture: UIPanGestureRecognizer) {
        switch gesture.state {
        case .began:
            originalTransform = gesture.view!.transform
    
        case .changed, .ended:
            let translation = gesture.translation(in: gesture.view)
            gesture.view?.transform = originalTransform.translatedBy(x: translation.x, y: translation.y)
    
        default:
            break
        }
    }
    

    This technique works, but also feels a bit fragile, dependent upon poorly documented behavior between constraints and view transforms.

    Another, more robust, but a little more complicated, approach is to adjust the constraints’ constant property. For example, you can create @IBOutlet references for the constraints, themselves, hook up those outlets in IB, and then you gesture recognizer can update their constant properties accordingly:

    @IBOutlet weak var horizontalConstraint: NSLayoutConstraint!
    @IBOutlet weak var verticalConstraint: NSLayoutConstraint!
    
    private var originalHorizontalConstant: CGFloat!
    private var originalVerticalConstant: CGFloat!
    
    @IBAction func handlePan(_ gesture: UIPanGestureRecognizer) {
        switch gesture.state {
        case .began:
            originalHorizontalConstant = horizontalConstraint.constant
            originalVerticalConstant = verticalConstraint.constant
    
        case .changed, .ended:
            let translation = gesture.translation(in: gesture.view)
            horizontalConstraint.constant = originalHorizontalConstant + translation.x
            verticalConstraint.constant = originalVerticalConstant + translation.y
    
        default:
            break
        }
    }
    

    Now, clearly if you’re potentially dragging different views around this can get a little messier, but hopefully the above illustrates the basic idea.

    Whichever technique you apply, the basic notion is to avoid updating center or frame of a view whose position is dictated by auto-layout constraints.