Issues with UILongPressGestureRecognizer in Xcode 10

UILongPressGestureRecognizer was working on iOS 11 but no longer firing its action method on iOS 12.


I believe the issue may stem more from Xcode 10 than from iOS 12?


I am away from my machine with Xcode 9 so I can't test using Xcode 9 at the moment, but I believe that when building the app via Xcode 9, UILongPressGestureRecognizer was working on both iOS 12 and iOS 11.


Having just updated my iOS 11 device to iOS 12, I can no longer test on iOS 11 either.


TESTS:

  1. Building from Xcode 10 to development device with iOS 12: UILongPressGestureRecognizer not working
  2. Downloading from the App Store a build which was archived and submitted using Xcode 10 to a device with iOS 12: UILongPressGestureRecognizer not working


//the viewcontroller is initiated with UIGestureRecognizerDelegate


let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPress))


//in cellForRowAt:

longPressGesture.minimumPressDuration = 1.0

longPressGesture.delegate = self

longPressGesture.cancelsTouchesInView = false

cell.addGestureRecognizer(longPressGesture)


@objc func longPress(longPressGestureRecognizer: UILongPressGestureRecognizer) {

//never called

}

I also tried adding the gesture recognizer to a button in viewDidLoad to ensure it wasn't an issue with the tableview, and the longPress function is still never called.


Would very much like to get this working again... anybody know if anything has changed in Xcode 10 or iOS 12 that could affect a UILongPressGestureRecognizer?

As far as I tested your code (adding the gesture recognizer to a button in viewDidLoad)


- iOS 12 Simulator (iPhone XR)

- iOS 11.4 devide (iPhone 7 plus)


In both environments, ` longPress(longPressGestureRecognizer:)` was called.


So, it may not be the issue of (UILongPressGestureRecognizer & Xcode 10).


Aren't you using sort of complex view hierarchy or UIScrollView or something else that may prevent UILongPressGestureRecognizer to work?

Does it work if you add the argument in selector ?


let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPress(longPressGestureRecognizer: )))

Thanks so much for writing. I was hoping so much this would fix it, but no...


I wrote a new project with only this code and it does not work on my device


import UIKit

class ViewController: UIViewController, UIGestureRecognizerDelegate {
   
    let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPress(longPressGestureRecognizer: )))

   
    var startBtn = UIButton()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
       
        startBtn.frame = CGRect(x: self.view.frame.size.width * 0.6, y: self.view.frame.size.height * 0.4, width: self.view.frame.size.height*0.18, height: self.view.frame.size.height*0.18)
        startBtn.backgroundColor = .orange
       
        longPressGesture.minimumPressDuration = 1.0 // 1 second press
        longPressGesture.delegate = self
        longPressGesture.cancelsTouchesInView = false
        startBtn.addGestureRecognizer(longPressGesture)
        self.view.addSubview(startBtn)
    }

    @objc func longPress(longPressGestureRecognizer: UILongPressGestureRecognizer) {
       
       
        if longPressGestureRecognizer.state == UIGestureRecognizer.State.began {
           
            print("pressed for 1.0+ seconds")
           
        }
    }
}


Xcode 10.0

iOS 12.0.1

Thanks so much for writing. I wrote a new project with only this code and it does not work on my device


import UIKit

class ViewController: UIViewController, UIGestureRecognizerDelegate {
   
    let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPress(longPressGestureRecognizer: )))

   
    var startBtn = UIButton()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
       
        startBtn.frame = CGRect(x: self.view.frame.size.width * 0.6, y: self.view.frame.size.height * 0.4, width: self.view.frame.size.height*0.18, height: self.view.frame.size.height*0.18)
        startBtn.backgroundColor = .orange
       
        longPressGesture.minimumPressDuration = 1.0 // 1 second press
        longPressGesture.delegate = self
        longPressGesture.cancelsTouchesInView = false
        startBtn.addGestureRecognizer(longPressGesture)
        self.view.addSubview(startBtn)
    }

    @objc func longPress(longPressGestureRecognizer: UILongPressGestureRecognizer) {
       
       
        if longPressGestureRecognizer.state == UIGestureRecognizer.State.began {
           
            print("pressed for 1.0+ seconds")
           
        }
    }
}


Xcode 10.0

iOS 12.0.1

I cannot test your code as I do not have iOS 12 device yet.


This issue may happen on some specific devices. What device are using for tesing?

Both on an iPhone 8 and on an iPad Pro 10.5" and on the simulator

I just made the following change to get it work:


move

    let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPress(longPressGestureRecognizer: )))


in viewDidLoad


   override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
       
        startBtn.frame = CGRect(x: self.view.frame.size.width * 0.6, y: self.view.frame.size.height * 0.4, width: self.view.frame.size.height*0.18, height: self.view.frame.size.height*0.18)
        startBtn.backgroundColor = .orange
       
       let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPress(longPressGestureRecognizer: )))

        longPressGesture.minimumPressDuration = 1.0 // 1 second press
        longPressGesture.delegate = self
        longPressGesture.cancelsTouchesInView = false
        startBtn.addGestureRecognizer(longPressGesture)
        self.view.addSubview(startBtn)
    }

Works as well without param name

      let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPress))
Accepted Answer

and on the simulator


I tested your simplified code on the iOS 12 simulator and found one thing wrong in it:

        let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPress(longPressGestureRecognizer: )))

You cannot use `self` inside the initial value expression of an instance property.


Move it inside a method:

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(longPress(longPressGestureRecognizer: )))
        
        startBtn.frame = CGRect(x: self.view.frame.size.width * 0.6, y: self.view.frame.size.height * 0.4, width: self.view.frame.size.height*0.18, height: self.view.frame.size.height*0.18)
        startBtn.backgroundColor = .orange
        
        longPressGesture.minimumPressDuration = 1.0 // 1 second press
        longPressGesture.delegate = self
        longPressGesture.cancelsTouchesInView = false
        startBtn.addGestureRecognizer(longPressGesture)
        self.view.addSubview(startBtn)
    }

Maybe the issue happening in your actual app is caused by some different things, so first, fix the simplified new project and see what happens.

Bullseye, this was it. Thanks so much for your attention here. I needed it declared as an instance property so I could add/remove it as needed, but I can define its target in viewDidLoad when self is able to be accessed. Very much appreciated. 🙂

Happy to here you have solved your issue. In my opinion, this is a bug of Swift and Swift should show a warning or an error where `self` cannot be used.

(`self` is treated as something we would never want to use when used in an initial value... But that's another issue.)

FWIW, I asked about this over on forums.swift.org (forums.swift.org/t/self-as-initializer-bug). Apparently, "self" in this context is taken as the name of NSObject's "self" method, so the value it's using here basically a closure of a curried static getter. (Yes, it's pretty obscure.)


It should probably be disallowed as valid syntax, since it's unlikely that anyone ever wants this meaning, but it is for now a pitfall to be careful of.

I noticed it didn't work, but would never have understood why. I was just thinking self was not yet defined at that stage.


BTW, I thought curried functions had disppeared from Swift (Swift reference 4.1 says : "removed discussion of variable function arguments and the special syntax for curried functions" ; is it only a reference book change, not a language change ?

You can write a simple example:

class MyClass: NSObject {
    let something: Any = self
    
    func showSomething() {
        print(something, type(of: something))
        print(MyClass.`self`, type(of: MyClass.`self`))
    }
}

let obj = MyClass()
obj.showSomething()

Output:

(Function) (MyClass) -> () -> MyClass

(Function) (MyClass) -> () -> MyClass


`NSObject` has a method named `self`:

func `self`() -> Self (declared in `NSObjectProtocol`.)


And `NSObject` descendant classes inherit it.


When you write simply `self` in some non-instance context of `NSObject` descendent classes, it is treated as the method reference.


removed discussion of variable function arguments and the special syntax for curried functions


The special syntax for curried functions is removed,

func someFunc(aNum: Int)(anotherNum: Int) -> Int {
    return aNum + anotherNum
}
//->Curried function declaration syntax has been removed; use a single parameter list


but you stil can use curried functions (functions returnig function).

func someFunc(_ aNum: Int) -> (_ anotherNum: Int) -> Int {
    return {anotherNum in
        return aNum + anotherNum
    }
}
print(someFunc(1)(2)) //->3

Thanks, that's really clear for self.


I've never found curried function much useful. Do I miss an important use case ?

I've never found curried function much useful. Do I miss an important use case ?

One reason Swift team has removed the special syntax for curried functions was that there were not many use cases that they found useful.


You may naturally be writing a method returning closure when it is required and/or useful.

You have no need to have it in mind that it really is a curried function.

Issues with UILongPressGestureRecognizer in Xcode 10
 
 
Q