Search code examples
arraysswiftfor-loopindexinguipangesturerecognizer

last item in index array not responsive to UIPanGestureRecognizer


My swift code below is supposed to add an image view if the function is called. Then when pressed on it should move around. Right now the first time the button is pressed the imageview does not work but if the function is called twice the first imageview moves. If the func is called 3 times the first and second imageviews move but the 3rd one does not. When a imageview is placed on the screen i want it to move immediately

import UIKit

class ViewController: UIViewController {

    var myArray = [UIImageView]()
    var bt = UIButton()
    var count : Int = 0
    var pgGesture = UIPanGestureRecognizer()
    var ht = -90

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.addSubview(bt)
        bt.backgroundColor = UIColor.systemOrange
        bt.frame = CGRect(x: view.center.x - 80, y: view.center.y , width: 50, height: 50)
        bt.addTarget(self, action: #selector(add), for: .touchUpInside)
    }

    @objc func add() { 
        pgGesture = UIPanGestureRecognizer(target: self, action: #selector(ViewController.pgGestureMethod(_:)))

        for index in myArray.indices {
            myArray[index].isUserInteractionEnabled = true
            myArray[index].addGestureRecognizer(pgGesture)
            myArray[index].tag = index
        }

        myArray.insert(UIImageView(), at: count)

        myArray[count].frame = CGRect(x: view.center.x - 0, y: view.center.y + CGFloat(ht), width: 50, height: 30)
        myArray.forEach({
            $0.backgroundColor = UIColor.systemTeal
            self.view.addSubview($0)
        })

        count += 1
        ht += 50  
    }

    @objc func pgGestureMethod(_ sender: UIPanGestureRecognizer){
        self.view.bringSubviewToFront(sender.view!)
        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

  • There are several issues here:

    1. Your gesture recognizer handler is updating the center of the gesture’s view. Clearly the intent is to add this gesture to the view that you will be dragging.

      So the main problem here is that you’re adding the gesture recognizer to the wrong view. You should be adding it to the teal-colored draggable views, but you’re adding it to the main view.

    2. It’s unrelated, but the add method is doing a lot of unnecessary stuff. Let’s say you’re adding the 100th new teal subview. Why would you re-update the previous 99 subviews again, too?

      Just configure the view you’re adding and get rid of those unnecessary for/forEach loops.

    3. Again, unrelated, but I’d suggest care when using center. This is where the view in question is located within its superview. So, when adding a the button and the teal subview to your view controller’s view, you should reference view.bounds.midX and .midY, not view.center. Because the view is taking up the whole screen, you won’t really see the difference, but if the view controller was a child view controller of another view, then there would be a world of difference.

    Thus:

    class ViewController: UIViewController {
        // var mySubviews = [UIImageView]()
        var addButton = UIButton()
        var count: Int = 0
        var ht = -90
    
        override func viewDidLoad() {
            super.viewDidLoad()
            view.addSubview(addButton)
            addButton.backgroundColor = .systemOrange
            addButton.addTarget(self, action: #selector(didTapAdd(_:)), for: .touchUpInside)
        }
    
    
        override func viewDidLayoutSubviews() {
            super.viewDidLayoutSubviews()
            addButton.frame = CGRect(x: view.bounds.midX - 80, y: view.bounds.midY, width: 50, height: 50)
        }
    
        @objc func didTapAdd(_ sender: UIButton) {
            let subview = UIImageView()
            subview.isUserInteractionEnabled = true
            view.addSubview(subview)
            subview.frame = CGRect(x: view.bounds.midX - 0, y: view.bounds.midY + CGFloat(ht), width: 50, height: 30)
            subview.backgroundColor = .systemTeal
            subview.tag = count
    
            let pan = UIPanGestureRecognizer(target: self, action: #selector(handlePanGesture(_:)))
            subview.addGestureRecognizer(pan)
    
            count += 1
            ht += 50
            // mySubviews.append(subview)
        }
    
        @objc func handlePanGesture(_ gesture: UIPanGestureRecognizer) {
            let draggedView = gesture.view!
            view.bringSubviewToFront(draggedView)
            let translation = gesture.translation(in: view)
            draggedView.center = CGPoint(x: draggedView.center.x + translation.x, y: draggedView.center.y + translation.y)
            gesture.setTranslation(.zero, in: view)
        }
    }
    

    A few final observations:

    1. I would discourage the use of the tag. It’s fine for diagnostic purposes, but that’s about it.

    2. In my code snippet, I’ve also commented out the declaration and appending to the array of subviews as it’s entirely unnecessary.

    3. Please forgive me, but I’ve renamed the methods and variables.

    4. I’m not sure why you’re using UIImageView. I’d personally use UIView unless you were really planning on eventually adding images at some later date.

    5. If you're going to hardcode the button's frame, you can't do this in viewDidLoad (because the view hasn't been laid out yet). If you're going to set frame, you’ll want to do that in viewDidLayoutSubviews. You can set up constraints in viewDidLoad, but not frames.