UIWebView scrolling down on input focus iOS 6 only - objective-c

I'm loading a UIWebView in a modal. The web page has some inputs that work exactly as intended in iOS 5. However in iOS 6, anytime an input gets focus, the keyboard is doing it's automatic 'centering' of the form even though the inputs have plenty of room to be shown above the keyboard. Since some of the inputs are at the top of the page, this forces them out of view so the user cannot see what they are typing. Scrolling back up to see the inputs causes the keyboard to stop working until dismissed and refocusing the input (in turn scrolling it out of view again). Is this an expected behavior in iOS 6? Is there any way to keep the webview from scrolling when an input gains focus (like how iOS 5 works)?

Without knowing the inner workings of your code, I'm going to try to help as much as I can. If nothing else, I hope to provide some food for thought. You asked two questions:
Is it expected behavior in iOS6?
What you're seeing is quite odd for sure. It's strange that the webView is centering the form rather than centering the input field. And it should definitely not cause the active input field to scroll out of view. Furthermore, the keyboard seems to stop working, which is quite odd. However, it is expected to see different behavior in iOS 5 and 6 with regards to the webView scrolling. As you said, iOS 5 scrolls the inputField into view while iOS6 puts it in center.
Is there any way to keep the webview from scrolling when an input gains focus (like how iOS 5 works)?
Yes. I provide code to do just this below -- namely to stop the webView from scrolling. If that is exactly what you want to do, then great. However, stopping the webView from scrolling is not the same as getting the same behavior as iOS5. Still, I wanted to give you this option as you requested it.
#import "ViewController.h"
#interface ViewController () <UIScrollViewDelegate> {
CGPoint origin;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:#"http://www.google.com"]];
[self.webView loadRequest:request];
self.webView.scrollView.delegate = self;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow)
name:UIKeyboardWillShowNotification
object:nil];
// NOTE THAT SIMPLY DISABLING THE SCROLL WILL NOT PREVENT KEYBOARD SCROLL
//[self.webView.scrollView setScrollEnabled:NO];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)keyboardWillShow {
NSLog(#"keyboard");
origin = self.webView.scrollView.contentOffset;
}
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
NSLog(#"scroll");
[self.webView.scrollView setContentOffset:origin];
}
#end
If you want consistent scroll behavior...
I took some time to code up this scroll modification. You can use it as a reference in making your own scroll behavior that is consistent across iOS 5&6. In this code: When the user clicks on a text input box on a webpage, the code will stop the default scrolling behavior by keeping the page scrolled to its current position. Unfortunately, there is no way to cancel all scrolling, but rather you have to override scrolls. Once the default scrolling has been "suppressed", it gets the position of the active input box and scrolls to that position. This will put the active input box in the top of the screen. You can modify this according to your preferences. For example, you can use [UIScrollView scrollRectToVisible] to make it more like iOS5.
#import "ViewController.h"
#interface ViewController () <UIScrollViewDelegate> {
CGPoint origin;
CGPoint activeElementOrigin;
BOOL pauseScroll;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:#"http://www.google.com"]];
[self.webView loadRequest:request];
self.webView.scrollView.delegate = self;
pauseScroll = NO;
origin = CGPointZero;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillShow)
name:UIKeyboardWillShowNotification
object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardDidShow)
name:UIKeyboardDidShowNotification
object:nil];
}
- (void)keyboardWillShow {
NSLog(#"keyboard");
pauseScroll = YES;
origin = self.webView.scrollView.contentOffset;
}
- (void)keyboardDidShow {
NSLog(#"keyboard DID SHOW");
pauseScroll = NO;
[self.webView.scrollView setContentOffset:activeElementOrigin animated:YES];
}
-(void)scrollViewDidScroll:(UIScrollView *)scrollView {
NSLog(#"scroll");
if (pauseScroll) {
[self.webView.scrollView setContentOffset:origin animated:NO];
NSString *javaScript = [NSString stringWithFormat:#"function f(){ var textField = document.activeElement; return textField.getBoundingClientRect().top; } f();"];
NSString *textFieldRectTop = [self.webView stringByEvaluatingJavaScriptFromString:javaScript];
activeElementOrigin = origin;
activeElementOrigin.y = [textFieldRectTop floatValue]-10;
}
}
#end
If this code helped you, it would be very nice of you to reward the bounty to me.
I would be humbly appreciative.

A workaround could be disabling the scroll in this UIWebView :
UIScrollView *scrollView = webView.scrollView;
[scrollview setScrollEnabled:NO];
[scrollView bounces:NO];
Another could be setting the UIEdgeInsets of this scrollView but I don't know much about this.
I think this is a standard behaviour in both iOS 5 and 6 to scroll to make the input field just on top of te keyboard (just like Safari does).

The checked answer on this post has your answer:
How to make a UITextField move up when keyboard is present?
In short, you need to move up the view containing the inputs which are being pushed out of view. The post also contains sample code on how to do it.
Hope this helps.

Related

iOS7 Autoresize When Switching Views Programmatically

I have googled for a few hours with no luck, so I'm coming to you guys to save me here!
Apparently, I can't find the right information on how exactly to go about doing this (or the best way). I have an app that supports portrait and landscape (no support for upside down though). However, the portrait and landscape views are COMPLETELY different, so I'll need to use two views to represent each. Am I correct in assuming I need 3 viewcontrollers in my storyboard (the main one, and then one for portrait and one for landscape? I was going to use just two but I didn't see how to if I start with the portrait, and then need to load landscape, I would have to delete portrait, which is where my code is?
My viewcontroller has the correct constraints in place to keep the label top center, but when replacing or swapping the views programmatically, it seems the auto-resize doesn't get called. I finally fixed this by resetting the frames on the subviews, but now when the device is flipped upside down, the portrait label is forever shifted to the right. So I'd just like to know the proper way to do this, as I'm sure this can't be it.
As far as code, I have one obj-c viewcontroller class with the following modified methods...
#interface AMBViewController ()
#property (strong, nonatomic) UIViewController *portraitViewController;
#property (strong, nonatomic) UIViewController *landscapeViewController;
#end
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
UIApplication *app = [UIApplication sharedApplication];
UIInterfaceOrientation currentOrientation = app.statusBarOrientation;
[self doLayoutForOrientation:currentOrientation];
}
-(void) willAnimateRotationToInterfaceOrientation:
(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration {
[self doLayoutForOrientation:toInterfaceOrientation];
}
-(void) doLayoutForOrientation:(UIInterfaceOrientation)orientation {
if (UIInterfaceOrientationIsPortrait(orientation)) {
self.portraitViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"Portrait"];
if (self.landscapeViewController != nil ) {
[self.landscapeViewController.view removeFromSuperview];
self.landscapeViewController = nil;
}
self.portraitViewController.view.frame = self.view.bounds;
[self.view insertSubview:self.portraitViewController.view atIndex:0];
} else {
self.landscapeViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"Landscape"];
if (self.portraitViewController != nil ) {
[self.portraitViewController.view removeFromSuperview];
self.portraitViewController = nil;
}
self.landscapeViewController.view.frame = self.view.bounds;
[self.view insertSubview:self.landscapeViewController.view atIndex:0];
}
}
Just to be clear on my storyboard, I have one blank root controller (subclass AMBViewController) and two other view controllers "Landscape" and "Portrait"
I might also mention that the label only gets off in portrait view IF you rotate the device in a full circle (4 right or 4 left rotations). If you go right right (now it's upside down) but then left left, it's still fine. It's only when the screen flips from Right/Left Landscape to Left/Right Landscape that it messes up. Really weird, I know I must be omitting something important.
Any help is greatly appreciated. Thanks!
Solution: After finding a guide located on Apple's Dev Site (finally) I was able to come up with a solution using segues and a modal window. First view controller is portrait, second view controller is landscape, connected by a modal segue. The first view controller has the following modified methods:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
_isShowingLandscapeView = NO;
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(orientationChanged:)
name:UIDeviceOrientationDidChangeNotification
object:nil];
}
-(void) orientationChanged:(NSNotification *)notification {
UIDeviceOrientation orientation = [UIDevice currentDevice].orientation;
if (UIInterfaceOrientationIsPortrait(orientation) && _isShowingLandscapeView
&& orientation != UIInterfaceOrientationPortraitUpsideDown) {
[self dismissViewControllerAnimated:NO completion:nil];
_isShowingLandscapeView = NO;
} else if (UIInterfaceOrientationIsLandscape(orientation) && !_isShowingLandscapeView ) {
[self performSegueWithIdentifier:#"ShowLandscape" sender:self];
_isShowingLandscapeView = YES;
}
}
Thanks to all who might have looked into this!

MPMoviePlayerController Overlay iOS 6

Having an issue with MPMoviePLayerController with an overlay in iOS6, prior to iOS6 things were working fine.
It seems I can play a movie in full screen, before I had this code:
#interface MovieOverlayViewController : UIViewController
{
UIImageView *skiparrow;
}
#end
#implementation MovieOverlayViewController
-(void) viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
touchtoskip.frame = CGRectMake( xAdjust,
yAdjust,
touchtoskip.image.size.width / scale,
touchtoskip.image.size.height / scale);
[self.view addSubview:touchtoskip];
}
Then:
overlay = [[MovieOverlayViewController alloc] init];
UIWindow *keyWnd = [[UIApplication sharedApplication] keyWindow];
[keyWnd addSubview:overlay.view];
On my MoviePlayerViewController the view DOES appear. And adds the UIViews, but I see NOTHING anymore.
Really stuck, any suggestions?
I think part of the problem is that you are getting the key window and adding a subview to that, rather than getting the window's view and adding a subview to that.
Have a look at the MoviePlayer sample, which shows how to add a player with subviews to control playback.

UIKeyboard avoidance and Auto Layout

Given the focus on Auto Layout in iOS 6, and the recommendation by Apple engineers (see WWDC 2012 videos) that we no longer manipulate a views' frame directly, how would one go about avoiding the keyboard using only Auto Layout and NSLayoutConstraint?
Update
This looks like a reasonable solution: An example of keyboard sensitive layout (GitHub source) but one potential issue I see is what happens when a user rotates the device and the keyboard is already on screen?
That blog post is great, but I'd like to suggest some improvements to it. First, you can register to observe frame changes, so you don't need to register to observe both show and hide notifications. Second, you should convert the CGRects for the keyboard from screen to view coordinates. Last, you can copy the exact animation curve used by iOS for the keyboard itself, so the keyboard and the tracking views move in synchrony.
Putting it all together, you get the following:
#interface MyViewController ()
// This IBOutlet holds a reference to the bottom vertical spacer
// constraint that positions the "tracking view",i.e., the view that
// we want to track the vertical motion of the keyboard
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *bottomVerticalSpacerConstraint;
#end
#implementation MyViewController
-(void)viewDidLoad
{
[super viewDidLoad];
// register for notifications about the keyboard changing frame
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(keyboardWillChangeFrame:)
name:UIKeyboardWillChangeFrameNotification
object:self.view.window];
}
-(void)keyboardWillChangeFrame:(NSNotification*)notification
{
NSDictionary * userInfo = notification.userInfo;
UIViewAnimationCurve animationCurve = [userInfo[UIKeyboardAnimationCurveUserInfoKey] intValue];
NSTimeInterval duration = [userInfo[UIKeyboardAnimationDurationUserInfoKey] doubleValue];
// convert the keyboard's CGRect from screen coords to view coords
CGRect kbEndFrame = [self.view convertRect:[[userInfo objectForKey:UIKeyboardFrameEndUserInfoKey] CGRectValue]
fromView:self.view.window];
CGRect kbBeginFrame = [self.view convertRect:[[userInfo objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue]
fromView:self.view.window];
CGFloat deltaKeyBoardOrigin = kbEndFrame.origin.y - kbBeginFrame.origin.y;
// update the constant factor of the constraint governing your tracking view
self.bottomVerticalSpacerConstraint.constant -= deltaKeyBoardOrigin;
// tell the constraint solver it needs to re-solve other constraints.
[self.view setNeedsUpdateConstraints];
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:duration];
[UIView setAnimationCurve:animationCurve];
[UIView setAnimationBeginsFromCurrentState:YES];
// within this animation block, force the layout engine to apply
// the new layout changes immediately, so that we
// animate to that new layout. We need to use old-style
// UIView animations to pass the curve type.
[self.view layoutIfNeeded];
[UIView commitAnimations];
}
-(void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self
name:UIKeyboardWillChangeFrameNotification
object:nil];
}
#end
This will work, as long as you don't change orientation while the keyboard is up.
It was an answer on How to mimic Keyboard animation on iOS 7 to add "Done" button to numeric keyboard? showed how to mimic the keyboard animation curve correctly.
One last thing to beware of with respect to all these notification-based solutions: they can produce unexpected effects if some other screen in your app also uses the keyboard, because your view controller will still receive the notifications as long as it has not been deallocated, even if it's views are unloaded. One remedy for this is to put a conditional in the notification handler to ensure it only operates when the view controller is on screen.
Using the KeyboardLayoutConstraint in the Spring framework is the simplest solution I've found so far.
My idea is to create a UIView, let's call it keyboard view, and place it to your view controller's view. Then observe keyboard frame change notifications UIKeyboardDidChangeFrameNotification and match the frame of the keyboard to the keyboard view (I recommend to animate the change). Observing this notification handles the rotation you mentioned and also moving keyboard on iPad.
Then simply create your constraints relative to this keyboard view. Don't forget to add the constraint to their common superview.
To get the keyboard frame correctly translated and rotated to your view coordinates check out the docs for UIKeyboardFrameEndUserInfoKey.
I created a view like this that would watch the keyboard and change its own constraints when the keyboard comes on/off the screen.
#interface YMKeyboardLayoutHelperView ()
#property (nonatomic) CGFloat desiredHeight;
#property (nonatomic) CGFloat duration;
#end
#implementation YMKeyboardLayoutHelperView
- (id)init
{
self = [super init];
if (self) {
self.translatesAutoresizingMaskIntoConstraints = NO;
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillShow:) name:#"UIKeyboardWillShowNotification" object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(keyboardWillHide:) name:#"UIKeyboardWillHideNotification" object:nil];
}
return self;
}
- (void)keyboardWillShow:(NSNotification *)notification
{
// Save the height of keyboard and animation duration
NSDictionary *userInfo = [notification userInfo];
CGRect keyboardRect = [userInfo[#"UIKeyboardBoundsUserInfoKey"] CGRectValue];
self.desiredHeight = CGRectGetHeight(keyboardRect);
self.duration = [userInfo[#"UIKeyboardAnimationDurationUserInfoKey"] floatValue];
[self setNeedsUpdateConstraints];
}
- (void)keyboardWillHide:(NSNotification *)notification
{
// Reset the desired height (keep the duration)
self.desiredHeight = 0.0f;
[self setNeedsUpdateConstraints];
}
- (void)updateConstraints
{
[super updateConstraints];
// Remove old constraints
if ([self.constraints count]) {
[self removeConstraints:self.constraints];
}
// Add new constraint with desired height
NSString *constraintFormat = [NSString stringWithFormat:#"V:[self(%f)]", self.desiredHeight];
[self addVisualConstraints:constraintFormat views:#{#"self": self}];
// Animate transition
[UIView animateWithDuration:self.duration animations:^{
[self.superview layoutIfNeeded];
}];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
#end
Ive written a library that will do it all for you (supports Auto Layout and Springs & Struts)
IHKeyboardAvoiding https://github.com/IdleHandsApps/IHKeyboardAvoiding
Just call [IHKeyboardAvoiding setAvoidingView:self.myView];
For auto layout with keyboard case, I use static table view. This keeps your codes much simpler and not need to keep track of keyboard height. One thing I learned about table view is to keep each table row as narrow as possible. If you put too many UIs vertically in one row, you may get keyboard overlap.

MPMoviePlayerViewController doesn't show well when playing video

I trying to show video on my App. The App hides the iPhone top panel. The player seems to work fine. There is just one annoying problem: when the player shows the video, it sometimes show the top panel and sometime hides it. When it is hidden, the video player panel is pushed a little (the same size of the panel that used to be there). Is is Apple bug? Am I doing something wrong?
Here is my code:
- (void) showFullscreenMediaWithURL: (NSURL *) mediaURL
{
MPMoviePlayerViewController *ctrl = [[MPMoviePlayerViewController alloc] initWithContentURL: mediaURL];
[[NSNotificationCenter defaultCenter] addObserver:self selector: #selector(playbackDidFinish:) name:MPMoviePlayerPlaybackDidFinishNotification object:ctrl.moviePlayer];
ctrl.moviePlayer.movieSourceType = MPMovieSourceTypeFile;
ctrl.modalTransitionStyle = UIModalTransitionStyleCrossDissolve;
[ctrl setWantsFullScreenLayout:YES];
[self presentMoviePlayerViewControllerAnimated:ctrl];
[ctrl release];
}
-(void) playbackDidFinish:(NSNotification*)aNotification
{
NSLog(#"Finished playback");
MPMoviePlayerController *player = [aNotification object];
[[NSNotificationCenter defaultCenter] removeObserver:self name:MPMoviePlayerPlaybackDidFinishNotification
object:player];
[player stop];
[self dismissMoviePlayerViewControllerAnimated];
[[captureManager session] startRunning];
}
if by iPhone top panel you mean the iPhone Status bar, then the solution should be simple.
Just before present/dismissMoviePlayerViewControllerAnimated add the following:
// Hide Status Bar
[[UIApplication sharedApplication] setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
// Show Status Bar
[[UIApplication sharedApplication] setStatusBarHidden:NO withAnimation:UIStatusBarAnimationNone];
Update: I can see what seems to be your problem.
First, the upper bar with the network indication icons and other information is the status bar (and nothing else). Your problem seems to be more ViewController related then a MediaPlayer. In other words, If you would have try to "push" some other ViewController to full screen (as the player is) you would have experience the exact same issue.
Second, the proper way, or I might say: my preferred way, of loading a view controller to full screen is the following:
Setup a full screen rootViewController which will be loaded on applicationDidFinishLaunchingWithOptions on your appDelegate.
On the rootViewController init put your default viewController (the one you used to load from appDelegate). Make sure that the rootViewController.view's frame is filling the screen.
Create 2 messages on rootViewController: LoadFullscreen:viewController and dismissFullscreen using present/dismissModelViewController. the setStatusBarHidden messages should be called from here.
To Lunch the player on full screen, create the player viewController and perform [rootViewController LoadFullscreen:player];
There are some other ways, but generally, this is the best practice and the method I recommend. It's relatively "a lot of code" to implement, thats why I couldn't help you with code snippers, but the general idea is relatively simple.
I hope that's help, E.G :)

Easy way to dismiss keyboard?

I have quite a few controls scattered throughout many table cells in my table, and I was wondering if there's an easier way to dismiss the keyboard without having to loop through all my controls and resigning them all as the first responder. I guess the question is.. How would I get the current first responder to the keyboard?
Try:
[self.view endEditing:YES];
You can force the currently-editing view to resign its first responder status with [view endEditing:YES]. This hides the keyboard.
Unlike -[UIResponder resignFirstResponder], -[UIView endEditing:] will search through subviews to find the current first responder. So you can send it to your top-level view (e.g. self.view in a UIViewController) and it will do the right thing.
(This answer previously included a couple of other solutions, which also worked but were more complicated than is necessary. I've removed them to avoid confusion.)
You can send a nil targeted action to the application, it'll resign first responder at any time without having to worry about which view currently has first responder status.
Objective-C:
[[UIApplication sharedApplication] sendAction:#selector(resignFirstResponder) to:nil from:nil forEvent:nil];
Swift 3.0:
UIApplication.shared.sendAction(#selector(resignFirstResponder), to: nil, from: nil, for: nil)
Nil targeted actions are common on Mac OS X for menu commands, and here's a use for them on iOS.
To be honest, I'm not crazy about any of the solutions proposed here. I did find a nice way to use a TapGestureRecognizer that I think gets to the heart of your problem: When you click on anything besides the keyboard, dismiss the keyboard.
In viewDidLoad, register to receive keyboard notifications and create a UITapGestureRecognizer:
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(keyboardWillShow:) name:
UIKeyboardWillShowNotification object:nil];
[nc addObserver:self selector:#selector(keyboardWillHide:) name:
UIKeyboardWillHideNotification object:nil];
tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(didTapAnywhere:)];
Add the keyboard show/hide responders. There you add and remove the TapGestureRecognizer to the UIView that should dismiss the keyboard when tapped. Note: You do not have to add it to all of the sub-views or controls.
-(void) keyboardWillShow:(NSNotification *) note {
[self.view addGestureRecognizer:tapRecognizer];
}
-(void) keyboardWillHide:(NSNotification *) note
{
[self.view removeGestureRecognizer:tapRecognizer];
}
The TapGestureRecognizer will call your function when it gets a tap and you can dismiss the keyboard like this:
-(void)didTapAnywhere: (UITapGestureRecognizer*) recognizer {
[textField resignFirstResponder];
}
The nice thing about this solution is that it only filters for Taps, not swipes. So if you have scrolling content above the keyboard, swipes will still scroll and leave the keyboard displayed. By removing the gesture recognizer after the keyboard is gone, future taps on your view get handled normally.
This is a solution to make the keyboard go away when hit return in any textfield, by adding code in one place (so don't have to add a handler for each textfield):
consider this scenario:
i have a viewcontroller with two textfields (username and password).
and the viewcontroller implements UITextFieldDelegate protocol
i do this in viewDidLoad
- (void)viewDidLoad
{
[super viewDidLoad];
username.delegate = self;
password.delegate = self;
}
and the viewcontroller implements the optional method as
- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
[textField resignFirstResponder];
return YES;
}
and irrespective of the textfield you are in, as soon as i hit return in the keyboard, it gets dismissed!
In your case, the same would work as long as you set all the textfield's delegate to self and implement textFieldShouldReturn
A better approach is to have something "steal" first responder status.
Since UIApplication is a subclass of UIResponder, you could try:
[[UIApplication sharedApplication] becomeFirstResponder]
[[UIApplication sharedApplication] resignFirstResponder]
Failing that, create a new UITextField with a zero sized frame, add it to a view somewhere and do something similar (become followed by resign).
Tuck this away in some utility class.
+ (void)dismissKeyboard {
[self globalResignFirstResponder];
}
+ (void) globalResignFirstResponder {
UIWindow * window = [[UIApplication sharedApplication] keyWindow];
for (UIView * view in [window subviews]){
[self globalResignFirstResponderRec:view];
}
}
+ (void) globalResignFirstResponderRec:(UIView*) view {
if ([view respondsToSelector:#selector(resignFirstResponder)]){
[view resignFirstResponder];
}
for (UIView * subview in [view subviews]){
[self globalResignFirstResponderRec:subview];
}
}
#Nicholas Riley & #Kendall Helmstetter Geln & #cannyboy:
Absolutely brilliant!
Thank you.
Considering your advice and the advice of others in this thread, this is what I've done:
What it looks like when used:
[[self appDelegate] dismissKeyboard]; (note: I added appDelegate as an addition to NSObject so I can use anywhere on anything)
What it looks like under the hood:
- (void)dismissKeyboard
{
UITextField *tempTextField = [[[UITextField alloc] initWithFrame:CGRectZero] autorelease];
tempTextField.enabled = NO;
[myRootViewController.view addSubview:tempTextField];
[tempTextField becomeFirstResponder];
[tempTextField resignFirstResponder];
[tempTextField removeFromSuperview];
}
EDIT
Amendment to my answer to included tempTextField.enabled = NO;. Disabling the text field will prevent UIKeyboardWillShowNotification and UIKeyboardWillHideNotification keyboard notifications from being sent should you rely on these notifications throughout your app.
Quick tip on how to dismiss the keyboard in iOS when a user touches anywhere on the screen outside of the UITextField or keyboard. Considering how much real estate the iOS keyboard can take up, it makes sense to have an easy and intuitive way for your users to dismiss the keyboard.
Here's a link
A lot of overly-complicated answers here, perhaps because this is not easy to find in the iOS documentation. JosephH had it right above:
[[view window] endEditing:YES];
Here's what I use in my code. It works like a charm!
In yourviewcontroller.h add:
#property (nonatomic) UITapGestureRecognizer *tapRecognizer;
Now in the .m file, add this to your ViewDidLoad function:
- (void)viewDidLoad {
//Keyboard stuff
tapRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(didTapAnywhere:)];
tapRecognizer.cancelsTouchesInView = NO;
[self.view addGestureRecognizer:tapRecognizer];
}
Also, add this function in the .m file:
- (void)handleSingleTap:(UITapGestureRecognizer *) sender
{
[self.view endEditing:YES];
}
Even Simpler than Meagar's answer
overwrite touchesBegan:withEvent:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[textField resignFirstResponder];`
}
This will dismiss the keyboardwhen you touch anywhere in the background.
You should send endEditing: to working window being the subclass of UIView
[[UIApplication sharedApplication].windows.firstObject endEditing:NO];
In your view controller's header file add <UITextFieldDelegate> to the definition of your controller's interface so that it conform to the UITextField delegate protocol...
#interface someViewController : UIViewController <UITextFieldDelegate>
... In the controller's implementation file (.m) add the following method, or the code inside it if you already have a viewDidLoad method ...
- (void)viewDidLoad
{
// Do any additional setup after loading the view, typically from a nib.
self.yourTextBox.delegate = self;
}
... Then, link yourTextBox to your actual text field
- (BOOL)textFieldShouldReturn:(UITextField *)theTextField
{
if (theTextField == yourTextBox) {
[theTextField resignFirstResponder];
}
return YES;
}
The best way to dismiss keyboard from UITableView and UIScrollView are:
tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag
In swift 3 you can do the following
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
Jeremy's answer wasn't quite working for me, I think because I had a navigation stack in a tab view with a modal dialog on top of it. I'm using the following right now and it is working for me, but your mileage may vary.
// dismiss keyboard (mostly macro)
[[UIApplication sharedApplication].delegate dismissKeyboard]; // call this in your to app dismiss the keybaord
// --- dismiss keyboard (in indexAppDelegate.h) (mostly macro)
- (void)dismissKeyboard;
// --- dismiss keyboard (in indexAppDelegate.m) (mostly macro)
// do this from anywhere to dismiss the keybard
- (void)dismissKeyboard { // from: http://stackoverflow.com/questions/741185/easy-way-to-dismiss-keyboard
UITextField *tempTextField = [[UITextField alloc] initWithFrame:CGRectZero];
UIViewController *myRootViewController = <#viewController#>; // for simple apps (INPUT: viewController is whatever your root controller is called. Probably is a way to determine this progragrammatically)
UIViewController *uivc;
if (myRootViewController.navigationController != nil) { // for when there is a nav stack
uivc = myRootViewController.navigationController;
} else {
uivc = myRootViewController;
}
if (uivc.modalViewController != nil) { // for when there is something modal
uivc = uivc.modalViewController;
}
[uivc.view addSubview:tempTextField];
[tempTextField becomeFirstResponder];
[tempTextField resignFirstResponder];
[tempTextField removeFromSuperview];
[tempTextField release];
}
You may also need to override UIViewController disablesAutomaticKeyboardDismissal to get this to work in some cases. This may have to be done on the UINavigationController if you have one.
Subclass your textfields... and also textviews
In the subclass put this code..
-(void)conformsToKeyboardDismissNotification{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(dismissKeyBoard) name:KEYBOARD_DISMISS object:nil];
}
-(void)deConformsToKeyboardDismissNotification{
[[NSNotificationCenter defaultCenter] removeObserver:self name:KEYBOARD_DISMISS object:nil];
}
- (void)dealloc{
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self resignFirstResponder];
}
In the textfield delegates (similarly for textview delegates)
-(void)textFieldDidBeginEditing:(JCPTextField *)textField{
[textField conformsToKeyboardDismissNotification];
}
- (void)textFieldDidEndEditing:(JCPTextField *)textField{
[textField deConformsToKeyboardDismissNotification];
}
All set.. Now just post the notification from anywhere in your code. It will resign any keyboard.
And in swift we can do
UIApplication.sharedApplication().sendAction("resignFirstResponder", to: nil, from: nil, forEvent: nil)
To dismiss a keyboard after the keyboard has popped up, there are 2 cases,
when the UITextField is inside a UIScrollView
when the UITextField is outside a UIScrollView
2.when the UITextField is outside a UIScrollView
override the method in your UIViewController subclass
you must also add delegate for all UITextView
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self.view endEditing:YES];
}
In a scroll view, Tapping outside will not fire any event, so in that case use a Tap Gesture Recognizer,
Drag and drop a UITapGesture for the scroll view and create an IBAction for it.
to create a IBAction, press ctrl+ click the UITapGesture and drag it to the .h file of viewcontroller.
Here I have named tappedEvent as my action name
- (IBAction)tappedEvent:(id)sender {
[self.view endEditing:YES]; }
the abouve given Information was derived from the following link, please refer for more information or contact me if you dont understand the abouve data.
http://samwize.com/2014/03/27/dismiss-keyboard-when-tap-outside-a-uitextfield-slash-uitextview/
I hate that there's no "global" way to programmatically dismiss the keyboard without using private API calls. Frequently, I have the need to dismiss the keyboard programmatically without knowing what object is the first responder. I've resorted to inspecting the self using the Objective-C runtime API, enumerating through all of its properties, pulling out those which are of type UITextField, and sending them the resignFirstResponder message.
It shouldn't be this hard to do this...
It's not pretty, but the way I resign the firstResponder when I don't know what that the responder is:
Create an UITextField, either in IB or programmatically. Make it Hidden. Link it up to your code if you made it in IB.
Then, when you want to dismiss the keyboard, you switch the responder to the invisible text field, and immediately resign it:
[self.invisibleField becomeFirstResponder];
[self.invisibleField resignFirstResponder];
You can recursively iterate through subviews, store an array of all UITextFields, and then loop through them and resign them all.
Not really a great solution, especially if you have a lot of subviews, but for simple apps it should do the trick.
I solved this in a much more complicated, but much more performant way, but using a singleton/manager for the animation engine of my app, and any time a text field became the responder, I would assign assign it to a static which would get swept up (resigned) based on certain other events... its almost impossible for me to explain in a paragraph.
Be creative, it only took me 10 minutes to think through this for my app after I found this question.
A slightly more robust method I needed to use recently:
- (void) dismissKeyboard {
NSArray *windows = [UIApplication sharedApplication].windows;
for(UIWindow *window in windows) [window endEditing:true];
// Or if you're only working with one UIWindow:
[[UIApplication sharedApplication].keyWindow endEditing:true];
}
I found some of the other "global" methods didn't work (for example, UIWebView & WKWebView refused to resign).
Add A Tap Gesture Recognizer to your view.And define it ibaction
your .m file will be like
- (IBAction)hideKeyboardGesture:(id)sender {
NSArray *windows = [UIApplication sharedApplication].windows;
for(UIWindow *window in windows) [window endEditing:true];
[[UIApplication sharedApplication].keyWindow endEditing:true];
}
It's worked for me
Yes, endEditing is the best option. And From iOW 7.0, UIScrollView has a cool feature to dismiss the keyboard on interacting with the scroll view. For achieving this, you can set keyboardDismissMode property of UIScrollView.
Set the keyboard dismiss mode as:
tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag
It has few other types. Have a look at this apple document.
In swift :
self.view.endEditing(true)
the easist way is to call the method
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
if(![txtfld resignFirstResponder])
{
[txtfld resignFirstResponder];
}
else
{
}
[super touchesBegan:touches withEvent:event];
}
You have to use one of these methods,
[self.view endEditing:YES];
or
[self.textField resignFirstResponder];