When my iPhone app starts up, the main screen has a keyboard. Currently the keyboard rises as soon as the rest of the interface is displayed and this is visually distracting.
How can I have the view display with the keyboard already up?
Since I am already faking some of the rest of the screen during startup so that the user sees what they last were doing, I thought that I could fake the keyboard as well. But if the motion is there when the real keyboard appears, I've lost the effect. The keyboard is, as far as I know, on a separate window, not just a separate view, so I can't overlay it with my own image.
Is there a way to either overlay the keyboard withy my own image as it appears, or not show the keyboard until it is all the way up, or make its animation instant?
My original answer has the keyboard animate in along with the view controller if it's an animated transition (i.e. pushing a view controller or presenting a modal controller with animated: YES). However, the keyboard still animates in if the new view controller is displayed without an animated transition, so it doesn't solve your problem.
Here's another approach that worked in my testing. Try disabling animations while you're displaying the controller + keyboard.
[UIWindow beginAnimations: nil context: NULL];
[UIWindow setAnimationsEnabled: NO];
RestoredController *controller = [[[RestoredController alloc] init] autorelease];
[self.navigationController pushViewController: controller animated: NO];
[UIWindow commitAnimations];
You'll still need to make field the first responder in viewWillAppear: or viewDidAppear:
In viewWillAppear:, ensure the view is loaded (via self.view) and set the first responder to the correct field. This will display the keyboard fully when the view is actually displayed instead of animating it in.
For example:
- (void) viewWillAppear: (BOOL) animated {
[super viewWillAppear:animated];
NSString *storedID = [[NSUserDefaults standardUserDefaults] stringForKey:#"storedID"];
if ([storedID length] > 0) {
idField.text = storedEmail;
[passwordField becomeFirstResponder];
} else {
[idField becomeFirstResponder];
}
}
Not an answer, but here is some code.
I set the font for my UITextView in viewDidLoad. The view is instantiated by the NIB and the outlet is correctly set up.
-(void)viewWillAppear:(BOOL)animated;
{
[super viewWillAppear:animated];
// Start with the text from the currently edited message
NSString *startString = self.messageManager.editingMessage.text;
// start string processing omitted
self.editingMessageEditingView.text = startString;
if([self showingMessageList]) {
[self.editingMessageEditingView resignFirstResponder];
} else {
#if DEFAULT_SCREEN==1
[[self.tools.items objectAtIndex:kPVToolBarItemDelete] setEnabled:NO];
[[self.tools.items objectAtIndex:kPVToolBarItemSendLater] setEnabled:NO];
#else
[self.editingMessageEditingView becomeFirstResponder];
#endif
}
Related
I have this code in my viewdidload:
[_txtName setDelegate:self];
[_txtName becomeFirstResponder];
_txtName.enabled = YES;
_txtName.text = #"";
But when my view loads, the keyboard does not show, any idea why?
_txtName is the UITextField
As pointed out in a comment by #lukya you should place the call to becomeFirstResponder in either viewWillAppear: or viewDidAppear:. If you place the call in viewWillAppear: the keyboard will be shown on screen when the view is shown. If you would like the keyboard to animate in from the bottom when the view appears you should put the call in viewDidAppear:.
I added a modalView to my App, everything working fine, but on closing the modal, the whole modalView jumps about 1-2 centimeters to left while it disappears.
I did not find any reason for it yet, so here is the code regarding modal:
AppController:
- (void) showNameModal:(Player *)player
{
namesModal = [[PlayerModalView alloc] init];
namesModal.delegate = self;
namesModal.player = player;
UINavigationController *navCon = [[UINavigationController alloc] initWithRootViewController:namesModal];
navCon.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:navCon animated:YES];
[navCon release];
[namesModal release];
}
- (void)didDismissModalView
{
[self dismissModalViewControllerAnimated:YES];
}
ModalView:
- (void)dismissView:(id)sender
{
[delegate didDismissModalView];
}
called via navigation buttons as well ass via keyboard by
[self dismissView:nil];
As you can see, there is nothing special in it, could be taken from a manual actually.
What happens in detail:
Modal appears in center of screen, slides in from the bottom. centered all time.
i can handle some actions in the modalView, it stays centered.
now, dismissing the view makes it jumping to the left, than slides out.
Since it's a forced landscape-right app (currently), I was only able to notify the left-jump.
Any ideas how to get this jumping away?
Thanks
Try this,
- (void)didmissView:(id)sender
{
[self.navigationController didmissModelViewControllerAnimated:YES];
}
You are not modally presenting an instance of PlayerModalView but rather a UINavigationController. The left jerk you see is most likely the default animation of the navigation controller attempting a slide transform to the (non-existant) previous view.
It doesn't sound like you need a navigation controller for the PlayerModalView. Instead, you should create an ordinary view controller for it.
This solution seems to work well: Modal View Controller with keyboard on landscape iPad changes location when dismissed
To simplify resigning the first responder (if finding it is difficult), you can just call
[self.view endEditing:YES];
[self dismissModalViewControllerAnimated:YES];
The problem is that the UIViewController you're showing modally doesn't allow the orientation you're presenting it in, so when it disappears, it will do that in a direction that it considers "allowed".
Add this to the UIViewController for you modal view:
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
So I have a modal view displaying in my app that has a little info for the user to fill out. The problem is that when the device is rotated, some animation occurs, but only in the frame. The form itself does not rotate. All the autorotate options are set to YES. I am displaying it when the user clicks on a field in a popover. This makes me suspect it has something to do with that but I am not sure. It is bizzare because if the device is in either view and then the modal window is displayed, it is fine. It only happens when the device is rotated in the modal view. Does anyone have any idea what may be causing this behavior when the device is rotated? Thanks!
Here is a snippet that is handled in the popover view controller:
if (currentLevel == 1 && businessOrLocation == 0){
if(tbsViewController == nil)
tbsViewController = [[BusinessFilteredViewController alloc] initWithNibName:#"BusinessFilteredView" bundle:[NSBundle mainBundle]];
NSMutableArray *tempBusiness = [[NSMutableArray alloc] init];
for (id theKey in appDelegate.groupedBusiness) {
NSMutableArray *tempArr = [appDelegate.groupedBusiness objectForKey:theKey];
[tempBusiness addObject:tempArr];
}
tbsViewController.businessOrLocation = businessOrLocation;
tbsViewController.modalPresentationStyle = UIModalPresentationFullScreen;
tbsViewController.modalTransitionStyle = UIModalPresentationFullScreen;
[self presentModalViewController:tbsViewController animated:YES];
}
I ran into this problem as well. The fundamental problem is that popover controllers cannot present modal views—it seems that case wasn’t properly considered or designed for. In my situation, it was easy enough to work around. I just extended the delegate protocol for my popover-hosted view controller. The main view sets itself up as the delegate to the popover view, and takes responsibility for displaying and dismissing the modal views the user requests from within the popover.
Since I already had a delegate protocol to cleanly dismiss the popover view when the user clicks “done” it was only a small stretch to get autorotation working the way I wanted it to. Here are some snippets:
#protocol InfoViewControllerDelegate <NSObject>
#optional
// Implement this to close the info view once the user clicks done.
- (void)infoViewDidFinish:(InfoViewController *)view;
// Implement this method if the delegate launched us as a popup view and must therefore
// take responsibility for diplaying help.
- (void)infoViewDidRequestHelp:(InfoViewController *)view;
#end
And in my main iPad view which presents this popup view:
#pragma mark - InfoViewControllerDelegate methods
- (void)infoViewDidFinish:(InfoViewController *)view {
[self hideInfo:self];
}
- (void)infoViewDidRequestHelp:(InfoViewController *)view {
[self hideInfo:self]; // Close the info view first
HelpViewController *help = [[HelpViewController alloc] init];
help.delegate = self;
[self presentModalViewController:help animated:YES];
[help release];
}
To make life simple for cases where I am launching the info view outside of a popup view (for example, on the iPhone, it is a simple modal view), it checks to see if the delegate handles the modal subviews, and if not, handles them itself. That way I didn’t need to change the iPhone base controller at all, since autorotation already worked fine there. Here’s the “Help” button action in the info view controller, showing how I did that:
- (IBAction)help:(id)sender {
if ([delegate respondsToSelector:#selector(infoViewDidRequestHelp:)]) {
[delegate infoViewDidRequestHelp:self];
} else {
HelpViewController *help = [[HelpViewController alloc] init];
help.delegate = self;
[self presentModalViewController:help animated:YES];
[help release];
}
}
With this code in place, my entire interface autorotates smoothly on both devices, whether or not popup views were involved.
Just so i understand correctly... You are displaying a popover and inside that popover if the user taps a certain element then you are displaying a full screen modal view controller? Vie never tried that before and it seems odd for two reasons.
First it seems jarring to the user in my opinion. The popover gives you a nice, integrated UI and the modal takes you away.
More importantly though, your popover view controller doesn't really have authority over the whole screen so presenting a full screen modal from a popover just seems inherently wrong.
I would suggest you display a nav controller in the popover controller and instead of presenting the new view controller modally over the whole screen just push it on to the nav controller in the popover and keep the user inside the popover.
If that doesn't really work for you, then I would suggest reviewing your UI needs and redesigning the layout.
I am guessing that you implemented - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation in BusinessFilteredViewController and returns YES
Could you check that you add more than 1 subviews to application window . If so, try to create container UIViewController for all viewControllers that you want to add to window.
I have a toolbar in my RootViewController and I then hide the toolbar in a SubViewController using the following code:
RootViewController
- (void)viewDidLoad {
...
[self.navigationController setToolbarHidden:FALSE animated:FALSE];
...
}
- (void)viewDidAppear:(BOOL)animated {
[self.navigationController setToolbarHidden:FALSE animated:TRUE];
[super viewDidAppear:animated];
}
SubViewController
- (void)viewDidLoad {
...
[self.navigationController setToolbarHidden:YES animated:YES];
[super viewDidLoad];
}
This all works as expected i.e. the toolbar will be hidden and unhidden using a nice vertical animation when moving from one view to another and back again.
However, there appears to be a nasty animation issue when moving from the RootViewController to the SubViewController. As the toolbar is being hidden, a white bar will appear where the toolbar was, and then quickly disappears across the screen from right to left.
Hopefully I've explained this well enough for you to understand.
Any ideas how to fix this?
Have you tried doing the animation in SubViewController's -viewWillAppear: method? You may have better luck there.
I have seen this problem a couple of times and I have found that putting the call to setToolbarHidden:animated: in the viewWillAppear: method does not always give a smooth animation with no white rectangle artifacts.
What does always work is to put the setToolbarHidden:animated: call in the viewDidAppear: method. This means that the toolbar hiding animation is triggered after the navigation controller has finished pushing the new view onto the stack, so no white rectangles. However, it also means that the whole animation is in two stages: the first animates the view in, the second hides the toolbar, so you have the appearance of a "delayed" toolbar hide. I acknowledge that this isn't always what you want.
TRY THIS
- (IBAction)hideTheToolBar:(id)sender{
//[toolBar setHidden:YES];
if (toolBar.hidden == NO)
{
[UIView animateWithDuration:0.25 delay:0.0
options:UIViewAnimationOptionCurveLinear | UIViewAnimationOptionAllowUserInteraction
animations:^(void)
{
toolBar.alpha = 0.0f;
}
completion:^(BOOL finished)
{
toolBar.hidden = YES;
}
];
}
}
You can (probably should) do this in the subview controller's designated initializer, e.g. initWithNibName:bundle:
I have found very useful to set the hidesBottomBarWhenPushed property in the init of your view controller.
For instance:
- (id)init
{
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
// Custom initialization
self.navigationItem.rightBarButtonItem = self.editButtonItem;
self.hidesBottomBarWhenPushed = YES;
}
return self;
}
It hides those spurious toolbars that appear in the push and pop transitions. Also, it frees you from manually hiding the toolbar in the ViewWillAppear method or similar approaches.
Does anyone know how to cancel (resign First Responder) out of a UISearchBar when you tap below the search text box and above the keyboard? Can anyone help post some code to handle this?
Thanks
Add a tap gesture in the parent view (of the UISearchbar)
[self addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:searchBar action:#selector(resignFirstResponder)]];
I accomplished this by using a UITapGestureRecognizer:
UIGestureRecognizer* cancelGesture;
- (void) backgroundTouched:(id)sender {
[self.view endEditing:YES];
}
#pragma mark - UISearchBarDelegate
-(void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
cancelGesture = [UITapGestureRecognizer new];
[cancelGesture addTarget:self action:#selector(backgroundTouched:)];
[self.view addGestureRecognizer:cancelGesture];
}
-(void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
if (cancelGesture) {
[self.view removeGestureRecognizer:cancelGesture];
[cancelGesture release];
cancelGesture = nil;
}
}
The code is a bare, but you can see the intent. When the SearchBar starts editing, you attach a tap gesture recognizer to the view controller's view, and remove it when it stops editing.
There are a couple caveats that you can work around: doing this will make it so if you click anything besides the keyboard or the search bar's text field, the recognizer traps the click -- so if you use the clear, cancel, scope or results button they won't respond correctly.
In my particular scenario, I had a UITableView that was covering the exposed area of the view so I attached the gesture recognizer to it instead of the view controllers main view, isolating the area to which the gesture would respond.
An alternative idea I got from iphonedevbook, sample code project 04, was to use one big transparent button that lies behind all other controls which does nothing but resign all first responders if tapped. I.e. if the user taps anywhere where there isn't a more important control - which is the intuitive behavior - the search bar and keyboard disappear.
I ended up using a hybrid of Hauke's and Beau Scott's approach. There were two problems I ran into using their solutions:
1) If there's anything else on the screen, tapping it won't result in resignFirstResponder being called. For example, if the user taps a button rather than the space around the button, the button will eat the event. Beau Scott's solution addresses this issue, however.
2) Tapping the search bar itself will result in resignFirstResponder getting called. Clearly you don't want the keyboard to disappear when you tap UISearchBar. A small change described below addresses this.
I ended up setting up my view as follows. The parent view has two children - the UISearchBar and a subview which holds the rest of my UI elements. The subview takes up the entire screen below the UISearchBar. Then I used Beau Scott's exact code to add and remove the gesture recognizer, but instead of adding it to self.view I added it to the subview:
IBOutlet UIView *gestureRecognizer;
...
-(void)searchBarTextDidBeginEditing:(UISearchBar *)searchBar {
cancelGesture = [UITapGestureRecognizer new];
[cancelGesture addTarget:self action:#selector(backgroundTouch:)];
[gestureRecognizer addGestureRecognizer:cancelGesture];
}
-(void)searchBarTextDidEndEditing:(UISearchBar *)searchBar {
if (cancelGesture) {
[gestureRecognizer removeGestureRecognizer:cancelGesture];
[cancelGesture release];
cancelGesture = nil;
}
}
First, you need a reference to the search bar. Let's assume that your controller object has an object reference UISearchBar *theSearchBar, and that you assign it when you create the UISearchBar object.
Next, you need to detect that the containing view has been touched. The view that is touched "knows", but you need get that information to the controller. Sadly, Apple didn't provide a simple way to do this, but it's not that hard either.
My solution is to replace the standard UIView that a UIViewController object normally creates with a UIControl, and then make the UIViewController respond to touch events.
MainController.m
- (void) loadView {
UIControl *control = [[UIControl alloc] initWithFrame: <desired frame>];
[control addTarget: self action: #selector(touchUpInside)
forControlEvents: UIControlEventTouchUpInside];
// or touch down events, or whatever you like
self.view = control;
[control release];
}
- (void) viewDidLoad {
[super viewDidLoad];
theSearchBar = [[UISearchBar alloc] initWithFrame: <desired frame>];
// insert code to finish customizing the search bar
[self.view addSubview: theSearchBar];
}
- (void) touchUpInside {
if [theSearchBar isFirstResponder] {
// grab any data you need from the search bar
[theSearchBar resignFirstResponder];
}
}
MainController.h
#interface MainController : UIViewController
{
UISearchBar *theSearchBar;
}
Clarification:
There is only a single object -- let's call the class MainController -- which is a subclass of UIViewController. All of the methods listed above are implemented in MainController. theSearchBar is declared as a UISearchBar* in the .h file.
Are you defining your view and controller using Interface Builder? If so, I suggest you learn how to NOT use it -- once you get into the kind of tricks we are discussing here, it becomes more of a hindrance than a help -- I don't use it at all, ever.
#Gia Dang's answer is the simplest, but I don't subclass the UIView, only the UIViewController, so my call is slightly different. Also, since I don't know the overhead for actually calling resignFirstResponder, I prefer to check first. It's more code, but since all of this is done on the main thread (which can slow down the UI), I'd rather check first.
#implementation MyController : UIViewController {
#private
UISearchController *_uiSearchController;
}
- (void)viewDidLoad {
// add tap on view to resign the responder if we're in the middle of typing in the search
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(closeKeyboardIfNeeded)];
[self.view addGestureRecognizer:tapGestureRecognizer];
}
- (void)closeKeyboardIfNeeded {
if (![_uiSearchController.searchBar isFirstResponder]) {
return;
}
[_uiSearchController.searchBar resignFirstResponder];
}
#end
As for the other answers, be careful about constantly recreating objects. There is always a performance hit, whether it's the creation itself or the garbage collection through ARC, and these things will slow down your main thread. Depending on what you're doing also on the main thread, it may have a significant performance impact.