I'm making an app with 2 table views. the first has a bunch of cells which lead to the next table view (which can have different data depending on which cell is selected).
My question is, is it better to have a bunch of view controllers for the second menu (1 for each cell selection, or to have one view controller and load different data on it.
There is no right or false. However, I would recommend you to use different viewcontrollers. Especially if you are using a storyboard, this is very straightforward. Just connect your different cell types with the appropriate viewcontroller and pass your data in the -performSegueWithIdentifier: method.
Maybe if you would add some details about the kind of data etc. I could give you a more adequate answer.
Edit:
In this case it would actually make more sense to work with a single second tableviewcontroller, as the input format is always the same and the output is based on the input data. You could do something like this:
FirstTableViewController.h
#interface FirstTableViewController.h : UITableViewController
// array containing NSArrays which themselves contain NSStrings
#property (nonatomic, strong) NSArray *textArrays;
#end
FirstTableViewController.m
#implementation FirstTableViewController
#synthesize textArrays;
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.textArrays.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *Identifier = #"Your Identifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:Identifier forIndexPath:indexPath];
if(cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:Identifier];
}
NSArray *textArray = self.textArrays[indexPath.row];
if (textArray != nil && [textArray isKindOfClass:[NSArray class]]) {
// configure cell
}
return cell;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
UITableViewCell *cell = (UITableViewCell *)sender;
NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
SecondTableViewController *controller = [segue destinationViewController].
controller.textArray = self.textArrays[indexPath.row];
}
#end
SecondTableViewController.h
#interface SecondTableViewController.h : UITableViewController
// array containing NSStrings
#property (nonatomic, strong) NSArray *textArray;
#end
SecondTableViewController.m
#implementation SecondTableViewController
#synthesize textArray;
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.textArray.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *Identifier = #"Your Identifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:Identifier forIndexPath:indexPath];
if(cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:Identifier];
}
NSString *text = self.textArray[indexPath.row];
if (text != nil && [text isKindOfClass:[NSString class]]) {
// configure cell, e.g. cell.textLabel.text = text;
}
return cell;
}
#end
Remember that there are always several ways to achieve something. This is just my way and it does not have to be yours. However, I hope this helps.
Make sure you understand the difference between a class and an instance of that class, i.e. an object. Typically, when a user taps on a cell in your first table, you'll create a new view controller object, load it up with whatever data it needs to display the content corresponding to the tapped cell, and push it onto your navigation stack. When the user is done looking at that content, they hit the "back" button to switch back to the previous view controller. When that happens, the "detail" view controller is normally deallocated because no other objects are referencing it. When the user taps another cell, the process repeats -- a new view controller is created, configured, pushed, and eventually dismissed.
So, that means you'll have two view controller classes, and at any given time one or two view controller objects (not counting a navigation controller), but over time you end up creating many instances of the detail view controller class. You could create just a single instance of the detail view controller and reuse it as necessary, but there's no real advantage to doing so and it ends up being another thing that the main view controller needs to keep track of.
Of course, if different cells in your main table could lead to significantly different kinds of data that need to be displayed in different ways, then you might end up with more than just the two view controller classes. In that case, when the user taps a cell you'd have to figure out which kind of detail view controller you want to use and then instantiate that one. For example, tapping one cell might lead to information about a person while another might lead to a picture and a third might lead to a map.
I would use 2 TableViewController and Custom Cells to do this. Basically once the user chooses a cell on the first TableView, you populate the second Table View with the appropriate data and dequeue the appropriate custom cell to display the data.
Related
I have a form in my app that exists out of a UITableView with custom cells. These cells can contain a UITextField, UISegmentedControl or a UISwitch. This is how I set this up:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 5;
}
- (UITableViewCell *)tableView:(UITableView *)tableViewInner cellForRowAtIndexPath:(NSIndexPath *)indexPath {
DetailTableViewCell *cell;
static NSString *MyIdentifier = #"MyIdentifier";
DetailTableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil)
{
cell = [[DetailTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier];
}
[cell setTextField:#"John Appleseed"];
// or
[cell setSegment];
[cell setSegmentIndex:1];
// or
[cell setSwitch];
[cell setSwitchEnabled:YES];
return cell;
}
Now, when a user taps the save button I need to fetch all this information and init a model with it, like this:
[[Restaurant alloc] initWithName:#"Name here" withNotifications:1 withFrequency:1 withDate:#"Date here" andWithDistance:#"Distance here"];
What's the best and cleanest way possible to convert all these inputs to data in my model? I feel like looping over all the cells is a bit over the top.
like looping over all the cells is a bit over the top
It isn't just over the top: it's totally wrong. The data doesn't live in the cells; it lives in the data. Model, view, controller; the cell is just view! Its job is to represent the model (the data). There should be nothing to loop over, therefore; you should already have the data as data.
Now, when a user taps the save button I need to fetch all this information
Actually, what I would do is capture the information when the user makes the change. Give the text field, switch, or segmented control a control action-target so that a message is sent to you telling you that something happened (e.g. the switch value changed, the text was edited, and so on) and capture the data right then.
The only question then becomes: I've received a message from a control: what row of the table is it in? To find out, walk the hierarchy up from the control until you come to the cell, and then ask the table what row this cell represents:
UIView* v = sender; // the control
do {
v = v.superview;
} while (![v isKindOfClass: [UITableViewCell class]]);
UITableViewCell* cell = (UITableViewCell*)v;
NSIndexPath* ip = [self.tableView indexPathForCell:cell];
A more cleaner approach using custom blocks.
DetailTableViewCell.h
typedef void (^ saveBlock_block_t )(WhateverYourReturnObjectType *obj);
#interface DetailTableViewCell : UITableViewCell
- (void)configureCell:(NSString *)textFieldVal
cellBlock:(saveBlock_block_t)cellBlock;
#end
DetailTableViewCell.m
#interface DetailTableViewCell ()
{
#property (copy, nonatomic) saveBlock_block_t saveBlock;
}
#end
#implementation DetailTableViewCell
- (void)configureCell:(NSString *)textFieldVal
cellBlock:(saveBlock_block_t)cellBlock
{
[cell setTextField: textFieldVal];
[self setSaveBlock:cellBlock];
}
-(IBAction)saveButtonAction:(id)sender //Action on your save button
{
self.cellBlock(obj); // Whatever object you want to return to class having your table object
}
#end
Then from cellForRowAtIndexPath call it as -
[cell configureCell:#"John Appleseed”
cellBlock:^(WhateverYourReturnObjectType *obj){
//Do what you want to do with 'obj' which is returned by block instance in the cell
}];
I am new to IOS and i was just working on UITableView. I want to populate data in table view from two different View Controllers. I want to update cell.textLabel.text from View Controller say viewController1 and cell.detailTextLabel.text from another View Controller say viewController2.
I have a barbutton at the top right of the tableView and clicking on which it navigates to View Controller A. I have a text label in View Controller A and it returns what ever user has entered to tableView.
When i click the cell, it navigates to View Controller B. I also have a text label in View Controller B.
Here is my tableView datasource method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
Names *namesToDisplay = [self.lists objectAtIndex:indexPath.row];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = namesToDisplay.firstName;
return cell;
}
where Names is a model class of NSObject and lists is an NSArray in tableView.
It works fine if I update cell.textlabel.text and cell.detailtextlable.text from a single View Controller either View Controller1 or View Controller2.
I can get data from viewController2 but where should I have cell.detailTextLabel.text ?
coding conventions... http://en.wikipedia.org/wiki/Naming_convention_%28programming%29#Java is a good orientation.
It seems like simple parameter handling. You can share the information between the views in the init process.
i.e.
// your details view (vc2) header
#class Names;
#interface MyDetailsViewController
{
Names *names;
// some variable declaration here
}
// maybe some properties...
- (id)initWithObject:(Names *)p_names;
// ...
#end
...
// your details view (vc2) main
- (id)initWithObject:(Names *)p_names
{
if ((self = [super init]))
{
names = p_names; // so you have the data when the tableView is loading
}
}
...
// your main view (vc1)
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
Names *namesToDisplay = [self.lists objectAtIndex:indexPath.row];
MyDetailsViewController *viewController2 = [[MyDetailsViewController alloc] initWithObject:namesToDisplay];
[self.navigationController pushViewController:viewController2 animated:YES];
}
if there are further problems, please give some more information where exactly ;)
I have two table views, one called mainTableViewController (mtvc), the other called detailTableViewController (dtvc). It's very typical click the accessory button on the main tableview cell bring you to the detail tableview kinda thing.
In the prepareForSegue method, the data passed from the main tableview to detail tableview is a NSMutableArray called item.
And this is how I got it displayed: cell.detailTextLabel.text = self.item[indexPath.row];
The cool thing is I managed to do in-place editing on the detail table view cell (overwrote the NSTableViewCell, added a UITextField as subview to each cell).
everything works, the last thing I spent whole day cannot figure out is how do I update the NSMutableArray item after in-place editing taken place, the ultimate goal is in-place editing, and the main tableview data shall reflect the change.
I tried to use delegation and protocol but it does not work (the in-place edited content didn't got passed back, part of the reason is I don't know how to capture the edited content, it's not like it's a text field with a name, I can't just do updatedContent = self.myTextField.text to grab the change)
I'm running out of ideas, any help would be highly appreciated, thanks.
Here's the prepareForSegue in the main tableview controller
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"toInventoryDetail"]) {
NSMutableArray *selectedItem = nil;
if (self.searchDisplayController.active) {
selectedItem = _searchResults[[sender row]];
} else {
selectedItem = _appDelegate.items[[sender row]];
}
UPFInventoryDetailTableViewController *idtvc = segue.destinationViewController;
idtvc.item = selectedItem;
}
}
and here's the cellForRowAtIndex at the detail tableview controller
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UPFEditableUITableViewCell *cell = [[UPFEditableUITableViewCell alloc] initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:CellIdentifier];
cell.textLabel.text = _appDelegate.title[indexPath.row];
cell.detailTextLabel.text = self.item[indexPath.row];
[cell showEditingField:YES];
return cell;
}
I wrote the delegation but delete them after cause they didn't work.
I had an idea, still using delegation and protocol obviously: when the 'done' button in the detail tableview hit, I go grab all the row contents and build a new array, using delegation to pass this new array back to the main tableview controller, add this new array into the model meanwhile delete the old one. The tricky thing is still HOW CAN I GRAB ALL THE CONTENTS in the detail tableview?
update:
Haha! I think solved half of the puzzle !
here's the solution for the detail tableview controller
- (IBAction)doneUpdate:(UIBarButtonItem *)sender {
[self.delegate addItem:[self newItem]];
}
- (NSMutableArray *)saveItem
{
NSMutableArray *newItem = [[NSMutableArray alloc] init];
NSArray *indexPathes = [self.tableView indexPathsForVisibleRows];
for (NSIndexPath *indexPath in indexPathes) {
UPFEditableUITableViewCell *cell = (UPFEditableUITableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath];
[newItem addObject:cell.editField.text];
}
return newItem;
}
and here's the main tableview controller
- (void)addItem:(NSArray *)item
{
//take the updated item then insert the items array as new item
[_appDelegate.items addObject:item];
//remove the selected item (the one being updated) from the items array
[_appDelegate.items removeObject:_appDelegate.selectedItem];
[self.tableView reloadData];
[self.navigationController popViewControllerAnimated:YES];
}
When you creating a cell - give tags to your UITextFields
You can collect data entered by its delegate methods - you can either make NSDictionary/ key value pairs or you can add it to NSArray.
- (void)textFieldDidEndEditing:(UITextField *)textField {
if(textField.tag == 11) {
// you can add it to your desired array/dictionary
}
}
OR
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
if(textField.tag == 11) {
// you can add it to your desired array/dictionary
}
}
You can use Delegation/Protocol or store this values in NSUserDefault and get it back on mainViewController.
Do you have a separate data model class(classes) for your selectedItem? That would be the appropriate way to persist data between the two TableViewControllers. It can be Core Data or simply a NSMutableArray that lives in memory. The DetailViewController updates the item and saves the changes, then the mainTableViewController reloads the TableView (or even just the data backing the previously edited cell.
Perhaps even consider the Model-View-Controller-Store pattern promoted by BigNerdRanch.
I am just beginning to learn objective-c and programming in general, and I am really stuck on this issue. I am using storyboards to create an app that keeps scores for different types of games. I have a master view controller that is a table view and it has prototype cells with detail labels. There is an AddKeeperViewController that allows the user to input player names and the game type. The game type is then used as the detail label for the prototype cells.
I want to then have each cell push to a different view controller, depending on what the detail text label is. I only have 2 game types at the moment, I know I need to set up logic in tableView didSelectRowAtIndexPath that will choose which segue to take. I set up my segues from the view controller, not the cells, and they each have a unique identifier. I just don't know how to set up my if statements to use the gameType as the deciding factor for which segue to take.
Here is some of my code from my MasterViewController.m file:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"scoreKeeperCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
scoreKeeper *keeperAtIndex = [self.dataController objectInListAtIndex:indexPath.row];
[[cell textLabel]setText:keeperAtIndex.name];
[[cell detailTextLabel] setText:keeperAtIndex.gameType];
return cell;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if(cell.detailTextLabel) {
//statement
}
if ([[segue identifier] isEqualToString:#"ShowKeeperDetails"]) {
MADDetailViewController *detailViewController = [segue destinationViewController];
detailViewController.keeper = [self.dataController objectInListAtIndex:[self.tableView indexPathForSelectedRow].row];
}
}
It seems like you're on the right track. However, I think you're waiting a bit too long to choose which segue to take. Perhaps something like this would work for you:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if ([cell.detailTextLabel.text isEqualToString:#"TEXT"])
{
[self performSegueWithIdentifier:#"segueTypeOne" sender:nil];
}
else
{
[self performSegueWithIdentifier:#"segueTypeTwo" sender:nil];
}
}
You may also create different cell's prototypes, each one connected with a different segue leading to different views, and then use the right prototype to obtain cells in dequeueReusableCellWithIdentifier.
I'm new to iPhone dev and wanted to get advice on the general design pattern / guide for putting a certain kind of app together.
I'm trying to build a TabBar type application. One of the tabs needs to display a TableView and selecting a cell from within the table view will do something else - maybe show another table view or a web page. I need a Navigation Bar to be able to take me back from the table view/web page.
The approach I've taken so far is to:
Create an app based around UITabBarController as the rootcontroller
i.e.
#interface MyAppDelegate : NSObject <UIApplicationDelegate>
{
IBOutlet UIWindow *window;
IBOutlet UITabBarController *rootController;
}
Create a load of UIViewController derived classes and associated NIBs and wire everything up in IB so when I run the app I get the basic tabs working.
I then take the UIViewController derived class and modify it to the following:
#interface MyViewController : UIViewController<UITableViewDataSource, UITableViewDelegate>
{
}
and I add the delegate methods to the implementation of MyViewController
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 2;
}
- (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];
}
if (indexPath.row == 0)
{
cell.textLabel.text = #"Mummy";
}
else
{
cell.textLabel.text = #"Daddy";
}
return cell;
}
Go back to IB , open MyViewController.xib and drop a UITableView onto it. Set the Files Owner to be MyViewController and then set the delegate and datasource of the UITableView to be MyViewController.
If I run the app now, I get the table view appearing with mummy and daddy working nicely. So far so good.
The question is how do I go about incorporating a Navigation Bar into my current code for when I implement:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath()
{
// get row selected
NSUInteger row = [indexPath row];
if (row == 0)
{
// Show another table
}
else if (row == 1)
{
// Show a web view
}
}
Do I drop a NavigationBar UI control onto MyControllerView.xib ? Should I create it programmatically? Should I be using a UINavigationController somewhere ? I've tried dropping a NavigationBar onto my MyControllerView.xib in IB but its not shown when I run the app, only the TableView is displayed.
http://miketeo.net/wp/index.php/2008/08/31/simple-iphone-tutorial-part-3.html
this code was very helpfull for me. You can download and use this code..