I have an NSArray of songs fetched from a server. That is my raw data source. The array contains custom objects which have an NSDictionary and NSArray as backend.
I am wondering if implementing a formatted data source using an NSDictionary will be wise. The dictionary will have section headers as keys, and the value of a certain key will have an NSArray containing the rows for that section.
I will be iterating through my raw data source and arranging it alphabetically into the dictionary.
I have a feeling that this is not a solid implementation and is very expensive. Is there any other, more solid implementation than this?
For small tables, rather than NSDictionary, I generally use NSArray, since dictionaries don't preserve order (and you probably don't want to continually re-sort). So I usually have an array of sections, for which I have for each section entry, at the very least, a section title and an array of rows. My array of rows has, that information that I need to present a given row (e.g. the text of the row, etc.).
The individual row and section objects, you can implement those as a NSDictionary objects themselves (and sometimes when parsing the data from JSON or XML, that's easiest), but I generally define my own Row and Section objects, e.g.:
#interface Row : NSObject
#property (nonatomic, strong) NSString *title;
#property (nonatomic, strong) NSString *subtitle;
#end
and
#interface Section : NSObject
#property (nonatomic, strong) NSString *title;
#property (nonatomic, strong) NSMutableArray *rows;
#end
Then my table view controller has an NSArray for the sections:
#property (nonatomic, strong) NSMutableArray *sections;
And I populate it like so:
self.sections = [NSMutableArray array];
Section *sectionObject;
sectionObject = [[Section alloc] initWithTitle:#"Marx Brothers" rows:nil];
[sectionObject.rows addObject:[[Row alloc] initWithTitle:#"Chico" subtitle:#"Leonard Marx"]];
[sectionObject.rows addObject:[[Row alloc] initWithTitle:#"Harpo" subtitle:#"Adolph Marx"]];
[sectionObject.rows addObject:[[Row alloc] initWithTitle:#"Groucho" subtitle:#"Julius Henry Marx"]];
[sectionObject.rows addObject:[[Row alloc] initWithTitle:#"Zeppo" subtitle:#"Herbert Manfred Marx"]];
[self.sections addObject:sectionObject];
sectionObject = [[Section alloc] initWithTitle:#"Three Stooges" rows:nil];
[sectionObject.rows addObject:[[Row alloc] initWithTitle:#"Moe" subtitle:#"Moses Harry Horwitz"]];
[sectionObject.rows addObject:[[Row alloc] initWithTitle:#"Larry" subtitle:#"Louis Feinberg"]];
[sectionObject.rows addObject:[[Row alloc] initWithTitle:#"Curly" subtitle:#"Jerome Lester \"Jerry\" Horwitz"]];
[self.sections addObject:sectionObject];
And then I have the typical UITableViewDataSource methods:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [self.sections count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
Section *sectionObject = self.sections[section];
return [sectionObject.rows count];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
Section *sectionObject = self.sections[section];
return sectionObject.title;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell"];
Section *sectionObject = self.sections[indexPath.section];
Row *rowObject = sectionObject.rows[indexPath.row];
cell.textLabel.text = rowObject.title;
cell.detailTextLabel.text = rowObject.subtitle;
return cell;
}
For bigger, database data driven tables, I might not keep the data in arrays, but rather use Core Data or SQLite, but the idea is the same. Make sure I have Section and Row classes that keep my table view controller code self-explanatory and insulated from the details of the data implementation.
Have you tried RestKit? All you need is to provide source of json encoded objects and create model classes
You can use a model class and then create objects of this model class with the data from the server. Then use the array of model class objects as a source to your UITableView. You could also consider core-data , in which its very easy to create data models and relations. However it comes with a lot of additional features which might be extra baggage for you.
Related
I am having some trouble trying to share a NSMutableArray of Strings to another class. I have a tableView that is populated with Strings that I would like to add to a NSMutableArray. Then use that SAME NSMutableArray in another ViewController
I have created a class with the subclass of NSMutableArray
.h
#import <Foundation/Foundation.h>
#interface HYServicesMArray : NSMutableArray
#property (nonatomic, weak)NSMutableArray * arrServicesUserChoice;
#end
.m
#import "HYServicesMArray.h"
#implementation HYServicesMArray
#dynamic arrServicesUserChoice;
#end
I am trying to add elements to this NSMutableArray from a tableView didSelectRowAtIndexPath:
.m
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSLog(#"You have selected: %#",cell.textLabel.text);
// add cell.textLabel.text to arrServicesUserChoice
// Tried the code below but causes my app to crash
arrServicesUserChoice = [[NSMutableArray alloc]init];
[arrServicesUserChoice addObject:cell.textLabel.text];
}
How ever I am unable to add elements to the arrServicesUserChoice. I am stuck please help! Thank you in advance!
YOu declared a mutable array, and the code you used to add returns an array, but it dont add anyting. you have to add the objects like:
[arrAvaialbleServicesList addObject:cell.textlabel.text];
After this, your mutable array will have the added data
or you can declare another array, and do it your way"
NSArray *test=[arrAvaialbleServicesList arrayByAddingObject:cell.textLabel.text];
after this, the new array will have the array with added data.
I think you should change
#dynamic arrServicesUserChoice;
To
#synthesize arrServicesUserChoice;
The original initialization to MutableArray does nothing really and does not specify arrAvalabelServiceList as Mutable - arrayByAddingObject creates a brand new instance which is NSArray, NOT NSMutableArray
arrAvaialbleServicesList = [[NSMutableArray alloc]init];
[arrAvaialbleServicesList arrayByAddingObject:cell.textLabel.text];
You are however not assigning this new instance to anything, if you did:
arrAvaialbleServicesList = [arrAvaialbleServicesList arrayByAddingObject:cell.textLabel.text];
You would at least get a handle to the new NSArray instance.
What you should probably to is to create your list when you setup the table:
arrAvaialbleServicesList = [[NSMutableArray alloc]init];
The on your didSelect you would just add the text to the list:
NSString *selected = [NSString stringWithString:cell.textLabel.text];
[arrAvaialbleServicesList addObject:selected];
or something close to this then you would add something to your list whenever you select a row in the table.
What you try to create is a kind of public static var correct? i think the better way to do that is creating an Class that can handle your MutableArray object. Doing her as a singleton class is a good way to guarantee that you will not create another instance of your object and lost your MutableArray. Here is a sample:
HYServicesMArray.h
#import <Foundation/Foundation.h>
#interface HYServicesMArray : NSObject
#property (nonatomic, strong) NSMutableArray *arrServicesUserChoice;
+ (HYServicesMArray *) instance;
#end
HYServicesMArray.m
#import "HYServicesMArray.h"
#implementation HYServicesMArray
+ (HYServicesMArray *)instance
{
static HYServicesMArray *_ServicesMArray = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_ServicesMArray = [[self alloc] init];
});
return _ServicesMArray;
}
-(id)init {
self.arrServicesUserChoice = [[NSMutableArray alloc] init];
return self;
}
#end
And the application on your didSelectRowAtIndexPath method:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSLog(#"You have selected: %#",cell.textLabel.text);
[[HYServicesMArray instance].arrServicesUserChoice addObject:cell.textLabel.text];
}
You should make it #synthesized and dereference the property as self.arrServicesUserChoice. (You can leave the self away only in Swift.)
This should compile:
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
NSLog(#"You have selected: %#",cell.textLabel.text);
self.arrServicesUserChoice = [[NSMutableArray alloc] init];
[self.arrServicesUserChoice addObject:cell.textLabel.text];
}
You do not need to declare it #dynamic, You need to #synthesize it. If you don't want to #synthesize it then use self. You can check the difference between here between these two here.
#synthesize vs #dynamic, what are the differences?
If you want to use this same array in another class then you should declare a property in that class and pass this class's array to another class using segue.
Goal
I'm trying to create a table view like Instegram's home screen.
I've made a custom cell, I'm initialising it with data, the cell suppose to hold the "Post".
Logic
I save each cell in a NSMutableDictionary , the key is the index of the posts order and the value is the post it self.
Current Result
I scroll down, and everything is fine. The order you see is post1, post2, post3...post 8 but when I scroll up, everything mess up and the post order is post8, post6, post7, post8, post5... You get the point.
(Before asking here I tried doing it with small objects - a REGULAR ! (not custom) cell containing only strings.
for some reason it worked ! the order was perfect.)
Code
this is my UITableViewController - my "Home" screen "cellForRow" Method.
if we scroll up and the index of the Tableview is alrdy have been initialised, I pull the post form the dictionary.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *PC = #"PostCell";
PostCell *Pcell = [tableView dequeueReusableCellWithIdentifier:PC forIndexPath:indexPath];
NSString *key = [[NSString alloc] initWithFormat:#"%i", indexPath.section];
NSLog(#"Cell %i", indexPath.section);
// Checking if alrdy visted this indexpath.
if (![_allcells objectForKey:key]) {
[self setPostUserName:[[NSString alloc] initWithFormat:#"username: %i", indexPath.section]:Pcell];
// Saving a postcell I wont return, just to save in a dictionary.
// When we get here again it will get another pointer like that my object wont change.
PostCell* toSave = [[PostCell alloc] init];
// saving it with current post data.
[self copyPost:toSave :Pcell];
[_allcells setObject:toSave forKey:key];
}
else {
// Copying post daya
[self copyPost:Pcell :[_allcells objectForKey:key]];
}
NSLog(#"Cell %i Returning: %#", indexPath.section, Pcell.userName.text);
return Pcell;
}
// Check if it reached the end
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
float endScrolling = scrollView.contentOffset.y + scrollView.frame.size.height;
if (endScrolling >= scrollView.contentSize.height)
{
NSDictionary *temparr = [[NSDictionary alloc] initWithDictionary:_allcells];
[self.tableView reloadData];
_allcells = [[NSMutableDictionary alloc] initWithDictionary:temparr];
}
}
and this is my PostCell.h , so you can see the attributes.
#interface PostCell : UITableViewCell
#property (strong, nonatomic) IBOutlet UIImageView *profilePic;
#property (strong, nonatomic) IBOutlet UILabel *userName;
#property (strong, nonatomic) IBOutlet UILabel *checkIn;
#property (strong, nonatomic) IBOutlet UILabel *uploadedAgo;
#property (strong, nonatomic) IBOutlet UIImageView *mainPic;
#property (strong, nonatomic) IBOutlet UILabel *likes;
#property (strong, nonatomic) IBOutlet UILabel *participants;
#end
By the way, if you got a project example that has a result similar to Instagram home screen it would be great if you can link me to it!
You shouldn't store references to the cells, since they are being reused by the tableview when they leave the screen. At the moment everything works fine for you while scrolling down the first time, because you create the cells new. On scrolling up, you get the stored reference which now points to one of the newly created cells, so things look messed up.
What you should do is just populating the reused cells with the right data and only create them if needed. Like:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *PC = #"PostCell";
PostCell *Pcell = (PostCell*)[tableView dequeueReusableCellWithIdentifier:PC forIndexPath:indexPath];
// feed the needed data to the cell
return Pcell;
}
Not sure why you are accessing only indexPath.section since usually you would populate the table with multiple cells per section, using indexPath.row.
Okay, I found out the mistake, Because I was first I was saving the id of each Post and than took it out of the dictionary it didn't work.
than I was trying to only save the attributes of the post by creating a "fake" postCell and saving the attribues of the original cell in the fake cell and than took it out of the array and made the cell copy ONLY the attributes of the cell I just took out and it didn't work.
why? because no matter what I was saving a POINTER ! to those fields !.
I've created a class which was ment to save my desired data that's the class I entered my dictionary.
now each time I dequeue a cell, I load it with the data of the index I'm at. :)
So nice to finally solve it and to learn something new, thanks for giving me leads !
A UITableView works best with an array. Also an array will stay in order as it is indexed, while dictionaries have no index, and thus will not retain order. Also separate cells of the same type, in this case PostCell, should not have different sections, but instead different rows. Use section to separate different categories of cells.
Another tip; NSMutableDictionary takes up more memory than an NSDictionary. Once you have set everything in your NSMutableDictionary, store it in an NSDictionary. If you want to modify it in the future, copy it back into an NSMutableDictionary, modify it, and then store it again as an NSDictionary.
#property (strong, nonatomic) NSDictionary *post;
// To create an NSDictionary
NSMutableDictionary *tempDict = [[NSMutableDictionary alloc] init];
[tempDict setValue:#"74 degrees" forKeyPath:#"weather"];
post = tempDict;
// To modify an NSDictionary
tempDict = [[NSMutableDictionary alloc] initWithDictionary:post];
[tempDict removeObjectForKey:#"weather"];
post = tempDict;
Then store it in an array.
#property (strong, nonatomic) NSArray *allPosts;
// NSMutableArray takes up more memory than NSArray
NSMutableArray *tempArray = [[NSMutableArray alloc] initWithArray:allPosts];
[tempArray addObject:post];
allPosts = tempArray;
Finally, display it in your tableview.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *PC = #"PostCell";
PostCell *pCell = [tableView dequeueReusableCellWithIdentifier:PC];
if (!cell) {
pCcell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:PC];
}
NSDictionary *currentPost = [allPosts objectAtIndex:indexPath.row];
// Instead of having post1, post2, post3 and so forth, each post is now in currentPost. If there are 10 posts, then this function will run 10 times. Just write the code as if you are handling one post, and the UITableView will automatically fill in the rest of the posts for you.
return cell;
}
I'm using a UITableViewController to display a list of articles from a web service. Once the data is retrieved this delegate method is called:
-(void)itemsDownloaded:(NSArray *)items
{
// Set the items to the array
_feedItems = items;
// Reload the table view
[self.tableView reloadData];
}
I'm also using a custom cell so that the label's height varies, therefore displaying the whole of the article's title with the following code (followed this tutorial Table View Cells With Varying Row Heights):
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellIdentifier = #"BasicCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];
[self configureCell:cell forRowAtIndexPath:indexPath];
return cell;
}
- (void)configureCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([cell isKindOfClass:[CustomTableViewCell class]])
{
CustomTableViewCell *textCell = (CustomTableViewCell *)cell;
Article *article_item = _feedItems[indexPath.row];
NSString *fulltitle = article_item.Title;
// fulltitle = article_item.Cat_Name; // testing category name
if (article_item.Subtitle != nil && article_item.Subtitle.length != 0) {
fulltitle = [fulltitle stringByAppendingString:#": "];
fulltitle = [fulltitle stringByAppendingString:article_item.Subtitle];
}
textCell.lineLabel.text = fulltitle;
textCell.lineLabel.numberOfLines = 0;
textCell.lineLabel.font = [UIFont fontWithName:#"Novecento wide" size:12.0f];
}
}
- (CustomTableViewCell *)prototypeCell
{
NSString *cellIdentifier = #"BasicCell";
if (!_prototypeCell)
{
_prototypeCell = [self.tableView dequeueReusableCellWithIdentifier:cellIdentifier];
}
return _prototypeCell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
[self configureCell:self.prototypeCell forRowAtIndexPath:indexPath];
self.prototypeCell.bounds = CGRectMake(0.0f, 0.0f, CGRectGetWidth(self.tableView.bounds), CGRectGetHeight(self.prototypeCell.bounds));
[self.prototypeCell layoutIfNeeded];
CGSize size = [self.prototypeCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height+1;
}
The first issue is that the method forRowAtIndexPath is being called twice instead of once. Therefore if the _feeditems has 10 objects, the method is called 20 times. The second time the method is called I'm getting two properties (ID and Cat_Name) of the Article object null since of deallocation:
*** -[CFString retain]: message sent to deallocated instance 0x9c8eea0
*** -[CFNumber respondsToSelector:]: message sent to deallocated instance 0x9c8e370
This fires an EXC_BAD_ACCESS when trying to display the category name.
I'm not sure what can be the problem exactly, I've tried removing the code to vary the height of the labels to see if that was causing this problem by using this code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Retrieve cell
NSString *cellIdentifier = #"BasicCell";
UITableViewCell *myCell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
// Get article
Article *item = _feedItems[indexPath.row];
myCell.textLabel.text = item.Title;
return myCell;
}
The only difference was that the method was being called once meaning 10 times if _feeditems has 10 objects. But the Article's properties ID and Cat_Name were still being deallocated.
At the point of getting the data, all objects' properties in _feeditems are intact, nothing deallocated. I guess it's happening in cellForRowAtIndexPath or forRowAtIndexPath.
UPDATE
As suggested by #Ilya K. not calling configureCell:forRowAtIndexPath: from tableView:heightForRowAtIndexPath stopped the issue of having it being called twice. I've also tried having a property of feedItems. So far this was being set in the #interface of the Controller (TableViewController.m):
#interface TableViewController () {
HomeModel *_homeModel;
NSArray *_feedItems;
Article *_selectedArticle;
}
I've removed it from the interface and added it as a property (TableViewController.h):
#interface TableViewController : UITableViewController <HomeModelProtocol>
#property (weak, nonatomic) IBOutlet UIBarButtonItem *sidebarButton;
#property (nonatomic, strong) CustomTableViewCell *prototypeCell;
#property(nonatomic) NSString *type;
#property(nonatomic) NSString *data;
#property(copy) NSArray *_feedItems;
#end
It's still giving deallocated messages though.
UPDATE 2
I've looked through the code using Instruments with a Zombie template (thanks to the answer of this question ViewController respondsToSelector: message sent to deallocated instance (CRASH)). This is the error I'm getting from Instruments:
Zombie Messaged
An Objective-C message was sent to a deallocated 'CFString (immutable)' object (zombie) at address: 0x10c64def0
All Release/Retain Event Types point to the following method, connectionDidFinishLoading, which is being used when the JSON data is retrieved from the web service and create Article objects for each article retrieved:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
// Create an array to store the articles
NSMutableArray *_articles = [[NSMutableArray alloc] init];
// Parse the JSON that came in
NSError *error;
// Highlighted in blue
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:_downloadedData options:kNilOptions error:&error];
NSArray *fetchedArr = [json objectForKey:#"result"];
// Loop through Json objects, create question objects and add them to our questions array
for (int i = 0; i < fetchedArr.count; i++)
{
NSDictionary *jsonElement = fetchedArr[i];
// Create a new location object and set its props to JsonElement properties
Article *newArticle = [[Article alloc] init];
newArticle.ID = jsonElement[#"ID"];
newArticle.Title = jsonElement[#"Title"];
newArticle.Subtitle = jsonElement[#"Subtitle"];
newArticle.Content = jsonElement[#"Content"];
newArticle.ImageUrl = jsonElement[#"ImageUrl"];
newArticle.Author = jsonElement[#"Author"];
newArticle.PostId = jsonElement[#"PostId"];
newArticle.ArticleOrder = jsonElement[#"ArticleOrder"];
newArticle.Cat_Id = jsonElement[#"CategoryId"];
// Highlighted in yellow
newArticle.Cat_Name = jsonElement[#"CategoryName"];
// Add this article object to the articles array
// Highlighted in yellow
[_articles addObject:newArticle];
}
// Ready to notify delegate that data is ready and pass back items
if (self.delegate)
{
[self.delegate itemsDownloaded:_articles];
}
}
I still can't figure out what is wrong though.
UPDATE 3
More testing on connectionDidFinishLoading I've removed the two properties that are being deallocated and no deallocated messages are shown. I don't know what's causing these two properties (ID and Cat_Name) to be deallocated, these are not being accessed from anywhere at this point.
You don't need to call to configureCell:forRowAtIndexPath: from tableView:heightForRowAtIndexPath: you should determine cell height using Article object with sizeWithAttributes:
Your prototypeCell function just creates unrelated empty cell of type CustomTableViewCell and there is no point of trying re-size it.
tableView:cellForRowAtIndexPath: called each time your tableview needs redraw, when you scroll for example. That means that your _feeditems array should be allocated and consistent to work with UITableView at any point of instance life time.
Also make sure you declare a property for _feeditems and assign data using this property.
Example:
#property (strong) NSArray *feeditems; or #property (copy) NSArray *feeditems;
in itemsDownloaded:
self.feeditems = items;
Finally solved the deallocated messages issue. While using Instruments with a Zombie template (using Instruments and Zombie template: ViewController respondsToSelector: message sent to deallocated instance (CRASH)) I found that this line:
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:_downloadedData options:kNilOptions error:&error];
in the connectionDidFinishLoading method was causing this problem. I searched for NSJSONSerialization causing deallocation messages and I got the answer from this question Loading properties from JSON, getting "message sent to deallocated instance". The Article class had a few properties that were set to assign instead of strong:
#interface Article : NSObject
#property (nonatomic, assign) NSNumber *ID; // changed assign to strong
#property (nonatomic, strong) NSString *Title;
#property (nonatomic, strong) NSString *Subtitle;
#property (nonatomic, strong) NSString *Content;
#property (nonatomic, strong) NSString *ImageUrl;
#property (nonatomic, assign) NSNumber *PostId; // changed assign to strong
#property (nonatomic, strong) NSString *Author;
#property (nonatomic, assign) NSNumber *ArticleOrder; // changed assign to strong
#property (nonatomic, assign) NSNumber *Cat_Id; // changed assign to strong
#property (nonatomic, assign) NSString *Cat_Name; // changed assign to strong
#end
After changing the properties to strong, all deallocated messages stopped.
I know that this error seems to be very specific to each project and the cause of it may vary but in case someone has something similar, this is how I solved it.
In my iPhone app I want to show a table view with section headers.
I have a datasource like this(Example)
(
{date : 12-10-2014 rowdata:abc},
{date : 12-10-2014 rowdata:pqr},
{date : 12-10-2014 rowdata:xyz},
{date : 13-10-2014 rowdata:123},
{date : 13-10-2014 rowdata:780},
{date : 14-10-2014 rowdata:tuv},
)
I want to show the section header as the date - and have to show row data in it like this
(just consider it as a format of table view-dates are it header views and below rows)
12-10-2014
abc
pqr
xyz
13-10-2014
123
780
13-10-2014
tuv
please give me a logic to do how to change or create new arrays with my data source, I am clear working with sectioned tableview and header views.
i can create a seperate array with dates for header view, but how can I show rowdata under every section, because in every section row starts with index 0.
You are basically asking how to categorise your rowdata data by its date key.
Simple. You need to do the following:
Create data models for your data couplets so that its nicer to populate your table with and store in arrays :)
Create a dictionary that will store the date as keys - you can imagine them as categories - and the row data as objects stored in an array for each category.
Step 1:
Create your custom data model to make data management more easier and cleaner.
Create two new files called rowDataModel.h and rowDataModel.m. They will be a subclass of the NSObject class.
This is what your .h file would look like:
#import <Foundation/Foundation.h>
#interface rowDataModel : NSObject
#property (nonatomic, retain) NSString * rowDataDate;
#property (nonatomic, retain) NSString * rowDataInformation;
-(id)initWithJSONData:(NSDictionary*)data;
#end
and in your .m file you will have this:
#import "rowDataModel.h"
#implementation rowDataModel
#synthesize rowDataDate;
#synthesize rowDataInformation;
-(id)initWithJSONData:(NSDictionary*)data{
if (self = [super init]) {
self.rowDataDate = [data objectForKey:#"date"];
self.rowDataInformation = [data objectForKey:#"rowdata"];
}
return self;
}
#end
We are doing this as this will contain easy to work with containers. Its a good way to manage your custom json objects too.
Step 2
Now in your view controller where you will be populating the database and categorising your data, make sure you import your custom rowDataModel.h file
NSMutableDictionary *myData = [[NSMutableDictionary alloc] init];
//Have a for loop that iterates through your array
for(NSDictionary *currentItem in serverArray){
RowData *newItem = [[RowData alloc] initWithJSONData:currentItem];
//If category the date key does not exist, then create a new category for it
if([myData objectForKey:newItem.rowDataDate] == nil){
//We want to create an array for each category that will hold all the row datas
[myDate setObject:[NSMutableArray new] forKey:newItem.rowDataDate];
}
//Grab the array from a particular category and add the current item to this category
[[myDate objectForKey:newItem.rowDataDate] addObject:newItem];
}
Now when you are populating your table delegate methods, in your titleForHeader tableview delegate methods, you simply grab the category titles: the dates from your myData dictionary using the allKeys property, and call the `objectForIndex: method for the section counter in your delegate method, ad you use that string to set the title of your header view by using the titleForHeader table view delegate method.
And in your cellForRowAtIndexPath: delegate method, you simply grab the correct category in your myData array by using the keys, and then access the individual objects in the array based on the row count.
And thats all bro. Goodluck
disclaimer, I have not tested this code, this should work right off the bat :)
The best way to do this would be creating a custom object, let's call it MyDate. My date would have two properties: a date, and an array.
#interface MyDate
#property NSDate *date;
#property NSArray *items;
#end
You parse the string, and for each information bit, you check the date. If the date is not in the array you add it. Then you add the other string to the items array for the appropriate MyDate object.
This way, every object in items starts with index 0, just like the rows in each section. So managing your model this way makes it easy to handle the user interface.
You could do this with arrays and dictionaries but it get's a little messy and you are probably best of creating new objects that can handle this.
I would probably start with something that looks a little like this
#interface NMKSection : NSObject
#property (nonatomic, strong) NSDate *date;
#property (nonatomic, strong) NSArray *rows;
#end
#interface NMKDataSource : NSObject
#property (nonatomic, strong) NSArray *data;
#property (nonatomic, strong) NSArray *sectionSortDescriptors;
#property (nonatomic, strong) NSArray *rowSortDescriptors;
- (NMKSection *)sectionAtIndex:(NSInteger)index;
- (void)processData;
#end
This would then mean that your UITableViewDataSource can look very simple
- (void)methodToSetupDataSource
{
self.dataSource = [[NMKDataSource alloc] init];
self.dataSource.data = data
self.dataSource.sectionSortDescriptors = // optional;
self.dataSource.rowSortDescriptors = // optional;
[self processData];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
NMKSection *section = [self.dataSource sectionAtIndex:section];
return [self.dataFormatter stringFromData:section.date];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = // get cell
id row = [self.dataSource sectionAtIndex:indexPath.section].rows[indexPath.row];
// configure cell
return cell;
}
To achieve this all the heavy lifting is hidden in the NMKDataSource. The implementation fo NMKSection can just be left blank as it will contain no logic
#implementation NMKSection
#end
All the hard work is started from processData
#interface NMKDataSource ()
#property (nonatomic, strong) NSArray *sections;
#end
#implementation NMKDataSource
- (id)init;
{
self = [super init];
if (self) {
_sections = [[NSMutableArray alloc] init];
}
return self;
}
- (NMKSection *)sectionAtIndex:(NSInteger)index;
{
return self.sections[index];
}
- (void)processData
{
if (self.sectionSortDescriptors) {
self.sections = [self.unsortedSections sortedArrayUsingDescriptors:self.sectionSortDescriptors];
} else {
self.sections = [self.unsortedSections copy];
}
}
- (NSDictionary *)groupedData;
{
NSMutableDictionary *groupedData = [[NSMutableDictionary alloc] init];
[self.data enumerateObjectsUsingBlock:^(NSDictionary *row, NSUInteger idx, BOOL *stop) {
NSString *key = row[#"date"];
NSMutableArray *rows = groupedData[key];
if (!rows) {
groupedData[key] = rows = [[NSMutableArray alloc] init];
}
[rows addObject:row[#"rowdata"]];
}];
return groupedData;
}
- (NSArray *)unsortedSections;
{
NSMutableArray *unsortedSections = [[NSMutableArray alloc] init];
[self.groupedData enumerateKeysAndObjectsUsingBlock:^(NSDate *date, NSArray *rows, BOOL *stop) {
[unsortedSections addObject:({
NMKSection *section = [[NMKSection alloc] init];
section.date = date;
section.rows = rows;
if (self.rowSortDescriptors) {
section.rows = [rows sortedArrayUsingDescriptors:self.rowSortDescriptors];
}
section;
})];
}];
return [unsortedSections copy];
}
#end
This was written in the browser so it's not been tested. The code it just an example of where I would probably start and develop this further until I was happy
I have a NSMutableArray of seven bools in managerViewController, these represent days of the week when a shop is open. I am running out of space in this view and most of the time the user will be happy with the default setting of open all hours.
the user needs to be able to change them to suit their business needs, my current approach to this is to have a uitableview of seven rows all of which have switches in them. where I am stuck is how to the actions of in uitableview modify the original nsmuntable array in the manageViewController class.
I am new to iOS, but I have built the UITableView and all the other bits, it is just accessing the NSMutableArray I am stuck on.
Use replaceObjectAtIndex:withObject: or, with the new objective-c literals it's much easier:
NSMutableArray *myArray = [NSMutableArray array];
myArray[0] = #(YES); // the same as: [myArray addObject:[NSNumber numberWithBool:YES]];
myArray[0] = #(NO); // the same as: [myArray replaceObjectAtIndex:0 withObject:[NSNumber numberWithBool:YES]];
Define a delegate to your table view controller, sayTableViewControllerDelegate. Add a delegate property to your table view controller
#property (weak, nonatomic) id delegate;
Make your ManagerViewController conform to the delegate protocol, and upon showing TableViewController, set its delegate to be your ManagerViewController instance.
Add a method in the delegate to inform the action on a day, for example:
- (void) didModifyDay:(NSInteger)dayNumber;
or
- (void) didChangeDay:(NSInteger)dayNumber toState:(BOOL)selected;
When table view is loading in the
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
make sure to check initial values for all of the switch and set values from your data source (the mutable array). For easily handle the situation use a custom and put its object in the array.
#interface Days : NSObject
#property (nonatomic, retain) NSString *day;
#property (nonatomic, assign) BOOL isOpen;
#end
i.e.
for(int ii = 0; ii < 7; ii++ ){
Days *days = [[Days alloc] initWithDay:ii isOpen:YES];
[array addObject:days];
[days release];
}
Assume that you have a custom UITableViewCell which have a property set to the switch.
i am using cell.switch for example.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *identifier = #"reusableCells";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if(cell == nil)
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];
Days *days = [array objectAtIndex:indexPath.row];
cell.textLabel.text = days.day;
[cell.switch setOn:days.isOpen animated:YES];
return cell;
}
and this will handle your switch.
now assume you toggle the switch on/off when press the table cell so handle the didSelectRowAtIndexPath method of the tableview delegate
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
Days *days = [array objectAtIndex:indexPath.row];
days.isOpen != days.isOpen;
[cell.switch setOn:days.isOpen animated:YES];
}
That's all. you will get the switch toggle on/off and the will be manipulated from the array.
You can use the app delegate class of your application to access the NSMutableArray.
In the app delegate class create the property for the class in which that NSMutableArray is present for e.g let say class name is TestController.
//then in .h file of Appdelegate
#import TestController.h
//in the interface
TestController *objTest;
//declare the property
#property (nonatomic, strong) TestController *objTest;
//then in the .m file of app delegate synthesize the object
#synthesize objTest;
Now in the similar way declare the property for NSMutableArray in the TestController
Create the share instance of the app delegate class where you want to access the NSMutable array object(say arr1) as below
AppDelegate *objAppDelegate = (AppDelegate*)[[UIApplication sharedApplication] delegate];
You can access that array as "objAppDelegate.objTest.arr1"
Or
You can create the share instance in the view controller class
//in the .h of view controller
+ (id)sharedInstance;
//in the .m of view controller
+ (id)sharedInstance
{
// structure used to test whether the block has completed or not
static dispatch_once_t p = 0;
// initialize sharedObject as nil (first call only)
__strong static id _sharedObject = nil;
// executes a block object once and only once for the lifetime of an application
dispatch_once(&p, ^{
_sharedObject = [[self alloc] init];
});
// returns the same object each time
return _sharedObject;
}
Then in the application where you fill the array use,
TestViewController *objTest=[TestViewController sharedInstance];
objTest.arr1=[[NSMutableArray alloc]initWithObjects:#"1",#"1",#"1",#"1", nil];
And in the table view controller you can access it as below
TestViewController *objTest=[TestViewController sharedInstance];
objTest.arr1 will be the resulted array.