I am quite new to xcode / objective-c and I am facing a problem for a while now.
In my application I have a Json array receiving from my server, I send the array to the secondViewController by segue. This all goes right.
In my secondViewController I receive the array and select the part I needed by;
self.excersiseSection = [self.schemeData objectForKey:#"user_excersise_sections"];
This outputs the part of the array I've pushed to this controller so that's allright. Now, I want
to select the nested 'exercises' array (to print my table cells) in this array I have tried to do it like the code above with the index 'exercises' but no success.
{
"user_training_days": [
{
"day_id": 1,
"day_name": "Monday",
"training": "trainingsdag",
"user_excersise_sections": [
{
"section_title": "Legs",
"exercises": [
{
"exercise_title": "Leg press",
"excercise_sets": "4",
"excercise_reps": "12",
}
]
},...etc
Here is a sample code that will iterate over your json, and simple print it out
for (NSDictionary *section in excersiseSection) {
NSLog(#"--- section_title: %#", section[#"section_title"]);
for (NSDictionary *excercise in section[#"exercises"]) {
NSLog(#"----- exercise_title: %#", excercise[#"exercise_title"]);
NSLog(#"----- excercise_sets: %#", excercise[#"excercise_sets"]);
NSLog(#"----- excercise_reps: %#", excercise[#"excercise_reps"]);
}
}
This should help you, to construct your tableview.
EDIT:
Here is a complete solution to render your tableView.
1) Create a TableViewController in strotybaord, and assign a Custom class in storyboard.
2) Paste below code in the class
3) You are ready to go! :)
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return self.excersiseSection.count;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.excersiseSection[section][#"exercises"] count];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return self.excersiseSection[section][#"section_title"];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"Cell"];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"Cell"];
}
NSDictionary *excercise = self.excersiseSection[indexPath.section][#"exercises"][indexPath.row];
cell.textLabel.text = excercise[#"title"];
cell.detailTextLabel.text = [NSString stringWithFormat:#"Sets: %#, Reps: %#",
excercise[#"excercise_sets"], excercise[#"excercise_reps"]];
return cell;
}
I don't know how your data model is structured, but couldn't you try something like the following:
self.excersiseSection = [[[self.schemeData objectForKey:#"user_excersise_sections"] objectAtIndex:0] objectForKey:#"exercises"];
Just chain the calls together. Note that this won't work if user_excersise_sections and exercises are not NSArray objects.
I'd suggest creating particular classes like UserTrainingDay, UserExcersiseSection and Excercise and use for example Mantle framework to establish mapping between your JSON and model classes.
Related
I have 2 UITableViewControllers in my project.
The problem I am having is that I am getting blank cell entries in the tableView opposite to the tableView where the data is entered.
I can't seem to figure out why this is the case.
It's creating blank rows in this tableView even though the information is from the other UITableViewController.
Here's the main tableView part from the one of the 2 UITableViewControllers:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSLog(#"number of addedSpaceObjects %lu",(unsigned long)[self.diaryoptions count]);
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"number of sections %ld",(long)section);
// Return the number of rows in the section.
return [self.diaryoptions count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentification = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentification
forIndexPath:indexPath];
Data2 *diary = [self.diaryoptions objectAtIndex:indexPath.row];
cell.textLabel.text = diary.diaryname;
cell.detailTextLabel.text = diary.diaryWeight;
return cell;
}
And from other UITableViewController:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
NSLog(#"number of addedSpaceObjects %lu",(unsigned long)[self.addedSpaceObjects count]);
// Return the number of sections.
if ([self.addedSpaceObjects count]) {
return 2;
}
else {
return 1;
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSLog(#"number of sections %ld",(long)section);
// Return the number of rows in the section.
if (section == 1) {
return [self.addedSpaceObjects count];
}
else {
return [self.recipes count];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentification = #"Josh";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentification
forIndexPath:indexPath];
if (indexPath.section == 1) {
Data *recipe = [self.addedSpaceObjects objectAtIndex:indexPath.row];
cell.textLabel.text = recipe.name;
}
else {
// Configure the cell...
Data *recipe = [self.recipes objectAtIndex:indexPath.row];
cell.textLabel.text = recipe.name;
}
return cell;
}
Here is the full project on GitHub. https://github.com/josher32/Plant-Diet
Appreciate any help anyone can offer!
Ok, so I checked out the app and I'll try my best to explain the problem as precisely as I can to cover it adequately.
Firstly, the classes in question are:
RecipesTableTableViewController
AddRecipeViewController
Data
DiaryTableViewController
AddDiaryViewController
Data2
Secondly, we'll need to look into your
#define ADDED_SPACE_OBJECTS2 #"Added Space Objects Array"
AddRecipeViewController
So... AddRecipeViewController basically creates a Data object that is kept in an array and eventually stored in NSUserDefaults under the key name Added Space Objects Array.
Great!! So you now have got recipe related stuff in some Data object.
AddDiaryViewController
Same thing here.
AddDiaryViewController creates a Data2 object that is eventually stored in NSUserDefaults under the same key name Added Space Objects Array.
But before storing this, you're taking the old value of the key Added Space Objects Array, which is an array, and adding a new object to it before placing it back into NSUserDefaults.
But now... this array will now have a combination of Data as well as Data2 objects!
RecipesTableTableViewController
When we come here, things get real.
- (void)viewDidLoad
{
//...
NSArray *myRecipeAsPropertyLists = [[NSUserDefaults standardUserDefaults] arrayForKey:ADDED_SPACE_OBJECTS_KEY];
for (NSDictionary *dictionary in myRecipeAsPropertyLists) {
Data *spaceObject = [self spaceObjectForDictionary:dictionary];
[self.addedSpaceObjects addObject:spaceObject];
}
}
Since we already realized that self.addedSpaceObjects can contain Data as well as Data2 objects, in the case whendictionary is containing stuff specific to type Data2, spaceObjectForDictionary will not be able to translate it properly to the required Data object.
We're expecting name, title, ingredients, directions but we're getting diaryentry, diaryname,diaryWeight.
So (in this scenario):
The values of name, title, ingredients, directions will be nil
The section-row count will be incorrect because it will give count of both Data as well as Data2 objects (and we don't care about Data2 objects in the RecipesTableTableViewController class... right?... well anyways, I assumed)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//...
if (indexPath.section == 1) {
Data *recipe = [self.addedSpaceObjects objectAtIndex:indexPath.row];
cell.textLabel.text = recipe.name;
}
//...
}
We see recipe.name is nil, for some indexPaths, ergo blank rows and vice versa in DiaryTableViewController.
Solution:
Firstly, I wouldn't recommend NSUserDefaults for your purposes but anyways...
Basically, don't use a single #"Added Space Objects Array" key for your NSUserDefaults stuff.
I'd suggest you use 2 separate keys.
//replace
//#define ADDED_SPACE_OBJECTS2 #"Added Space Objects Array"
//with
#define ADDED_SPACE_OBJECTS2 #"RecipeEntries" //in RecipesTableTableViewController
//and
#define ADDED_SPACE_OBJECTS2 #"DiaryEntries" //in DiaryTableViewController
Basically, segregate the entries instead of mixing them up under a single key name.
This seems like the quickest way to solve your problem without changing your logic.
I've seen a number of people who have asked a similar thing, but answers to their questions are not the answers to mine.
1) I have created a single view application with an empty View Controller. In that, I dragged a new Table View (style Plain) with a single prototype cell of style Basic.
2) I am trying to learn about dynamically changing the behaviour of TableViews, so I have a mutable array called sectionRows, which will contain the number of rows per section. At the moment, a single section with a number of rows would be an achievement :)
3) In my ViewController.h I have set the delegates
#interface ViewController : UIViewController <UITableViewDataSource, UITableViewDelegate>
#end
I have also control-dragged from the TableView to the ViewController Yellow-Circle and set the datasource and delegate outlets.
4) In my ViewController.m, I have defined some global variables
#interface ViewController ()
{
NSMutableArray *sectionRows;
UITableView *myTableView;
}
The first is my data array (containing the number of rows per section and the second is a pointer to my TableView, which I have identified using a numeric View tag of '1'.
5) In my viewDidLoad, I initialize everything:
- (void)viewDidLoad
{
[super viewDidLoad];
myTableView = (UITableView *)[self.view viewWithTag:1];
sectionRows = [[NSMutableArray alloc] init]; // Create sectionarray
[sectionRows addObject:[NSNumber numberWithInteger:1]];
myTableView.dataSource = self;
myTableView.delegate = self;
[myTableView reloadData];
}
As you can see, I even make sure that I set the datasource and delegate again but this hasn't made any difference.
5) I have overloaded 3 methods.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSLog(#"Returning %# rows", [sectionRows objectAtIndex:section]);
return (NSInteger)[sectionRows objectAtIndex:section]; // the number referenced in the array...
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
NSLog(#"Returning %li sections", sectionRows.count);
return sectionRows.count; // the size of the sectionRows array
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = (UITableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// Configure the cell...
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.textLabel.text = #"xxx";
NSLog(#"Setting cell to %#", cell);
return cell;
}
Now, when I run this, I am getting NSLog returning confirmation that there is a single section and a single row:
2014-07-27 19:58:34.599 TableViewTests[12877:60b] Returning 1 sections
2014-07-27 19:58:34.600 TableViewTests[12877:60b] Returning 1 rows
However, as you can see cellForRowAtIndexPath is not being called.
None of the other things I have seen point to what I am doing wrong. I am doing what I thought I did successfully in another simple project (to learn) but I must be doing something else differently.
Any ideas what I am missing?
Thanks in advance
Jon
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSLog(#"Returning %# rows", [sectionRows objectAtIndex:section]);
return (NSInteger)[sectionRows objectAtIndex:section]; // the number referenced in the array...
}
This is incorrect. You cannot cast what you get from your array to an NSInteger. It's a pointer. Assuming you store NSNumbers into the array:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
NSLog(#"Returning %# rows", [sectionRows objectAtIndex:section]);
return [[sectionRows objectAtIndex:section] integerValue]; // the number referenced in the array...
}
For example, I have 3 articles and when I display articles I want to display one more cell before the first one (that would be then 4 total).
I need to display that first article which is not in array and then articles which are in array.
UPDATE
I have tried next:
- (NSInteger)tableView:(
UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return ([arr count] + 1);
}
But my app then crash sometimes and I see over NSLOG then that app enters cellForRowAtIndexPath before I call [tableView reloadData].
This is something you should not really do.
Well, you can cheat the framework by returning a view (from -tableView:cellForRowAtIndexPath:) which would contain 2 subviews: your "first article" cell and the original first cell; don't forget to modify -tableView:heightForCellAtIndexPath: as well (otherwise your view would get cut).
But in general, you should change you data model behind the table view to dispay 4 cells – this is just a more valid approach.
You can do like this :
Return an additional row using this method :
// Each row array object contains the members for that section
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [YouArray count]+1;
}
At the end check for this added row :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Create a cell if one is not already available
UITableViewCell *cell = [self.mContactsTable dequeueReusableCellWithIdentifier:#"any-cell"];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"any-cell"] autorelease];
}
//Identify the added row
if(indexpath.row==0)
{
NSLog(#"This is first row");
}
else{
// Write your existing code
}
}
You need to add a extra value in the array using insertObject
[arr insertObject:[NSNull null] atIndex:0];
And implement the methods like:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [arr 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];
}
if([arr onjectAtIndex:indexPath.row] == [NSNull null])
{
// 1st cell extra cell do your stuff
}
return cell;
}
Do you keep all your articles in an array? You should add the new article to the array too. The array is your datasource. I take it that you'd want to insert the new article as the first element in your array if you'd like it to appear in the top cell. As soon as you've updated your array you should call [mytableView reloadData] which triggers all the datasource methods to be called and reload your table's data.
I have two tableViewControllers, my first tableView contains a list of items called folders, on click of which I will open the another tableView called ItemsInfolder and display if they are present or else it will empty.
So for this I know that ItemsInFolder View should be an array of arrays, with each array at index position (row position) , but I am confused, on how to do it , how to call array present at each index on click of each folder.
updated according to comments below
//FolderTableView
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
ItemsTableViewController *selectedFolder = [[ItemsTableViewController alloc] initWithNibName:#"ItemsTableViewController" bundle:nil];
selectedFolder.selectedfolder = [indexPath row];
[self.navigationController pushViewController:selectedFolder animated:YES];
}
//ItemsTableView
-(void)xyz operation
{
[m_ItemArray addObject:item];
[appDelegate.2dArray addObject: m_ItemArray];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [[appDelegate.2dArray objectAtIndex:m_selectedfolder]count];(selected folder index coming from, folderTable)
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSMutableArray *testArray = [appDelegate.2dArray objectAtIndex:[indexPath row]];
tableCell.items.text = [testArray objectAtIndex:[indexPath row]];
return tableCell;
}
So friends, please help me out.
Regards
Ranjit
Considered a arrayOfFolders global and initialized, full of arrays containing your files, following this hierarchy :
arrayOfFolders :
{
[0] //Folder 0 (NSArray)
{
file1
file2
file3
}
[1] //Folder 1 (NSArray)
{
file4
file5
}
}
When you'll click on your folder inside your tableView, get the child array like that :
-(void)didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//To retrieve your array within your array at the seleted index
NSArray * arrayOfItemsInFolder = [[NSArray alloc] initWithArray:[self.arrayOfFolders objectAtIndexPath:indexPath.row] copyItems:YES];
//Do your stuff
}
Does this seems right to you?
I need to create a grouped uitableview that includes some sections and possibly different cell types in each sections.
I am trying to create something like old foursquare app, user page (includes 'leaderboard', 'friend suggestions', 'friends', 'stats', 'most explored categories' ... sections).
I am fairly new to ios programming, so that view may not be a grouped uitableview.
What I especially stuck is creating different cells for sections, and finding out which cells are clicked.
My data source will be 2 different NSArray* that consists of different data types, that's why I need different custom cells.
Since you have two different sets of data and you need to display both in different sections, you have to split the data source methods into two.
Basically, choose which dataset you want to be first and off you go.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
if(section)return secondArray.count;
//Essentially, if statements evaluate TRUE and move forward if the inside is 1 or greater (TRUE == 1)
return firstArray.count;
//If the first if statement return hits, then the code will never reach this statement which turns this into a lighter if else statement
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexPath.section)
{
//do stuff with second array and choose cell type x
}
else
{
//do stuff with first array and choose cell type y
}
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
//Get the cell with: UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
if(indexPath.section)
{
//perform action for second dataset
}
else
{
//perform action for first dataset
}
}
For headers, you can use either of these methods and just keep the same type of styling as above:
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section;
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section;
You can create multiple custom subclasses of UITableViewCell, and in the tableView:cellForRowAtIndexPath: method for your UITableViewDataSource, you can use if-statements to determine what type of cell to use.
For example, here's a rough outline of what I might do:
-(UITableViewCell *)tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
//First, determine what type of object we're showing
if (indexPath.section == 0) {
//Create and return this cell.
} else if (indexPath.section == 1) {
//Create and return this cell.
}...
}
Here's how you'd implement numberOfRowsInSection:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
if (section == 0) {
return [firstSectionArray count];
} else if (section == 1) {
return [secondSectionArray count];
} ...
}
For didSelectRowAtIndexPath
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0) {
ObjectSelected *objectSelected = [firstArray objectAtIndex:indexPath.row];
//Now you've got the object, so push a view controller:
DetailViewController *dvc = [[DetailViewController alloc] init];
dvc.objectSelected = objectSelected;
[self.navigationController pushViewController:dvc];
} else if (indexPath.section == 1) {
//Same thing, just call [secondArray objectAtIndex:indexPath.row] instead!
}
}