In my quest for trying to write a simple Native iPhone app, I’ve come to realize that many things are just not done for you. Unlike iPhone WebApp development, native development requires you to handle so much more stuff, some of which i think is a pain.
After a fair amount of time looking through the various Alert, TextField, Responders and some information about view controllers, i’ve come to notice that not only does the iPhone SDK not hide the Keyboard for you (thus the resignfirstresponder stuff), it also doesn’t scroll views for you automatically. This is VERY unlike Webapp development where Safari pretty much handles this for you. So as a developer, why should you have to worry this? It just seems so fundamental…
Searching for information online gives a variety of answers to this problem. Some uses a variety of global variables, some uses very little, but the way the code behaves just doesn’t seem too logical for me. So i set myself out to try to define a set of rules such that once it is followed, it should pretty much always setup a mechanism to control the scrolling of the view. Editable fields should then ALWAYS be visible, and Keyboards should be tucked away when not required.
Here are the rules that i have setup.
- Always place a UIScrollView immediately after the View window IF there are editable fields (UITextField) in the window.
- Place all other items UNDER the UIScrollView
- Setup the UITextField such that its delegate belongs to File Owner. This is because we are going to create our own action method to listen to our text field when it s going to be edited.
Once these are done, more so in Interface Builder, the rest is just code. Do remember that i treat the SCREEN of the device as a VIEW; therefore, thats the only thing that i adjust.
In my Xcode example project, i only use properties on my class to track all that’s required for the scroll to work. In fact, keyboardShown might not even be required but i use that for good measure.
UITextField *activeField;
BOOL keyboardShown;
The part that tripped me over for the longest time was the method textFieldDidBeginEditing and textFieldDidBeginEditing , as it was not declared anywhere. Take note that this is called ONLY IF you have setup the textField delegate to be File Owner. You use these methods to setup the activeField variable so that you can keep track of which variable is being acted on.
Three notifications are setup so that we can detect and act on 3 points of time
- UIKeyboardDidShowNotification
- UIKeyboardWillHideNotification
- UIKeyboardDidHideNotification
We want the keyboard to “push” the main view upwards, so we act AFTER the keyboard shows. WillHide tells us when the keyboard is just about to be hidden, so we adjust the frame size of the view but once the keyboard DidHide, we animate the view such that the movement of the view would be smoother. All these notifications are unregistered if the view disappears.
scrollWindow.contentSize = [[UIScreen mainScreen] applicationFrame].size;
We also setup the default size of the content to be that of the applicationFrame size. Do take note that at this point, i haven’t painted anything OUTSIDE of the viewable size, so the applicationFrame size should contain everything required for the application.
Once everything is all setup, the rest is just some fancy math to get the correct new View size and adjusting that to the new scrollWindow Frame.
// Normal method of resigning the keyboard first responder - (IBAction) clearKeyboard:(id)sender { [input resignFirstResponder]; } // Adjust the viewable frame size when keyboard shows up // Scroll the activeField to be 10px above baseline and animate // Keyboard will appear before this action takes place to look smoother - (void) keyboardDidShow:(NSNotification *) notification { if (keyboardShown) return; CGSize keyboardSize = [self getKeyboardSize:notification]; // Determine size of new scrollWindow CGRect viewFrame = scrollWindow.frame; viewFrame.size.height -= keyboardSize.height; scrollWindow.frame = viewFrame; // Scroll active field to 10px above the keyboard CGRect activeFieldRect = activeField.frame; activeFieldRect.origin.y += 10; [scrollWindow scrollRectToVisible:activeFieldRect animated:YES]; keyboardShown = YES; } // Adjust viewable frame back to normal size - (void) keyboardWillHide:(NSNotification *) notification { if (!keyboardShown) return; CGSize keyboardSize = [self getKeyboardSize:notification]; // Determine size of new scrollWindow CGRect viewFrame = scrollWindow.frame; viewFrame.size.height += keyboardSize.height; scrollWindow.frame = viewFrame; } // Animate resize as soon as keyboard hides - (void) keyboardDidHide:(NSNotification *) notification { [scrollWindow scrollRectToVisible:CGRectMake(0, 0, 1, 1) animated:YES]; keyboardShown = NO; } // Delegates for handling textField editing as Textfield delegates are now assigned // to File Owner - (BOOL) textFieldDidBeginEditing:(UITextField *) sender { activeField = sender; return YES; } - (BOOL) textFieldDidEndEditing:(UITextField *) sender { return YES; } // Obtain the size of the keyboard - (CGSize) getKeyboardSize:(NSNotification *) notification { NSDictionary *userDict = [notification userInfo]; NSValue *keyboardValue = [userDict objectForKey:UIKeyboardBoundsUserInfoKey]; return [keyboardValue CGRectValue].size; }
The bulk of the code that makes everything works is shown above. As this method/mindset i have chosen only makes use of 2 global variables in the class, it makes it rather easy to use and interface to in other code. in my point of view, it makes this rather re-usable. This method also continues to make use of straightforward ways of resigning the keyboard responder by using a IBAction with a hidden button listening to the Touch Up Inside event. I believe that this makes the code more predictable and readable. Anyhow, i discovered this method somewhere and found it straightforward for a noob like me.
Hope this help anyone in the same situation as i was when trying to wrap my mind around this issue.
NOTE: UIKeyboardBoundsUserInfoKey seems to be deprecated in the newer SDKs. As i am still using the pre-iPad SDK, i can’t use it. However, as i have setup another getKeyboardSize method, this makes migrating the method of obtaining the keyboard size rather easy.