In a collection view, I create a gesture recognizer at class init time. In the viewDidLoad method, I then add the gesture recognizer to the collection view.
class ViewController: UIViewController {
#IBOutlet weak var collectionView: UICollectionView!
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongGesture(gesture:)))
#objc func handleLongGesture(gesture: UILongPressGestureRecognizer) {
// some code
}
override func viewDidLoad() {
super.viewDidLoad()
collectionView.addGestureRecognizer(longPressGesture)
}
}
With this, the gesture recognizer does not work.
The fix is easy: it suffices to move the line with let longPressGesture to the viewDidLoad method and everything works as expected. However, I find it a bit surprising that the first version would not work.
Can anyone explain why the first version is not working? Is it because, when the gesture recognizer is created, the collection view is not yet ready to have gestures? So, what must a gesture recognizer know about its target in order to be created?
Good question. That's because you are trying to use self when not fully initialized.
Now, how to make that work with the way you wanted? Perhaps declare it lazily, like so:
private lazy var longPressGesture: UILongPressGestureRecognizer! = {
let gesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongGesture(gesture:)))
return gesture
}()
Edit: Quoting giorashc's answer from this question:
Due to swift's 2-phase initialization you need to initialize the
parent class before you can use self in the inheriting class.
In your implementation self is yet to be initialized by the parent
class so as you said you should move it to the init method of your
view controller and create the button after calling the parent's
initialization method
2 Phase Initialization SO Question & Answer.
Related
I see that there are a ton of these questions, and I think I'm following the accepted Swift 3 methodology, but I'm still getting nothing. I can see that the UITapGestureRecognizer has been attached. Here's my code:
let tileClick = UITapGestureRecognizer(target: self, action: #selector(GameManagement.initiateTileClick(_:)))
newView.addGestureRecognizer(tileClick)
newView.isUserInteractionEnabled = true
func initiateTileClick(_ sender: UITapGestureRecognizer) {
print("initiate tile click")
}
A few things to note:
1) The view that I'm attaching the gesture recognizer to has a two views and a label within it that each cover the entire frame of the view, however, I tried attaching the recognizer to the label, which is the topmost child item and it still doesn't work.
2) Both the function that adds the recognizer and the function that is called on the tap are contained in an NSObject file. I have a variety of interconnected functions that I want to be able to call from multiple view controllers and would prefer to keep this in the separate NSObject file. The process worked when I had everything in a UIViewController file and stopped working when I moved the functions to the NSObject file.
3) I've tried changing GameManagement.initiateTileClick to self.initiateTileClick or just initiateTileClick and none of those worked.
If you are putting your views inside NSObject subclass then these views will lose their behaviors for UIResponder which manages the UI interactions as I am not able to see how you are adding these views to interface.
As you said, it was working inside ViewController because it manages view hierarchy and responder chain.
The solution would be to write extensions to separate code or better abstractions.
extension YourViewController {
newView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(GameManagement.initiateTileClick(_:))))
newView.isUserInteractionEnabled = true
func initiateTileClick(_ sender: UITapGestureRecognizer) {
print("initiate tile click")
}
}
Giving you an idea how the tap recogniser works.
Firstly add Tap gesture recogniser to your view controller. You have to put the object here as shown in the image.
Then control+drag the tap gesture object to your view and select delegate.
Then control+drag the recogniser to your swift file and action will be like this.
#IBAction func tapGesture(_ sender: UITapGestureRecognizer) {
}
Now you must have seen when you give some input to a text field, the keyboard appears. But if you press outside the text field, that is anywhere in the view, the keyboard hides. This is because of the tap gesture recogniser.
Consider you have a text field such that if you click in that text field, keyboard is appeared. But when you tap outside the textfield, the keyboard must hide.
Add this delegate
UITextFieldDelegate
Implement this:
#IBOutlet var phoneText: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
exampleText.delegate = self
}
#IBAction func tapGesture(_ sender: UITapGestureRecognizer) {
exampleText.endEditing(true)
}
Obviously,this function is instance method.
func initiateTileClick(_ sender: UITapGestureRecognizer) {
print("initiate tile click")
}
-
UITapGestureRecognizer(target: self, action:#selector(GameManagement.initiateTileClick(_:)))
but thisGameManagement.initiateTileClick(_:) looks like a class is calling a class method!The target should be the caller of method.self can't call GameManagement.initiateTileClick(_:).
I have two copies of a control (its called a RatingControl). How do I write handlers that can be invoked on the correct object when somebody double taps on them?
I have:
#IBOutlet weak var ratingControl: RatingControl!
#IBOutlet weak var ratingControl2: RatingControl!
inside a TableViewController and then
override func viewDidLoad() {
super.viewDidLoad()
let tapGR = UITapGestureRecognizer(target: ratingControl, action: #selector(RatingControl.doubleTap(_:)))
tapGR.numberOfTapsRequired = 2
self.view.addGestureRecognizer(tapGR)
let tapGR2 = UITapGestureRecognizer(target: ratingControl2, action: #selector(RatingControl.doubleTap(_:)))
tapGR2.numberOfTapsRequired = 2
self.view.addGestureRecognizer(tapGR2)
}
RatingControl.doubleTap(_) is an innocuous event handler.
When there is a double tap on the second rating control, the doubleTap method is called but is dispatched on the first rating control object!
I have tried setting two targets on a single UITapGestureRecognizer but it runs into the same problem.
Thanks much!
You need to add the gesture recognizers to the two rating controls instead of to self.view.
Try this:
let tapGR = UITapGestureRecognizer(target: self, action: #selector(RatingControl.doubleTap(_:)))
tapGR.numberOfTapsRequired = 2
ratingControl.addGestureRecognizer(tapGR) // ratingControl, not self.view
let tapGR2 = UITapGestureRecognizer(target: self, action: #selector(RatingControl.doubleTap(_:)))
tapGR2.numberOfTapsRequired = 2
ratingControl2.addGestureRecognizer(tapGR2) // ratingControl2, not self.view
There are 2 parts to hooking up a gesture recognizer: the target, which determines which object gets notified when the recognizer is triggered, and the view it's attached to, which determines from which view the recognizer recognizes the gesture.
You've got 2 gesture recognizes configured the same way, both attached to self.view. They are therefore going to respond to taps on self.view (which I assume is the view controller's content view.) I don't think it's clear which gesture recognizer is going to be triggered when you tap in that case.
You should have 2 different views and attach a different gesture recognizer to each one. If ratingControl1 and ratingControl2 are view objects, perhaps you meant to attach the gesture recognizers directly to them, rather than to self.view?
I have a tap gesture on a UIImageView within a class that extends UITableViewCell. This code should work, I don't see why it doesn't. The only thing I am iffy on is what the "target" should be - should it be the profileImage, or the overall ViewController that things are in?
#IBOutlet weak var profileImage: UIImageView!
var vc: TweetsViewController? = nil
override func awakeFromNib() {
super.awakeFromNib()
let tapGester = UITapGestureRecognizer(target: vc, action: Selector("handleTapGester:"))
tapGester.delegate = self
profileImage.addGestureRecognizer(tapGester)
}
func handleTapGester(tapGesture: UITapGestureRecognizer) {
print("*******hi*******")
vc?.performSegueWithIdentifier("showProfile", sender: nil)
}
And for the record, as this may seem like a relevant error, I initialize vc when the table cell loads.
The target should be the object that will handle the tap gesture and the handleTapGester function should be inside the object class you specified as the target, not inside the UITableViewCell subclass.
You also need to enable user interaction on the UIImageView by saying:
imageView.userInteractionEnabled = true
Why not just add a tap gesture recogniser to the view, then when called query indexPathForRowAtPoint to find out which cell is being tapped?
If you know the cell you can then determine if the UIImage is being tapped and make your call to performSegueWithIdentifier from there.
If it's tapped on a cell that you're not interested in let it fall through and be handled by the table by calling cancelsTouchesInView on the recogniser.
I am new to swift, please kindly advise the best way to achieve this.
Let say if there are 3 to 4 (custom) UIView(s) added under a parent view
when user select/highlighted a particular one (e.g. the 2nd UIView), and this will get removed and the whole layout will re-render immediately. Any idea?
Connect all views to one IBOutletCollection, add gesture recognizer for tap and in recognizer callback just get the touch point and check if the point is contained in one of the views from the outlet collection.
#IBOutlet var views: [UIView]!
override func viewDidLoad() {
super.viewDidLoad()
let tapGesture = UITapGestureRecognizer(target: self, action: Selector("viewTapped:"))
self.view.addGestureRecognizer(tapGesture)
}
func viewTapped(tapGesture: UITapGestureRecognizer) {
let locationInView = tapGesture.locationInView(view)
for v in views {
if CGRectContainsPoint(v.frame, locationInView) {
v.removeFromSuperview()
}
}
}
Make sure you have your autolayout setup for the state in which each of the view is not there.
I have a webview inside view controller "A", is it possible to tap on web view and seaque to view controller "B"?
I have tried do following
#IBOutlet weak var mainWebView: UIWebView!
let tapUIWebView = UITapGestureRecognizer()
override func viewDidLoad() {
super.viewDidLoad()
tapUIWebView.addTarget(self, action: "webViewTapped")
mainWebView.userInteractionEnabled = true
mainWebView.addGestureRecognizer(tapUIWebView)
func webViewTapped(){
println("Tapped")
}
}
Thank you
1) Use a UITapGestureRecognizer, create the instance.
2) Attach it to the webview.
3) Implement the action method that handles the gesture.
4) SinceUIWebView already recognises a touch, you need to also take care that your recognizer does the job first. And you decide what to do with the touch.
5) If it suits you, you can disable handling the tap in the built-in recogniser completely.