I have had no issues following the following tutorial: http://www.raywenderlich.com/1797/how-to-create-a-simple-iphone-app-tutorial-part-1. In essence, this tutorial teaches one to create a simple table view populated with data from a NSMutableArray and the subsequent drilling down into the DetailView of each item.
I then tried to enhance what I had by creating a TabViewController and having the created Table View as the 'Tab item 2' of this controller.
This has broken the auto-population of the Table.
The code used to populate the tables from AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
Person *person1 = [[Person alloc] initWithName:#"Monica"
balance:420
thumbImage:[UIImage imageNamed:#"01.jpg"]
fullImage:[UIImage imageNamed:#"01_t.jpg"]];
Person *person2 = [[Person alloc] initWithName:#"Ross"
balance:630
thumbImage:[UIImage imageNamed:#"02.jpg"]
fullImage:[UIImage imageNamed:#"02_t.jpg"]];
Person *person3 = [[Person alloc] initWithName:#"Rachel"
balance:420
thumbImage:[UIImage imageNamed:#"03.jpg"]
fullImage:[UIImage imageNamed:#"03_t.jpg"]];
NSMutableArray *persons = [NSMutableArray arrayWithObjects:person1, person2, person3, nil];
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:#"MainStoryboard" bundle:nil];
UIViewController *initialViewController = (UIViewController *)[storyboard instantiateViewControllerWithIdentifier:#"RootViewController"];
self.window.rootViewController = initialViewController;
TableViewController * peopleTableViewController = [storyboard instantiateViewControllerWithIdentifier:#"PersonTableViewController"];
[peopleTableViewController setPersons: persons];
// Override point for customization after application launch.
return YES;
}
'persons' is an array of Person instances which is a property of TableViewController. The following is the population of the table based on the 'persons' array from TableViewController.m:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"PersonCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
//For each person
Person *person = [self.persons objectAtIndex:indexPath.row];
cell.textLabel.text = person.name;
cell.imageView.image = person.thumbImage;
cell.detailTextLabel.text = [person getPrice];
return cell;
}
Lastly, the storyboard is located here: http://i.stack.imgur.com/9axs8.png
And just to re-iterate, this all compiles fine but during tests, the table is empty upon opening tab item #2 (where the table view controller exists as per the storyboard). This was working before I added the tabs.
I am still very new to Objective C, so please let me know if there is any other information you may require.
Your problem is that you are instantiating new view controllers rather that using the ones that are already in the storyboard. When this app starts up, the tab bar controller, as well as its content view controllers will all be instantiated. Also, because one of the controllers, the one in tab 2, is a navigation controller, its root view controller, TableViewController will also be instantiated. So, you just need to get a reference to this table view controller, not create a new one:
UITabBarController *tabVC = (UITabBarController *)self.window.rootViewController;
UINavigationController *nav = tabVC.viewControllers[1];
TableViewController *peopleTableViewController = nav.topViewController;
[peopleTableViewController setPersons:persons];
This should work, but I think it would be better to move the creation of the Person objects and the persons array to the viewDidLoad method of the table view controller.
I think your problem is here:
Person *person = [self.persons objectAtIndex:indexPath.row];
Try put your code in ViewDidLoad instead of AppDelegateand run it. (just for test it)
Related
I have a to-do list app with a bunch of tasks. Each task has a UITableViewCell. After each table view cell is tapped, it creates a view controller with the task at that row's index path's property. These view controllers are all stored in a NSDictionary. This is the code representation of what I just said:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
DetailViewController *detailVC;
if (![self.detailViewsDictionary.allKeys containsObject:indexPath]){
detailVC = [[DetailViewController alloc]initWithNibName:#"DetailViewController" bundle:nil];
[self.detailViewsDictionary setObject:detailVC forKey:indexPath];
detailVC.context = self.managedObjectContext;
}else{
detailVC = self.detailViewsDictionary[indexPath];
}
Tasks *task = [[self fetchedResultsController] objectAtIndexPath:indexPath];
detailVC.testTask = task;
[[self navigationController] pushViewController:detailVC animated:YES];
NSLog(#"%#", self.detailViewsDictionary);
}
So this method of creating unique view controllers and storing them with a certain key almost always works. The problem arises when I delete or move the view controllers:
I was under the impression that a cell's gets recycled as you scroll down (dequeue). This means that marking each cell with a number identifier would result in multiple cells for the same identifier.
Also, if you stored each view controller with a indexPath key, how do you make sure the key isn't set to two view controllers..? For example. Let's say you have 4 cells, which means 4 view controllers. You delete cell 3. Cell 4 moves down to cell 3s spot. You create a new cell which goes to spot 4. Now you have two controllers with the same indexPath key! How do you avoid this?? It's screwing up my app right now because tasks that have already been moved are loading their properties in the wrong view controller/cell!
I was suggested this to solve the problem before: "You maintain an NSMutableArray that "shadows" the contents of the table." I was also suggested to use tags. However, I don't understand what how to implement these.
edit: ---random string---
In tasks.m
-(NSString *)uniqueIdentifierString{
static NSString *alphabet = #"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXZY0123456789";
NSMutableString *s = [NSMutableString stringWithCapacity:20];
for (NSUInteger i = 0U; i < 20; i++) {
u_int32_t r = arc4random() % [alphabet length];
unichar c = [alphabet characterAtIndex:r];
[s appendFormat:#"%C", c];
}
return s;
}
In tableviewcontroller.m
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
DetailViewController *detailVC;
Tasks *task = [[self fetchedResultsController] objectAtIndexPath:indexPath];
if (![self.detailViewsDictionary.allKeys containsObject:task.uniqueIdentifierString]){
detailVC = [[DetailViewController alloc]initWithNibName:#"DetailViewController" bundle:nil];
[self.detailViewsDictionary setObject:detailVC forKey:task.uniqueIdentifierString];
detailVC.context = self.managedObjectContext;
}else{
detailVC = self.detailViewsDictionary[task.uniqueIdentifierString];
}
detailVC.testTask = task;
[[self navigationController] pushViewController:detailVC animated:YES];
NSLog(#"%#", detailVC);
NSLog(#"%#", task.uniqueIdentifierString);
}
One solution would be to decouple your VCs from the table cells. You could store the VCs in your dictionary referenced by an ID that is unique to the task, but not worry about the table cell. If the task is moved or deleted, you can handle that elsewhere without worrying about the VC array being instantly wrong.
Your other option would be to hook into the move/edit methods the logic for iterating over the VC array to adjust the array at that time.
Given that UITableView expects cells to be reused, you can see how it isn't meant to be a storage mechanism for other data.
It would be best to decouple the data so that you aren't bound to the order or existence of them.
I have been trying to figure this out for a while and not coming up with a solution. I have a view controller with a table and the first cell of the table is allocated for a button called "Add Friends". When clicked, it takes you to another view controller with a list of contacts in a table. When you click on a person, it goes back to the other view controller and adds the selected person. This is what I have so far.
ContactsViewController.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
FirstViewController *newVC = [self.storyboard instantiateViewControllerWithIdentifier:#"newVCSegue"];
newVC.peopleArray = [[NSMutableArray alloc] init];
Person *user = [contactsList objectAtIndex:indexPath.row];
NSArray *userKeys = [NSArray arrayWithObjects:#"FirstName", #"LastName", nil];
NSArray *userObjects = [NSArray arrayWithObjects:user.firstName, user.lastName, nil];
NSDictionary *userDictionary = [NSDictionary dictionaryWithObjects:userObjects forKeys:userKeys];
[newVC.peopleArray addObject:userDictionary];
[self.navigationController pushViewController:newVC animated:YES];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
FirstViewController.h
#property (strong, nonatomic) NSMutableArray *peopleArray;
FirstViewController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//...
if (indexPath.row == 0) {
contactName.text = #"Add Person";
imgView.image = [UIImage imageNamed:#"plus-icon.png"];
} else {
NSString *firstName = [[peopleArray objectAtIndex:(indexPath.row)-1] objectForKey:#"firstName"];
NSString *lastName = [[peopleArray objectAtIndex:(indexPath.row)-1] objectForKey:#"lastName"];
contactName.text = [NSString stringWithFormat:#"%# %#", firstName, lastName];
}
return cell;
}
This lets me add one friend so far and if I decided to add another to the list, it replaces the first friend added.
What's basically happening is every time you select a new contact, you're recreating the array in the first view controller, hence it is replacing things. You ideally want to try and avoid getting the FirstViewController using the storyboard like that as well, it's pretty bad practice and may well lead to various problems later.
What I'd suggest in this situation is creating a protocol (look at the delegate pattern). This way, what you'd have is :
Use taps "Add Contact"
Contacts list appears, and FirstViewController is set as the delegate
User taps contact to add them
ContactsViewController informs the delegate of the user that was selected
FirstViewController adds the user, and dismissed the view controller
This is generally the approach you'd take, and it's pretty simple to implement. Start with the protocol
#protocol ContactsDelegate
-(void) contactsViewController:(ContactsViewController *)vc didSelectContact:(Person *)person;
#end
Then, make your FirstViewController implement this protocol. To do this, in your header file, in the angle brackets after the name (< >) add ContactsDelegate
In the implementation of FirstViewController, add the new method of the contacts delegate.
In your ContactsViewController.h file, add
#property (nonatomic, assign) NSObject<ContactsDelegate> *delegate;
Then when you display your contacts view controller, set the delegate
userVc.delegate = self;
[self presentModalViewController:userVc];
Then, in the user view controllers didSelectRowAtIndexPath:, simply inform the delegate that you've selected that person
[delegate contactsViewController:self didSelectContact:[contactsList objectAtIndex:indexPath.row]];
And lastly, in your FirstViewController, in the delegate method you added, we need to ADD the user to the list, not re-create the list
[peopleArray addObject:person];
And that should do what you're after :)
From what I understand, you are instantiating a new FirstViewController every time you select a contact in the ContactsViewController. Instead, you should reference the original FirstViewController (perhaps save it before transitioning to ContactsViewController), and use this reference to add the contact to the original array [original.people addObject:userDict]. As long as you make sure to reload the table, this should work.
I have UIViewController that contains NSMutableArray , I want to pass this array to UITableViewController and view it on the table .. how can I do that ??
I mean I want to (pass) NSMutableArray or any Variable from UIViewController to UITableViewController not (create) a table
I want to pass newBooksArray to UITableViewController, I wrote in UIViewController:
mytable.gettedBooks = newBooksArray; // gettedBooks is NSMutableArray in UITableViewController
mytable.try = #"Emyyyyy"; // try is a NSString in UITableViewController
and in UITableViewController in DidloadView i wrote
NSLog(#"Try: %#", try); // out is null
NSLog(#"my getted array count: %d", [gettedBooks count]); // out is 0
any help ???
Creating a UITableView and filling with an array
I created the above tutorial specially for this problem.
There are also more methods you can learn about on the developer documents
Firstly, you want to make sure you have the required delegate calls in your #interface:
#interface RootViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
{
NSMutableArray * feed;
UITableView * tableView;
}
You want something similar to the following in your controller:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [feed count];
}
- (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];
}
cell.textLabel.text = [mutableArray objectAtIndex:indexPath.row];
return cell;
}
numberOfRowsInSection makes sure you actually load the required number of cell rows from your NSMutableArray. And cellForRowAtIndexPath actually loads the content from each row of your NSMutableArray into each row of the UITableView.
For passing it to another controller, don't you want something like this?
UITableViewController *viewController = [[UITableViewController alloc] initWithNibName:#"TableXIB" bundle:nil];
[viewController setGettedBooks:newBooksArray];
[self.navigationController pushViewController:viewController animated:YES];
UITableview tutorial and sample code
Hope,this will help you..enjoy
If you are using a Tab Bar controller, and First View controller is table view controller and second is UIView controller. You can Pass data to Table View controller by followoing code segment. You need to declare variable called arrayData (NSMutableArray) in table view controller and set property (Since we need to access this from another class.) From this arrayData, you need to load data in tableView. In View controller class write following code.
NSArray *viewControllers = [self.tabBarController viewControllers];
MyTableViewController *mTable = [viewControllers objectAtIndex:0];
[mTable SetArrayData:arrayFromViewController];
[mTable.tableView reloadData];
If you are using Navigation controller, you can do
NSArray *viewControllers = [self.navigationController viewControllers];
MyTableViewController *mTable = [viewControllers objectAtIndex:0];
[mTable SetArrayData:arrayFromViewController];
[mTable.tableView reloadData];
Optionally you can use delegates.
I have a UITableViewController that has data populated from an NSMutableArray called userList. When specific user is selected, it goes to a detail view, and updates the title in the NavBar, but it wont pass the data to update a UILabel in the UIView.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:NO];
//Attempt at a singleton to pass the data
DetailView *details = [[DetailView alloc] init];
details.userNameLbl = [userList objectAtIndex:indexPath.row];
DetailView *detailVC = [[DetailView alloc] initWithNibName:nil bundle:nil];
//This line doesn't pass the data
detailVC.userNameLbl.text = [userList objectAtIndex:indexPath.row];
//This line does update the title in the NavBar
detailVC.navigationItem.title = [userList objectAtIndex:indexPath.row];
[self.navigationController pushViewController:detailVC animated:YES];
[details release];
[detailVC release];
}
Should I be trying to push the data from the table view to the detail view, or have the detail view try to pull the data from the table view?
DetailView.h has the following line that is in fact hooked up in IB.
IBOutlet UILabel *userNameLbl
The userNamelbl isn't instantiated until the view is loaded from the nib file. This doesn't happen immediately at initialisation, but will have happened by the time viewDidLoad is called.
So, you should to declare a property in DetailView to store your title, and then assign that value to userNamelbl.text in the viewDidLoad method.
For example, in your table viewController:
DetailView *detailVC = [[DetailView alloc] initWithNibName:nil bundle:nil];
detailVC.userName = [userList objectAtIndex: indexPath.row];
and in your detail viewController:
- (void) viewDidLoad
{
[super viewDidLoad];
self.userNameLbl.text = self.userName;
}
The viewController's navigationItem property is created when the viewController is initialised, hence you can assign to the navigationItem.title immediately.
Swift code
let detailVC = DetailView(nibName: nil, bundle: nil)
detailVC.userName = userList.objectAtIndex(indexPath.row) as? NSString
and
class DetailView: UIViewController {
#IBOutlet var userNameLbl: UILabel
var userName:NSString?
override func viewDidLoad() {
super.viewDidLoad()
self.userNameLbl.text = self.userName
}
}
Are you mixing UIViews and UIViewControllers? You should have a DetailViewController class that inherits from UIViewController or some sublcass (like UITableViewController); it will have DetailViewController.m and DetailViewController.h files to declare and define your class. It should have a corresponding nib that defines the UIView that the UIViewController loads; it will have a DetailView.xib file.
You can't assign the value to the UILabel directly because UIView hasn't been loaded at the time you need to assign the user name value.
In order to do what you want, you should declare a public property (userName) to "push" the value onto the detail view controller from the master controller. Once the detail view is loaded, it can assign the value from the property to the label and nav bar.
In your master view controller (UITableViewController):
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:NO];
DetailViewController *detailVC = [[DetailView alloc] initWithNibName:#"DetailView" bundle:nil];
detailVC.userName = [userList objectAtIndex:indexPath.row];
[self.navigationController pushViewController:detailVC animated:YES];
[detailVC release];
}
In your detail view controller:
DetailViewController.h
#property (retain) NSString* userName;
#property (nonatomic, retain) IBOutlet UILabel *userNameLbl;
DetailViewController.m
#synthesize userName;
#synthesize userNameLbl;
-(void) viewDidLoad {
[super viewDidLoad];
self.userNameLbl.text = self.userName;
self.navigationItem.title = self.userName;
}
Presumably your DetailView requires the data from your selected row to function?
If so, I'd say the 'correct' approach would be to create a custom init method that passed in the data you required. For example:
[[DetailView alloc] initWithTitle:(NSString *)title text:(NSString *)text]
There's nothing inherently wrong with your approach, but passing the data the view controller requires at its creation is architecturally better (in my opinion, I'm sure someone will disagree!). For example, think of a table view controller - you pass in the table view style in the init method, you don't set it later on. Same for a UIImageView - you have an initWithImage method that lets you set the image at creation.
I am trying to feed in some JSON data to my iPhone app, the data is coming in fine as I have NSLog's telling me so.
The problem I am having is trying to get the results to show in a UITableView. I have a navigation controller underneath a tab bar controller, the navigation controller contains a table view controller which loads another NIB file with a table view connected to a class which is the delegate and data source delegate.
I also need to categorize the results into sections - these being
England
Scotland
Wales
N.Ireland
To get an idea of what JSON string I am using see this one.
As you can see the JSON does not cater for the sections but I am yet to implement this, so i would need to know beforehand so I do not have to amend much code later on.
OK - I am using Stig JSON parser.
Here is my ListVenuesView.h (connected to table view)
#import <UIKit/UIKit.h>
#import "SBJson.h"
#interface ListVenuesView : UITableViewController <UITableViewDelegate, UITableViewDataSource> {
IBOutlet UITableView *venueList;
NSMutableDictionary *jsonArray;
}
#property (nonatomic, retain) IBOutlet UITableView *venueList;
#property (nonatomic, retain) NSMutableDictionary *jsonArray;
#end
jsonArray is used to store the JSON data and eventually the proper array.
And here is my ListVenuesView.m (key areas in question)
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"Table View Loaded");
// 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;
// This is where we load the JSON data
NSURL *jsonURL = [NSURL URLWithString:#"http://www.thebigfishexperience.org.uk/sources/ajax/venue-json.php?location=peterborough"];
NSString *jsonData = [[NSString alloc] initWithContentsOfURL:jsonURL];
NSLog(#"%#", jsonData);
// Convert jsonData to array
self.jsonArray = [jsonData JSONValue];
NSLog(#"%#", jsonArray);
NSLog(#"count is: %i", [self.jsonArray count]);
// Release NSString and NSURL
[jsonURL release];
[jsonData release];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [self.jsonArray count];
}
- (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];
}
NSMutableDictionary *dict = [self.jsonArray objectAtIndex: indexPath.row];
cell.textLabel.font = [UIFont fontWithName:#"Arial" size:15.0];
cell.textLabel.text = [dict objectForKey:#"venueName"];
cell.detailTextLabel.text = [dict objectForKey:#"venueCordDist"];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
// Configure the cell...
return cell;
Also how can I use the data in the cells to go to another subview of the nav controller which gives me a back button and displays the info from the JSON string just for that particular cell that has been tapped.
I think this has something to do with it? Not sure though as this is my first app i am building! So probably expect more pleas of assistance - ha ! ;)
- (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];
[detailViewController release];
*/
}
On selecting a row, as mentioned by u, we are navigating to another view. Let us assume that the view controller is DetailViewController which is a sub-class of UIViewController.
In the DetailViewController.h , declare a NSDictionary object.
In DetailViewController.m, add
-(void)setVenueDict:(NSDictionary*)venueDict
{
if( _venueDict )
{
[_venueDict release];
}
_venueDict = [venueDict retain];
}
In ParentViewController, ur didSelectRow.. method should be like this.
- (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.
NSDictionary *dict = [self.jsonArray objectAtIndex: indexPath.row];
[detailViewController setVenueDict:dict];
detailViewController.title = [dict objectForKey:#"venueName"];
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
}
In the second view controller, u can do whatever u want with the _venueDict.
Nathan,
since you want to reuse the data parsed from the JSON feed over more than one ViewController the best way to approach this is to build an object Model so that you can pass the object for the selected row in the list to the detail ViewController.
I would also separate the JSON parsing code into a separate class and not keep it in the ViewController.
You can find classes to fetch JSON on this link.
The result from the custom code to parse the JSON feed would give back a NSDictionary with as keys the section names you mention. And the value in the NSDictionary for those keys would be an array of your custom objects that contain all the relevant data for one row (and detail screen).
Hope this helps you on your way.
jsonArray is NSMutableDictionary.
have to use
[jsonArray objectForKey:key];
//check this line
NSMutableDictionary *dict = [self.jsonArray objectAtIndex: indexPath.row];
this may help.