I have a MainWindow class that is built from a nib and set up as follows:
- (void)applicationDidFinishLaunching:(UIApplication *)application
{
MainViewController *mainView = [[MainViewController alloc] initWithNibName:#"MainViewController"
bundle:nil];
if(!mainView)
{
return;
}
naviController = [[UINavigationController alloc] initWithRootViewController:mainView];
[naviController setToolbarHidden:YES];
[[naviController navigationBar] setTintColor:[UIColor blackColor]];
[[naviController toolbar] setTintColor:[UIColor blackColor]];
[self.window setRootViewController:naviController];
[self.window makeKeyAndVisible];
}
This works and correctly displays the MainViewController, but when I try to scroll down in the MainViewController's table view it throws an EXC_BAD_ACCESS. Apparently UIKit is referring to a second MainViewController that was built in [self.window makeKeyAndVisible]; I can't figure out why it is referencing that over the one I passed into initWithRootViewController:mainView.
Here are the two MainViewControllers. The first one I initialize, the second is created in makeKeyAndVisible.
Here is the second MainViewController being called as a zombie.
Any ideas on why this is happening?
As requested:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
MainViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MainViewCell"];
int i = [indexPath row];
NSLog(#"%d\n",i);
if (cell == nil) {
// Create a temporary UIViewController to instantiate the custom cell.
UIViewController *temporaryController = [[UIViewController alloc] initWithNibName:#"MainViewCell" bundle:nil];
// Grab a pointer to the custom cell.
cell = (MainViewCell *)temporaryController.view;
// Release the temporary UIViewController.
[temporaryController release];
}
[[cell icon] setImage:[UIImage imageNamed:[[moduleXMLList objectAtIndex:i] objectForKey:#"thumbnail"]]];
[[cell title] setText:[[moduleXMLList objectAtIndex:i] objectForKey:#"title"]];
[[cell description] setText:[[moduleXMLList objectAtIndex:i] objectForKey:#"description"]];
return cell;
}
The problem was that I was using File's Owner as a MainViewController while having a second UIViewController under Objects. This is wrong, and so once I got rid of the Object and just used File's Owner it worked like a charm.
Related
I am kinda new in Xcode and now I am working on a Expandable/Collapsable UITableView. The basic idea is to create new sub dropdown cells when the first master cell is clicked and subcells can segue to a detail view. But I hope to segue to a detail view from those new sub cells. I found there are two options:
Create a segue
Create a pushviewcontroller.
I dragged a new view controller and defined its storyboard ID, and tried to programmatically create a segue to push the new detail view. but it is not working. Could anyone help me? Thanks.
//didSelectRowAtIndexPath
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//to change the button image when selecting a row.
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
UIButton *arrowBtn = (UIButton*)[cell viewWithTag:10];
NSDictionary *dic=[self.itemsinTable objectAtIndex:indexPath.row];
if([dic valueForKey:#"list"])
{
NSArray *listarray=[dic valueForKey:#"list"];
BOOL isTableExpanded=NO;
for(NSDictionary *subitems in listarray )
{
NSInteger index=[self.itemsinTable indexOfObjectIdenticalTo:subitems];
isTableExpanded=(index>0 && index!=NSIntegerMax);
if(isTableExpanded) break;
}
if(isTableExpanded)
{
[self CollapseRows:listarray];
[arrowBtn setImage:[UIImage imageNamed:#"down_arrow.png"] forState:UIControlStateNormal];
}
else
{
NSUInteger count=indexPath.row+1;
NSMutableArray *arrCells=[NSMutableArray array];
for(NSDictionary *dInner in listarray )
{
[arrCells addObject:[NSIndexPath indexPathForRow:count inSection:0]];
[self.itemsinTable insertObject:dInner atIndex:count++];
}
[self.expandingtableView insertRowsAtIndexPaths:arrCells withRowAnimation:UITableViewRowAnimationTop];
[arrowBtn setImage:[UIImage imageNamed:#"up_arrow.png"] forState:UIControlStateNormal];
//This segue is not working...
UIViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"detailvcontroller"];
[self.navigationController pushViewController:controller animated:YES];}}}
detailvcontroller *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"detailvcontroller"];
[self.navigationController pushViewController:controller animated:YES];
You need to check couple of things here:
Storyboard identifier for DetailViewController is set correctly.
self.storyboard is initialized correctly.
Root view have navigation Controller.
I just added below storyboard intialization code. Please ensure your self.storyboard is being set this way:
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
DetailViewController *viewController = (DetailViewController *)[storyboard instantiateViewControllerWithIdentifier:#"detailvcontroller"];
[self.navigationController pushViewController:controller animated:YES];
In my project I have tableView with 10 cells. And in didSelectRowAtIndexPath all cells have multiple ViewController (files) so, my didSelectRowAtIndexPath looks like
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexPath.row == 0) {
CallViewController *viewc = [[CallViewController alloc] initWithNibName:#"CallViewController" bundle:nil];
[self.navigationController pushViewController:viewc animated:YES];
}else if(indexPath.row == 1) {
BirthdayViewController *viewc = [[BirthdayViewController alloc] initWithNibName:#"BirthdayViewController" bundle:nil];
[self.navigationController pushViewController:viewc animated:YES];
}
so I don't want these conditions
I want my code be clean
I would suggest you to have an array that contains the Class object of your class and then create object and push like
//view did load
mutableArr = [[NSMutableArray alloc] init];
[mutableArr addObject:[CallViewController class]];
[mutableArr addObject:[BirthdayViewController class]];
....
....
Then in your
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row < [mutableArr count]) {
Class *obj = [mutableArr objectAtIndex:indexPath.row];
UIViewController *controller = [[objc alloc] initWithNibName:NSStringFromClass(obj) bundle:nil];
[self.navigationController pushViewController:controller animated:YES];
}
}
OR if this is looking more weird then you can do like this
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UIViewController *controller = nil;
switch(indexPath.row) {
case 0:
controller = [[CallViewController alloc] initWithNibName:#"CallViewController" bundle:nil];
break;
case 1;
controller = [[BirthdayViewController alloc] initWithNibName:#"BirthdayViewController " bundle:nil];
break;
}
[self.navigationController pushViewController:controller animated:YES];
}
What is your underlying data model for tableview? If you could add additional attribute as ViewController Class Name you could do something like
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *row = [rowData objectAtIndex:indexPath.row];
Class viewControllerClass =
NSClassFromString([row objectForKey:#"viewControllerClassName"]);
//Default VC init looks for nib file named as VC afaik, if not, you could
//add another attribute with init selector name
UIViewController *viewController =
[[viewControllerClass alloc] init];
[self.navigationController pushViewController:viewController animated:YES];
}
You can create array of strings corresponding to file names and use NSClassFromString function to allocate view controllers
NSArray *viewControllers = [NSArray arrayWithObjects:#"VC1", #"VC2", #"VC3", nil];
id viewController = [[NSClassFromString([viewControllers objectAtIndex:indexPath.row]) alloc] initWithNibName:[viewControllers objectAtIndex:indexPath.row] bundle:nil];
[[self navigationController] pushViewController:viewController animated:YES];
[viewController release];
You can do this:
Add all your classes to NSArray, and select the correct class by the indexPath.row.
- (void)tableView:(UITableView *)tableView
didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSArray * conrollersClasses =#[[CallViewController class],[BirthdayViewController class]];
UIViewController *controller = [[conrollersClasses[indexPath.row] alloc] init];
[[self navigationController] controller animated:YES];
}
I want to pass selected row's text to another view's label. But this not work. Not pushing second view. How can I solve this problem?
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
SecondViewController *detailViewController = [[SecondViewController alloc] initWithNibName:#"SecondView" bundle:nil];
// ...
// Pass the selected object to the new view controller.
NSUInteger row2 = [indexPath row];
NSLog(#"rowden %i",row2);
denemelik=row2;
NSLog(#"kontrol %i",denemelik);
[self.navigationController pushViewController:detailViewController animated:YES];
detailViewController.stringdir= [ws2.CustomerName objectAtIndex:[indexPath row]];
NSLog(#"hoff %#", [ws2.CustomerName objectAtIndex:[indexPath row]]);
NSLog(#"kontrol2 %#",ws2.CustomerName);
[detailViewController release];
}
in my secondViewController.m viewDidLoad I set the 'stringdir' to label's text:
enyakinfirma.text = stringdir;
You just need to swap this 2 line of codes around
detailViewController.stringdir= [ws2.CustomerName objectAtIndex:[indexPath row]];
[self.navigationController pushViewController:detailViewController animated:YES];
You should set the value before calling the navigationController. At least that's what i am doing.
My root view contains three sections in a table view. Depending on which section is selected a corresponding view controller will be popped onto the view stack. The following didSelectRowAtIndexPath method is from my code and it works as I expect, but I was wondering if this is the correct/most elegant way to do it. I'm an Objective-C newcomer so I'm not sure if I should be intitializing the viewController to nil first.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UIViewController *viewController = nil;
if (indexPath.section == 0) {
viewController = [[FirstViewController alloc] initWithNibName:#"FirstViewController" bundle:nil];
}
else if (indexPath.section == 1) {
viewController = [[SecondViewController alloc] initWithNibName:#"SecondViewController" bundle:nil];
}
else if (indexPath.section == 2) {
viewController = [[ThirdViewController alloc] initWithNibName:#"ThirdViewController" bundle:nil];
}
[self.navigationController pushViewController:viewController animated:YES];
[viewController release];
}
You could do this in a swtch() statement, but otherwise, I think this is good. The only other change would be to check for a viewController before pushing:
if (viewController != nil) [self.navigationController pushViewController...
While trying out an experimental UINavigationController-based iPhone application, I ran into a problem when the user navigates back to the previous view.
The simple application uses a UINavigationController, onto which new instances of UIViewControllers are pushed.
These instances are all of the same class (in this example, class MyViewController a sub-class of UIViewController), and are manually created (not using a NIB). Each instance contains a separate UITableView instance as the UIViewController's view.
The following tableView:didSelectRowAtIndexPath: method is from the MyViewController class. It creates and pushes another MyViewController instance onto the navigationController when the user selects a table cell:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
MyViewController *nextViewController = [[MyViewController alloc] initWithNibName:nil bundle:nil];
[self.navigationController pushViewController:nextViewController animated:YES];
[nextViewController release];
}
The user can navigate forwards through a sequence of views, each one containing a table. The problem occurs when navigating back to the previous screen. The application aborts and xcode starts the debugger.
The error can be prevented by not releasing the MyViewController instance in the tableView:didSelectRowAtIndexPath: method above, or by not calling dealloc on the 'myTableView' instance in MyViewController's dealloc method.
However, that's not a real solution. As far as I know, the UINavigationController "owns" the pushed UIViewController instance, which can then safely be released from the client that allocated it. So, what can be wrong with this experimental app? Why would it terminate when the user navigates back?
Below are a few other methods of the MyViewController class:
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
self.title = #"My Table";
myTableView = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
myTableView.delegate = self;
myTableView.dataSource = self;
self.view = myTableView;
}
return self;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyTable"];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithFrame:CGRectMake(0,0, 300, 50) reuseIdentifier:#"MyTable"];
[cell autorelease];
}
cell.text = [NSString stringWithFormat:#"Sample: %d", indexPath.row];
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 3; // always show three sample cells in table
}
- (void)dealloc {
[myTableView dealloc];
[super dealloc];
}
EDIT:
Problem fixed - thanks Rob Napier for pointing out the problem.
The -loadView method now sets up the view using a local UITableView instance:
- (void)loadView {
UITableView *myTableView = [[UITableView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
myTableView.delegate = self;
myTableView.dataSource = self;
self.view = myTableView;
[myTableView release];
}
You're setting the view in the wrong method. You should set this up in -loadView, not -initwithNibName:bundle:. Look in View Controller Programming Guide "Using View Controllers" for an example.