swiftuitextfieldnslayoutconstraintuipangesturerecognizersender

Entering text into textfield causing uipangesture object to move back to its orginal position


My swift code below has a image view connected to a pangesture. When something is entered into a textfield when after the image view is moved. The image view reverts back to its original position.The gif represents that what is going on. I just don't want the effect of the pangesutre to be nullified after text is entered into the textfield.

enter image description here LINK TO GITHUB https://github.com/redrock34/sse

import UIKit

class ViewController: UIViewController {

    var pic = UIImageView()
    let fight = (0..<10).map { _ in UIImageView() }
    var textEnter = UITextField()
    var g2 = UIPanGestureRecognizer()

    var slider = UISlider()

    override func viewDidLoad() {
        super.viewDidLoad()

        fight[0].image  = UIImage(named: "a.png")

        fight.forEach{
            $0.isUserInteractionEnabled = true
        }
        [slider,textEnter].forEach{
            $0.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview($0)

            $0.backgroundColor = .blue
        }

        slider.backgroundColor = .clear

        g2 = UIPanGestureRecognizer(target: self, action: #selector(ViewController.g1Method))
        fight[0].addGestureRecognizer(g2)

        pic.backgroundColor = .clear
        pic.backgroundColor = .systemGreen

        fight.forEach{
            $0.backgroundColor = .clear
            view.addSubview($0)
            $0.translatesAutoresizingMaskIntoConstraints = false
        }            

        [pic].forEach{

            view.addSubview($0)
            $0.translatesAutoresizingMaskIntoConstraints = false                
        }

        // Do any additional setup after loading the view.
        NSLayoutConstraint.activate ([

            pic.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant :0),
            pic.topAnchor.constraint(equalTo: fight[0].bottomAnchor, constant : 0),

            pic.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.62, constant: 0),
            pic.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant : 0),

            textEnter.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant :0),
            textEnter.topAnchor.constraint(equalTo: view.topAnchor, constant : 0),
            textEnter.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.1, constant: 0),
            textEnter.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant : 0),

            fight[0].trailingAnchor.constraint(equalTo: view.trailingAnchor, constant :0),
            fight[0].topAnchor.constraint(equalTo: textEnter.bottomAnchor, constant : 0),

            fight[0].heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.10, constant: 0),
            fight[0].widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.10, constant: 0),
            fight[0].leadingAnchor.constraint(equalTo: view.leadingAnchor, constant : 0),

            slider.topAnchor.constraint(equalTo: pic.bottomAnchor, constant : 0),

            slider.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.08, constant: 0),
            slider.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1, constant: 0),
            slider.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant : 0),

        ])
        textEnter.textAlignment = .center

        self.view.sendSubviewToBack(pic)
    }

    @objc func g1Method(_ sender: UIPanGestureRecognizer){

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

Solution

  • You cannot mix explicit frame setting with auto-layout constraints for the same object.

    For example, if you:

    then, via code...

    you will see the label move, but when the next UI update happens (you edit a text field, tap a button, rotate the device, etc), auto-layout will reset the frame of the label based on its constraints.

    So, in your pan gesture handler, you can either update the .constant values for the fight[0] object (instead of changing its .center), or...

    You can leave label.translatesAutoresizingMaskIntoConstraints = true for your fight objects and explicitly set their frames and centers.

    Note: in either case, you do not want to constrain any other elements relative to the fight objects, or they will move when you move fight[0].

    Here is a modification to your ViewController class (from your GitHub zip), implementing the method of not using auto-layout constraints on your fight objects.

    class ViewController: UIViewController {
    
        var pic = UIImageView()
        let fight = (0..<10).map { _ in UIImageView() }
        var textEnter = UITextField()
        var g2 = UIPanGestureRecognizer()
    
        var slider = UISlider()
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            fight[0].image  = UIImage(named: "a.png")
    
            fight.forEach{
                $0.isUserInteractionEnabled = true
            }
            [slider,textEnter].forEach{
                $0.translatesAutoresizingMaskIntoConstraints = false
                view.addSubview($0)
                $0.backgroundColor = .blue
            }
    
            slider.backgroundColor = .clear
    
            g2 = UIPanGestureRecognizer(target: self, action: #selector(ViewController.g1Method))
            fight[0].addGestureRecognizer(g2)
    
            pic.backgroundColor = .systemGreen
    
            fight.forEach{
                $0.backgroundColor = .clear
                view.addSubview($0)
                // do NOT use auto-layout for fight views
                //$0.translatesAutoresizingMaskIntoConstraints = false
            }
    
            [pic].forEach{
                view.addSubview($0)
                $0.translatesAutoresizingMaskIntoConstraints = false
            }
    
            // need a non-rendering "spacer" to vertically separate textEnter from Pic
            let spacer = UILayoutGuide()
            view.addLayoutGuide(spacer)
    
            // NOTE: do NOT constrain any elements relative to fight views
    
            NSLayoutConstraint.activate ([
    
                // constrain textEnter top / leading / trailing to view
                textEnter.topAnchor.constraint(equalTo: view.topAnchor, constant : 0),
                textEnter.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant : 0),
                textEnter.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant :0),
                // constrain textEnter height to 0.1 * view height
                textEnter.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.1, constant: 0),
    
                // constrain spacer top to bottom of textEnter
                spacer.topAnchor.constraint(equalTo: textEnter.bottomAnchor, constant: 0.0),
                // constrain spacer leading to view leading
                spacer.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 0.0),
                // constrain spacer height to 0.1 * view height
                spacer.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.10),
                // spacer width doesn't matter
                spacer.widthAnchor.constraint(equalToConstant: 1.0),
    
                // constrain pic Top to spacer bottom
                pic.topAnchor.constraint(equalTo: spacer.bottomAnchor, constant: 0.0),
                // constrain pic leading / trailing to view
                pic.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant : 0),
                pic.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant :0),
    
                // constrain pic height as you had it
                pic.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.62, constant: 0),
    
                // slider constraints
                slider.topAnchor.constraint(equalTo: pic.bottomAnchor, constant : 0),
                slider.heightAnchor.constraint(equalTo: view.heightAnchor, multiplier: 0.08, constant: 0),
                slider.widthAnchor.constraint(equalTo: view.widthAnchor, multiplier: 1, constant: 0),
                slider.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant : 0),
    
            ])
    
            textEnter.textAlignment = .center
    
            self.view.sendSubviewToBack(pic)
    
        }
    
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
            // set fight[0] frame *after* textEnter has been laid-out
            fight[0].frame.size = CGSize(width: view.frame.width * 0.10, height: view.frame.height * 0.10)
            let x = view.frame.origin.x
            let y = textEnter.frame.origin.y + textEnter.frame.size.height
            fight[0].frame.origin = CGPoint(x: x, y: y)
        }
    
        @objc func g1Method(_ sender: UIPanGestureRecognizer){
    
            let tranistioon = sender.translation(in: self.view)
            sender.view!.center = CGPoint(x: sender.view!.center.x + tranistioon.x, y: sender.view!.center.y + tranistioon.y)
            sender.setTranslation(CGPoint.zero,in: self.view)
    
        }
    
    }