How do I detect a touch in navigationController.view? - ios

I was implementing this function.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let point = touches.first?.location(in: self.view) else { return }
for i in 0..<paths.count {
if paths[i].contains(point) { addViews(selectedView: i); break }
}
}
My designers decided they want to make a small part of the views I add to overlay the navigationBar. The problem is that after I add the views to the navigationController.view instead, I am no longer able to detect the touches. I have tried location(in: self.navigationController.view) and that didn't work. Before I go down the rabbit hole of endless different things that might work, does anyone have an easy answer for me?

While navigationController is nominally a ViewController, it's 'view' outlet is usually not used, and probably not put in the responder chain.
My solution was to create a custom view (in the storyboard) as outlet for navigationItem.titleView for the UIViewController.
You can set up the custom view in viewDidLoad() (instead of storyboard)
self.navigationItem.titleView = myCustomView;
In viewDidLoad I set up a gesture like:
UITapGestureRecognizer *singleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleStatusTap:)];
[chatTitleView addGestureRecognizer:singleTap];

Related

How to capture touch events from a UIScrollView when user is scrolling the screen vertically on iOS with Swift?

I'm new to the iOS development and Swift and I have the following problem:
I would like to capture touch events from the screen. I have no problems with doing this on regular views. Unfortunately, I encountered issues with the UIScrollView component. When user is tapping on the screen, long pressing on the screen or moving finger horizontally, then I'm able to capture touches, but when user is moving finger vertically and activating vertical scrolling at the same time, then I'm not able to capture touches.
I found a few threads on StackOverflow regarding similar issue, but solutions provided there did not resolve my problem.
Structure of my views is as follows:
+- View (top-level view)
|
|
+--- UIScrollView
|
|
+-- other views inside...
Here is my code:
class MyViewController : UIViewController {
#IBOutlet weak var scrollView: UIScrollView!
override func viewDidLoad() {
super.viewDidLoad()
view.addGestureRecognizer(myRecognizer)
}
}
Gesture recognizer is added into the top-level view.
I also have basic custom implementation of the UIGestureRecognizer:
class MyRecognizer: UIGestureRecognizer {
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesBegan(touches, with: event)
// handle events...
}
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesMoved(touches, with: event)
// handle events...
}
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
super.touchesEnded(touches, with: event)
// handle events...
}
}
What did I try to do to solve this issue?
setting parameter scrollView.canCancelContentTouches = false - didn't help
setting parameter scrollView.isScrollEnabled = false - I was able to capture all touch events, but scrollView stopped working - didn't help
adding gesture recognizer to the scrollView and removing it from the top-level view - didn't help
adding gesture recognizer both to the scrollView and the top-level view - didn't help
setting parameter scrollView.isUserInteractionEnabled = false - I was able to capture all touch events, but scrollView and all UI components stopped working - didn't help
reordering calls in MyRecognizer class: handling events first and then calling method from the upper class super.touches... - didn't help
Right now, I have no idea what else I can do to capture touch events inside the UIScrollView while user is scrolling the screen vertically.
Any suggestions or help are appreciated!
Regards,
Piotr
So UIScrollView has its own set of gestures that have cancelsTouchesInView set to true by default. Try setting this property to false for each gesture in the scrollview like so:
for gesture in scrollView.gestureRecognizers ?? [] {
gesture.cancelsTouchesInView = false
}
EDIT:
Solution to this problem is creating a separate custom UIScrollView and capture touches inside it. It is described in details in this thread: https://stackoverflow.com/a/70594220/10953499 (also linked in the comment).

Getting gestures to work with Subclassed UIButton

I have a subclassed UIButton (DragButton) that I would like to be able to perform pan / pinch / rotate on. Instances of DragButton are created programmatically. There may be 1, there may be 20. All depends on the user.
I am experiencing issues in getting these subclassed buttons to properly work with gestures. The main issue is none of the gestures trigger! Within DragButton class, inside the awakeFromNib instance method, the gestures don't work at all. Inside layoutSubviews, the panning works properly the first drag, but each subsequent hold and drag increases the velocity of the movement a considerably amount. (Pinch and Rotate work properly in layoutSubviews though.) It's almost like layoutSubviews is 'doubling up' the gestures on each interaction. But awakeFromNib doesn't work at all. My guess here is the new buttons have not been unarchived yet.
Here is the code in DragButton
class DragButton: UIButton, UIGestureRecognizerDelegate {
override func awakeFromNib() {
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.draggedView(_:)))
let pinchRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(self.pinchedView(_:)))
let rotateRecognizer = UIRotationGestureRecognizer(target: self, action: #selector(self.rotatedView(_:)))
panRecognizer.delegate = self
pinchRecognizer.delegate = self
rotateRecognizer.delegate = self
self.isUserInteractionEnabled = true
self.isMultipleTouchEnabled = true
self.addGestureRecognizer(panRecognizer)
self.addGestureRecognizer(pinchRecognizer)
self.addGestureRecognizer(rotateRecognizer)
}
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
superview?.bringSubview(toFront: self)
}
Also in DragButton are the methods for handling the gestures, however I don't think my problem lies in there.
So to recap - I need a subclassed button (DragButton) to be instantiated programmatically, and have it react to all the 3 gestures. The issue with my code above is none of the gestures work with awakeFromNib, however they work faulty with layoutSubviews on the first tap, then each subsequent tap doubles up the velocity of the panning (hence the fault).

Hide keyboard by touching background of UITableViewContoller

Would like to hide the keyboard while touching on the background of UITableViewContoller. At the moment I have implemented removing keyboard while pressing return but while pressing outside somewhere still not working. My code:
class SecondViewController: UITableViewController, UITextFieldDelegate {
...
// Hide Keyboard while pressing return
func textFieldShouldReturn(textField: UITextField) -> Bool {
tb_Name.resignFirstResponder()
tb_Description.resignFirstResponder()
return true
}
// Hide Keyboard while pressing somewhere on the UI
override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
self.view.endEditing(true)
}
}
Strange thing is, when I add this code in a "normal" view controller, then it works fine. In an Table View Contoller not.
Thx.
You can probably try to add a gesture on it:
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc]
initWithTarget:self action:#selector(hideKeyboard)];
tapGestureRecognizer.cancelsTouchesInView = NO;
[self.tableView addGestureRecognizer:tapGestureRecognizer];
And actually there are so many ways to handle that, it really depends on what you desired behaviors.

UIControl endTrackingWithTouch not called

I have a view with a tap gesture recognizer. A subview of this view is an instance of my custom class, which inherits from UIControl. I am having an issue where the UIControl subclass will sometimes allow touch events to pass through to the parent view when it shouldn't.
Within the UIControl subclass, I have overridden these functions (code is in Swift)
override func beginTrackingWithTouch(touch: UITouch, withEvent event: UIEvent) -> Bool
{
return true
}
override func continueTrackingWithTouch(touch: UITouch, withEvent event: UIEvent) -> Bool
{
// The code here moves this UIControl so its center is at the touchpoint
return true
}
override func endTrackingWithTouch(touch: UITouch,withEvent event: UIEvent)
{
// Something important happens here!
}
This system works just fine if the user touches down within the UIControl, drags the control around in both X and Y directions, and then lifts off the screen. In this case, all three of these functions are called, and the "something important" happens.
However, if the user touches down with the UIControl, drags the control around only in the X direction, and then lifts off the screen, we have a problem. The first two functions are called, but when the touchpoint lifts off the screen, the tap gesture recognizer is called, and endTrackingWithTouch is not called.
How do I make sure that endTrackingWithTouch is always called?
I fixed this in a way that I consider to be a hack, but there's really no alternative, given how UIGestureRecognizer works.
What was happening was that the tap gesture recognizer was canceling the control's tracking and registering a tap gesture. This was because when I was dragging horizontally, I just happened to be dragging short distances, which gets interpreted as a tap gesture.
The tap gesture recognizer must be disabled while the UIControl is tracking:
override func beginTrackingWithTouch(touch: UITouch, withEvent event: UIEvent) -> Bool
{
pointerToSuperview.pauseGestureRecognizer()
return true
}
override func continueTrackingWithTouch(touch: UITouch, withEvent event: UIEvent) -> Bool
{
// The code here moves this UIControl so its center is at the touchpoint
return true
}
override func endTrackingWithTouch(touch: UITouch,withEvent event: UIEvent)
{
// Something important happens here!
pointerToSuperview.resumeGestureRecognizer()
}
override func cancelTrackingWithEvent(event: UIEvent?)
{
pointerToSuperview.resumeGestureRecognizer()
}
In the superview's class:
pauseGestureRecognizer()
{
tapGestureRecognizer.enabled = false
}
resumeGestureRecognizer()
{
tapGestureRecognizer.enabled = true
}
This works because I'm not dealing with multitouch (it's OK for me not to receive tap touch events while tracking touches with the UIControl).
Ideally, the control shouldn't have to tell the view to pause the gesture recognizer - the gesture recognizer shouldn't be meddling with the control's touches to begin with! However, even setting the gesture recognizer's cancelsTouchesInView to false cannot prevent this.
There's a way to fix this that's nicely self-contained: instantiate your own TapGestureRecognizer and attach it to your custom control, e.g. in Objective-C,
_tapTest = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapped:)];
_tapTest.numberOfTapsRequired = 1;
[self addGestureRecognizer:_tapTest];
and then implement the tapped action handler to process the tap:
- (void)tapped:(UITapGestureRecognizer *)recognizer {...}
In my case, I handle tapped the same as endTrackingWithTouch:withEvent:; your mileage may vary.
This way, you get the tap before any superview can snatch it, and you don't have to worry about the view hierarchy behind your control.
When a UIControl is moved while tracking touches, it might cancel its tracking. Try overriding cancelTrackingWithEvent and see if this is the case. If you do see the cancel, you're going to have to track your touches in an unmoving view somewhere in the parent hierarchy of this control.
I know this is old, but I run into the same problem, check if one of your superviews has gesture recogniser, and deactivate them when you need to use the UIControl.
I actually ended changed the superview of the UIControl to the main window to avoid this conflicts (Because it was in a popup).

Possible to discard touch events?

I have a iOS game and during loading screens any touches the user does seem to be buffered up, so once the loading it done (it can take a few seconds), I get the touch events.
Is there way for me to discard all touches?
Ok this is a really old question but for anyone stumbling upon this can simply follow either of the below two approaches:
Approach 1.
DiscardTouchView.addGestureRecognizer(UITapGestureRecognizer())
Basically adding an empty gesture. So on tapping that view nothing happens.
Approach 2.
In this case you don't add empty gesture recognizer to the view. In case DiscardTouchView is a subview of SomeParentView which has a UIGestureRecognizer object, you can get that object and ignore it.
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
guard let tappedView = touches.first?.view else { return }
if(tappedView == DiscardTouchView) {
guard let recognizer = touches.first?.gestureRecognizers?.first else { return }
recognizer.ignore(touches.first!, for: event!)
}
}
Setting userInteractionEnabled to false actually passes the touch event from subview to superview. It doesn't discard the touch event.
Setting userInteractionEnabled to true for a simple UIView does the same.

Resources