iPhone: Creating a hierarchy-based table navigation - iphone

I've tried to ask this before, but nothing got answered. Basically, I would like someone to explain to me how to create a table, which when a cell is tapped, pushes the user to the next view for that cell. I have this so far:
Click here to view what I have.
I would further like to, say when CSS is tapped, it goes to a new view which has another table in it. This table would then take the user to a detail view, which is scrollable and you can switch pages through it.
I would appreciate longer, more structured tutorials on how to do each and every bit to get it to work.
Here's my array in my implementation file:
- (void)viewDidLoad {
arryClientSide = [[NSArray alloc] initWithObjects:#"CSS", #"HTML", #"JavaScript", #"XML", nil];
arryServerSide = [[NSArray alloc] initWithObjects:#"Apache", #"PHP", #"SQL", nil];
self.title = #"Select a Language";
[super viewDidLoad];
}
and my .h:
#interface RootViewController : UITableViewController <UITableViewDelegate, UITableViewDataSource> {
IBOutlet UITableView *tblSimpleTable;
NSArray *arryClientSide;
NSArray *arryServerSide;
}
My current code crashes the script, and this error is returned in the console:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[UITableViewController loadView] loaded the "NextView" nib but didn't get a UITableView.'
If that error is the source of why it's not pushing, then an explanation of how to remedy that would also be appreciated
NextViewController implementation
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
arryBasicCSS = [[NSArray alloc] initWithObjects:#"Implement", #"Syntax", #"Classes and IDs", #"Fonts", #"Backgrounds", #"Lists", #"Links", nil];
arryIntermediateCSS = [[NSArray alloc] initWithObjects:#"Padding and Margin", #"Alignment and Floating", #"Pseudo-class and Element", #"Opacity and Display", nil];
arryAdvancedCSS = [[NSArray alloc] initWithObjects:#"Sprites", #"Attribute Selectors", #"Animation", nil];
self.title = #"CSS";
[super viewDidLoad];
}
- (IBAction) changeItemTable:(NSString *)str{
tblCSS = str;
}
NextViewController.h
#interface NextViewController : UITableViewController {
IBOutlet UITableView *tblCSS;
NSArray *arryBasicCSS;
NSArray *arryIntermediateCSS;
NSArray *arryAdvancedCSS;
}
Many thanks,
Jack

First the "Terminating app due to uncaught exception" error. I noticed that your RootViewController contains:
IBOutlet UITableView *tblSimpleTable;
Check to make sure that you have properly connected RootViewController's "view" property to your UITableView, and not just connected tblSimpleTable to your TableView. The view property in a UITableViewController needs to point to a UITableView.
Assuming that tblSimpleTable is the TableView that you want to control from this UIViewController, delete this outlet and just use the "view" or "tableView" property of the UITableViewController, they will both be valid.
For your original problem of hierarchical table views, have at look at this sample project:
TheElements

If the SDK's own documentation isn't providing the answers you need, try a Google search for UITableView Interface Builder tutorial. That should return a number of useful step-by-step tutorials.
The reason you're getting the exception is because you haven't connected your tblSimpleTable outlet to your table view object in Interface Builder.
Open NextView.xib in Interface Builder. Select the File's Owner object. Open the Inspector pane, and you should see something similar to the image I've posted. Instead of "searchTable" yours should read "tblSimpleTable". To connect the outlet to the file's owner, click-and-hold on the circle to the right of "tblSimpleTable" and drag the line to your "tblSimpleTable" object.
Save your changes, rebuild your project.

See this:
http://adeem.me/blog/2009/05/19/iphone-sdk-tutorial-part-2-navigation-in-uitableview/

Related

Scroll position in Text View object on detail view doesn't reset when returning

I have information in a Master View which uses a UITableView object and a detail ViewContrailer that comes up after I push a user selection to it.
My challenge is with scrolling in a UITextView object, that doesn't go back to the top of the scrolling area on the detail View.
The user chooses a detail object from a list in the Master View Controller, then , brings it up in the detail ViewController. She then scrolls within the UITextView object - dogScrollingInfoTextView- on that view, going farther down so that upper lines don't show anymore. She then return to the Master View Controller, and selects a different row. Upon returning to the detail view, the new object's UITextView object (dogScrollingInfoTextView) is still positioned where the previous view left it.
The object being passed into this UITextView in the detailViewController, looks like this in the Master View Controller
self.detailViewController.dogScrollingInfo = dog.whatMakesDogSpecial;
In the original class definition, this property is declared like this
#property (strong, nonatomic) NSString *whatMakesDogSpecial;
The object used to transfer within the detailView controller class to the view are declared like this
#property (strong, nonatomic) NSString *dogScrollingInfo;
#property (weak, nonatomic) IBOutlet UITextView *dogScrollingInfoTextView;
and in the detailViewController implementation the transfer looks like this
self.dogScrollingInfoTextView.text = [self.dogScrollingInfo description];
I'm not sure if I should attach screen shots ?
Should I put in any other code? (I have put two methods below, one from each view controller class)
I can't find anything specifically related to scrolling in the UITextView Class or the UIView class, that tells me I have to do something programatically. This seems like it may have something to do with InterfaceBuilder. I'm using xib's NOT storyboards. But I can't find any choices in the different Inspectors that change this behavior.
Is configureView, which I use in the detail view controller class, perhaps not the right method to use for this? I think I found an example using the configureView method in the Xcode Master-Detail project template, though I built my application up from a Single View project template. And if not, how would I figure out what method to use?
I'm trying to learn to work with the apple documentation to solve challenges like this, but I find it hard navigating it. So pointers as to how to use the documentation better are much appreciated.
.... Code Snip from the Master View Controller class...
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (!self.detailViewController) {
self.detailViewController = [[HardlyWorkingDogsDetailViewController alloc]
initWithNibName:#"HardlyWorkingDogsDetailViewController"
bundle:nil ];
}
NSLog(#"The user selected dog #array position %d", indexPath.row);
Dog *dog = self.sortedDogDictionaryArray[indexPath.row];
self.detailViewController.dogName= dog.dogName;
self.detailViewController.dogLicense = dog.licenseString;
self.detailViewController.dogScrollingInfo = dog.whatMakesDogSpecial;
self.detailViewController.dogPhoto = dog.dogPhoto;
[self.navigationController pushViewController:self.detailViewController animated:YES];
}
.... Code Snip from the Detail View Controller class...
-(void)configureView {
//NSLog(#"ConfigureView # 1 in detail vu");
if (self.dogLicense) {
self.dogLicenseUILabel.text = [self.dogLicense description];
self.dogNameUIText.text = [self.dogName description];
self.dogScrollingInfoTextView.text = [self.dogScrollingInfo description];
self.dogPhotoUIImage.image = self.dogPhoto;
}
}
Thank you
Laurel
Since you are reusing a HardlyWorkingDogsDetailViewController, this behavior makes sense. If HardlyWorkingDogsDetailViewController is light weight enough, there really isn't any need to reuse it. Simply reinstantiating it everytime should fix your problem:
self.detailViewController = [[HardlyWorkingDogsDetailViewController alloc] initWithNibName:#"HardlyWorkingDogsDetailViewController" bundle:nil ];
Dog *dog = self.sortedDogDictionaryArray[indexPath.row];
self.detailViewController.dogName= dog.dogName;
self.detailViewController.dogLicense = dog.licenseString;
self.detailViewController.dogScrollingInfo = dog.whatMakesDogSpecial;
self.detailViewController.dogPhoto = dog.dogPhoto;
[self.navigationController pushViewController:self.detailViewController animated:YES];
Although, having not seen your other logic, I can't say that won't introduce other bugs if any of your code depends on self.detailViewController only being created once (keeping a reference to it elsewhere, for example). If that is the case, or you just don't want to change the MasterViewController code, you can simple put the following in HardlyWorkingDogsDetailViewController:
-(void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[self.dogScrollingInfoTextView setContentOffset:CGPointMake(0, 0) animated:NO];
}
This makes it so every time the view is about to appear on the screen, the scroll view will get set back to the top. (viewDidLoad, viewWillAppear:, viewDidAppear:, viewWillDisappear:, viewDidDisappear: are part of UIViewController lifecycle and are very useful. You should read up on them)
On a side note, UITextView is implemented using a UIScrollView, so any scrolling method a UIScrollView has, the UITextView can do as well. More info here

Handling string objects and View Controllers

I am trying to pass a string back and forth between the view Controllers, so for example as soon as I click on a tab bar button (+) in the first View, second view opens (PresentModalViewController) and it has a Text Field. So anything I type, I take it into a string(this string is an object of the first view) and I am trying to append that string to a tableview loaded in the first View.
Note: My string object is declared like this
View1.h
NSString *string
#property (copy) NSString *string;
View1.m
#synthesize string;
And in the View 2 I am passing the textField Value like this
View2.m
View1 *view1 = [[View1 alloc] initWithNibName:#"View1" bundle:nil];
view1.string = [[NSString alloc] initWithFormat:#"%#", TextField.text];
Problem - When I NSLog this value inside the View2, it grabs the value from the Text Field but in order for me to load the previous view, I need to dismiss this View2. So as soon as this View2 is dismissed when I try to access the same string object in my view 1. It says the string object is null.
Question - Could someone tell me
1. How to get the text Field value from view 2 to view 1 after dismissing View 2 (does it really makes all its objects null when dismissed?)
2. How to append that string to the last index of a NSMutableArray?
This is a very good question that I also had trouble figuring out when I started coding for the iOS. Basically, you don't need to initialize a new view1 because the tabbar controller already holds the view1 object in its viewControllers property. Also, alloc/init'ing the string in not necessary in this situation.
Therefore, you would want to change this:
View1 *view1 = [[View1 alloc] initWithNibName:#"View1" bundle:nil];
view1.string = [[NSString alloc] initWithFormat:#"%#", TextField.text];
To something like this:
View1 *view1 = [self.tabbarController.viewControllers objectAtIndex:0];
view1.string = textField.text;
Or even:
((View1 *)[self.tabbarController.viewControllers objectAtIndex:0]).string = textField.text;
Part 2:
How to append that string to the last index of a NSMutableArray?
NSMutableArray *someArray = [[NSMutableArray alloc] init];
[someArray addObject:string];
[someArray addObject:#"anotherString"];
The answer from #chown will definitely work if the ViewController you're sending the string to is the base controller of a tabBarController.
If you were several levels deep into a NavigationController stack, then you'll need a different approach.
The approach I'd recommend would be to create a protocol. This is where you create a delegate of view2 to pass the string back down the stack before the view is dismissed.
There are loads of examples of this code both in the Apple Documentation and on the Internet (StackOverflow included) but here's a quick run down...
In View2.h
#import <UIKit/UIKit.h>
//define the protocol, so you can set the delegate to this type
#protocol View2Delegate;
#interface View2 : UIViewController
//other properties etc
#property (assign) id <View2Delegate> delegate;
#end
//put the actual protocol definition here so we can pass a reference to ourself back up too if needed...
#protocol View2Delegate
- (void)view2:(View2*)view passingStringBack:(NSString *)stringToPassBack;
#end
In View2.m you can call that delegate method where ever you like but here's an example:
- (void)viewWillDisappear:(BOOL)animated
{
if(self.delegate)
[self.delegate view2:self passingStringBack:#"String I'm passing back"];
[super viewWillDisappear:animated];
}
Then in View1.h
#interface View2 : UIViewController <View2Delegate>
and View1.m
- (void)view2:(View2*)view passingStringBack:(NSString *)stringToPassBack
{
NSLog(#"%#", stringToPassBack);
}
Another option would be to post a notification, but that is more a broadcast scenario than a targeted message so I won't bother posting example code for that.

Loading UITableView form UIView

I am working on navigation based application in which i can navigate to many views starting from default UITableView which is starting view in application template.
I have added another UIView and added tableView control on that UIView.
I am calling that view form one of the many views on a button click. Its showing the view but not populating the data in the table. And I also want to handle the event when user taps on the cell of the table of that view. Below is the Code I am using on button click:
if(self.lstView == nil)
{
ListViewController *viewController = [[ListViewController alloc] initWithNibName:#"ListViewController" bundle:nil];
self.lstView = viewController;
[viewController release];
}
[self.navigationController pushViewController:lstView animated:YES];
self.lstView.title = #"Select";//#"System";
[self.lstView.tblList.dataSource fillList];
Below is the fillList function code:
-(NSArray *)fillList
{
NSArray *tempArray = [[[NSArray alloc] initWithObjects:#"Item 1" , #"Item 2" , nil]autorelease];
return tempArray;
}
I am pretty new in Iphone programming and don't have much understanding.
Helping with detailed description and code will be highly appreciated.
Thanks.
try setting tableviews datasource and delegate property
tblList.dataSource=self;
tblList.delegate=self;
Rest of the work you have to do in delegate methods of the table view..

Pop-up modal with UITableView on iPhone

I need to pop up a quick dialog for the user to select one option in a UITableView from a list of roughly 2-5 items. Dialog will be modal and only take up about 1/2 of screen. I go back and forth between how to handle this. Should I subclass UIView and make it a UITableViewDelegate & DataSource?
I'd also prefer to lay out this view in IB. So to display I'd do something like this from my view controller (assume I have a property in my view controller for DialogView *myDialog;)
NSArray* nibViews = [[NSBundle mainBundle] loadNibNamed:#"DialogView" owner:myDialog options:nil];
myDialog = [nibViews objectAtIndex:0];
[self.view addSubview:myDialog];
problem is i'm trying to pass owner:myDialog which is nil as it hasn't been instantiated...i could pass owner:self but that would make my view controller the File's Owner and that's not how that dialog view is wired in IB.
So that leads me to think this dialog wants to be another full blown UIViewController... But, from all I've read you should only have ONE UIViewController per screen so this confuses me because I could benefit from viewDidLoad, etc. that come along with view controllers...
Can someone please straighten this out for me?
There is no such thing as a view controller being on the screen; its view is on the screen. With that said, you can present as many views as you want on the screen at once.
I would create a new view and view controller. You would not make a UIView be a UITableViewDelegate, you make a UIViewController be a UITableViewDelegate. But instead of doing that manually, instead make your new view controller a subclass of UITableViewController, if you're using iPhone OS 3.x+. You can then present this view controller modally.
You probably want to give the user a chance to cancel out of the selection. A good way to do that is to wrap your new dialog view controller in a UINavigationController and then put a "Cancel" button in the nav bar. Then use the delegate pattern to inform the parent view controller that the user has made their choice so you can pop the stack.
Here's what the code will look like inside your parent view controller, when you want to present this option dialog:
- (void)showOptionView
{
OptionViewController* optionViewController = [[OptionViewController alloc] initWithNibName:#"OptionView" bundle:nil];
optionViewController.delegate = self;
UINavigationController* navController = [[UINavigationController alloc] initWithRootViewController:optionViewController];
[self.navigationController presentModalViewController:navController animated:YES];
[navController release];
[optionViewController release];
}
Your OptionViewController .h will look like this:
#protocol OptionViewControllerDelegate;
#interface OptionViewController : UITableViewController
{
id<OptionViewControllerDelegate> delegate;
}
#property (nonatomic, assign) id<OptionViewControllerDelegate> delegate;
#end
#protocol OptionViewControllerDelegate <NSObject>
- (void)OptionViewController:(OptionViewController*)OptionViewController didFinishWithSelection:(NSString*)selection;
// or maybe
- (void)OptionViewController:(OptionViewController*)OptionViewController didFinishWithSelection:(NSUInteger)selection;
// etc.
#end
Your OptionViewController.m will have something like this:
- (void)madeSelection:(NSUInteger)selection
{
[delegate OptionViewController:self didFinishWithSelection:selection];
}
Which has a matching method back in your original view controller like:
- (void)OptionViewController:(OptionViewController*)OptionViewController didFinishWithSelection:(NSUInteger)selection
{
// Do something with selection here
[self.navigationController dismissModalViewControllerAnimated:YES];
}
There are plenty of examples throughout Apple's sample source code that follow this general pattern.

iPhone: IBAction Causes "Unrecognized Selector Sent to Instance" Error

I'm working on my first real iPhone app, a simple To-Do list application to help me organize stuff, except I'm getting an "unrecognized selector sent to instance 0x".
Specifically:
2010-02-20 14:30:09.200 ToDoApp[88562:20b] *** -[NSCFDictionary switchViews:]: unrecognized selector sent to instance 0x3d22de0
2010-02-20 14:30:09.201 ToDoApp[88562:20b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[NSCFDictionary switchViews:]: unrecognized selector sent to instance 0x3d22de0'
I've looked around and figured out that it might be a connection problem in IB, but I'm new to this whole connecting thing (man, I wish they supported Java or Python), so here's how it's laid out. I've got 3 classes, a SwitchViewController, a MainScreenViewController, and a ToDoListViewController. When I hit a button on MainScreenViewController, I trigger the "switchViews" function that's throwing this problem. They way I've got it set up is that the button (a UIBarButtonItem) has the "sentAction" go to switchViews. This ViewButton has its reference outlet as a IBOutlet in SwitchViewController.
So here's the .h for SVC:
#import <UIKit/UIKit.h>
#class MainScreenViewController;
#class ToDoListViewController;
#class EditViewController;
#define kMinimumGestureLength 25
#define kMaximumVariance 5
#interface SwitchViewController : UIViewController {
MainScreenViewController *mainScreenViewController;
ToDoListViewController *toDoListViewController;
EditViewController *editViewController;
IBOutlet UIBarButtonItem *viewButton;
CGPoint gestureStartPoint;
}
#property (retain, nonatomic) MainScreenViewController *mainScreenViewController;
#property (retain, nonatomic) ToDoListViewController *toDoListViewController;
#property (retain, nonatomic) EditViewController *editViewController;
#property (retain, nonatomic) IBOutlet UIBarButtonItem *viewButton;
#property CGPoint gestureStartPoint;
-(IBAction)switchViews:(id)sender;
And for the switchViews function:
-(IBAction) switchViews:(id)sender
{
NSLog(#"Switching views");
if(self.toDoListViewController.view.superview == nil){
if(self.toDoListViewController ==nil){
ToDoListViewController *toDoVC = [[ToDoListViewController alloc] initWithNibName:#"ToDoListView" bundle:nil];
self.toDoListViewController = toDoVC;
//[toDoVC release];
}
[mainScreenViewController.view removeFromSuperview];
[self.view insertSubview:toDoListViewController.view atIndex:0];
}
else{
if(self.mainScreenViewController == nil){
MainScreenViewController *mainController = [[MainScreenViewController alloc] initWithNibName:#"MainScreenView" bundle:nil];
self.mainScreenViewController = mainController;
//[mainController release];
}
[toDoListViewController.view removeFromSuperview];
[self.view insertSubview:mainScreenViewController.view atIndex:0];
}
}
So in short, I'm totally lost, and this is really frustrating. Anyone have any advice, or need any more code?
We just ran into the same problem. It seems we released the ViewController object in the AppDelegate and then our nib view tried to invoke an IBAction (on the view controller). Half the time we were getting "EXC_BAD_ACCESS" (aka messaging a released object), and half the time we were getting "unrecognized selector sent to instance" of NSCFString, NSCFArray, all sorts of things (aka messaging an area of memory now taken up by a different object).
Just check your ViewController hasn't been released.
Okay, got the solution pointed out to me. Should have had it routed through FirstResponder (I ... really don't understand why that works, but at this point I'm just happy it works.)
I'm not sure how first responder works anyways (none of the books I have really mention it), but it... works? If someone wants to give me a rundown, that'd be useful... but this question has been answered.
I'm going to guess the problem is in your nib file.
The error means that upon clicking the button, the button tries to send a message/method-call of switchView to an NSDictionary object which of course has no such method. The error then lays in where the buttons action is pointed.
Check the nib for this view. Look at the File Owner and check the class assigned to it. Make sure it is SwitchViewController and not a dictionary for some reason. If the File Owner property is set to a dictionary class it will load a dictionary and try to send the action method it to it.
The right answer is this:
The view controller that we assign as the first screen in the app delegate shouldn't be released in the - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions method. In your case the first screen is MainScreenViewController.
It (MainScreenViewController instance)should be released in app delegate's dealloc method.
- (void)dealloc
{
[_window release];
[mainScreenViewController release];
[super dealloc];
}
This might also help. The Analyzer routine recommended I release a few objects, and I did. Turns out, I needed those objects in the app. In the xAppDelegate.m file (or whatever) in the appDidFinishLaunching method/message/function/routine/thing, use
UINavigationController *navController = [[UINavigationController alloc] init];
instead of
UINavigationController *navController = [[[UINavigationController alloc] init] autorelease];
Also, Analyzer recommended I release the object I pushed onto the nav controller. Big mistake. That file was my menu screen and when I pressed a button, I received a unrecognized selector sent to instance. Apparently, it was calling IBAction on NSString and NSDictionary, which is not good.
FYI I was getting this when using ARC and the xib was being loading and put onto the screen, but somehow the VC itself was not being retained.
I solved it by adding a variable to store reference in the VC that was presenting it.
The problem is, you have initiated the UIViewController instance as a method variable. So the view controller have no scope after the execution of the method and so it is released from the memory cycle. So you have to make your view controller instance as class level.
#interface SwitchViewController () {
ToDoListViewController *toDoVC;
MainScreenViewController *mainController;
}
-(IBAction) switchViews:(id)sender
{
if (!toDoVC)
toDoVC = [[ToDoListViewController alloc] initWithNibName:#"ToDoListView" bundle:nil];
if (!mainController)
mainController = [[MainScreenViewController alloc] initWithNibName:#"MainScreenView" bundle:nil];
//Your stuff with the view controllers...
}