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

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.

Related

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

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.

View set as inputAccessoryView throws exception when added back into normal view

I have a text view, some decorations etc. in a view and I want that view to dock on keyboard, just like any other messaging app.
When I'm about to display my text view, here is how I attach my view to the keyboard:
-(BOOL)textViewShouldBeginEditing:(UITextView *)textView{
UIView *inputView = self.textInputView; //connected at IB outlet from storyboard, also contains the text view itself.
constraintsOfTextInputView = [self constraintsRelatedToView:inputView]; //to be able to add them again
[[UIApplication sharedApplication].keyWindow addSubview:self.textInputView];
textView.inputAccessoryView = inputView;
editingTextView = textView;
return YES;
}
And when dismissing:
//using notification hook for UIKeyboardWillHideNotification because
//textView[Will|Did]EndEditing is called too late
-(void)keyboardWillHide{
if(editingTextView && constraintsOfTextInputView){
editingTextView.inputAccessoryView = nil;
[self.textInputView removeFromSuperview];
[self.view addSubview:self.textInputView]; <--- EXCEPTION
[self.view addConstraints:constraintsOfTextInputView];
[self.view layoutIfNeeded];
editingTextView = nil;
constraintsOfTextInputView = nil;
}
}
Even though I'm doing exactly the opposite of what I do when adding, I'm getting this exception:
*** Terminating app due to uncaught exception
'UIViewControllerHierarchyInconsistency', reason: 'child view controller:
<UICompatibilityInputViewController: 0x13307ba20> should have parent view
controller:<ULPostViewController: 0x1307a7c00> but actual parent is:
<UIInputWindowController: 0x12f0be200>'
How can I get rid of this problem? I'm on iOS 8 (and don't support older versions).
UPDATE: I've created a git repository demonstrating the problem:
https://github.com/can16358p/CPInputAccessoryTest
You are abusing the inputView for something that it's not. The input view should be used for views the user an input data with, the input accessory view something you may want to extend that with. But its supposed be something like a keyboard.
Your text view is NOT part of that. It is the view the user is inputting data into. Decide if you want the view as an input view or in the main view hierarchy, but don't move it around before the two.
Your real problem is that you want to move the text view when the keyboard appears in order to keep it visible. To do so, observe the keyboard notifications
UIKeyboardWillShowNotification
UIKeyboardDidShowNotification
UIKeyboardWillHideNotification
UIKeyboardDidHideNotification
and move your views around accordingly. You can adjust the view controller's view's bounds origin for example, to move everything up.
You can use the text view as an input accessory view of the entire view controller. See my pull request.
Except for the growing text view part, this is pretty much the behavior of the Messages app.

Navigation bar title bug with interactivePopGestureRecognizer

I am having a weird problem with UINavigationBar's title in an app when interactivePopGestureRecognizer comes into play. I have made a demo app to showcase this bug.
Setup:
The rootViewController is a UINavigationController.
FirstViewController has the navigation bar hidden, and interactivePopGestureRecognizer.enabled = NO;
Second and ThirdViewControllers have the navigation bar visible and the popgesture enabled.
Bug:
The bug occurs when going back from the Second to the First view using the popgesture. If you pull the second view halfway and then go back to the second view, the navigation title will show "Second View" (as expected).But when you go to the Third view, the title will not change to "Third View". And then on clicking the back button of the Third view, the navbar will get messed up.
Please check out my demo app. Any help explaining why this bug is happening will be appreciated. Thanks!
Remove Red Herrings
First of all, your example can be greatly simplified. You should delete all the viewDidLoad stuff, as it is a complete red herring and just complicates the issue. You should not be playing around with the pop gesture recognizer delegate on every change of view controller; and turning the pop gesture recognizer off and on is irrelevant to the example (it is on by default, and should just be left on for this example). So delete this kind of thing in all three view controllers:
- (void)viewDidLoad {
[super viewDidLoad];
if ([self.navigationController respondsToSelector:#selector(interactivePopGestureRecognizer)]) {
self.navigationController.interactivePopGestureRecognizer.enabled = NO;
self.navigationController.interactivePopGestureRecognizer.delegate = self;
}
}
(Don't delete the code that sets self.title, though you could have made things even simpler by doing that in the xib file for each view controller.)
You can also get rid of other unused methods throughout, such as the init... methods and memory alert methods.
Another issue, by the way, is that you have forgotten to call super in your implementations of viewWillAppear:. It is required that you do this. I don't think that affects the bug, but it is well to obey all the rules before you start trying to track these things down.
Now the bug still happens but we have much simpler code, so we can start to isolate the issue.
How The Pop Gesture Works
So what's the cause of the problem? I think the most obvious way to understand it is to realize how the pop gesture works. This is an interactive view controller transition animation. That's right - it's an animation. The way it works is that the pop animation (slide from the left) is attached to the superview layer, but with a speed of 0 so that it doesn't actually run. As the gesture proceeds, the timeOffset of the layer is constantly being updated, so that the corresponding "frame" of the animation appears. Thus it looks like you are dragging the view, but you are not; you are just making a gesture, and animation is proceeding at the same rate and to the same degree. I have explained this mechanism in this answer: https://stackoverflow.com/a/22677298/341994
Most important (pay attention to this part), if the gesture is abandoned in the middle (which it almost certainly will be), a decision is made as to whether the gesture is more than half-way completed, and based on this, either the animation is rapidly played to the end (i.e. the speed is set to something like 3) or the animation is run backwards to the start (i.e. the speed is set to something like -3).
Solutions And Why They Work
Now let's talk about the bug. There are two complications here that you've accidentally banged into:
As the pop animation and pop gesture begin, viewWillAppear: is called for the previous view controller even though the view may not ultimately appear (because this is an interactive gesture and the gesture may be cancelled). This can be a serious issue if you are used to the assumption that viewWillAppear: is always followed by the view actually taking over the screen (and viewDidAppear: being called), because this is a situation in which those things might not happen. (As Apple says in the WWDC 2013 videos, "view will appear" actually means "view might appear".)
There is a secondary set of animations, namely, everything connected with the navigation bar - the change of title (it is supposed to fade into view) and, in this case, the change between not hidden and hidden. The runtime is trying to coordinate the secondary set of animations with the sliding view animation. But you have made that difficult by calling for no animation when the bar is hidden or shown.
Thus, as you've already been told, one solution is to change animated:NO to animated:YES throughout your code. This way, the showing and hiding of the navigation bar is ordered up as part of the animation. Therefore, when the gesture is cancelled and the animation is run backwards to the start, the showing/hiding of the navigation is also run backwards to the start - the two things are now staying coordinated.
But what if you really don't want to make that change? Well, another solution is to change viewWillAppear: to viewDidAppear: throughout. As I've already said, viewWillAppear: is called at the start of the animation, even if the gesture won't be completed, which is causing things to get out of whack. But viewDidAppear: is called only if the gesture is completed (not canceled) and when the animation is already over.
Which of those two solutions do I prefer? Neither of them! They both force you to make changes you don't want to make. The real solution, it seems to me, is to use the transition coordinator.
The Transition Coordinator
The transition coordinator is an object supplied by the system for this very purpose, i.e., to detect that we're involved in an interactive transition and to behave differently depending on whether it is canceled or not.
Concentrate just on the OneViewController implementation of viewWillAppear:. This is where things are getting messed up. When you're in TwoViewController and you start the pan gesture from the left, OneViewController's viewWillAppear: is being called. But then you cancel, letting go of the gesture without completing it. In just that one case, you want not to do what you were doing in OneViewController's viewWillAppear:. And that is exactly what the transition coordinator allows you to do.
Here, then, is a rewrite of OneViewController's viewWillAppear:. This fixes the problem without your having to make any other changes:
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
id<UIViewControllerTransitionCoordinator> tc = self.transitionCoordinator;
if (tc && [tc initiallyInteractive]) {
[tc notifyWhenInteractionEndsUsingBlock:
^(id<UIViewControllerTransitionCoordinatorContext> context) {
if ([context isCancelled]) {
// do nothing!
} else { // not cancelled, do it
[self.navigationController setNavigationBarHidden:YES animated:NO];
}
}];
} else { // not interactive, do it
[self.navigationController setNavigationBarHidden:YES animated:NO];
}
}
The fix is simple , but I don't have any explanation at the moment why this is happening.
One your OneViewController change your viewWillAppear to ,
-(void)viewWillAppear:(BOOL)animated{
// [self.navigationController setNavigationBarHidden:YES animated:NO];
self.navigationController.navigationBar.hidden = YES;
}
and on the second and third view controllers change it to,
-(void)viewWillAppear:(BOOL)animated{
//[self.navigationController setNavigationBarHidden:NO animated:NO];
self.navigationController.navigationBar.hidden = NO;
}
Strange but this will fix the issue when we directly use the hidden property of the UINavigationBar.
I don't know how do you make "FirstViewController has the navigation bar hidden".
I have the same problem, and I fixed it by replacing
self.navigationController.navigationBarHidden = YES / NO;
by
[self.navigationController setNavigationBarHidden:YES / NO animated:animated];
I gave up trying to make this work used my own swipe recognizer that pops the navigation stack:
override func viewDidLoad() {
super.viewDidLoad()
// disable system swipe back gesture and add our own
navigationController?.interactivePopGestureRecognizer?.enabled = false
let swipeBackGestureRecognizer = UISwipeGestureRecognizer(target: self, action: "swipeBackAction:")
swipeBackGestureRecognizer.direction = UISwipeGestureRecognizerDirection.Right
tableView.addGestureRecognizer(swipeBackGestureRecognizer)
}
func swipeBackAction(sender: UISwipeGestureRecognizer) {
navigationController?.popViewControllerAnimated(true)
}
Disable the system interactivePopGestureRecognizer
Create your own UISwipeGestureRecognizer with a Right direction
Pop the navigation stack animated when he swipe is detected
Here's what fixed it for me (Swift)
1st view controller:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(true, animated: animated)
}
2nd and 3rd view controllers:
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.navigationController?.setNavigationBarHidden(false, animated: animated)
}

How do I hide a button so that it turns see-through rather than white?

I am trying to program a view which has a list of exercises in a table view. The user can either swipe them to delete or go into edit mode and click multiple rows to delete faster (much like in mail).
At the bottom of the screen there is a finish button where the user will click when s/he has finished choosing the exercises they want in their workout. Currently I am trying to bring up the toolbar when the user enters edit mode to allow them to delete their multiple choices. When this happens the view is compressed and so the finish button raises up above the toolbar. This isn't what I want as it looks stupid to be able to finish while editing something else.
I have tried hiding the button but this leaves a white square just above the toolbar
I am not sure if I need to make the button transparent or if I am hiding it incorrectly.
How can I have the bottom button disappear completely when edit mode is entered to the extent it doesn't affect my table view and then reappear and almost 'swap' with the tool bar when editing mode is exited?
- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
// Activates multiple selection
exercisesSelectedTableView.allowsMultipleSelectionDuringEditing = editing;
// Lets us know we have clicked editing - changes it to done
[super setEditing:editing animated:animated];
if (editing) {
// This bit is done when someone clicks edit
// Sets the view into editing mode
[exercisesSelectedTableView setEditing:editing animated:YES];
// Unhides the tool bar
[self.navigationController setToolbarHidden:NO animated:YES];
// Hides the finish button
[finishedButton setHidden:YES];
}
else {
// This bit is called once someone clicks done
[self.navigationController setToolbarHidden:YES animated:YES];
// Reveals finish button
[finishedButton setHidden:NO];
[super setEditing:NO animated:YES];
[exercisesSelectedTableView setEditing:NO animated:YES];
}
}
Found the answer to this out eventually.
Using the setToolBarHidden works fine but in the XIB file I needed to check that the table view went all the way down to the bottom of the view. As it was the table view only went to the top of the finished button. Therefore when it was hidden it left the white gap I assumed was the shadow of the button.
Morale of the story: Check the XIB files carefully

Present a UIScrollView when opening an App for the first time

I have an iPad App that I want to be compatible from iOS 5.0 to 6.0. My main view contains a scroll view z-indexed on the front, which is initially set to hidden. I also have a toolbar containing a button that cycle the scroll view hidden or not.
I would like to add a feature to present the scroll view as initially visible when the user opens the App for the first time to make the help visible by default to new users.
My code to cycle between visible and hidden is the following:
- (void)showHelpView:(id)sender {
BOOL hidden = [blackTranslucent isHidden];
[self.view bringSubviewToFront:scrollViewOutlet];
if (hidden) {
[scrollViewOutlet setHidden:FALSE animationStyle:KGAnimationFade duration:0.7];
[blackTranslucent setHidden:FALSE animationStyle:KGAnimationFade duration:0.5];
}
else {
[scrollViewOutlet setHidden:TRUE animationStyle:KGAnimationFade duration:0.5];
[blackTranslucent setHidden:TRUE animationStyle:KGAnimationFade duration:0.7];
}
}
where the sender is my toolbar button, blackTranslucent is a view on top of the main view and scrollViewOutlet is my scroll view IBOutlet.
Add a property "isNewUser" on NSUserDefaults in the application:willFinishLaunchingWithOptions: method which will only be written once by checking if the key exists.
In your main view in the viewDidLoad check this property if true make the view visible and update the key to false. if not just continue regularly.
Further information on NSUserDefaults
Hope that helps

Resources