What are some reasons why custom UITableViewCells might work in iOS 6 but not iOS 5? - objective-c

I am having a ton of trouble trying to get a table view to work on my iPhone. The weird thing is that it seems to work completely fine on my iOS simulator (i.e., I can add an entry to an array, and that entry shows up in my table view). However, when I try to add an entry when using my iOS device, the codes breaks on the line dequeueReusableCellWithIdentifier:. I've checked for capitalization inconsistencies, name inconsistencies, have reimplemented prepareForReuse in the custom UITableViewCell subclass, have tried defining fields in my UITableViewCell subclass using IBOutlets v. tags, and perhaps a few more things but none have worked.
This questions is tangentially related to my previous question: Debugging strategies when UITableView's cells don't load?
The tough part about programming is always knowing which question to ask, so I apologize if it turns out I am asking the wrong questions.
UPDATE 6: Problem Is Custom Layout For UITableViewCell On iOS 5
I tested using a subclass of UITableViewCell and UITableViewCell with a custom layout. Using a subclass of UITableViewCell with style UITableViewCellStyleDefault does work on both iOS 5 and iOS 6 iPhone simulator. However, using a generic UITableViewCell with a custom style crashes on iOS 5 but not iOS 6. Interestingly, I don't see a declaration for a custom UITableViewCellStyle in the documentation for UITableViewCell...
UPDATE 5: iOS 5 v. 6 + Custom UITableViewCell Subclass?
Hello: Continued testing today and it appears that it is an issue between how iOS 5 and 6 treat custom UITableViewCell subclasses. No solution yet :(
UPDATE 4: iOS 5 v. iOS 6?
So all I've been able to notice is that this seems to be an issue with iOS 5 versus iOS 6. When testing on iOS 6 using line GlassboxCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier] the code below works. However, neither that line nor GlassboxCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath] work in iOS 5. Any ideas? I somehow got it to work exactly once by changing the identifier to protocell.
UPDATE 3: I now use GitHub!
Here is the relevant repo: https://github.com/kenmhaggerty/Glassbox
UPDATE 2: More Observations
So I had added in #synthesize tableView = _tableView because I read in a response somewhere that it might help, but I now realize that it stopped my data from loading in my table view even when running on the iOS simulator. Commenting out that line of code returns the code back to how I describe it above: it works just fine on the iOS simulator but breaks on line dequeueReusableCellWithIdentifier: with no specified error, just Thread 1: breakpoint 1.1.
UPDATE 1: Relevant Code
GlassboxTableViewController.h
//
// GlassboxTableViewController.h
// Glassbox
//
// Created by Ken M. Haggerty on 10/22/12.
// Copyright (c) 2012 Ken M. Haggerty. All rights reserved.
//
#pragma mark - // NOTES (Public) //
#pragma mark - // IMPORTS (Public) //
#import <UIKit/UIKit.h>
#pragma mark - // PROTOCOLS //
//#protocol GlassboxTableViewDatasource <NSObject>
//#property (nonatomic, weak) NSMutableArray *arrayOfPlayers;
//#end
#pragma mark - // DEFINITIONS (Public) //
#interface GlassboxTableViewController : UITableViewController
#property (nonatomic, strong) NSMutableArray *arrayOfPlayers;
- (IBAction)addPlayer:(UIBarButtonItem *)sender;
//#property (nonatomic, strong) id <GlassboxTableViewDatasource> datasource;
#end
GlassboxTableViewController.m
//
// GlassboxTableViewController.m
// Glassbox
//
// Created by Ken M. Haggerty on 10/22/12.
// Copyright (c) 2012 Ken M. Haggerty. All rights reserved.
//
#pragma mark - // NOTES (Private) //
#pragma mark - // IMPORTS (Private) //
#import "GlassboxTableViewController.h"
#import "GlassboxCell.h"
#import "Player.h"
#import <MobileCoreServices/MobileCoreServices.h>
#pragma mark - // DEFINITIONS (Private) //
#define SIDEBAR_WIDTH_PERCENT 0.75
#interface GlassboxTableViewController () <UITableViewDataSource, UITableViewDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate>
//#property (nonatomic, weak) IBOutlet UITableView *tableView;
- (void)setup;
#end
#implementation GlassboxTableViewController
#pragma mark - // SETTERS AND GETTERS //
#synthesize arrayOfPlayers = _arrayOfPlayers;
#synthesize tableView = _tableView;
//#synthesize datasource = _datasource;
- (void)setArrayOfPlayers:(NSMutableArray *)arrayOfPlayers
{
_arrayOfPlayers = arrayOfPlayers;
}
- (NSMutableArray *)arrayOfPlayers
{
if (!_arrayOfPlayers) _arrayOfPlayers = [[NSMutableArray alloc] init];
// [_arrayOfPlayers addObject:[[Player alloc] initWithUsername:#"Ken H.:"]];
return _arrayOfPlayers;
}
#pragma mark - // INITS AND LOADS //
- (void)setup
{
self.tableView.dataSource = self;
self.tableView.delegate = self;
}
- (id)initWithStyle:(UITableViewStyle)style
{
NSLog(#"[initWithStyle]");
self = [super initWithStyle:style];
if (self) {
[self setup];
}
return self;
}
- (void)viewDidLoad
{
NSLog(#"[viewDidLoad]");
[super viewDidLoad];
[self setup];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
}
//- (void)viewDidAppear:(BOOL)animated
//{
// [super viewDidAppear:animated];
// [self.view setFrame:CGRectMake(self.view.frame.origin.x, self.view.frame.origin.y, self.view.frame.size.width*SIDEBAR_WIDTH_PERCENT, self.view.frame.size.height)];
//}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - // PUBLIC FUNCTIONS //
- (IBAction)addPlayer:(UIBarButtonItem *)sender
{
[self alertAddPlayer];
}
#pragma mark - // PRIVATE FUNCTIONS //
- (void)alertAddPlayer
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Add New Player" message:#"Please type player name:" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"OK",nil];
alert.alertViewStyle = UIAlertViewStylePlainTextInput;
alert.tag = 1;
[alert show];
}
- (void)alertInvalidPlayer
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Invalid Name" message:#"Please type another name:" delegate:self cancelButtonTitle:#"Cancel" otherButtonTitles:#"OK",nil];
alert.alertViewStyle = UIAlertViewStylePlainTextInput;
alert.tag = 1;
[alert show];
}
- (void)alertAddPhoto
{
NSLog(#"[TEST] alertAddPhoto");
if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera])
{
NSArray *mediaTypes = [UIImagePickerController availableMediaTypesForSourceType:UIImagePickerControllerSourceTypeCamera];
if ([mediaTypes containsObject:(NSString *)kUTTypeImage])
{
UIImagePickerController *imagePickerController = [[UIImagePickerController alloc] init];
imagePickerController.delegate = self;
imagePickerController.sourceType = UIImagePickerControllerSourceTypeCamera;
imagePickerController.allowsEditing = YES;
// imagePickerController.cameraDevice = UIImagePickerControllerCameraDeviceFront;
// imagePickerController.cameraCaptureMode = UIImagePickerControllerCameraCaptureModePhoto;
imagePickerController.mediaTypes = [NSArray arrayWithObject:(NSString *)kUTTypeImage];
// [self presentViewController:imagePickerController animated:YES completion:nil];
imagePickerController.cameraDevice = UIImagePickerControllerCameraDeviceFront;
[self presentModalViewController:imagePickerController animated:YES];
return;
}
}
NSLog(#"[TEST] No camera available");
[self.tableView reloadData];
}
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
UIImage *image = [info objectForKey:UIImagePickerControllerEditedImage];
if (!image) image = [info objectForKey:UIImagePickerControllerOriginalImage];
if (image)
{
[[self.arrayOfPlayers lastObject] setPhoto:[[UIImageView alloc] initWithImage:image]];
}
[self dismissImagePicker];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker
{
[self dismissImagePicker];
}
- (void)dismissImagePicker
{
// [self dismissViewControllerAnimated:YES completion:^{
// [self.tableView reloadData];
// }];
[self dismissModalViewControllerAnimated:YES];
[self.tableView reloadData];
}
- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{
if (buttonIndex == 0) NSLog(#"Cancel tapped");
else
{
if (alertView.tag == 1)
{
if (buttonIndex == 1)
{
if ([[[alertView textFieldAtIndex:0] text] length] != 0)
{
[self.arrayOfPlayers addObject:[[Player alloc] initWithUsername:[[alertView textFieldAtIndex:0] text]]];
[self alertAddPhoto];
}
else [self alertInvalidPlayer];
}
}
}
}
#pragma mark - // PRIVATE FUNCTIONS (Miscellaneous) //
// TableView data source //
//- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
//{
//#warning Potentially incomplete method implementation.
// // Return the number of sections.
// return 0;
//}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// return self.datasource.arrayOfPlayers.count;
return self.arrayOfPlayers.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"New Cell";
// GlassboxCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
GlassboxCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
cell.name.text = [[self.arrayOfPlayers objectAtIndex:indexPath.row] username];
cell.action.text = #"LOADED SUCCESSFULLY";
cell.time.text = #"Just now";
cell.photo = [[self.arrayOfPlayers objectAtIndex:indexPath.row] photo];
// [((UILabel *)[cell viewWithTag:1]) setText:[[self.arrayOfPlayers objectAtIndex:indexPath.row] username]];
// [((UILabel *)[cell viewWithTag:2]) setText:#"has been added."];
// [((UILabel *)[cell viewWithTag:3]) setText:#"Just now"];
// [((UIImageView *)[cell viewWithTag:4]) setImage:[[[self.arrayOfPlayers objectAtIndex:indexPath.row] photo] image]];
[cell.contentView setFrame:CGRectMake(cell.contentView.frame.origin.x, cell.contentView.frame.origin.y, cell.contentView.frame.size.width, 120)];
return cell;
}
/*
// Override to support conditional editing of the table view.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the specified item to be editable.
return YES;
}
*/
/*
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
*/
/*
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
}
*/
/*
// Override to support conditional rearranging of the table view.
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the item to be re-orderable.
return YES;
}
*/
// TableView delegate //
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
/*
<#DetailViewController#> *detailViewController = [[<#DetailViewController#> alloc] initWithNibName:#"<#Nib name#>" bundle:nil];
// ...
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:detailViewController animated:YES];
*/
}
#end
Let me know if I should post more.

(Your github project does not compile (Player.h/Player.m missing), so it is difficult to reproduce the issue.)
But I noticed that "Use Autolayout" is on in the MainStoryboard file. Autolayout works only on iOS 6 and later, not on iOS 5!

Please post your code. I can advice you to leave the xib files for uitableviewcell and try again. Do you use one kind of cell? or you use few different style?
===== ANSWER =====
When you create cell GlassboxCell *cell and use dequeueReusableCellWIthIdentifier you need check if cell is allocated.
// use dequeueReusableCellWithIdentifier for init cell
if (cell == nil){
cell = [[[GlassboxCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:yourIdentifier]] autorelease];
}
//rest your code for init properties for cell

ok man try to use this function instead of yours.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"NewCell";
GlassboxCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[GlassboxCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.name.text = [[self.arrayOfPlayers objectAtIndex:indexPath.row] username];
cell.action.text = #"LOADED SUCCESSFULLY";
cell.time.text = #"Just now";
cell.photo = [[self.arrayOfPlayers objectAtIndex:indexPath.row] photo];
[cell.contentView setFrame:CGRectMake(cell.contentView.frame.origin.x, cell.contentView.frame.origin.y, cell.contentView.frame.size.width, 120)];
return cell;
}

I think I found the problem...at least it works on my project.
When you make a UITableViewController class with an older version of Xcode (say 4), the generated code for cellForRowAtIndexPath is this:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
But when you make a UITableViewController with Xcode 4.5.2 (mine), the generated code for cellForRowAtIndexPath is this:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
So in my case all I did was remove the "forIndexPath:indexPath" portion and it works!
(This is my first answer ever so please be gentle if I screwed up something)

Related

TableView reloadData doesn't call cellForRowAtIndexPath

I have two view controllers/
1. SetupRingBoardViewController
2. SetupRingBoard*ADD*ViewController
the first view controller is UITableViewController.
when we first launch the view - the ViewController has a 1 fixed section with a 1 fixed row.
In that ViewController, there is a UIBarButton that is calling the SetupRingBoardADDViewController (modal - default, I'm using storyboard).
the second view controller is UIView controller.
this viewController contains a UITableView and a UINavigationBar.
the UITableView is actually a one big form, that the user can enter data into it.
the UINavigationBar contains an 'Add' UIBarButton.
When this button is being pressed, the method 'addButton' is being called.
The 'addButton' method should refresh the UITableView in the SetupRingBoardViewController.
In the end, after pressing the 'addButton' button - there should be 2 sections in the SetupRingBoardViewController's UITableView:
1. The fixed section with 1 row in it.
2. A section with X rows in it, each row will have a title: #"A Row!";
(X = the number of 'addButton' being pressed).
Finally, here's the code:
SetupRingBoardViewController.h :
//
// SetupRingBoardViewController.h
//
//
// Created by on 12/24/12.
// Copyright (c) 2012 Noam. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "SetupRingBoardADDViewController.h"
//#import "StudyHour.h"
#interface SetupRingBoardViewController : UITableViewController
#property (nonatomic, strong) NSMutableArray *listOfStudyHours;
#end
SetupRingBoardViewController.m :
//
// SetupRingBoardViewController.m
//
//
// Created by on 12/24/12.
// Copyright (c) 2012 Noam. All rights reserved.
//
#import "SetupRingBoardViewController.h"
#import "SetupEmptyListViewController.h"
#interface SetupRingBoardViewController ()
#end
#implementation SetupRingBoardViewController
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
-(id)init
{
self = [super init];
if(self != nil)
{
if(!_listOfStudyHours) _listOfStudyHours = [[NSMutableArray alloc] init];
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
if(_listOfStudyHours) NSLog(#"%#",_listOfStudyHours);
[self.tableView reloadData];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
//[self.tableView reloadData];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
if(![_listOfStudyHours count])
{
NSLog(#"numberOfSectionsInTableView: 1");
return 1;
}
else return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
if(section == 0) return 1;
else return [_listOfStudyHours count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(![indexPath section])
{
NSLog(#"It got to the first");
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
// Configure the cell...
return cell;
}
else
{
NSLog(#"It got to the second");
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
cell.textLabel.text = [_listOfStudyHours objectAtIndex:indexPath.row];
return cell;
}
}
/*
// Override to support conditional editing of the table view.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the specified item to be editable.
return YES;
}
*/
/*
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
*/
/*
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
}
*/
/*
// Override to support conditional rearranging of the table view.
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the item to be re-orderable.
return YES;
}
*/
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
// Navigation logic may go here. Create and push another view controller.
/*
<#DetailViewController#> *detailViewController = [[<#DetailViewController#> alloc] initWithNibName:#"<#Nib name#>" bundle:nil];
// ...
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:detailViewController animated:YES];
*/
}
#end
SetupRingBoardADDViewController.h :
// SetupRingBoardADDViewController.h
//
//
// Created by on 12/26/12.
// Copyright (c) 2012 Noam. All rights reserved.
//
#import <UIKit/UIKit.h>
#import "SetupRingBoardViewController.h"
#import "UITableViewCell+Checkmark.h"
//#import "StudyHour.h"
#interface SetupRingBoardADDViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
- (IBAction)addButton:(id)sender;
#end
SetupRingBoardADDViewController.m :
- (IBAction)addButton:(id)sender {
SetupRingBoardViewController *rbVC = [[SetupRingBoardViewController alloc] init];
[rbVC.listOfStudyHours addObject:#"A row!"];
NSLog(#"%#",rbVC.listOfStudyHours);
[[rbVC tableView] reloadData];
[self dismissViewControllerAnimated:YES completion:nil];
}
(This is not the whole code, but it is the only thing that is relevant.)
The problem is that the method cellForRowAtIndexPath is not being called when I call the [tableView reloadData].
I hope you'll help me, I'm trying to figure it out for a long time :/
Please check if you have set/connected Delegate and Datasource to the File's Owner.
And check the array count of your model, if it contains value of not?
NSLog in the numberOfRowsInSection method and check it by using breakpoints and step over.
I think you need to make 2 changes. One, put the creation of the array in viewDidLoad instead of init (neither initWithStyle: nor init will be called if you created your table view controller in a storyboard -- initWithCoder: will be, so you could use that instead of viewDidLoad):
- (void)viewDidLoad
{
[super viewDidLoad];
if(!_listOfStudyHours) _listOfStudyHours = [[NSMutableArray alloc] init];
if(_listOfStudyHours) NSLog(#"%#",_listOfStudyHours);
[self.tableView reloadData];
}
Secondly, In your button method, you need to go back to the same instance that you came from, not create a new one. You can use the presentingViewController property to do that:
- (IBAction)addButton:(id)sender {
SetupRingBoardViewcontroller *rbVC = (SetupRingBoardViewcontroller *)self.presentingViewController;
[rbVC.listOfStudyHours addObject:#"A row!"];
NSLog(#"%#",rbVC.listOfStudyHours);
[[rbVC tableView] reloadData];
[self dismissViewControllerAnimated:YES completion:nil];
}

How to link delegate and sourcedata to only one tableview in a set of tableviews created in the mainstoryboard

Please bear with me as I am completely new at objective-c. Thanks in advance for any help you can provide!
So here is basically what I am trying to accomplish: I have 3 main tables whose contents will never change, that I therefore chose to construct in the mainstoryboard. Think of these are different grouping drilled down step by step into more and more details. So you have:
Table 1 (higher level to table 2) > Table 2 (higher level to table 3) > Table 3
Now I need to add a 4th table, but whose contents will be changed, based on a CSV file. For now I am ignoring how use CSV files and there seems to be quite a bit of info on this already. So I am electing to use Arrays using (NSArray) to store and retrive the information.
I first build the prototype of this table in the mainstoryboard so that I have an idea of what it will look like. Then I wrote the code below which ideally will update the information in table 4:
VIEWCONTROLLER.H file
#import <UIKit/UIKit.h>
#interface ViewController : UITableViewController
<UITableViewDataSource, UITableViewDelegate>
#end
VIEWCONTROLLER.M file
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
NSArray *nominalManagers;
NSArray *tipsManagers;
NSArray *tipsAmt;
NSArray *nominalAmt;
- (id)initWithStyle:(UITableViewStyle)style
{
self = [super initWithStyle:style];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
tipsManagers = [[NSArray alloc]
initWithObjects:
#"SSG",
nil];
tipsAmt = [[NSArray alloc]
initWithObjects:
#"$tip",
nil];
nominalManagers = [[NSArray alloc]
initWithObjects:
#"Wel",
#"Gold",
#"Colch",
#"Stand",
nil];
nominalAmt = [[NSArray alloc]
initWithObjects:
#"$Wel",
#"$Gold",
#"$Colch",
#"$Stand",
nil];
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#pragma mark - Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
#warning Potentially incomplete method implementation.
// Return the number of sections.
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
#warning Incomplete method implementation.
// Return the number of rows in the section.
NSUInteger rowNum;
if (section == 0) {
rowNum = 1;
}
else if (section == 1) {
rowNum = 4;
}
else {
rowNum = 0;
}
return rowNum;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier];
}
// Configure the cell...
NSUInteger row = [indexPath row];
NSInteger section = [indexPath section];
switch (section) {
case 0: // First cell in section 1
cell.textLabel.text = [tipsManagers objectAtIndex:[indexPath row]];
cell.detailTextLabel.text = [tipsAmt objectAtIndex:[indexPath row]];
break;
case 1: // Second cell in section 1
cell.textLabel.text = [nominalManagers objectAtIndex:[indexPath row]];
cell.detailTextLabel.text = [nominalAmt objectAtIndex:[indexPath row]];
break;
default:
cell.textLabel.text = #"WRONG SECTION";
break;
}
return cell;
}
/*
// Override to support conditional editing of the table view.
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the specified item to be editable.
return YES;
}
*/
/*
// Override to support editing the table view.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
else if (editingStyle == UITableViewCellEditingStyleInsert) {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
*/
/*
// Override to support rearranging the table view.
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath
{
}
*/
/*
// Override to support conditional rearranging of the table view.
- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
{
// Return NO if you do not want the item to be re-orderable.
return YES;
}
*/
#pragma mark - Table view delegate
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
// Navigation logic may go here. Create and push another view controller.
/*
<#DetailViewController#> *detailViewController = [[<#DetailViewController#> alloc] initWithNibName:#"<#Nib name#>" bundle:nil];
// ...
// Pass the selected object to the new view controller.
[self.navigationController pushViewController:detailViewController animated:YES];
*/
}
#end
My main issue is that I am unable to connect the delegate and sourcedata of this class to Table 4 (the table whose information will change). Can you help? Do you have any suggestions to better accomplish my goal?
What I understand from your question is you do not have a uitableview to connect your data source and delegate !?
I am not sure if you have added 4th viewcontroller to your storyboard yet, If not just add a UItableviewcontroller to your storyboard, create a push segue then in identity inspector choose your VIEWCONTROLLER as class name.
Add IBOutlet UITableViewto your .h file like below and in your interface builder connect your datasource and delegate. When you need refresh your tableview call [self.tableviewname reloadData];
You can use NSMutableArray to edit the items in your array, if you use NSArray array items will be static.
VIEWCONTROLLER.H file
#import <UIKit/UIKit.h>
#interface ViewController : UITableViewController
<UITableViewDataSource, UITableViewDelegate>
#property (nonatomic, weak) IBOutlet UITableView *fourthTable;
#end
VIEWCONTROLLER.M file
#implementation ViewController
#synthesize fourthTable;
- (void)viewDidLoad
{
[super viewDidLoad];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
tipsManagers = [[NSArray alloc]
initWithObjects:
#"SSG",
nil];
tipsAmt = [[NSArray alloc]
initWithObjects:
#"$tip",
nil];
nominalManagers = [[NSArray alloc]
initWithObjects:
#"Wel",
#"Gold",
#"Colch",
#"Stand",
nil];
nominalAmt = [[NSArray alloc]
initWithObjects:
#"$Wel",
#"$Gold",
#"$Colch",
#"$Stand",
nil];
[self.fourthTable reloadData];
}

My second level detail controller is not showing the initWithObjects in my array in the viewDidLoad method

I am creating a Two Level table view. And the second view is supposed to have the list of the movies I have listed below in my viewDidLoad method, but it is not showing.(You can see my screen shots attached)Does anyone know which file where I can look to see why it is not showing? The code below is from my DisclosureButtonController.m file which is to display this information after I hit the Disclosure Buttons instance on the First Level screen.
Regards,
#import "LWWDisclosureButtonController.h"
#import "LWWAppDelegate.h"
#import "LWWDisclosureDetailController.h"
#interface LWWDisclosureButtonController ()
#property (strong, nonatomic) LWWDisclosureDetailController *childController;
#end
#implementation LWWDisclosureButtonController
#synthesize list;
#synthesize childController;
//- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
//{
// self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
//if (self) {
// Custom initialization
//}
//return self;
//}
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *array = [[NSArray alloc] initWithObjects:#"Toy Story", #"A Bug's Life", #"Toy Story 2", #"Monsters, Inc.", #"Finding Nemo", #"The Incredibles", #"Cars", #"Ratatouille", #"WALL-E", #"Up", #"Toy Story 3", #"Cars 2", #"Brave", nil];
self.list = array;
// Do any additional setup after loading the view.
}
- (void)viewDidUnload
{
[super viewDidUnload];
self.list = nil;
self.childController = nil;
// Release any retained subviews of the main view.
}
#pragma mark -
#pragma mark Table Data Source Methods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [list count];//
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath
{
static NSString * DisclosureButtonCellIdentifier = #"DisclosureButtonCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:DisclosureButtonCellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc]
initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:DisclosureButtonCellIdentifier];
}
NSUInteger row = [indexPath row];
NSString *rowString = [list objectAtIndex:row];
cell.textLabel.text = rowString;
cell.accessoryType = UITableViewCellAccessoryDetailDisclosureButton;
return cell;
}
#pragma mark -
#pragma mark Table Delegate Methods
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Hey, boss do you see the disclosure button?" message:#"If you're trying to drill down, touch that instead mate!" delegate:nil cancelButtonTitle:#"Won't happen again" otherButtonTitles:nil];
[alert show];
}
- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath: (NSIndexPath *)indexPath
{
if (childController == nil)
{
childController = [[LWWDisclosureDetailController alloc]initWithNibName:#"LWWDisclosureDetail" bundle:nil];
}
childController.title = #"Disclosure Button Pressed";
NSUInteger row = [indexPath row];
NSString *selectedMovie = [list objectAtIndex:row];
NSString *detailMessage = [[NSString alloc]initWithFormat:#"You pressed the disclosure button for %#.", selectedMovie];
childController.message = detailMessage;
childController.title = selectedMovie;
[self.navigationController pushViewController:childController animated:YES];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
#end
Why not start the array with the VC instead of after the VC loads? Try overriding the init method.
- (id) init {
if((self == [super init])) {
//load your array here and set
}
return self;
}
That way, on older devices, you don't have to wait after the view loads to see the array. But, however, this is my preference. I love to override init methods and create my own.
Also, for some weird reason on my SDK, I have to use NSMutableArray instead of NSArray or it won't work. Maybe you have the same issue?
Also, I've noticed NSString *selectedMovie. Instead of using just "row", use the getter indexPath.row.
Hope these suggestions helped!
Call:
[self.tableView reloadData]; (put in place of self.tableView the var or property connected to the tableView)
after the:
self.list = array;
code line in the viewDidLoad method
and put a
NSLog(#"number of rows: %d", [self.list count]);
in
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [list count];//
}
to check if it is not zero.

Tabbed ios application with multiple table views

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!

Trying to solve a [DetailViewController setDataController:]: unrecognized selector sent to instance

I have an Xcode Universal Storyboard project that is properly displaying data for the iPhone but does not display data for the iPad. I had initialized the data array using the code below.
It is working properly in the iPhone but does not display data in the iPad and gets the error:
-[DetailViewController setDataController:]: unrecognized selector sent to instance
This is the AppDelegate.m
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad) {
UISplitViewController *splitViewController = (UISplitViewController *)self.window.rootViewController;
UINavigationController *navigationController = [splitViewController.viewControllers lastObject];
MasterViewController *masterViewController = (MasterViewController *)[navigationController topViewController];
DataController *controller = [[DataController alloc] init];
masterViewController.dataController = controller;
splitViewController.delegate = (id)navigationController.topViewController;
} else {
// Create the data controller and pass it to the master view controller.
UINavigationController *navigationController = (UINavigationController *)self.window.rootViewController;
MasterViewController *masterViewController = (MasterViewController *) [navigationController topViewController];
DataController *controller = [[DataController alloc] init];
masterViewController.dataController = controller;
}
The compiler is complaining about the DetailViewController here is that file.
#import "DetailViewController.h"
#import "Play.h"
#interface DetailViewController ()
#property (strong, nonatomic) UIPopoverController *masterPopoverController;
#end
#implementation DetailViewController
#synthesize masterPopoverController = _masterPopoverController;
#synthesize play;
#pragma mark -
#pragma mark View lifecycle
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// Scroll the table view to the top before it appears
[self.tableView reloadData];
[self.tableView setContentOffset:CGPointZero animated:YES];
//self.part = play.part;
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
} else {
return YES;
}
}
#pragma mark - Split view
- (void)splitViewController:(UISplitViewController *)splitController willHideViewController:(UITableViewController *)viewController withBarButtonItem:(UIBarButtonItem *)barButtonItem forPopoverController:(UIPopoverController *)popoverController
{
barButtonItem.title = NSLocalizedString(#"Trading Rules That Work", #"Trading Rules That Work");
[self.navigationItem setLeftBarButtonItem:barButtonItem animated:YES];
self.masterPopoverController = popoverController;
}
- (void)splitViewController:(UISplitViewController *)splitController willShowViewController:(UITableViewController *)viewController invalidatingBarButtonItem:(UIBarButtonItem *)barButtonItem
{
// Called when the view is shown again in the split view, invalidating the button and popover controller.
[self.navigationItem setLeftBarButtonItem:nil animated:YES];
self.masterPopoverController = nil;
}
#pragma mark -
#pragma mark Table view data source
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
// There are 2 sections, for rule, and media, in that order.
return 2;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
//The number of rows varies by section.
NSInteger rows = 0;
switch (section) {
case 0:
// For part and date there is just one row.
rows = 1;
break;
case 1:
// For the media section, there are as many rows as there are media.
rows = [play.media count];
break;
default:
break;
}
return rows;
}
- (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];
cell.autoresizingMask = UIViewAutoresizingFlexibleHeight;
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.textLabel.font = [UIFont boldSystemFontOfSize:11];
cell.textLabel.numberOfLines = 13;
}
NSString *cellText = nil;
switch (indexPath.section) {
case 0:
cellText = play.part;
break;
case 1:
cellText = [play.media objectAtIndex:indexPath.row];
break;
default:
break;
}
cell.textLabel.text = cellText;
return cell;
}
#pragma mark -
#pragma mark Section header titles
/*
HIG note: In this case, since the content of each section is obvious, there's probably no need to provide a title, but the code is useful for illustration.
*/
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
NSString *title = nil;
switch (section) {
case 0:
title = NSLocalizedString(#"Video Description", #"Part section title");
break;
case 1:
title = NSLocalizedString(#"Media", #"Main Media section title");
break;
default:
break;
}
return title;
}
#end
When you do
object.property = value;
in Objective-C code, that's a short cut for:
[object setProperty:value];
Since you don't appear to have a dataController property, there's no setDataController selector.
So either:
declare a dataController property in your DetailViewController's .h then synthesize it in your implementation, or
create a setter and manually assign it to an instance variable.
You are getting this error because in your iPad storyboard you do not have the class set properly for your navigation controller's rootViewController. Make sure the class for that viewController is set to DataController in the Identity Inspector. You probably have this set correctly in your iPhone version storyboard, which is why you don't get the error when running the iPhone version.