UITextField subview of UITableViewCell to become first responder? - iphone

I have a core data application which uses a navigation controller to drill down to a detail view and then if you edit one of the rows of data in the detail view you get taken to an Edit View for the that single line, like in Apples CoreDataBooks example (except CoreDataBooks only uses a UITextField on its own, not one which is a subview of UITableViewCell like mine)!
The edit view is a UITableviewController which creates its table with a single section single row and a UITextfield in the cell, programatically.
What I want to happen is when you select a row to edit and the edit view is pushed onto the nav stack and the edit view is animated moving across the screen, I want the textfield to be selected as firstResponder so that the keyboard is already showing as the view moves across the screen to take position. Like in the Contacts app or in the CoreDataBooks App.
I currently have the following code in my app which causes the view to load and then you see the keyboard appear (which isn't what I want, I want the keyboard to already be there)
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[theTextField becomeFirstResponder];
}
You can't put this in -viewWillAppear as the textfield hasn't been created yet so theTextField is nil. In the CoreDataBooks App where they achieve what i want they load their view from a nib so they use the same code but in -viewWillAppear as the textfield has already been created!
Is there anyway of getting around this without creating a nib, I want to keep the implementation programatic to enable greater flexibility.
Many Thanks

After speaking with the Apple Dev Support Team, I have an answer!
What you need to do is to create an offscreen UITextField in -(void)loadView; and then set it as first responder then on the viewDidLoad method you can set the UITextField in the UITableViewCell to be first responder. Heres some example code (remember I'm doing this in a UITableViewController so I am creating the tableview as well!
- (void)loadView
{
[super loadView];
//Set the view up.
UIView *theView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.view = theView;
[theView release];
//Create an negatively sized or offscreen textfield
UITextField *hiddenField = [[UITextField alloc] initWithFrame:CGRectMake(0, 0, -10, -10)];
hiddenTextField = hiddenField;
[self.view addSubview:hiddenTextField];
[hiddenField release];
//Create the tableview
UITableView *theTableView = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] bounds] style:UITableViewStyleGrouped];
theTableView.delegate = self;
theTableView.dataSource = self;
[self.view addSubview:theTableView];
[theTableView release];
//Set the hiddenTextField to become first responder
[hiddenTextField becomeFirstResponder];
//Background for a grouped tableview
self.view.backgroundColor = [UIColor groupTableViewBackgroundColor];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
//Now the the UITableViewCells UITextField has loaded you can set that as first responder
[theTextField becomeFirstResponder];
}
I hope this helps anyone stuck in the same position as me!
If anyone else can see a better way to do this please say.

Try do it in viewDidAppear method, works for me.

I think the obvious solution is to create the textfield in the init method of the view controller. That is usually where you configure the view because a view controller does require a populated view property.
Then you can set the textfield as first responder in viewWillAppear and the keyboard should be visible as the view slides in.

have you tried using the uinavigationcontroller delegate methods?:
navigationController:willShowViewController:animated:

Related

How can I create one UIViewController with two views that display one or the other depending on button clicked

I'm quite new to iOS development and I am stuck. Currently I am using one tab controller to switch between two view controllers (list and map view). This made it easier to use storyboard to configure the look of the two views.
Now the requirements have changed and the app needs to have one view controller with a segmented control that on click, displays either the list or the map view. In order to do this, I would need to make one view controller that can display list/map view.
I understand how the segmented controller part works, but I'm just stuck on how I can go about having two views with one or the other displayed in the same area.
How can I go about having two views in one view controller (if possible, utilizing storyboard)?
Thanks in advance!
You should not have two main views in a single view controller, instead you need to create one view controller per view that you want to show. However you can certainly have multiple subviews in a single view controller, which may be what works for you.
There are a number of approaches to solve this the problem, the correct approach would be to create a container UIViewController, and add as its childs the 2 viewcontrollers you want to show, them simply set the view to the view controller you want to display, but that would probably be overly complicated since you mention you are new to iOS development.
Therefore an easy solution (not sure if you can implement this in storyboard - since I don't like it), would be to have a single view controller, with the tabs, and 2 subviews of the main view, then you can simply switch between views by doing something like this:
[self.view addSubview:view1];
//to switch
[view1 removeFromSuperview];
[self.view addSubView:view2];
alternatively, you do not really need to remove it from superview but just hide it, and then use bringSubViewToFront to show the view that you need.
If you want to use the other approach I would recommend looking for this video the WWDC 2011 video titled "Implementing UIViewController Containment". This other question should be useful to: UISegmented control with 2 views
Hope that helps.
Using storyboard api you can switch between 2 viewControllers
- (void)viewDidLoad {
[super viewDidLoad];
UIViewController *viewController = [self viewControllerForSegmentIndex:self.typeSegmentedControl.selectedSegmentIndex];
[self addChildViewController:viewController];
viewController.view.frame = self.contentView.bounds;
[self.contentView addSubview:viewController.view];
self.currentViewController = viewController;
}
- (UIViewController *)viewControllerForSegmentIndex:(NSInteger)index {
UIViewController *viewController;
switch (index) {
case 0:
viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"FirstViewController"];
break;
case 1:
viewController = [self.storyboard instantiateViewControllerWithIdentifier:#"SecondViewController"];
break;
}
return viewController;
}
- (IBAction)segmentChanged:(UISegmentedControl *)sender {
UIViewController *viewController = [self viewControllerForSegmentIndex:sender.selectedSegmentIndex];
[self addChildViewController:viewController];
[self transitionFromViewController:self.currentViewController toViewController:viewController duration:0.0 options:UIViewAnimationOptionTransitionNone animations:^{
[self.currentViewController.view removeFromSuperview];
viewController.view.frame = self.contentView.bounds;
[self.contentView addSubview:viewController.view];
} completion:^(BOOL finished) {
[viewController didMoveToParentViewController:self];
[self.currentViewController removeFromParentViewController];
self.currentViewController = viewController ;
}];
self.navigationItem.title = viewController.title;
}
This is in reference to iOS tutorial by Raywenderlich. Hope this helps
With Storyboard it is possible in this way.
Create UIViewController with UISegmentControl and UITableView+UITableViewCell added to it.
Now you want to add MKMapView as well, hoverer, if you simply try to place the MapView on the ViewController, it will be added as new TableView cell, which is not what we want.
That's why you should not do it so. Instead, MapView has to be added to Storyboard's List of ViewControllers
Adjust the size and origin of MapView to be the same as TableView ones.
Now, setHidden to YES for either TableView of MapView, create and synthesize outlets for them. Then in Segment control Value Changed method implement switching:
- (IBAction)switchView:(id)sender {
self.theTableView.hidden = !self.theTableView.hidden;
self.theMapView.hidden = !self.theMapView.hidden;
if (!self.theTableView.hidden) {
[self.theTableView reloadData];
}
}

How to add 2nd UITableView programmatically from within a UINavigationController

I realize this is a popular topic, and I've searched through many posts here but haven't found anything that has helped my issue. I'm a beginner, for what it's worth (as you'll see by my question :-)
My app has a tab bar with 3 items. The first loads a UINavigationController that is intended to have 3 "screens" to drill-down through (first: UITableView, second: filtered UITableView, third: UIView). I cannot, for the life of me, figure out how to show the UITableView on the 2nd screen, programmatically.
I'm overriding - (void)loadView since I'm not using IB. At different times, I've tried things like:
- (void)loadView
{
[super loadView];
// first option (thought for sure this would work)
[[self view] addSubview:secondTableView];
// another...
[self tableView:secondTableView];
// another...
[[[[self navigationController] topLevelController] view] addSubview:secondTableController];
}
I do have the table view setup properly with it's delegate and datasource, I just can't figure out how to show the damn thing. The 2nd controller is also inheriting from UITableViewController. Additionally, I don't know how you can say "fit this table view within the navigation title and the tab bar menu". I'm using CGRectMake() currently to guess the sizes, but it seems like there should be a better way (maybe that's why you use IB :-). Either way, that's secondary to even getting something to show up in the first place.
Thanks in advance for any suggestions.
If you're inheriting from UITableViewController your view should be a UITableView not a plain UIView, then when you want to create a view programmatically you should allocate it, initialize it, set its properties and assign it as your controller's view, I haven't tried [super loadView] before but it's creating the tableView for you and assigning dataSource and delegate to self, so you don't have to do that. So it should go like this
- (void)loadView {
UITableView *tempPointer = [[UITableView alloc] initWithFrame:CGRectMake(0, 0, 320, 480) andStyle:UITableViewStylePlain];
self.view = tempPointer; // controller retains view property so we should release it
[tempPointer release];
// If you want your view to be resized automatically (to fit)
self.view.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.tableView.dataSource = self;
self.tableView.delegate = self;
//...
}
or
- (void)loadView {
[super loadView];
self.tableView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
secondTableView = self.tableView; // or change your code to cope with self.tableView instead of secondTableView...
//...
}
Clear out some uncertainties about view hierarchy creation, retaining etc in the documentation about both UIViewController and UITableViewController if you haven't already.
have you tried:
[Self.navigationController pushViewController:secondTableView];
Hope this helps, if not could you give a little more describing the situation.

Display Custom UIView On Top of UIViewController

I'm looking for opinions on the best way to implement the following functionality. I have a UIViewController with a UITableView in Grouped Format. Each TableViewCell contains an NSString. When the User taps on a cell I'd like to in my didSelectRowForIndexPath method popup a UIView with a single textfield, that's prepopulated with the NSString in the given cell that was selected. The reason for displaying the UIVIew is I want it to be about 280 x 380 frame and still see some of the UITableView.
The goal being that it behaves like a ModalViewController except for the iPhone. Does anyone have any suggestions on how to implement this behavior or if there is a better implementation?
Create the UIView (with the UITextField inside) beforehand, and make it hidden:
// Of course, these instance variable names are made up, and should be changed
self.myModalView = [[[UIView alloc] initWithFrame:CGRectMake(0, 0, 280, 380)] autorelease];
[self.view addSubview:self.myModalView]
self.myTextField = [[[UITextField alloc] init] autorelease];
[self.myModalView addSubview:self.myTextField];
self.myModalView.hidden = YES;
Then, when the user selects a row, populate the text field and show the modal view:
- (void)tableView:(UITableView*)tableView didSelectRowAtIndexPath:(NSIndexPath*)indexPath {
// Replace stringAtIndexPath with however your data source accesses the string
NSString* myString = [self stringAtIndexPath:indexPath];
self.myTextField.text = myString;
self.myModalView.hidden = NO;
}
If you want to get fancy, you can do some CATransition stuff before showing the modal view.
I think you can use "addSubView" in UITableView. Add the ModalView to your UITableView directly. It will work.
Use some animation to implement this effect. When the UIView appears, it will lift the UITableView, like some keyboard behavior. So, you have to addSubView on the self.view and modify the UITableView's frame. And, the UITableView should be a child view of self.view, if self.view is the same as the UITableView, then you has no self.view for adding this UIView.

Subview appears behind tableview

When I add a subview to my UITableViewController, it seems to be underneath the tableview. I may be loading my subview incorrectly, or calling addSubview in the wrong place. The subview I'm referring to is the red area above the tabbar that also contains the "Click me" button:
You can see that the cell lines kind of overlap. Here is where I'm adding the subview in my TableViewController:
- (void)viewDidLoad {
[super viewDidLoad];
CGRect hRect = CGRectMake(0, 170, 320, 480);
HomeScreenOverlayView *homeView = [[HomeScreenOverlayView alloc] initWithFrame:hRect];
[self.tableView addSubview:homeView];
[homeView release];
}
Any ideas? Thanks!
I have had this issue myself and resolved it by not adding a view to the table, but rather adding the view to the table's superview.
UIView *viewToAdd = [[UIView alloc] initWithFrame: self.view.frame];
[self.view.superview addSubview: viewToAdd];
This is particularly useful when you want to mask the entire table (e.g. loading screens).
N.B. I will usually add this to viewWillAppear: in the view's lifecycle.
When you call addSubview, it is probably the first view added to the table view. Later, all the cells and support views will be added over your view.
The best thing to do is create an empty view and add both the table view and overlay view to it, making sure the overlay view is above the table view.
Views serve the 3 roles of drawing, interaction and layout. It is fine to have a view that only fills one of those roles.
You can use - (void)sendSubviewToBack:(UIView *)view or - (void)insertSubview:(UIView *)view atIndex:(NSInteger)index. addSubview always puts the new view in front.
EDIT: Sorry, misread the question, it looks like you want the subview to be in front.

iPhone Dev - Lazy loading a Tab Bar app

How can I make it so when a tab is selected, the current one is unloaded, and the next one is loaded so only one loaded at a time? Or should I not even do this? I know how to do it with a normal UIViewController as the root VC, but not sure with a UITabBarController. Also, is there a way to animate the transition from one tab to the next? Any help? Thanks!!
EDIT: ... If I unload the view controllers, then their icons on the tab bar are gone... maybe I'll just unload their views..
I can answer both questions in one...
You just need a class that acts as the UITabBarController delegate, then implement a method like so:
// Animate tab selections so they fade in and fade out
-(void)tabBarController:(UITabBarController*)tbc didSelectViewController:(UIViewController*)newSelection
{
[UIView beginAnimations:#"TabFadeIn" context:nil];
[UIView setAnimationDuration:0.6];
for( UIViewController* vc in tbc.viewControllers )
vc.view.alpha = (vc==newSelection) ? 1 : 0;
[UIView commitAnimations];
}
Now my code simply makes the tab bars fade in and out, but you could also do work here to unload non-used tabs. Sometimes that is a good idea if some of the tabs will be using a ton of memory.
You cant really manage the UITabBarController unfortunaly so you cant do lazy loading. You can by managining your own TabBar but you said u knew that already,
to manage your own tab bar though all you gotta do is setup a UITabBar with its TabBarItems in a ViewController, then implement the TabBar Delegate protocol, mainly the – tabBar:didSelectItem: method which is called whenever the tabbarItem selection is changed, then based on the item id you can load your new ViewController and release any others
so: Edit: this code goes in your UIViewController
-(void)addTabBar{
NSMutableArray* items=[[NSMutableArray alloc] init];
UITabBarItem *eventsItem= [[UITabBarItem alloc] initWithTitle:#"Events" image:nil tag:0];
UITabBarItem *albumItems=[[UITabBarItem alloc] initWithTitle:#"Album" image:nil tag:1]; //the tag is how you tell what was clicked
[items addObject:homeItem];
[items addObject:albumItems];
//MyTabBar is of type UITabBar
myTabBar=[[UITabBar alloc] initWithFrame:CGRectMake(0,411,320,49)];
[myTabBar setItems:items];
myTabBar.delegate=self; //you gotta implement the UITabBar delegate protocol
[myTabBar setSelectedItem:eventItem]; //set the selected item
[homeItem release];
[eventsItem release];
[albumItems release];
[items release];
[self.view addSubview:myTabBar]
}
then the protocol method would look something like below
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item
{
if(item.tag == 0 )
{
//load the ViewController that pertains to this item and release others
}
...etc
}
Lazy loading is not an UITabBarController task. Instead, it is responsability of your viewControllers associated with your Tab.
To release the UIView, associated with each UIViewControllers, every time you change the TabBarItem, you must implement the following method in each UIViewController subclass, associated with your UITabBarController.viewControllers property:
-(void)viewDidDisappear {
[self.view removeFromSuperview];
self.view = nil;
}
Obviously, this will remove the self.view associated with your UIViewController. However, if your code is smart enough, this will remove all the related objects.
For example, suppose that your loadView method is as follow:
-(void)loadView {
UIView *contentVew = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.view = contentView;
…
...
UILabel *aLabel = [[UILabel alloc] initWithFrame:CGRectMake(0,0,320,50)];
…
…
[contentView addSubview:aLabel];
[aLabel release];
…
[contentView release];
}
This means that every object inside the contentView and their memory responsabilities are demanded to the contentView, that is released and attached to the self.view property.
In this scenario, removing the self.view (that's the reference to the contentView) resulting in a domino-style releasing of every object, that's your goal.
Best regards
Not sure why you'd want to do this, the current tab will get unloaded anyway if there's a memory issue involved. That's what -viewWillAppear, -viewDidUnload, etc. are for.
UITabBarController does lazy load all of its view controllers. When a tab is switched out, then it's view is subject to being deallocated in a memory tight situation. It is then recreated when it is chosen the second time. Furthermore, most of your memory hits are in your views and not the view controllers. Hence, don't worry about the memory hit from the view controller. The view is the proze.
If you are running on v3 of the OS, then you can use the -viewDidUnload method to ensure the maximal amount of memory reduction.
Andrew
I'm currently using this to unload inactive view controllers in the tab bar (based on Kendall's answer)
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController: (UIViewController *)viewController {
// reload all inactive view controllers in the tab bar
for (UIViewController *vc in tabBarController.viewControllers) {
if(vc != viewController)
[vc didReceiveMemoryWarning];
}
}