How to prevent double action from simultaneous button touches on UINavigation bar? - ios

In a UINavigation bar, there is a right custom share UIBarButtonItem and a back button in the left UIBarButtonItem. When simultaneously tapping on both buttons, the app produces a black view, possibly because both buttons are attempting to display a new view simultaneously - the share button presents a UIActivityViewController and the back button a VC from the prior screen.
In looking through similar questions here, I've tried the following solutions but neither prevented a black view from appearing on a simultaneous button touch:
Inserting exclusiveTouch into ViewDidLoad in the following 2 ways
a)
for(UIView *temp in self.navigationController.navigationBar.subviews)
{ [temp setExclusiveTouch:YES]; }
b) [self.navigationController.navigationBar setExclusiveTouch:YES];
Applying self.navigationController.navigationBar.userInteractionEnabled = NO; after a touch.
Are there other solutions?
Is this related to multi-threading?

In each touch event handler, add the following line:
[[UIApplication sharedApplication] beginIgnoringInteractionEvents];
When the handler has completed, execute the following:
[[UIApplication sharedApplication] endIgnoringInteractionEvents];
It's up to you to figure out what to consider the end of the handler. If you're pushing or popping view controllers, you might add that second line to the viewWillAppear of the relevant view controllers. If you're displaying a modal view controller, you can use the completion handler of -[UIViewController presentViewController:animated:completion:].

Pretty much simple you can use make use of ExclusiveTouch property in this case
[self.navigationController.navigationBar setExclusiveTouch:YES];
This is a Boolean value that indicates whether the receiver handles touch events exclusively.
Setting this property to YES causes the receiver to block the delivery of touch events to other views in the same window. The default value of this property is NO.

If you want only one button to respond to touches at a time, you need to set exclusiveTouch for that button, rather than for the parent view.
Put this just after you add your bar button items.
for(UIView *temp in self.navigationController.navigationBar.subviews)
{
[temp setExclusiveTouch:YES];
}
or you can set exclusiveTouch individually for each UIBarButton while creating them.

Related

UIAlertView exclusiveTouch on buttons iOS8

On iOS 7, when UIAlertView is presented, its buttons seem to automatically have exclusiveTouch=YES set. This way, if you simultaneously press more than one button on alert view, nothing terribly will happen.
This is not the case on iOS 8. When I present UIAlertView with 2 buttons. And I press these 2 buttons at the same time, the app freezes.
I cannot access the UIButtons on that UIAlertView. I cannot access any of the subviews of UIAlertView for that matter.
alertView.subview returns empty array, no matter in which part of the lifecycle I call it.
I have managed to find a solution to the problem. I implement the UIAlertView's delegate method:
-(void)didPresentAlertView:(UIAlertView *)alertView
{
UIView *view = ((UIApplication*)[UIApplication sharedApplication]).keyWindow.rootViewController.presentedViewController.view;
[self setExclusiveTouchForView:view];
}
-(void) setExclusiveTouchForView:(UIView*)view
{
for (UIView *subview in view.subviews) {
subview.exclusiveTouch = YES;
[self setExclusiveTouchForView:subview];
}
}
At first it was confusing to see that neither of all the subviews inside the view variable is of class UIButton or UIControl.
It is not actually necessary to set exclusiveTouch property on all of the subviews. But I posted it like this to reduce code complexity.
It works fine. App no longer freezes when both of UIAlertView's buttons are pressed at the same time. Only one of the presses is accepted.

I've hidden the navigation bar and the status bar, now the screen edge pan gesture to go back won't work, is this typical?

I'm curious, if I set the navigationBar to hidden, and also hide the status bar, my view controller no longer responds to the screen edge gesture to pop the view controller.
Is this expected behaviour? I tried to set the interactivePopGestureRecognizer to enabled in viewDidLoad after I hide the navigation bar, but it still won't work.
[self.navigationController.navigationBar setHidden:YES];<--doesn't remove pop gesture
[self.navigationController setNavigationBarHidden:YES];<-- disables pop gesture
Simply use the first option, and in your root controller's viewDidAppear method use:
[self.navigationController.navigationBar setHidden:NO];
Are you sure you've done things right? I've put together an example that seems to work for me. All I did was make the navigationController.navigationBar.hidden = YES and [[UIApplication sharedApplication] setStatusBarHidden:YES]
-edit-
After closer inspection, it looks like there are two different properties on UINavigationController. There is navigationBar which is the UINavigationBar view, and there is navigationBarHidden which is a boolean. When you set navigationBarHidden to true, the swipe gesture stops working. But if you set that actual view to be hidden with navigationBar.hidden then the gesture still works. Check the Git repository for an example.
A very simple work around:
Link a swipe gesture method to your navigation back button. Make the current view controller the target of the gesture recognizer (self), with the selector popThisViewController. Then install the gesture recognizer into the view that the user will swipe on. Don't forget to add your go back action
edit added swipe gesture for reference to other coders that don't know
Cleaner code will look like this:
UISwipeGestureRecognizer *gesture = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(goBack:)];
gesture.numberOfTouchesRequired = 1;
gesture.direction = UISwipeGestureRecognizerDirectionRight;
[self.view addGestureRecognizer:swipeRight];
}
-(IBAction)goBack:(id)sender {
[self.navigationController popViewControllerAnimated:YES];
}

How can I assign a pointer to the keyboard before I have assigned first responder

I am trying to create a user interface enabling users to switch between the keyboard and other menus when using a chat application.
On a click of the textField bar I want to raise either the keyboard or a collection view.
The problem occurs when I click the 'menu' button. I want the textField bar to raise revealing my menu view. Then, on a click on the keyboard button, instantly switch to the keyboard, rather than having it slide up from the bottom. This means I need to have the keyboard already loaded and hidden but in the background of the app.
Currently though the earliest I am managing to assign a variable to the keyboard is in the keyboardDidShow function.
-(void) keyboardDidShow: (NSNotification *) notification {
// Get the window the keyboard is a subview of
_window = [UIApplication sharedApplication].windows.lastObject;
_keyboard = _window.subviews[0];
}
This means that after it has been loaded once I can hide and reveal it, but I don't want it visible when it is loading this first time.
To achieve this using alternate means I have tried adding my extra views as subviews of the UIWindow the keyboard is created in:
[_window addSubview:_menuView];
[_window addSubview:_gamesView];
[_window addSubview:_stickerView];
[self hideSpecificView];
Unfortunately I keep coming across the same problem, until I have loaded the keyboard once it needs to fully load before I can get a pointer to it to hide it.
Here is a picture of my toolBar incase I am not being clear:
On clicking the menu icon or the stickers icon I want the bar to raise with a collection view. If I then click the textfield, with these views visible, I want to hide the visible view to immediately show the keyboard behind.
I have also tried experimenting with keyboardWillShow but as the window hasn't been loaded in front our screen I can't get a pointer to the keyboard to hide it before it loads.
An example of what I am after can be found many chat apps (facebook messenger, LINE, Kakao Talk)
Any help would be greatly appreciated
Although the way I came up with isn't perfect it works almost perfectly so hopefully this might help people in the future. If anyone else has solved it differently please post as it would be interesting to know how you did it.
I started by adding a class variable to a UIWindow in my header file and then setting off a timer to ping just after the keyboard will show method finishes. After this method has finished the keyboard has been created, just, and so I allocate it and hide it.
-(void) keyboardWillShow: (NSNotification *) notification {
// More keyboard code
_window = [UIApplication sharedApplication].windows.lastObject;
[NSTimer scheduledTimerWithTimeInterval:0.01
target:self
selector:#selector(allocateKeyboard)
userInfo:nil
repeats:NO];
}
- (void)allocateKeyboard {
if (!_keyboard) {
_keyboard = _window.subviews[0];
}
_keyboard.hidden = YES;
[self setViewForButtonType];
}
I have already previously added my other views, hidden them and constrained them to the bottom of the main view, this means that when the keyboard rises they do too.
- (void)viewDidLoad {
[self.view addSubview:_menuView];
[self.view addSubview:_gamesView];
[self.view addSubview:_stickerView];
}
...
- (void)hideViews {
_keyboard.hidden = YES;
_menuView.hidden = YES;
_gamesView.hidden = YES;
_stickerView.hidden = YES;
}
When buttons get pressed I simple then unhide the view that I want to see and hide the rest of the views.
When I say that this method doesn't work perfectly it is because if you load view main view and then click a button before the keyboard has loaded for the first time then you get a quick glimpse of the keyboard before the view appears over the top. This though only happens the first time and only if they don't click in the text field first.
Anyway, I found this was the best way of making views look like they are in front of the keyboard. Obviously my code was a lot longer and more complex (too long for here) but this is the gist of the method I used to solve it. Comment if you have any queries and I hope this helps.

UIView exclusiveTouch: strange behaviour with UIBarButtonItem

I set a UIButton's exclusiveTouch to YES, and it works fine (other views will not receive touch-events if I touch the button). The exception is that when I touch the UIBarButtonItem on the navigation bar, its action is triggered. It doesn't happen every time, but if you tap a bit higher on the UIBarButtonItemor just tap on the status bar close to the UIBarButtonItem, it happened.
Can anyone explain why this happens?
UIBarButtonItem is not a subclass of UIView. So it doesn't have exclusiveTouch property and won't be affected by other views' exclusiveTouch.
Try this in viewDidLoad
//Set exclusive touch on each navigationItem
for(UIView *navigationItem in self.navigationController.navigationBar.subviews)
{
[navigationItem setExclusiveTouch:YES];
}
Hope this helps.

How to handle backBarButtonItem pressed?

I have almost done this in all the application but I have 3 views stacked in navigationController and I need to jump from the third view to the first view.
As I understand I can do this via viewWillDisappear only. But if I try this "jump" I will get the navigationController panel from the second View which with a navigation buttons which cause exceptions/errors.
P.S. Do not advice me to make leftBarButtonitem looking like backBarButtonItem. It is too difficult and I don't know where to find an appropriate image for it.
To my knowledge, you have no choice but to provide your own UIBarButtonItem. You are not permitted from interrupting how UINavigationController works by default. That is, you cannot override the behavior of the back button. You must provide a custom bar button item and set it as the navigation item's left bar button item.
(As a side note, the sort of behavior you're looking for may be an indication of a poor navigation pattern. Back buttons should almost always back out of a navigation hierarchy sequentially.)
Let's say in navigation order your views stacked like top -> 3 -> 2 -> 1 . When you are in this position you can have a flag in your application delegate that shows you will doublePop when backButton pressed as below: ( You are doing this whenever third view appears in the order you mentioned)
MyApplicationDelegate * del = [[UIApplication sharedApplication]delegate];
del.doublePopEnabled = YES;
[del release];
In view 2 :
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
MyApplicationDelegate * del = [[UIApplication sharedApplication]delegate];
if(del.doublePopEnabled){
//Asssuming you have a reference to your navigationController in your view 2
del.doublePopEnabled = NO;
[del.release]
//Use animated as no if you don't want user to see doublePopping.
self.navigationController popViewControllerAnimated:NO];
}
}
Hope it helps.

Resources