For an iPhone app with users entering their data into text boxes,
I need the page to scroll both when the text boxes are selected for user to input and when keyboard has not been opened for user to review what they entered.
When using the Interface Builder and overlaying the scroll view, I can't get it to stay or save so that the page is actually scrollable.
I would also prefer to do this programmatically. Other solutions like this have not worked when inserted into the .m file.
#property (weak, nonatomic) IBOutlet UIScrollView *topScrollView;
#synthesize topScrollView;
[topScrollView setFrame:CGRectMake(320, 0, 320, 65)];
[topScrollView setContentSize:CGSizeMake(500, 100)];
[topScrollView setBackgroundColor:[UIColor greenColor]];
[topScrollView setScrollEnabled:YES];
[topScrollView setShowsHorizontalScrollIndicator:YES];
[topScrollView setShowsVerticalScrollIndicator:NO];
[[self view] addSubview:topScrollView];
You can place a UIView with all your textfields on top of the main view and set NSNotifications for the keyboard and move the view around accordingly.
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardDidShowWithNotification:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardDidHideWithNotification:) name:UIKeyboardWillHideNotification object:nil];
#pragma mark NSNotifications
- (void)keyboardDidShowWithNotification:(NSNotification *)aNotification
{
const int movementDistance = ([UIScreen mainScreen].bounds.size.height == 568 ? 155 : 180);
[UIView animateWithDuration:0.3
delay:0
options:UIViewAnimationCurveEaseOut|UIViewAnimationOptionBeginFromCurrentState
animations:^{
CGPoint adjust = CGPointMake(0, -movementDistance);
CGPoint newCenter = CGPointMake(self.loginContainer.center.x+adjust.x, self.loginContainer.center.y+adjust.y);
[self.loginContainer setCenter:newCenter];
}
completion:nil];
}
- (void)keyboardDidHideWithNotification:(NSNotification *)aNotification
{
[UIView animateWithDuration:0.3
delay:0
options:UIViewAnimationCurveEaseOut|UIViewAnimationOptionBeginFromCurrentState
animations:^{
self.loginContainer.frame = CGRectMake(0, 0, self.loginContainer.frame.size.width, self.loginContainer.frame.size.height);
}
completion:nil];
}
Related
When the [sendTextField becomeFirstResponder], the keyboard will upspring, and self.view will upspring too.
I have a requirement , when [sendTextField becomeFirstResponder], the keyboard upspring, but self.view do not upspring?
EDIT:For more clear for my question, I add a picture:
My requirement is:
The tableview do not upspring and the textview's back upspring with keyboard popup.
EDIT:For Ajay Gabani's answer, I test in my ipnone-5s using TPKeyboardAvoiding's example , and I found this, the tableView also upspring with the keyboard, the picture will show:
The origin status:
The tableview upspring:
Sorry I am not able to comment on your question.
For your case "when [sendTextField becomeFirstResponder], the keyboard upspring, but self.view do not upspring?"
You can manage textfield within scrollview or tableview with keyboard appearance.
TPKeyboardAvoiding
Hope this might help you.
If you don't want to use 3rd party SDK
you can use observer and manage UIView frame programatically like this
- (void)viewDidLoad {
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillBeHidden:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)keyboardWillShow:(NSNotification*)aNotification {
[UIView animateWithDuration:0.25 animations:^
{
CGRect newFrame = [customView frame];
newFrame.origin.y -= [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; // Adjust your tableview position by taking keyboard height
[tableView setFrame:newFrame];
}completion:^(BOOL finished)
{
}];
}
- (void)keyboardWillBeHidden:(NSNotification*)aNotification {
[UIView animateWithDuration:0.25 animations:^
{
CGRect newFrame = [customView frame];
newFrame.origin.y += [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height; // // Adjust your tableview position
[tableView setFrame:newFrame];
}completion:^(BOOL finished)
{
}];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.view endEditing:YES];
}
I've made it so that when a user taps on a text field, the view shifts up slightly so that the fields are still visible while a user is typing. It works great, however sometimes after the keyboard is dismissed, instead of returning to its original position, the view slides down too far (leaving a small blank black bar at the top). Does anyone know how I can just restore the view back to its original position? Here's my code:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_userField.returnKeyType = UIReturnKeyDone;
[_userField setDelegate:self];
_passField.returnKeyType = UIReturnKeyDone;
[_passField setDelegate:self];
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(dismissKeyboard)];
[self.view addGestureRecognizer:tap];
}
- (void)dismissKeyboard
{
[_userField resignFirstResponder];
[_passField resignFirstResponder];
}
-(void)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
}
-(void)textFieldDidBeginEditing:(UITextField *)textField
{
[self animateTextField:textField up:YES];
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{
[self animateTextField:textField up:NO];
}
-(void)animateTextField:(UITextField*)textField up:(BOOL)up
{
const int movementDistance = -130; // tweak as needed
const float movementDuration = 0.3f; // tweak as needed
int movement = (up ? movementDistance : -movementDistance);
[UIView beginAnimations: #"animateTextField" context: nil];
[UIView setAnimationBeginsFromCurrentState: YES];
[UIView setAnimationDuration: movementDuration];
self.view.frame = CGRectOffset(self.view.frame, 0, movement);
[UIView commitAnimations];
}
I would suggest you to work with the keyboard notifications instead. That way, it would work even if you have more than one field, you will be able to animate the change along with the keyboard animation, and you will know exactly how much screen estate you are loosing due to the keyboard showing.
First, in your view controller, register and unregister for the notifications:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShowNotification:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHideNotification:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}
Then implements the two methods being called when the notifications are triggered:
- (void)keyboardWillShowNotification:(NSNotification *)notification {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:[notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
[UIView setAnimationCurve:(UIViewAnimationCurve)[notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]];
[UIView setAnimationBeginsFromCurrentState:YES];
CGRect keyboardFrame = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
[self.mainView adjustContentWithKeyboardHeight:keyboardFrame.size.height];
[UIView commitAnimations];
}
- (void)keyboardWillHideNotification:(NSNotification *)notification {
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:[notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue]];
[UIView setAnimationCurve:(UIViewAnimationCurve)[notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue]];
[UIView setAnimationBeginsFromCurrentState:YES];
[self.mainView adjustContentWithKeyboardHeight:0];
[UIView commitAnimations];
}
Finally, implement a adjustContentWithKeyboardHeight public method in your view. Since that method is called in a UIView animation, all the changes will be animated.
If you are moving the views directly (put them all in a container view that you will move), keep the original Y position in a private property, then subtract from that property the keyboardHeight minus the remaining space below your textField, and assign that back to that field (or set the frame to your liking):
CGFloat offsetY = keyboardHeight - (CGRectGetHeight(self.bounds) - CGRectGetMaxY(self.containerView.frame));
CGRect containerFrame = self.containerView.frame;
containerFrame.origin.y = self.containerViewPositionY - offsetY;
self.containerView.frame = containerFrame;
I am moving the frame of my UIView depending on the actual UIKeyboardState (Shown/Hidden).
Now I'd like to write a Unit Test (XCTest) for this. Basically I want to check the frame of the UIView whenever the keyboard is shown or not.
This is my code for moving the UIView around, the methods get triggered via a NSNotification which I am registering in the viewWillAppear :
- (void)keyboardWillShow:(NSNotification *)notification
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.3];
[self.view setFrame:CGRectMake(0, -kOFFSET_FOR_KEYBOARD, self.view.frame.size.width, self.view.frame.size.height)];
[UIView commitAnimations];
}
- (void)keyboardWillHide:(NSNotification *)notification
{
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.3];
[self.view setFrame:CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height)];
[UIView commitAnimations];
}
Any idea how the unit test would look like ? I am very new to unit testing which is why i'm asking.
Here is a basic test case to test this functionality. You should replace UIViewController with your VC class. It is also not recommended to call -viewWillAppear: directly, but in the case of this specific unit test it could be alright.
-(void)testKeyboardShown
{
UIViewController* controller = [[UIViewController alloc] init];
[controller viewWillAppear:YES];
[[NSNotificationCenter defaultCenter] postNotificationName:UIKeyboardWillShowNotification object:nil];
XCTAssertEqual(controller.view.frame.origin.y, -kOFFSET_FOR_KEYBOARD, "View should move up");
[[NSNotificationCenter defaultCenter] postNotificationName:UIKeyboardWillHideNotification object:nil];
XCTAssertEqual(controller.view.frame.origin.y, 0, "View should move down");
}
Bonus: The userInfo dictionary of UIKeyboardWillShowNotification includes a property that tells you the height of the keyboard; you could use this value instead of hardcoding your own offset. It also includes values for animation duration and timing curve, so your animation could more correctly follow that of the keyboard rather than hardcoding 0.3 seconds.
EDIT
To test the dynamic keyboard height, you would need to pass a userInfo dictionary with the UIKeyboardWillShowNotification that includes a fake frame for the keyboard:
CGRect keyboardFrame = CGRectMake(0, 0, 0, 20);
[[NSNotificationCenter defaultCenter] postNotificationName:UIKeyboardWillShowNotification object:nil userInfo:#{ UIKeyboardFrameBeginUserInfoKey : [NSValue valueWithCGRect:keyboardFrame] }];
XCTAssertEqual(controller.view.frame.origin.y, -keyboardFrame.size.height, "View should move up");
I would like to have a keyboard with a non-transparent keyboard - I couldn't get this with any of the supported UIKeyboardTypes. Is there another way around this?
I suppose I could just overlay a background view under the keyboard with the color I want - would there be a good way to animate that background view in sync with the keyboard show animation?
The keyboard in iOS7 is translucent when app is compiled in Xcode 5 with a Base SDK of iOS 7.
If you build the app on Xcode 4.6.x instead, you'll have the non-translucent keyboard as before.
(i know this is a shitty fix but nonetheless, i thought i'd suggest it)
anyways, you could alternatively try making use of the default keyboard notifications:
UIKeyboardWillShowNotification
UIKeyboardWillHideNotification
should go something like:
-(void)viewWillAppear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow:)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillHide:)
name:UIKeyboardWillHideNotification
object:nil];
}
-(void)viewWillDisappear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillHideNotification
object:nil];
}
-(void)keyboardWillShow:(NSNotification *)note
{
/*
Would have used:
CGRect rectStart = [note.userInfo[UIKeyboardFrameBeginUserInfoKey] CGRectValue];
CGRect rectEnd = [note.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue];
Reason for not using:
The above two keys are not being used although, ideally, they should have been
since they seem to be buggy when app is in landscape mode
Resolution:
Using the deprecated UIKeyboardBoundsUserInfoKey since it works more efficiently
*/
CGRect rectStart_PROPER = [note.userInfo[UIKeyboardBoundsUserInfoKey] CGRectValue];
rectStart_PROPER.origin.y = self.view.frame.size.height;
UIView *vwUnderlay = [self.view viewWithTag:8080];
if (vwUnderlay) {
[vwUnderlay removeFromSuperview];
}
vwUnderlay = [[UIView alloc] init];
[vwUnderlay setFrame:rectStart_PROPER];
[vwUnderlay setBackgroundColor:[UIColor orangeColor]];
[vwUnderlay setTag:8080];
[self.view addSubview:vwUnderlay];
[UIView animateWithDuration:[note.userInfo[UIKeyboardAnimationDurationUserInfoKey] floatValue]
delay:0
options:[note.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue] << 16
animations:^{
[vwUnderlay setFrame:CGRectOffset(vwUnderlay.frame, 0, -vwUnderlay.frame.size.height)];
}
completion:nil];
}
-(void)keyboardWillHide:(NSNotification *)note
{
UIView *vwUnderlay = [self.view viewWithTag:8080];
[UIView animateWithDuration:[note.userInfo[UIKeyboardAnimationDurationUserInfoKey] floatValue]
delay:0
options:[note.userInfo[UIKeyboardAnimationCurveUserInfoKey] integerValue] << 16
animations:^{
[vwUnderlay setFrame:CGRectOffset(vwUnderlay.frame, 0, vwUnderlay.frame.size.height)];
}
completion:^(BOOL finished){
[vwUnderlay removeFromSuperview];
}];
}
Apple does not allow anyone to modify the default keyboard. If you are going to be using iOS 7 then you will have to deal with the translucent keyboard.
The only other way I can think of is designing your own keyboard, but that's a tedious process to go through.
I was looking into the same thing today, and I found a simple workaround (although, not yet sure how reliable it is).
In order to work, I've set up an inputAccessoryView for my keyboard controls (UITextView and UITextField). In the UIView class that I've set as the inputAccessoryView I added the followings:
-(void)layoutSubviews{
[super layoutSubviews];
frame = _lKeyboardBackground.frame;
frame.origin.y = [self convertPoint:self.frame.origin toView:self.superview].y+self.frame.size.height;
frame.size.width = self.bounds.size.width;
frame.origin.x = 0;
frame.size.height = 500;
_lKeyboardBackground.frame = frame;
[self refreshKeyboardBackground];
}
-(void)didMoveToSuperview{
[super didMoveToSuperview];
if (self.superview) {
[self.superview.layer insertSublayer:_lKeyboardBackground atIndex:lMagicLayerIndex];
}
else {
[_lKeyboardBackground removeFromSuperlayer];
}
}
-(void)setFrame:(CGRect)frame{
[super setFrame:frame];
[self refreshKeyboardBackground]; // setFrame: is called when keyboard changes (e.g: to a custom input view)
}
-(void)refreshKeyboardBackground{
if (_lKeyboardBackground.superlayer) {
CALayer *parent = _lKeyboardBackground.superlayer;
[_lKeyboardBackground removeFromSuperlayer];
[parent insertSublayer:_lKeyboardBackground atIndex:lMagicLayerIndex];
}
}
_lKeyboardBackground is a CALayer that I've setup in the init methods:
_lKeyboardBackground = [CALayer layer];
_lKeyboardBackground.backgroundColor = [[UIColor redColor] CGColor]; //or some less annoying color
This should theoretically pass Apple's approval, but I never tested. There's also plenty of changes this will not work in future versions, or in some other situations (e.g when there's a split keyboard on the iPad)
lMagicLayerIndex that I use is 1, which gives the absolute color.
Note that the blur can still be noticed on key-strokes.
Use keyboard notification to show/hide a custom black view behind the keyboard (or white if you use the white keyboard) and voila, transparent no more.
I have a toolbar which I need to use when editing text, and when not.
In previous apps, I've moved the toolbar manually (listening for notifications, etc.)
But I want to use inputAccessoryView... so in my viewDidLoad, I do
for (/*myTextFields*/) {
textField.inputAccessoryView = keyboardToolbar;
}
[self.view addSubView:keyboardToolbar];
Which works fine, the toolbar appears, I click on a text field, toolbar slides up - all good.
But when I then hide the keyboard, inputAccessoryView drags my toolbar off the screen. Is there any way to tell the inputAcessoryView where it's fixed location is? - Or do I have to go back to my old way of listening for notifications etc...?
I solved this by listening for Notifications and moving the toolbar up... oh well.
Something like this does the job:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
/* Listen for keyboard */
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
}
- (void)keyboardWillShow:(NSNotification *)notification
{
[keyboardToolbar setItems:itemSetFull animated:YES];
/* Move the toolbar to above the keyboard */
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.3];
CGRect frame = self.keyboardToolbar.frame;
frame.origin.y = self.view.frame.size.height - 210.0;
self.keyboardToolbar.frame = frame;
[UIView commitAnimations];
}
- (void)keyboardWillHide:(NSNotification *)notification
{
[keyboardToolbar setItems:itemSetSmall animated:YES];
/* Move the toolbar back to bottom of the screen */
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.3];
CGRect frame = self.keyboardToolbar.frame;
frame.origin.y = self.view.frame.size.height - frame.size.height;
self.keyboardToolbar.frame = frame;
[UIView commitAnimations];
}
I guess input accessory view is literally just meant for something stuck on top of the keyboard :)
I've figured it out recently, and it seems few people have. So, I would like to direct you to this answer: https://stackoverflow.com/a/24855095/299711, which I will just copy below:
Assign your UIToolbar to a property in your view controller:
#property (strong, nonatomic) UIToolbar *inputAccessoryToolbar;
In your top view controller, add these methods:
- (BOOL)canBecomeFirstResponder{
return YES;
}
- (UIView *)inputAccessoryView{
return self.inputAccessoryToolbar;
}
And then (optionally, as it usually shouldn't be necessary), whenever the keyboard gets hidden, just call:
[self becomeFirstResponder];
That way, your inputAccessoryToolbar will be both your view controller's and your text view's input accessory view.