Custom Keyboard (iPhone), UIKeyboardDidShowNotification and UITableViewController - iphone

On an iPhone App, I've got a custom keyboard which works like the standard keyboard; it appears if a custom textfield becomes first responder and hides if the field resigns first responder. I'm also posting the Generic UIKeyboardWillShowNotification, UIKeyboardDidShowNotification and their hiding counterparts, like follows:
NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithCapacity:5];
[userInfo setObject:[NSValue valueWithCGPoint:self.center]
forKey:UIKeyboardCenterBeginUserInfoKey];
[userInfo setObject:[NSValue valueWithCGPoint:shownCenter]
forKey:UIKeyboardCenterEndUserInfoKey];
[userInfo setObject:[NSValue valueWithCGRect:self.bounds]
forKey:UIKeyboardBoundsUserInfoKey];
[userInfo setObject:[NSNumber numberWithInt:UIViewAnimationCurveEaseOut]
forKey:UIKeyboardAnimationCurveUserInfoKey];
[userInfo setObject:[NSNumber numberWithDouble:thisAnimDuration]
forKey:UIKeyboardAnimationDurationUserInfoKey];
[[NSNotificationCenter defaultCenter] postNotificationName:UIKeyboardWillShowNotification
object:nil
userInfo:userInfo];
This code is working and I use it in UIViewController subclasses.
Now since iPhone OS 3.0, UITableViewController automatically resizes its tableView when the system keyboards show and hide. I'm only now compiling against 3.0 and I thought that the controller should also resize the table if my custom keyboard appears, since I'm posting the same notification. However it doesn't. The table view controller is set as the delegate of the input fields.
Does anyone have an idea why this might be the case? Has anyone implemented something similar successfully?
I have standard input fields along the custom ones, so if the user changes the fields the standard keyboard hides and the custom one shows. It would be beneficial if the tableView didn't resize to full height and I didn't have to resize it back with a custom method.

Well there are a few possibilities that you poke around in. From your description, it seems that UITableView is not using the UIKeyboard Notifications.
But possibly, it is the UINavigationController that is responding to this notification (or UITabBarController).
You could override methods like setFrame: drawRect: and setNeedsDisplay in the tableview to see what is happening in the call stack. You may be able to figure out what is actually causing the tableview to redraw at the correct size.
But in all likelihood, just changing the size of the tableView yourself is the much easier solution. These suggestions are just for fun!

I have done something similar. If I recall, I ended up just having the TableViewController subecibe to the notifications that either you send or the system sends, and then animate the change to the tableview's frame. Presumably there are doing something similar internally, but I think the end result just becomes two animation blocks wrapped around each other that both run when the system is posting the notifications, but the end result should be the same.
In your viewdidLoad:
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(keyboardWillShow:)
name: UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(keyboardDidShowOrHide:)
name: UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(keyboardWillHide:)
name: UIKeyboardWillHidewNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(keyboardDidShowOrHide:)
name: UIKeyboardDidHideNotification object:nil];
and in the view controller:
-(void) keyboardWillShow:(id)sender {
[UIView beginAnimations];
[UIView setAnimationDuration:0.3];
self.view.frame = //Your new size
}
-(void) keyboardDidShowOrHide:(id)sender {
[UIView commitAnimations];
}
-(void) keyboardWillHide:(id)sender {
[UIView beginAnimations];
[UIView setAnimationDuration:0.3];
self.view.frame = //Your old size
}

Related

UIKeyboardWillHide not triggered

I read many post here about this topic, but I wasn't able to find an answer to my question, so, hope you won't be bored about another UIKeyboard post :-)
In my view controller's implementation I added self as an observer for the two notifications UIKeyboardWillShowNotification and UIKeyboardWillHideNotification, passing the selectors keyboardWillShow: and keyboardWillHide: to handle to notifications. As I touch a UITextField, the keyboardWillShow: method is called but when I press a "Done" button (which dismisses the keyboard) the keyboardWillHide: method is not called.
Really, I'd like to make my UITextField show a keyboard with the "hide button" on the bottom right of the keyboard, but I wasn't able to find the right keyboard type. Maybe I need to set the textfield retuntype to "...Done". In that way I saw that "return" key turns to "done".
So I set a toolbar to be my UITextField's inputAccessoryView, so now I can show a standard keyboard with a tool bar above with the "Done" button. As a user touches that button, I hide the keyboard with the resignFirstResponder method.
The strange thing is that when I call resignFirstResponder, the UIKeyboardWillHideNotification isn't posted; at least the keyboardWillHide: method is not called.
What do you suggest to me? I really wanted to display a keyboard with the small button with the down arrow to hide the keyboard, but also this solution could be right, but I'd like to resize the view and to do this I need to observer UIKeyboardWillHideNotification.
Thank you very much for help...
(ADDED:)
In viewDidLoad:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:[[self view] window]];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:[[self view] window]];
I took these declarations from one of "yours" post :-) But the willShow works...
The action of the "Done" button that's in the UIToolbar that's assigned to be the inputAccessoryView of my text field is:
-(void)keyboardDone {
[msgTextField resignFirstResponder];
CLOSED:
OK! When a developer is stupid... it is stupid :-) :-)
This is my corrected willHide method:
-(void)keyboardWillHide:(NSNotification*)n {
NSDictionary* userInfo;
CGSize keyboardSize;
CGRect viewFrame;
/* This was the bad guy :) I forgot to delete it
* after I previously copied the willShow method that
* checks if keyboard is already shown (if so returns).
*
* if( keyboardIsShown )
* return;
*/
userInfo = [n userInfo];
keyboardSize = [[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
viewFrame = [[self scrollView] frame];
viewFrame.size.height += ( keyboardSize.height - TABBAR_HEIGHT );
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:0.5];
[[self scrollView] setFrame:viewFrame];
[UIView commitAnimations];
keyboardIsShown = NO;
NSLog(#"HIDE\n");
}
First of all I'd like to thank you all for this useless work in helping me. I'd like to give you some points, so I'll try to rise a "interest point" for each answer, but I need to choose the right one... hard part... :-)
Excuse me again... I really didn't see the if() statement...
If you read the documents for UIWindow it says that the notification object for these notifications is nil. You are passing self.view.window in as the object to the addObserver:selector:name:object: method. Try passing nil instead:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
It's important to note that when the user hides the software keyboard via the hide button, the hide methods aren't called. The show methods are called again, but the keyboard is nearly off screen except for the home row toolbar.
Check, if keyboardDone really gets called (i.e. with NSLog(#"%#", #"keyboard done called");). If its get called, but resignFirstResponder does not help dismissing the keyboard, then try this:
[self.view endEditing:YES];
Please also provide your keyboardWillHide: method.
To set the keyboard up so that it has a "Done" button, do this:
1) Setup your view controller so that it implements the UITextFieldDelegate. For Example:
#import <UIKit/UIKit.h>
#interface TX_ViewController : UIViewController <UITextFieldDelegate>
#property (nonatomic, retain) IBOutlet UITextField *textField;
#end
2) In your view controllers implementation file, use the following code to setup the keyboard:
- (void)viewDidLoad
{
[self.textField setDelegate:self];
[self.textField setReturnKeyType:UIReturnKeyDone];
[self.textField addTarget:self action:#selector(textFieldFinished:) forControlEvents:UIControlEventEditingDidEndOnExit];
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
3) And if you wish to do something when the DONE button is pressed, simply add the following function to your view controller's implementation file:
- (IBAction)textFieldFinished:(id)sender
{
[sender resignFirstResponder];
}
Also, if you are using Interface builder to create your interfaces, don't forget to setup your IBOutlet reference for the TextField; otherwise, your class won't receive the messages from the XIB.
I set this up in a sample application just to see if it works and it did perform in the way you wish for your application to perform.
Swift $
NotificationCenter.default.addObserver(self, selector: #selector(self.keyboardWillHide), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
func keyboardWillHide(notification: NSNotification){
print("keyboardWillHide")
}

Having trouble hiding keyboard using invisible button which sits on top of uiscrollview

I have 3 items in play...
1) UIView sits at the base of the hierarchy and contains the UIScrollview.
2) UIScrollview that is presenting a lengthy user form.
3) An invisible button on the UIScrollview that I'm using to provide "hide the keyboard" features.
Notice in the code below that I'm registering to be notified when the keyboard is going to appear and again when it's going to disappear. These are working great.
My problem is seemingly one of "layers". See below where I insert the button into the view atIndex:0. This causes the button to be activated and "stuffed" behind the scrollview so that when you click on it, the scrollview grabs the touch and the button is unaware. There is no way to "reach" the button and suppress the keyboard.
However, if I insert atIndex:1, the button gets super imposed on top of the text entry fields and so any touch at all is acted upon by the button, which immediately suppresses the keyboard and then disappears.
How do I insert the button on top of the UIScrollview but behind the UITextfields that sit there?
other logistics: I have a -(void) hidekeyboard function that I have setup with the UIButtion as an IBAction(). And I have the UIButton connected to "files owner" via a ctrl-drag/drop. (Do I need both of those conventions?)
This code in ViewDidLoad()...
[[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardWillShowNotification object:nil queue:nil usingBlock:^(NSNotification *notification){
[self.view insertSubview:self.keyboardDismissalButton atIndex:0];
}];
Here's a nifty way to do this without even needing the invisible button. This approach will only work on devices north of 3.2, but since we're already using the block methods on NSNotificationCenter, we know that we're at least at 4.0.
In lieu of a button, we instead add a tap gesture recognizer to our view. This tap gesture recognizer calls -dismissKeyboard: and then we ask our view to end all editing. This method is only available on 3.2 and later and works through it's subviews until it locates the current firstResponder and sends it -resignFirstResponder. The boolean argument that -endEditing: takes determines whether the resignation of firstResponder is forced or not. The documentation is a little vague, but I take it to mean that if forced, the -textFieldShouldEndEditing delegate method on UITextField will not be called.
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardWillShowNotification object:nil queue:nil usingBlock:^(NSNotification *notification) {
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(dismissKeyboard:)];
tap.numberOfTapsRequired = 1;
tap.numberOfTouchesRequired = 1;
[self.view addGestureRecognizer:tap];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardWillHideNotification object:nil queue:nil usingBlock:^(NSNotification *notification) {
[self.view removeGestureRecognizer:[self.view.gestureRecognizers lastObject]];
}];
}
- (void)dismissKeyboard:(UIGestureRecognizer *)gesture
{
[self.view endEditing:NO];
}
There is no way to have a view be in front of a sibling but behind the sibling's children. You can insert the button as a child of the scroll view (behind all the text fields), or you can override pointInside:withEvent: on the upper view to return false where it is over a text field and true otherwise (effectively "punching holes" in it).
Figured it out....
Here is the answer.
The code below is all within the ViewDidLoad() of the UIViewController. Begin by removing the button altogether from the UIView. (it will be rendered when the keyboard is activated).
Notice in the keyboard "WillShow" notification below that I am inserting the button onto my UIScrollView layer rather than the UIView layer, as before. However, once the button is clicked and the keyboard is about to be dismissed, I remove the button altogether from the UIView (i.e. self).
[self.keyboardDismissalButton removeFromSuperview];
[[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardWillShowNotification object:nil queue:nil usingBlock:^(NSNotification *notification){
[theScroller insertSubview:self.keyboardDismissalButton atIndex:0];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:UIKeyboardWillHideNotification object:nil queue:nil usingBlock:^(NSNotification *notification){
[self.keyboardDismissalButton removeFromSuperview];
}];

Is there a way to catch an WillRotateToInterfaceOrientation event from an UIView?

every UIViewController has a method called willRotateToInterface.
Is it possible to do this within a UIView too?
Does this match the idea of model view controller ?
The only way I can think of is to send the event from the UIViewController to the UIView.
Is there a global variable for the current orientation?
Observe UIDeviceOrientationDidChangeNotification:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(orientationDidChange:) name:UIDeviceOrientationDidChangeNotification object:nil];
...
- (void)orientationDidChange:(NSNotification *)note
{
NSLog(#"new orientation = %d", [[UIDevice currentDevice] orientation]);
}
UIDevice Class Reference
I should note that yo uneed to add -beginGeneratingDeviceOrientationNotifications when you want these notifications to be sent, and call -endGeneratingDeviceOrientationNotifications when you want them to stop. There is a battery impact of generating these, so you should only do so when your view is on screen. UIViewController does all this for you, so if you have a view controller, it is worth letting it do the work.
If you just want to adjust view to new size/layout when orientation changes, you should just override its layoutSubviews method.
It will be called whenever size of the view changes, which usually happens when view is rotated.
You can subscribe to a global notification and get a call when the device is rotated, it wont do anything for you though..
[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(didRotate:)
name:#"UIDeviceOrientationDidChangeNotification" object:nil];

iPhone SDK: handling keybaord appearance

I need to move UI elements of my view controller when keyboard appears. I do this by registering for the keyboard notifications in my app delegate:
[[NSNotificationCenter defaultCenter] addObserver:observer
selector:#selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:observer
selector:#selector(keyboardWasHidden:)
name:UIKeyboardDidHideNotification object:nil];
and then handling notification as prescribed by Apple (I have similar code for keyboard was shown) to scroll the view up and down:
- (void)keyboardWasHidden:(NSNotification*)aNotification
{
CGRect viewFrame = [self.view frame];
viewFrame.origin.y += keyboardSize.height - TOOLBAR_HEIGHT;
self.view.frame = viewFrame;
}
So far so good. Now problem description:
When I execute this code to show OS 3.0 specific message UI:
MFMailComposeViewController *picker = [[MFMailComposeViewController alloc] init];
picker.mailComposeDelegate = self;
[self presentModalViewController:picker animated:YES];
and when keyboard shows in the actual mail UI, I still get keyboard notification which scrolls my view and therefore breaking my UI (note that mail controller takes entire screen and my view is not even visible at this point).
I was hoping to temporary disable keyboard notification, so my scrolling code would not get called with this line:
[[NSNotificationCenter defaultCenter] removeObserver:self];
But it does not help, keyboard even still get posted.
What should I do avoid reacting on the keyboard when it created by the message UI?
Add a BOOL property or instance variable: careAboutKeyboard that's accessible to both your keyboardWasShown: and keywardWasHidden: methods, likely in the view controller those methods are in.
Have it set to YES when in the viewWillAppear method, and set to NO when you show the mail view and in viewWillDisappear.
Then put all of your scrolling logic in an if block:
if(careAboutKeyboard) {
// Scrolling logic
}

Activity indicator in a UITabBar application on the iPhone

I have a UITabBar + UINavigationController application which often needs data from the internet. Sometimes it takes quite a while before it gets it, so I would like to show an activity indicator.
What I was trying is to add a activityView to my window in my applicationDidFinishLaunching method:
[window addSubview:tabBarController.view];
fullscreenLoadingView.hidden = YES;
[window addSubview:fullscreenLoadingView];
And then I add the application delegate as a observer to the default notification center:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(startFullscreenLoading:) name:#"startFullscreenLoading" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(stopFullscreenLoading:) name:#"stopFullscreenLoading" object:nil];
and implement the methods:
- (void)startFullscreenLoading:(NSNotification *)notification {
fullscreenLoadingView.hidden = NO;
}
- (void)stopFullscreenLoading:(NSNotification *)notification {
fullscreenLoadingView.hidden = YES;
}
When I then use this directly in the applicationDidFinishLaunching method the loading indicator view shows upp as expected:
[[NSNotificationCenter defaultCenter] postNotificationName:#"startFullscreenLoading" object:self];
But when I use it from one of the navigation controllers the startFullscreenLoading: method is called but I don't see the loading indicator view. Why is that?
Are you loading your data on a background thread? Your main thread will be blocked & won't be able to redraw or process any other events.
The most likely thing I can guess is that your indicator view is below some other view. Try
[[fullScreenLoadingView superView] bringSubviewToFront:fullScreenLoadingView]
If that doesn't work, I would suggest breaking inside of -startFullscreenLoading to make sure the fullscreenLoadingView is still valid.
In my app I did this by adding the UIActivityView in IB and making sure it was above everything else (as a top-level object). It's set to be invisible when stopped.
Then in my code I make it appear with [activityIndicator startAnimating] and make it disappear with [activityIndicator stopAnimating];