iOS 13 UIPanGestureRecognizer selector not called
WKWebView might be suppressing the gesture, found related discussion here: Can't handle touches simultaneously with WKWebView on iOS 13.4
Hope this could help you.
UIPanGestureRecognizer does not work
Here is the answer:
class ViewController: UIViewController {
@IBOutlet weak var redView: UIView!
@IBOutlet weak var whiteView: UIView!
var lastLocation:CGPoint = CGPointMake(0, 0)
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let panRecognizer = UIPanGestureRecognizer(target:self, action:#selector(ViewController.handlePan))
whiteView.addGestureRecognizer(panRecognizer)
}
func handlePan(recognizer:UIPanGestureRecognizer) {
let translation = recognizer.translationInView(redView)
whiteView.center = CGPointMake(lastLocation.x + translation.x, lastLocation.y + translation.y)
}
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
// Promote the touched view
lastLocation = whiteView.center
}
}
UIPanGestureRecognizer doesn't trigger action
The problem you're experiencing is due to your definition of the panGestureRecognizer
variable in the class definition here:
//Here I create the reference to the recognizer
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
You can initialize it this way, but it seems like self
is not setup when this variable is created. So your action is never registered.
There are a couple ways you can fix this using your code, you can continue to initialize it as an instance variable, but you'll need to setup your target/action in your initialize()
function
let panGestureRecognizer = UIPanGestureRecognizer()
func initialize() {
// add your target/action here
panGestureRecognizer.addTarget(self, action: #selector(handlePan(_:)))
panGestureRecognizer.minimumNumberOfTouches = 1
panGestureRecognizer.maximumNumberOfTouches = 1
addGestureRecognizer(panGestureRecognizer)
}
Or you can simply initialize your gesture recognizer in your initialize function and not use an instance variable
func initialize() {
let panGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
panGestureRecognizer.minimumNumberOfTouches = 1
panGestureRecognizer.maximumNumberOfTouches = 1
addGestureRecognizer(panGestureRecognizer)
}
My original answer
Here's a solution that works using constraints with a view defined in the storyboard or by manipulating the frame directly.
Example with Constraints
import UIKit
// Delegate protocol for managing constraint updates if needed
protocol MorphDelegate: class {
// tells the delegate to change its size constraints
func morph(x: CGFloat, y: CGFloat)
}
class ExpandableView: UIView {
var delegate: MorphDelegate?
init() {
// frame is set later if needed by creator when using this init method
super.init(frame: CGRect.zero)
configureGestureRecognizers()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configureGestureRecognizers()
}
override init(frame: CGRect) {
super.init(frame: frame)
configureGestureRecognizers()
}
// setup UIPanGestureRecognizer
internal func configureGestureRecognizers() {
let panGR = UIPanGestureRecognizer.init(target: self, action: #selector(didPan(_:)))
addGestureRecognizer(panGR)
}
@objc func didPan(_ panGR: UIPanGestureRecognizer) {
// get the translation
let translation = panGR.translation(in: self).applying(transform)
if let delegate = delegate {
// tell delegate to change the constraints using this translation
delegate.morph(x: translation.x, y: translation.y)
} else {
// If you want the view to expand/contract in opposite direction
// of drag then swap the + and - in the 2 lines below
let newOriginY = frame.origin.y + translation.y
let newHeight = frame.size.height - translation.y
// expand self via frame manipulation
let newFrame = CGRect(x: frame.origin.x, y: newOriginY, width: frame.size.width, height: newHeight)
frame = newFrame
}
// reset translation
panGR.setTranslation(CGPoint.zero, in: self)
}
}
If you want to use this class by defining it in the storyboard and manipulating it's constraints you'd go about it like this.
First define your view in the storyboard and constrain it's width and height. For the demo I constrained it's x position to the view center and it's bottom to the SafeArea.bottom
Create an IBOutlet for the view, as well as it's height constraint to your ViewController file.
I set the background color to blue for this example.
In the view controller I defined a function to setup the view (currently only sets the delegate for constraint manipulation callbacks) and then defined an extension to handle delegate calls for updating constraints.
class ViewController: UIViewController {
@IBOutlet weak var expandingView: ExpandableView!
@IBOutlet weak var constraint_expViewHeight: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// call to configure our expanding view
configureExpandingView()
}
// this sets the delegate for an expanding view defined in the storyboard
func configureExpandingView() {
expandingView.delegate = self
}
}
// setup the delegate callback to handle constraint manipulation
extension ViewController: MorphDelegate {
func morph(x: CGFloat, y: CGFloat) {
// this will update the view's height based on the amount
// you drag your finger in the view. You can '+=' below if
// you want to reverse the expanding behavior based on pan
// movements.
constraint_expViewHeight.constant -= y
}
}
Doing it this way via constraints gives me this when I run the project:
Example with frame manipulation
To use this and manipulate the height using just the frame property the view controller might implement the view creation something like this:
override func viewDidLoad() {
super.viewDidLoad()
setupExpandingView()
}
func setupExpandingView() {
let newView = ExpandableView()
newView.backgroundColor = .red
newView.frame = CGRect(x: 20, y: 200, width: 100, height: 100)
view.addSubview(newView)
}
Using just frame manipulation I get this:
UIPanGestureRecognizer isn't firing up. Swift 3
OK, I took a look at the code and I see two possible issues:
1: You have pinch, pan, and rotate gesture recognizers set in the storyboard as well. These might be superfluous. Just mentioning that. This really isn't the issue your pan gesture recognizer isn't working though.
2: The UIView
for controls covers your emoji and image view and will take over touches. You don't need a full-screen view for the UI controls since you don't seem to do anything with the view itself. Instead, you can simply put the button at the right position on the main screen over the viewForImgAndEmoji
. This way, any on screen touches reach viewForImgAndEmoji
except at the spots where you have the buttons.
I modified the storyboard that way and I could get the pan gestures fine at that point. Do let me know if you need me to clarify anything since I am not sure if I was clear enough about the issue :)
Note: One issue that you will run into when you fix the above is that you will find that not all touches register because the gesture recognizer is attached to each sticker and the sticker itself might be too small to detect something like a pinch gesture. You might be better off adding viewForImgAndEmoji
as the gesture handler and activating each sticker/emoji as the recipient of the the gesture based on selecting the emoji by a tap before you try to pinch, rotate, or pan. Just a suggestion :)
Related Topics
How to Get .Adjustsfontsizetofitwidth to Function Properly
Why Does Cabasicanimation Try to Initialize Another Instance of My Custom Calayer
Swift: Binary Operator '==' Cannot Be Applied to Operands of Type "Protocol"
Type 'Bundle' Has No Member "Module"
Navigation Bar Items After Push from Swiftui to UIkit
Nstoolbarflexiblespaceitem Is Constraint to Nssplitviewitem in Swift
Naming Convention for Private Properties
Swift Difference Between Double and Float64
How to Find Realm File Location of a MAC App
How to Access The Firebase Topics a User Is Subscribed To
Mapping Swift Combine Future to Another Future
Bad_Access During Recursive Calls in Swift
How to Retrieve The Name of Participants in Msconversation
Swift & Firebase: Detect Revoked Token
Access Id Does Not Work When Testing a Textfield with 'Issecuretextentry = True'