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.
Related
I am trying to achieve this:
but i get this:
I have a view cotroller with a view table on it
This is the interface:
#interface LoginViewController : UIViewController<UITableViewDataSource, UITableViewDelegate>
#property (weak, nonatomic) IBOutlet UITableView *tblCredentials;
#end
This is the implementation:
#interface LoginViewController ()
#end
#implementation LoginViewController
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
}
-(void)viewWillAppear:(BOOL)animated
{
self.tblCredentials.delegate=self;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 2;
}
// Row display. Implementers should *always* try to reuse cells by setting each cell's reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier:
// Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"CellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
UITextField *textField = [[UITextField alloc] init];
textField.enablesReturnKeyAutomatically = YES;
textField.autocorrectionType = UITextAutocorrectionTypeNo;
textField.autocapitalizationType = UITextAutocapitalizationTypeNone;
CGRect cellBounds = cell.bounds;
CGFloat textFieldBorder = 10.f;
CGRect aRect = CGRectMake(textFieldBorder, 9.f, CGRectGetWidth(cellBounds)-(2*textFieldBorder), 31.f );
textField.frame = aRect;
if(indexPath.row==0)
{
textField.placeholder = #"Username";
textField.returnKeyType = UIReturnKeyNext;
textField.autocapitalizationType = UITextAutocapitalizationTypeWords;
}
else
{
textField.placeholder = #"Password";
textField.returnKeyType = UIReturnKeyDone;
textField.secureTextEntry = YES;
}
[cell.contentView addSubview:textField];
return cell;
}
#end
I put a breakpoint on the in the cellForRowAtIndexPath and it doesn't stop there, so those text fields don't get rendered.
What am I missing?
PS: Is this a bad approach to achieve the goal? (those two grouped text fields)
LE: I am using stroyboard with no xib files
In viewDidLoad, you must set the delegate and call [self.tblCredentials reloadData] in order for the table view to actually "load its data"
You need to create a Custom Table View cell. have a look at this github link.
You're setting the delegate of the table view, but not the datasource, which is where the number of rows etc. comes from.
You're also setting the delegate a bit late in the cycle. Since this is in a xib, why not set the delegate and datasource in the xib instead of in code? If you declare that your view controller conforms to the delegate and data source properties in the header, you will be able to make the connection in IB. If you insist on setting it in code, it should be in viewDidLoad.
Set delegate and dataSource in -viewDidLoad and put [self.tblCredentials reloadData] in -viewWillAppear:.
- (void)viewDidLoad
{
[super viewDidLoad];
self.tblCredentials.delegate=self;
self.tblCredentials.dataSource=self;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated]; // BTW, it's better to call super's -viewWillAppear: here, according to apple's documentation.
[self.tblCredentials reloadData];
}
I'm using XCode 4.2 and testing my build on iPad 5.0.
I started building an application using the standard Tabbed application in XCode and then added code to have 2 uitableviews inside the first tab.
It compiles, but the table data does not load into the view.
App delegate.h:
#interface dmbAppDelegate : UIResponder <UIApplicationDelegate, UITabBarControllerDelegate>
#property (strong, nonatomic) UIWindow *window;
#property (strong, nonatomic) UITabBarController *tabBarController;
#end
AppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]] autorelease];
// Override point for customization after application launch.
UIViewController *viewController1 = [[[dmbFirstViewController alloc] initWithNibName:#"dmbFirstViewController" bundle:nil] autorelease];
UIViewController *viewController2 = [[[dmbSecondViewController alloc] initWithNibName:#"dmbSecondViewController" bundle:nil] autorelease];
self.tabBarController = [[[UITabBarController alloc] init] autorelease];
self.tabBarController.viewControllers = [NSArray arrayWithObjects:viewController1, viewController2, nil];
NSLog(#"Loading first tab view from app delegate...");
self.window.rootViewController = self.tabBarController;
[self.window makeKeyAndVisible];
return YES;
}
FirstViewController.h:
#interface dmbFirstViewController : UIViewController {
ReservationsTable *reservationsController;
WaitlistTable *waitlistController;
IBOutlet UITableView *reserveTable;
IBOutlet UITableView *waitlistTable;
}
FirstViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSLog(#"FirstView Controller - View Loading Started");
[reserveTable setDataSource:reservationsController];
[waitlistTable setDataSource:waitlistController];
NSLog(#"FirstView Controller - Loading Table Views..");
[reserveTable setDelegate:reservationsController];
[waitlistTable setDelegate:waitlistController];
reservationsController.view = reservationsController.tableView;
waitlistController.view = waitlistController.tableView;
NSLog(#"FirstView Controller - View Loading Finished");
}
Both the tables have a .h and .m with the standard table methods implemented. I also added 2 tables in the first view nib file and linked them to the file owner.
Update:
ReserveTable.h:
#interface WaitlistTable : UITableViewController <UITableViewDataSource, UITableViewDelegate>{
NSMutableArray *waitlistitems;
}
ReserveTable.m:
- (void)viewDidLoad
{
NSLog(#"View Did Load - Wait List Table");
waitlistitems = [[NSMutableArray arrayWithObjects:#"1",#"2",#"3",#"4",#"5",#"6",#"6",#"8",#"9",#"10",#"11",#"12",#"13",#"14",#"15",#"16",#"17",nil] retain];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
NSLog(#"Inside number of section for Wait List table...");
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"Inside numberofRows for Wait List table...");
// Return the number of rows in the section.
return [waitlistitems count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"Inside cell for row at index path for Wait List table...");
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell...
cell.textLabel.text = [NSString stringWithFormat:#"1.%#" ,[waitlistitems objectAtIndex:indexPath.row]];
return cell;
}
Thoughts?
Change your viewDidLoad to this:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
reservationsController = [[ReservationsTable alloc] init];
waitlistController = [[WaitlistTable alloc] init];
NSLog(#"FirstView Controller - View Loading Started");
[reserveTable setDataSource:reservationsController];
[waitlistTable setDataSource:waitlistController];
NSLog(#"FirstView Controller - Loading Table Views..");
[reserveTable setDelegate:reservationsController];
[waitlistTable setDelegate:waitlistController];
NSLog(#"FirstView Controller - View Loading Finished");
}
basically, you are never initializing your tableViewControllers (I guess that is the name of both of them, I would change their names to something like "WaitlistTableViewController" and "ReservationsTableViewController", but that is just me.) Also, setting the 'tableView' to the 'view' is unnecessary.
Or even better, initialize them in the init method for your dmbFirstViewController.
Or just use dmbFirstViewController like this:
dmbFirstViewController.h:
#interface dmbFirstViewController : UIViewController <UITableViewDelegate, UITableViewDataSource> {
ReservationsTable *reservationsController;
WaitlistTable *waitlistController;
IBOutlet UITableView *reserveTable;
IBOutlet UITableView *waitlistTable;
NSMutableArray *waitlistitems;
NSMutableArray *reserveitems;
}
dmbFirstViewController.m:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
waitlistitems = [[NSMutableArray arrayWithObjects:#"1",#"2",#"3",#"4",#"5",#"6",#"6",#"8",#"9",#"10",#"11",#"12",#"13",#"14",#"15",#"16",#"17",nil] retain];
NSLog(#"FirstView Controller - View Loading Started");
[reserveTable setDataSource:self];
[waitlistTable setDataSource:self];
NSLog(#"FirstView Controller - Loading Table Views..");
[reserveTable setDelegate:self];
[waitlistTable setDelegate:self];
NSLog(#"FirstView Controller - View Loading Finished");
}
- (void)viewDidAppear:(BOOL)animated
{
[reserveTable reloadData];
[waitlistTable reloadData];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
NSLog(#"Inside number of section for Wait List table...");
if(tableView == waitlistTable)
{
//Return sections for waitlistTable
return 1;
}else{
//Return sections for reservedTable
return 1;
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if(tableView==waitlistTable)
{
NSLog(#"Inside numberofRows for Wait List table...");
// Return the number of rows in waitlistTable section.
return [waitlistitems count];
}else{
// Return the number of rows in reservedTable section.
return [reserveditems count];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(tableView == waitlistTable)
{
NSLog(#"Inside cell for row at index path for Wait List table...");
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell...
cell.textLabel.text = [NSString stringWithFormat:#"1.%#" ,[waitlistitems objectAtIndex:indexPath.row]];
return cell;
} else {
//Create cell for reservedTable Cell
.....
return cell;
}
}
You'll have to finish off the part about reservedTable cells, I didn't have that code. Plus, I guessed on the items array for reservedTable and did not initialize it.
I think in your firstViewController you should push the views.
Assuming that reserveTable is your main table, push it like so
[self.view addSubView:reserveTable];
Also, did you import the ReserveTable.h to your firstViewController?
If yes, you should initialize the table there though.
But what i suggest, though, is to transform the firstViewController in a tableViewController, editing the App Delegate and the firstViewController.h and m, and from there initialize the table (even adding the other one too). So that would be easier!
I am having a weird problem and i couldn`t find a solution (or something similar).
The thing is, my UITableView Populates with initial info (for testing), but no matter what i do i can't seem to put it to grouped style (i can select it on the UI but it wont show)
I initially started a TabBar project and added a third navigationController view in the tabs.
#import <UIKit/UIKit.h>
#interface RootViewController : UITableViewController <UITableViewDelegate, UITableViewDataSource> {
NSMutableArray *tableData;
}
#property (nonatomic, retain) NSMutableArray *tableData;
-(void)initTableData;
#end
This is the header, and as you can see it has nothing out of the ordinary. The following code is inside the .m file of the header i just posted(ill only be posting uncommented code:
#synthesize tableData;
-(void)initTableData
{
tableData = [[NSMutableArray alloc] init];
[tableData addObject:#"Cidade"];
[tableData addObject:#"Veículo"];
[tableData addObject:#"Ano"];
[tableData addObject:#"Valor"];
[tableData addObject:#"Cor"];
[tableData addObject:#"Combustível"];
}
- (void)viewDidLoad {
[super viewDidLoad];
self.title = #"Busca";
UIBarButtonItem *_backButton = [[UIBarButtonItem alloc] initWithTitle:#"Back" style:UIBarButtonItemStyleDone target:nil action:nil];
self.navigationItem.backBarButtonItem = _backButton;
[self initTableData];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
// Return the number of rows in the section.
return 6;
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
// Configure the cell...
cell.textLabel.text = [tableData objectAtIndex:[indexPath row]];
return cell;
}
- (void)dealloc {
[tableData release];
[super dealloc];
}
Nothing out of the ordinary again as you can see...
Any idea of what may be causing this? I tried
- (id)initWithStyle:(UITableViewStyle)style {
// Override initWithStyle: if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
self = [super initWithStyle:UITableViewStyleGrouped];
if (self) {
// Custom initialization.
}
return self;
}
because i don't know what else to do. (also didn't worked)
Again, i got the delegate and datasource set to File`s Owner.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
// Set the tab bar controller as the window's root view controller and display.
self.window.rootViewController = self.tabBarController;
RootViewController *rvc = [[RootViewController alloc] initWithStyle: UITableViewStyleGrouped];
[self.window makeKeyAndVisible];
return YES;
}
As you have modified the - (id)initWithStyle:(UITableViewStyle)style initializer to return a UITableView with a grouped style, do you call this initializer when you initialize the RootViewController?
RootViewController *rvc = [[RootViewController] alloc] initWithStyle: UITableViewStyleGrouped];
Grouped tables respond to the sections. You only have 1 section listed so you will only see the 1 group. Try and add a 2nd tableData for the 2nd group and return 2 sections. You will also have to split the data in your -cellForRowAtIndexPath by section as well to make sure the data goes to the right section.
if (indexpath.section == 0) {
// first section and first tableData
}
if (indexpath.section == 1) {
// second section and second tableData
}
Hi I am using a searchDisplayController to filter through my search results. My code snippet is as follows:
- (void)viewDidLoad
{
[super viewDidLoad];
//Set up search bar
UISearchBar *tempBar = [[UISearchBar alloc]initWithFrame:CGRectMake(0,0,320,40)];
self.sBar = tempBar;
[tempBar release];
self.sBar.delegate = self;
self.sBar.tintColor = [UIColor colorWithHexString:#"#b6c0c7"];
self.sBar.placeholder = #"Search Questions and Topics";
[self.view addSubview:sBar];
self.filteredListContent = [NSMutableArray arrayWithCapacity:[self.listContent count]];
self.searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:sBar contentsController:self];
[self setSearchDisplayController:searchDisplayController];
[searchDisplayController setDelegate:self];
[searchDisplayController setSearchResultsDataSource:self];
// restore search settings if they were saved in didReceiveMemoryWarning.
if (self.savedSearchTerm)
{
[self.searchDisplayController setActive:self.searchWasActive];
[self.searchDisplayController.searchBar setText:savedSearchTerm];
self.savedSearchTerm = nil;
}
[self.resultTableView reloadData];
self.resultTableView.scrollEnabled = YES;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"zzz");
}
Do I need to setup anything in my view did load to trigger the uitable delegate method?
Note that I have already declared UISearchDisplayDelegate, UISearchBarDelegate, UITableViewDataSource, UITableViewDelegate in my .h files. Also my tableView cellForRowAtIndexPath method is working.
Have you actually set
self.tableView.delegate = self
or connected it up in Interface Builder
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...