I've ran into quite an unfortunate bug in iOS 9. It seems that when you set a UITextField.inputAccessoryView, that view's viewWillDisappear: and viewDidDisappear: methods are called prematurely (right when the keyboard finishes animating up).
I've included a gif to demonstrate the issue. When the view turns red is when its viewWillDisappear: method has been called. Oddly when you dismiss the keyboard, viewWillDisappear: and viewDidDisappear: are called again. However, viewWillAppear: is only called once.
Has anyone run into a similar issue? I use viewWillDisappear: and viewDidDisappear: to wind down the controller, and obviously an early call is causing unwanted behaviour.
Note: Below is how I create and set the accessory view. Nothing notable in AccessoryViewController.m. Reproduced the issue in a clean project. And it is not present on iOS 8.
- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField {
if (self.textField.inputAccessoryView == nil) {
self.textField.inputAccessoryView = self.vc.view;
}
return YES;
}
- (UIViewController *)vc {
if (!_vc) _vc = [[AccessoryViewController alloc] init];
return _vc;
}
The AccessoryViewController is not stored strongly on the ViewController. Store it in an instance variable so it does not get cleaned up.
My solution (Swift):
var accessoryView: AccessoryViewController! // works
vs
weak var accessoryView: AccessoryViewController!
Related
I've spent quite a bit of time searching online and talking to other developers about this issue to no avail. The exact issue is described in this SO post (Focus on the UISearchBar but the keyboard not appear), although it's many years old.
I recently switched from using the deprecated UISearchDisplayController and UISearchBar in IB, and switched over to UISearchController via the code for iOS8.
The problem I'm getting however, is that focus is assigned correctly (you can tell because the cancel button animates to the right of the search bar after the view loads), however the keyboard does not show up.
Here's the code that I have.
.h
#property (nonatomic, strong) UISearchController *searchController;
.m
- (void)viewDidLoad {
[super viewDidLoad];
...
[self initializeSearchController];
....
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.searchController setActive:YES];
[self.searchController.searchBar becomeFirstResponder];
}
- (void)initializeSearchController {
self.searchController = [[UISearchController alloc] initWithSearchResultsController:nil];
self.searchController.searchResultsUpdater = self;
self.searchController.dimsBackgroundDuringPresentation = NO;
self.searchController.delegate = self;
self.searchController.searchBar.delegate = self;
[self.searchController.searchBar sizeToFit];
[self.tableView setTableHeaderView:self.searchController.searchBar];
self.definesPresentationContext = YES;
}
The things I've tried so far.
I've tried calling becomeFirstResponder on a 0.2 second delay, as suggested in another SO post.
I've set a breakpoint in viewDidAppear, and verified that both self.searchController and self.searchController.searchBar are both valid objects, neither nil.
I've tried conforming to the UISearchControllerDelegate and using the following snippet of code
here:
- (void)didPresentSearchController:(UISearchController *)searchController {
//no matter what code I put in here to becomeFirstResponder, it doesn't
//matter because this is never called, despite setting the
//self.searchController.delegate = self AND
//self.searchController.searchBar.delegate = self.
}
I've created a new view from scratch in storyboards, and segued to that one instead, to make sure I didn't have some old searchBar remnant in my view. This did not work either.
I've only tested this on a real device (iPhone 6), and it's not a simulator issue of not showing the keyboard.
I'm out of ideas, and I've seen every question and answer related to this one the web. Nothing is working.
To clarify again what's going on, the searchBar correctly becomes the first responder, the cancel button to the right of it animates onscreen proving this, but the keyboard does not appear and the cursor does not blink in the searchBar.
Your code looks ok. What you are describing isn't normal behaviour.
The first thing you can do is to create a new project with just the UISearchController functionality and see how it goes. You can edit your question with it so we'll have a better view.
There's a good example on how to implement UISearchController here: Sample-UISearchController
Adding:
-(void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.searchController.searchBar becomeFirstResponder];
}
to MasterViewController_TableResults.m gave the expected results and the keyboard popped up on launch on an iPad & iPhone with iOS 8.3.
You can go over that project and see what you did differently,
Edit:
Apparently if [self.searchController setActive:YES] is called before becomeFirstResponder the keyboard won't show. I wonder if that's a bug or not.
Had the same annoying issue.
You would think that by setting the SearchController as active would both present the the search controller and the keyboard. Unfortunately, it only does the first part.
My solution
in viewDidAppear make the Search Controller active:
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
resultSearchController.active = true
}
once it is active, in didPresentSearchController make as first responder
func didPresentSearchController(searchController: UISearchController) {
searchController.searchBar.becomeFirstResponder()
}
Swift 3.0 (iOS 10) working solution:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
searchController.isActive = true
DispatchQueue.main.async { [unowned self] in
self.searchController.searchBar.becomeFirstResponder()
}
}
On iOS 9 I've found its sufficient to delay becomeFirstResponder() to the next run loop:
func focusSearchField() {
searchController?.active = true
// skipping to the next run loop is required, otherwise the keyboard does not appear
dispatch_async(dispatch_get_main_queue(), { [weak self] in
self?.searchController?.searchBar.becomeFirstResponder()
})
}
Working Solution:-
Don't use [self.searchController setActive:YES] before becomeFirstResponder.
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_async(dispatch_get_main_queue(), ^{
// [self.searchController setActive:YES];
[self.searchController.searchBar becomeFirstResponder];
});
});
}
In iOS 10, I had to run the code in delegate method on main thread. First I set the active to YES in viewDidAppear,
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[self.searchController setActive:YES];
}
and then in the delegate method:
- (void)didPresentSearchController:(UISearchController *)searchController
{
dispatch_async(dispatch_get_main_queue(), ^{
[searchController.searchBar becomeFirstResponder];
});
}
The solution that will work is as follows :
1.Override ViewDidLayoutSubviews in the view controller in which you are showing UISearchController
2.Override ViewDidLayoutSubviews and inside it make search bar first responder.
Tested it on iOS > 9.0
Caution : Put a null check before making it First responder as follows
if((searchController != null)&&(searchController.SearchBar != null))
searchController.SearchBar.BecomeFirstResponder();
This is because ViewDidLayoutSubviews also gets called when cancel button is pressed.
This worked for me in Xamarin.
I had trouble with an UISearchBar not displaying the keyboard when doing
[searchBar becomeFirstResponder];
By searching on the net, i found this thread on the Apple developer website
that helped me to discover that the keyboard won't open if you don't have a keyWindow.
The application i work on do something like this :
Window A (KeyWindow)
do some things
open Window B (KeyWindow)
do some things
close Window B (resign KeyWindow)
I just had to do
[[[[UIApplication sharedApplication] windows] firstObject] makeKeyWindow];
after the resigning of window B and no more trouble with the keyboard.
This might also be related to Simulator Settings. Just disable Hardware -> Keyboard -> "Connect Hardware Keyboard" .
For further details: UISearchBar not showing keyboard when tapped
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.searchController setActive:YES];
}
//and then in the delegate method:
- (void)didPresentSearchController:(UISearchController *)searchController
{
dispatch_async(dispatch_get_main_queue(), ^{
[searchController.searchBar becomeFirstResponder];
});
}
//The above works for me in addition to this I had to add:
-(void)viewWillDisappear:(BOOL)animated {
[searchController setActive:NO];
}
I want to check the pasteboard and show an alert if it contains specific values when the view appears. I can place the code into viewDidLoad to ensure it's only invoked once, but the problem is that the alert view shows too quickly. I know I can set a timer to defer the alert's appearance, but it's not a good work-around I think.
I checked the question iOS 7 - Difference between viewDidLoad and viewDidAppear and found that there is one step for checking whether the view exists. So I wonder if there's any api for doing this?
Update: The "only once" means the lifetime of the view controller instance.
There is a standard, built-in method you can use for this.
Objective-C:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if ([self isBeingPresented] || [self isMovingToParentViewController]) {
// Perform an action that will only be done once
}
}
Swift 3:
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
if self.isBeingPresented || self.isMovingToParentViewController {
// Perform an action that will only be done once
}
}
The call to isBeingPresented is true when a view controller is first being shown as a result of being shown modally. isMovingToParentViewController is true when a view controller is first being pushed onto the navigation stack. One of the two will be true the first time the view controller appears.
No need to deal with BOOL ivars or any other trick to track the first call.
rmaddy's answers is really good but it does not solve the problem when the view controller is the root view controller of a navigation controller and all other containers that do not pass these flags to its child view controller.
So such situations i find best to use a flag and consume it later on.
#interface SomeViewController()
{
BOOL isfirstAppeareanceExecutionDone;
}
#end
#implementation SomeViewController
-(void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if(isfirstAppeareanceExecutionDone == NO) {
// Do your stuff
isfirstAppeareanceExecutionDone = YES;
}
}
#end
If I understand your question correctly, you can simply set a BOOL variable to recognize that viewDidAppear has already been called, ex:
- (void)viewDidAppear {
if (!self.viewHasBeenSet) { // <-- BOOL default value equals NO
// Perform whatever code you'd like to perform
// the first time viewDidAppear is called
self.viewHasBeenSet = YES;
}
}
This solution will call viewDidAppear only once throughout the life cycle of the app even if you create the multiple object of the view controller this won't be called after one time. Please refer to the rmaddy's answer above
You can either perform selector in viewDidLoad or you can use dispatch_once_t in you viewDidAppear. If you find a better solution then please do share with me. This is how I do the stuff.
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelector:#selector(myMethod) withObject:nil afterDelay:2.0];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
static dispatch_once_t once;
dispatch_once(&once, ^{
//your stuff
[self myMethod];
});
}
By reading other comments (and based on #rmaddy 's answer), I know this is not what OP asked for, but for those who come here because of title of the question:
extension UIViewController {
var isPresentingForFirstTime: Bool {
return isBeingPresented() || isMovingToParentViewController()
}
}
UPDATE
You should use this method in viewDidAppear and viewWillAppear. (thanks to #rmaddy)
UPDATE 2
This method only works with modally presented view controllers and pushed view controllers. it's not working with a childViewController. using didMoveToParentViewController would be better with childViewControllers.
You shouldn't have issues in nested view controllers with this check
extension UIViewController {
var isPresentingForFirstTime: Bool {
if let parent = parent {
return parent.isPresentingForFirstTime
}
return isBeingPresented || isMovingFromParent
}
}
Try to set a BOOL value, when the situation happens call it.
#interface AViewController : UIViewController
#property(nonatomic) BOOL doSomeStuff;
#end
#implementation AViewController
- (void) viewWillAppear:(BOOL)animated
{
if(doSomeStuff)
{
[self doSomeStuff];
doSomeStuff = NO;
}
}
in somewhere you init AViewController instance:
AddEventViewController *ad = [AddEventViewController new];
ad.doSomeStuff = YES;
Not sure why you do this in ViewDidAppear? But if you want doSomeStuff is private and soSomeStuff was called only once, here is another solution by notification:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(doSomeStuff) name:#"do_some_stuff" object:nil];
- (void) doSomeStuff
{}
Then post when somewhere:
[[NSNotificationCenter defaultCenter] postNotificationName:#"do_some_stuff" object:nil];
swift 5
I've tried isBeingPresented() or isMovingToParent.
But It doesn't work.
So I tried below code. and It's work for me!
override func viewDidAppear(_ animated: Bool) {
if (self.isViewLoaded) {
// run only once
}
}
You can use this function in ViewDidLoad method
performSelector:withObject:afterDelay:
it will call that function after delay. so you don't have to use any custom timer object.
and For once you can use
dispatch_once DCD block.Just performSelector in the dispatch_once block it will call performSelector only once when ViewDidLoad is called
Hope it helps
My ViewController implements UITextView delegate method textViewDidChangeSelection. Everything works just as expected when testing it. However, if the app is put in the background, and then becomes active again, the delegate method is not getting called when changing the selection in the TextView. Anyone else who had this problem?
My UITextView subclass does this:
self.inputView = [[UIView alloc] initWithFrame:CGRectZero];
The above is in order for the keyboard not to show up, but at the same time keeping the TextView enabled.
The subclass also does this:
-(BOOL)canPerformAction:(SEL)action withSender:(id)sender{
{
if ( [UIMenuController sharedMenuController] )
{
[UIMenuController sharedMenuController].menuVisible = NO;
}
return NO;
}
This is in order not to show the copy paste pop up when clicking the UITextView. I think this method looks a bit weird, but I found it on SO a while ago, and it has done what it should.
The UITextView delegate method textViewDidChangeSelection: will fire again when the app returns from the background. So something else must be going on in your app.
Just to make sure I created a simple app with 2 UITextView controls, set their delegate to be the view controller and added this bit of code:
- (void) textViewDidChangeSelection:(UITextView *)textView
{
NSLog(#"Fire change selection.");
}
Works fine both before backgrounding the app and after coming back from backgrounding.
What solved the problem for me was to call
[myTextView reloadInputViews]
In the AppDelegate method:
-(void)applicationWillEnterForeground:
I don't know exactly why it works, so if someone has any good explanation, that would be very appreciated.
I need to keep track of which text field is the firstResponder for my custom keyboard to work. In the code below, I have grossly oversimplified my program, but here is the gist of the problem:
#implementation SimplePickerViewController
#synthesize pickerKeyboard;
#synthesize textView;
#synthesize textView2;
#synthesize firstResponder;
-(void)viewDidLoad{
pickerKeyboard = [[PickerKeyboardViewController alloc] initWithNibName:#"PickerKeyboard" bundle:nil];
pickerKeyboard.delegate = self;
[self.textView setInputView:pickerKeyboard.view];
[self.textView setDelegate:self];
[self.textView2 setInputView:pickerKeyboard.view];
[self.textView2 setDelegate:self];
}
-(void)hideKeyboard{
[self.firstResponder resignFirstResponder];
self.firstResponder = nil; //without this line, the code doesn't work.
}
- (BOOL)textViewShouldBeginEditing:(UITextView *)textView{
self.firstResponder = textView;
[self.pickerKeyboard.picker reloadAllComponents];
return YES;
}
If I remove the line setting the firstResponder to nil, the code ceases to function properly, but I am not sure why. (Without that line, I can select the first textView to bring up the keyboard, but after that I can never bring the keyboard back. Any ideas? Thanks!
I'm not sure that I understand why firstResponder needs to be kept track of for a custom keyboard to work. I use a custom keyboard without knowing what the first responder is.
Do you use:
textView.inputView = pickerKeyboard
How about the following, called on the view to resign the first responder:
[self.view endEditing:NO];
I have had a similar problem and I have just figured out the issue. Somewhere in some part of Apple's first responder code, they are using a selector named firstResponder. When you created the property firstResponder you inadvertently overrode that selector. That will cause Apple's code to fail. This, in my humble opinion, is a bug in Apple's framework, and the firstResponder method isn't documented anywhere. Name your property myFirstResponder or anything else and everything should work just fine.
See Why does the keyboard not show when a view is popped from the navigation stack?
I am running into an issue where the keyboard does not get dismissed when leaving a UITextField or UITextView in a UIModalPresentationFormSheet. In addition, I've created a large button to serve as the view's background so if the user taps outside the fields it gets triggered. I am using the same code in a regular view controller, and it works as expected. In the modal view controller it does nothing. Any suggestions would be appreciated.
- (BOOL)textFieldShouldReturn:(id)sender {
[titleTextField resignFirstResponder];
return YES;
}
- (BOOL)textViewShouldReturn:(id)sender {
[synopsisTextView resignFirstResponder];
return YES;
}
- (IBAction)textFieldDoneEditing:(id)sender {
[sender resignFirstResponder];
}
- (IBAction)textViewDoneEditing:(id)sender {
[sender resignFirstResponder];
}
- (IBAction)backgroundClick:(id)sender {
[titleTextField resignFirstResponder];
[synopsisTextView resignFirstResponder];
}
Overriding disablesAutomaticKeyboardDismissal to return NO as below fixed the same problem of mine. You should put this code to your view controller, from which you initiate the keyboard:
- (BOOL)disablesAutomaticKeyboardDismissal {
return NO;
}
Also, check this SO question if you want to get a detailed explanation.
For those having trouble with UINavigationController, I think there is a better solution than a category on UIViewController. We should change the behavior of UINavigationController to ask its topViewController (in my opinion, this is how all ViewController containers should handle this).
#implementation UINavigationController (DelegateAutomaticDismissKeyboard)
- (BOOL)disablesAutomaticKeyboardDismissal {
return [self.topViewController disablesAutomaticKeyboardDismissal];
}
If you're presenting a modal view with presentation style "form sheet", Apple apparently does not dismiss the keyboard, thinking that they don't want the keyboard to jump in and out where a user will be doing a lot of editing (i.e. "forms"). The fix would be to change presentation style or live with it.
If you implement the UITextFieldDelegate protocol you can inadvertently cause this behavior if you do text validation. If your validation codes returns false from textFieldShouldEndEditing when the text is invalid, the field can't relinquish it's firstResponder status and the keyboard will remain on screen in the next view.
More details at UITextField's keyboard won't dismiss. No, really
I solved this by resizing a UIModalPresentationPageSheet. See my answer here.
The disablesAutomaticKeyboardDismissal refused to work for me on iOS 7.
But... I managed to solve this issue by simply disabling the UITextFields on the screen.
My solution is described here.
This workaround even works on Modal UIViewControllers.
Yeah... it surprised me aswell !!
i have also facing same problem and also done everything but not thing works then i start thinking and get some result.
but this answer for those who want to dismiss keyboard on textfield click and then open pop up.
so all you need to call text field delegate
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
if textField == self.myTxtFieldName{
self.view.endEditing(true) // keyboard hide code
// here you can call your model or pop up code and keyboard will dismiss and your pop up open
return false
}
return true
}
Sorry if this is not working for you
if there is other answer then please edit it
Thank you