I wanted my UIView to animate to a new position when the keyboard was shown, so used the UIKeyboardWillShowNotification and UIKeyboardWillChangeFrameNotification. The problem is that when the device is rotated without the keyboard, the view has autoresizing and rotates as it should - it looks perfect.
Unfortunately, with the keyboard displayed, rotating the device sends those notifications and thus performing a UIView animation in response gives it an odd animation. It could best be described as looking like it jumps into a new position and is then anchored round by a corner to the new orientation. Perhaps you know what I'm talking about.
Is there any way for me to detect when the device is rotating or otherwise deal with the problem when rotating when the keyboard is being shown?
For orientation-change detection use the UIDeviceOrientationDidChangeNotification.
I guess the following code will help you..
#property(nonatomic,strong)BOOL keyBoardShow;
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillBeShown:)
name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillBeHidden:)
name:UIKeyboardWillHideNotification object:nil];
[super viewDidLoad];
}
-(void)keyboardWillBeShown:(NSNotification *)aNotification {
keyBoardShow = YES;
}
-(void)keyboardWillBeHidden:(NSNotification *)aNotification {
keyBoardShow = NO;
}
- (void)willRotateToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
if (keyBoardShow) {
// Do the needful when the keyboard is shown
}else{
}
}
Just set bool value in keyboardWillBeShown and keyboardWillBeHidden delegate methods. And do your uiview position in willRotateToInterfaceOrientation.
Instead of relying on the built-in notifications, why don't you instead rely on what's triggering the keyboard showing in the first place? I'm guessing the keyboard pops up when a particular UITextField becomes the first responder. You should be able to use that.
Register a delegate for that UITextField and implement -textFieldDidBeginEditing:. The delegate should be a view controller. Then, to keep things loosely coupled, post your own notification from the delegate and have your view registered for that.
It's a bit more work, but it gives you much better control.
- (void)textFieldDidBeginEditing:(UITextField *)textField
Apple documentation: UITextFieldDelegate Protocol
Related
In my iPad application, i am presenting a controller using form sheet style as
controller.modalPresentationStyle=UIModalPresentationFormSheet;
In landscape mode while device's keyboard open i m setting size of tableView so that user can able to see all records of table.
To get event of show/hide keyboard. I have set NSNotification
Problem
But when user tap in textField of table cell using external/virtual keyboard, i m not getting event of keyboard show/hide.
So when textfield becomes first responder, Tableview size is decreasing but it's no need while user connected with external keyboard.
Can anyone please guide/help here, what can i do? So that i can stop do set size when using external keyboard.
Register Keyboard Event
- (void)registerForKeyboardNotifications{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWasHidden:)
name:UIKeyboardDidHideNotification object:nil];
}
Set Frame While AutoRotate and Text Field Become First Responder
-(void)setFramesOfTable
{
CGRect rct=tableView.frame;
if(appDel.isThisIPad && ([[UIApplication sharedApplication] statusBarOrientation]==UIInterfaceOrientationLandscapeLeft || [[UIApplication sharedApplication] statusBarOrientation]==UIInterfaceOrientationLandscapeRight) && [selectedField isFirstResponder])
{
rct.size.height=400.0;
}
else
{
rct.size.height=576.0;
}
tableView.frame=rct;
}
- (void)textFieldDidBeginEditing:(UITextField *)textField{
selectedField = textField;
[self setFramesOfTable];
}
-(NSUInteger)supportedInterfaceOrientations
{
[self setFramesOfTable];
return UIInterfaceOrientationMaskAll;
}
Thanks.
Its not a good idea to change the frame of the table when the text field begins editing. On the iPad, the user can have an external keyboard, the docked keyboard or the split keyboard.
If the user has an external keyboard, you don't need to resize your window. The onscreen keyboard does not appear when using an external keyboard so there is no reason to resize windows.
If the user is using the split keyboard, you don't really need to worry about resizing windows. if they split the keyboard, they could put the keyboard in the middle of the UI, making it impossible (or at very least impractical) to rearrange your UI so its not covered by at least a small portion of the split keyboard. If the user splits the keyboard and covers up important UI components, they need to move the keyboard out of the way.
The best way to resize your UI is in the keyboard will ChangeFrame/Hide methods
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillChangeFrame:) name:UIKeyboardWillChangeFrameNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:UIKeyboardWillHideNotification object:nil];
inside your handlers of these events, you can get the keyboard height, and adjust the UI accordingly
-(void)keyboardWillChangeFrame:(NSNotification*)notification
{
NSDictionary* info = [notification userInfo];
NSValue* kbFrame = info[UIKeyboardFrameEndUserInfoKey];
NSTimeInterval animationDuration = [info[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
CGRect keyboardFrame = [kbFrame CGRectValue];
BOOL isPortrait = UIDeviceOrientationIsPortrait([UIApplication sharedApplication].statusBarOrientation);
CGFloat height = isPortrait ? keyboardFrame.size.height : keyboardFrame.size.width;
}
this gets you the animationDuration and the height of the keyboard so that you can use a UIView animateWithDuration block to animate the frame change to your tableview so that it is not obscured by the keyboard.
in keyboardWillHide: you only need to get the animationDuration (the same way as above) from the NSNotification (the height will obviously be 0). Then use another UIView animateWithDuration block to animate your tableview resizing back to its original size
I have one view window which I created in the interface builder. I created a UIScrollView which fills the entire window and dragged some other items into it, including a UITextView. The problem I encountered was that when I click to write into the TextView the keyboard blocks the view of the TextView, hence the use of a ScrollView.
Now I've searched around quite a bit and think I know what I need to do but if I'm doing it right is another matter.
I get the bounce, that is I can drag everything that's in the ScrollView and it will bounce back. When I then press to write in the TextView the keyboard pops up, this shrinks the ScrollView to "screen size" - "keyboard size" (I know this happens as I haven't implemented the "do this once I hide the keyboard" function yet, so when I hide the keyboard the ScrollView now ends where the keyboard started). But even though the view size is now smaller than the content size it does not scroll, simply continues to bounce.
Here below you can see the code I'm using. I call the registerForKeyboardNotifications in viewDidLoad.
// Call this method somewhere in your view controller setup code.
- (void)registerForKeyboardNotifications
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWasShown:)
name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillBeHidden:)
name:UIKeyboardWillHideNotification object:nil];
}
// Called when the UIKeyboardDidShowNotification is sent.
- (void)keyboardWasShown:(NSNotification *)n
{
NSLog(#"WoopWoopWoop");
NSDictionary* userInfo = [n userInfo];
// get the size of the keyboard
CGSize keyboardSize = [[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
// resize the noteView
CGRect viewFrame = self.mainScrollView.frame;
viewFrame.size.height -= (keyboardSize.height);
mainScrollView.bounces = YES;
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationBeginsFromCurrentState:YES];
[UIView setAnimationDuration:0.4];
[self.mainScrollView setFrame:viewFrame];
[UIView commitAnimations];
}
I've tried setting the content view to some arbitrary size such as
[mainScrollView setContentSize:CGSizeMake(3200.0,2300.0)];
but that has had no effect...
Any ideas?
Hi, you don't need to make use of notifications. Just make use of the UITextFieldDelegate protocol methods. You can set the content Offset for the scrollView in those methods.
For example:
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
//Check the necessary textfield and then change yValue accordingly
[scrollView setContentOffset:CGPointMake(0,yValue) animated:YES];
return YES;
}
when I click to write into the TextView the keyboard blocks the view of the TextView, hence the use of a ScrollView.
First, I would suggest that this use of UIScrollViews is not necessary (if you only have a UITextView to display. Also, remember that UITextView is a subclass of UIScrollView).
You can achieve repositioning and resizing of your UITextView by configuring it as a subview of a UIView instead. Enable autoresizesSubviews on your UIView and configure both views' springs and struts via the IB Inspector. The UIView container should take all the available space.
Using the same approach as described in your question, when the UIView is resized, it should automatically adjust the UITextView's frame as well.
Now, even if you do need to have a view hierarchy with a UIScrollView at the top, I would still suggest to wrap that into a plain UIView container, and configure autoresizing as I mentioned above.
Hope that helps!
In my app I have a text field on some view which is covered by the keyboard when it shows up. So I have to scroll the view (or even rearrange the subviews). To do this I:
register for keyboard notifications:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(moveViewUp)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(moveViewDown)
name:UIKeyboardWillHideNotification
object:nil];
upon receiving a notification, move the view using block animations like this:
- (void)moveViewUp {
void (^animations)(void) = nil;
oldViewFrame = self.view.frame;
animations = ^{
CGRect newViewFrame = oldViewFrame;
newViewFrame.origin.y -= kViewOffset;
self.view.frame = newViewFrame;
};
[UIView animateWithDuration:1.0
animations:animations];
}
- (void)moveViewDown {
void (^animations)(void) = nil;
animations = ^{
self.view.frame = oldViewFrame;
};
[UIView animateWithDuration:1.0
animations:animations];
}
This works fine, the view scrolls up and down, until I add some more animation. Specifically I'm adding a transition to a next view when the user taps a button:
- (IBAction)switchToNextView:(id)sender {
// [self presentModalViewController:nextViewController animated:YES];
[UIView transitionFromView:self.view
toView:self.nextView
duration:1.0
options:UIViewAnimationOptionTransitionFlipFromRight
completion:nil];
}
Now we got to the problem.
If the first view was shifted when the button was tapped (that means that the keyboard was visible), the transition to the next view starts simultaneously as the keyboard slides down, but the view itself doesn't move down, so for a split second we can actually see the underlying view. That's not right. When I present the next view modally (see the commented line) all animations go as I want them to: i.e. the keyboard is hiding, the view is flipping from right and scrolling down -- all at the same time. This would be fine, but the problem is that I actually don't have a UIViewController for that view. In fact I'm trying to simulate the modal behavior without UIViewController (why so? perhaps it's just a bad design, I'll post another question on that).
So why does in this case the animation from moveViewDown method is not triggered at the proper time?
Update 1
I added a debug print to each function to check the order of calling, this is what I get:
-[KeyboardAnimationViewController moveViewUp]
__-[KeyboardAnimationViewController moveViewUp]_block_invoke_1 <-- scroll up animation
-[KeyboardAnimationViewController switchToNextView:]
-[KeyboardAnimationViewController moveViewDown]
__-[KeyboardAnimationViewController moveViewDown]_block_invoke_1 <-- scroll down animation
Even if I explicitly move the view down before the transition like this
- (IBAction)switchToNextView:(id)sender {
// [self presentModalViewController:nextViewController animated:YES];
NSLog(#"%s", __PRETTY_FUNCTION__);
if (self.view.frame.origin.x < 0)
[self moveViewDown];
[UIView transitionFromView:self.view
toView:self.nextView
duration:1.0
options:UIViewAnimationOptionTransitionFlipFromRight
completion:nil];
}
I get exactly the same log.
Update 2
I've experimented some more and made following conclusions:
If I call moveViewDown or resignFirstResponder: explicitly, the animation is postponed until the end of current run loop, when all pending animations actually start to play. Though the animation block logs to the console immediately -- seems strange to me!
The method transitionFromView:toView:duration:options:completion: (perhaps transitionWithView:duration:options:animations:completion: too, didn't check this one) apparently makes a snapshot of the "from-view" and the "to-view" and creates an animation using these snapshots solely. Since the scrolling of the view is postponed, the snapshot is made when the view is still offset. The method somehow disregards even the UIViewAnimationOptionAllowAnimatedContent option.
I managed to get the desired effect using any of animateWithDuration: ... completion: methods. These methods seems to disregard transition options like UIViewAnimationOptionTransitionFlipFromRight.
The keyboard starts hiding (implicitly) and sends corresponding notification when removeFromSuperview is called.
Please correct me if I'm wrong somewhere.
If you are trying to simulate the modal behavior without UIViewController I guess you want your next view to show up from the bottom of the screen right?. correct me if I am wrong.
If you want such an animation you can try a work-around where you change the frame of the next view within an animation block such that it appears as if its similar to presentModalViewController
I have two instances of UIScrollView, and I want them to zoom at the same time.
Anyone have any experience doing that?
I'm using the NSNotificationCenter to tell my object when to zoom. Initially I thought I could somehow get at the currently visible rect, and just call zoomToRect:, but I don't see a way to do that. What I have now is setting the zoomScale and contentOffset properties. It looks like this:
- (void)registerForZoomNotifications {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveZoomNotification:)
name:ZOOM_NOTIFICATION_IDENTIFIER
object:nil];
}
- (void)receiveZoomNotification:(NSNotification*)notification {
UIScrollView *currentScrollView = (UIScrollView*)[notification object];
// zoomLevel
[(UIScrollView*)self.view setZoomScale:currentScrollView.zoomScale animated:NO];
// contentOffset
[(UIScrollView*)self.view setContentOffset:currentScrollView.contentOffset animated:NO];
}
#pragma mark -
#pragma mark UIScrollViewDelegate
- (void)scrollViewDidZoom:(UIScrollView *)pageScrollView {
[[NSNotificationCenter defaultCenter] postNotificationName:ZOOM_NOTIFICATION_IDENTIFIER object:pageScrollView];
}
It's not working though, and seems terribly erratic. Ideas anyone? Should I be taking a different approach?
EDIT: I should clarify that both scroll views are not visible at the same time. It's not important that they scroll at the EXACT same time, only that ones scroll view is at the same zoom level (and visible rect) as the other after scrolling completes.
An easier way is to implement the UIScrollViewDelegate Protocol for your UIView control which manage the 2 UIScrollView2
in your .h file just add in the #interface declaration
#interface yourUIViewControll : UIViewControll <UIScrollViewDelegate> {
UIScrollView *aUIScrollView;
UIScrollView *bUIScrollView;
}
this way now you can use all the methods you need when user scroll or zoom one of the 2 UIScrollView
so, for example, you wanna know when one is zooming or scrolling and wanna let the other too zoom and scroll you need these 2 in particular
in .m:
// called when a UIScrollView is zooming:
- (void)scrollViewDidZoom:(UIScrollView *)zoomViewInUse{
// just to test in log window:
// NSLog(#"changing zoom... scrollViewInUse.zoomScale: %.5f", zoomViewInUse.zoomScale);
//force both UIScrollViews to zoom at the new value
aUIScrollView.zoomScale = zoomViewInUse.zoomScale;
bUIScrollView.zoomScale = zoomViewInUse.zoomScale;
}
// called when a UIScrollView is scrolling:
- (void)scrollViewDidScroll:(UIScrollView *)scrollViewInUse{
// just to test in log window:
// NSLog(#"scrollViewInUse..contentOffset.x:%.1f", scrollViewInUse.contentOffset.y);
//force both UIScrollViews to scroll at the new value
aUIScrollView.contentOffset = scrollViewInUse.contentOffset;
bUIScrollView.contentOffset = scrollViewInUse.contentOffset;
}
The erratic behavior I witnessed was because I wasn't sending a notification when I scrolled, only zoomed. When I added the following additional delegate method, everything worked correctly:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[[NSNotificationCenter defaultCenter] postNotificationName:ZOOM_NOTIFICATION_IDENTIFIER object:scrollView];
}
How do I change or disable the rotating animation when screen orientation changes from landscape to portrait, or vice versa?
Yes, it is possible to disable the animation, without breaking everything apart.
The following codes will disable the "black box" rotation animation, without messing with other animations or orientation code:
- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation {
[UIView setAnimationsEnabled:YES];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
[UIView setAnimationsEnabled:NO];
/* Your original orientation booleans*/
}
Place it in your UIViewController and all should be well. Same method can be applied to any undesired animation in iOS.
Best of luck with your project.
If you dont want your view controllers to rotate just override the shouldAutoRotateToInterface view controller method to return false for whichever orientation you dont want to support...Here is a reference.
In the case that u just want to handle rotation some other way, you can return false in the above methods and register for UIDeviceOrientationDidChangeNotification like so
NSNotificationCenter* nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self
selector:#selector(handleOrientationDidChange:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
Now when u get the notifications u can do whatever you want with it...
The answer by #Nils Munch above is find for < iOS7. For iOS 7 or later you can use:
- (void) viewWillTransitionToSize:(CGSize)size withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
[UIView setAnimationsEnabled:NO];
[coordinator notifyWhenInteractionEndsUsingBlock:^(id<UIViewControllerTransitionCoordinatorContext> context) {
[UIView setAnimationsEnabled:YES];
}];
[super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
}