I have a uitableview with a list of items populated from a JSON file located locally within the app. Everything works as far as getting the list to the table and the multiple selection of items which when selected (or deselected) are then saved to a nsmutablearray.
The problem is when the user leaves the view and returns and selects another item (or deselects a currently selected item). At this point the mutable array is then empty.
I'm not sure if the nsuserdefaults saving of the mutable array is the problem. it saves it fine but then when the view reappears (the mutable array's value is fine at this point) and the user touches a table row the array is null once more.
my .h file:
#interface CategoriesViewController : UITableViewController {
NSMutableArray *_selectedItems;
NSString *filePath;
NSString *string;
}
// arForTable array will hold the JSON results from the api
#property (nonatomic, retain) NSArray *arForTable;
#property (nonatomic, retain) NSMutableArray *categorySelected;
#property (nonatomic, retain) NSString *jsonStringCategory;
#property(nonatomic, retain) UIView *accessoryView;
#end
my .m file:
#implementation CategoriesViewController
#synthesize arForTable = _arForTable;
- (void)viewDidLoad
{
[super viewDidLoad];
self.categorySelected = [[NSMutableArray alloc] init];
[self reloadMain];
// assignment reference so don't release
_selectedItems = [(AppDelegate *)[[UIApplication sharedApplication] delegate] selectedCategories];
self.tableView.hidden = NO;
}
-(void) reloadMain {
// countrySaved value from NSUserDefaults
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
NSString *countryString = [defaults stringForKey:#"selectedCountryTableString"];
NSString *cityString = [defaults stringForKey:#"selectedCityTableString"];
NSLog(#"countrystring from category is %#", countryString);
NSLog(#"citystring from category is %#", cityString);
// getting path to the file
if ([defaults stringForKey:#"selectedCountryTableString"] == NULL) {
filePath = [[NSBundle mainBundle] pathForResource:#"categoriesit" ofType:#"json"];
} else if ([countryString isEqualToString:#"UK"]) {
filePath = [[NSBundle mainBundle] pathForResource:#"categoriesuk" ofType:#"json"];
} else if ([countryString isEqualToString:#"Italy"]) {
filePath = [[NSBundle mainBundle] pathForResource:#"categoriesit" ofType:#"json"];
} else if ([countryString isEqualToString:#"Spain"]) {
filePath = [[NSBundle mainBundle] pathForResource:#"categorieses" ofType:#"json"];
} else if ([countryString isEqualToString:#"Brazil"]) {
filePath = [[NSBundle mainBundle] pathForResource:#"categoriesbr" ofType:#"json"];
}
NSString *fileContent = [[NSString alloc] initWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
//NSLog(#"File content: %#", fileContent);
// creating new parser
SBJSON *parser = [[SBJSON alloc] init];
// parsing the first level
NSDictionary *data = (NSDictionary *) [parser objectWithString:fileContent error:nil];
NSDictionary *menu = (NSDictionary *) [data objectForKey:#"menu"];
#ifdef DEBUG
NSLog(#"menu is %#",menu);
#endif
NSMutableArray *itemsTMP = [[NSMutableArray alloc] init];
NSData *jsonData = [NSData dataWithContentsOfFile:filePath];
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData options:nil error:nil];
// NSLog(#"results File test %#",dict);
itemsTMP = [dict objectForKey:#"results"];
// NSLog(#"itemsTMPitemsTMP File test %#",itemsTMP);
self.arForTable = [itemsTMP copy];
[self.tableView reloadData];
}
#pragma mark - Table view data source
- (int)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [self.arForTable 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];
[cell.textLabel setFont:[UIFont fontWithName: #"Asap-Bold" size: 14.0f]];
[cell.detailTextLabel setFont:[UIFont fontWithName: #"Asap-Bold" size: 14.0f]];
cell.selectedBackgroundView = [[UIView alloc] initWithFrame:CGRectZero];
cell.selectedBackgroundView.backgroundColor = [UIColor colorWithRed:204.0/255.0 green:56.0/255.0 blue:55.0/255.0 alpha:1];
}
UIImageView *cellAccessoryImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#"icon-tick.png"]] ;
UIImageView *cellAccessoryNoneImageView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:#""]] ;
if([_selectedItems containsObject:indexPath]){
cell.accessoryView = cellAccessoryImageView;
} else {
cell.accessoryView = cellAccessoryNoneImageView;
}
// Get item from tableData
NSDictionary *item = (NSDictionary *)[_arForTable objectAtIndex:indexPath.row];
// encoding fix
NSString *correctStringTitle = [NSString stringWithCString:[[item objectForKey:#"key"] cStringUsingEncoding:NSISOLatin1StringEncoding] encoding:NSUTF8StringEncoding];
cell.textLabel.text = [correctStringTitle capitalizedString];
NSNumber *num = [item objectForKey:#"id"];
cell.detailTextLabel.text = [num stringValue];
cell.detailTextLabel.hidden = YES;
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
if([_selectedItems containsObject:indexPath]){
[_selectedItems removeObject:indexPath];
[self.categorySelected removeObject:[[self.arForTable objectAtIndex:indexPath.row] objectForKey:#"id"]];
string = [self.categorySelected componentsJoinedByString:#","];
[defaults setObject:string forKey:#"selectedCategoryTableString"];
NSLog(#"%# defaults from did select remove categorySelected",[defaults stringForKey:#"selectedCategoryTableString"]);
NSLog(#"%# STRING FROM contains / removeObj",string);
} else {
[_selectedItems addObject:indexPath];
[self.categorySelected addObject:[[self.arForTable objectAtIndex:indexPath.row] objectForKey:#"id"]];
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
string = [self.categorySelected componentsJoinedByString:#","];
[defaults setObject:string forKey:#"selectedCategoryTableString"];
NSLog(#"%# providerSelected from did select add ",self.categorySelected);
NSLog(#"%# STRING FROM contains / addObj",string);
}
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
// [tableView reloadData];
}
-(void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:NO];
[self.navigationController setNavigationBarHidden:YES animated:NO];
self.navigationController.toolbarHidden = YES;
NSUserDefaults * defaults = [NSUserDefaults standardUserDefaults];
// NSLog(#"ALL DEFAULTS %#", [[NSUserDefaults standardUserDefaults] dictionaryRepresentation]);
NSLog(#"%# defaults from view appear categorySelected",[defaults stringForKey:#"selectedCategoryTableString"]);
string = [defaults stringForKey:#"selectedCategoryTableString"];
NSLog(#"%# STRING from will appear",string);
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
also in the app delegate I have in the .h:
#property (strong, nonatomic) NSMutableArray *selectedCategories;
and in the .m:
`_selectedCategories = [NSMutableArray new];
in the didFinishLaunchingWithOptions: method
just to be clear:
when the view appears again (if I nslog the output) the mutablearray has been saved and is retrieved correctly. the mutable array only clears itself when a tablerow is touched again.
thanks if anyone can help with this. I've been stuck on it for some time...
edit
// DONT EVER EVER EVER EVER EVER EVER DO THIS!!!
// We don't use types as variable names, that is implicit...
// I get it, this is a string, BUT WHAT IS IT A STRING OF, the name
// 'string' does you, and anyone else, no good. Think about all your
// code like you are writing it for someone else, because when you come
// back to it in 6 months, you will be someone else, and you won't know
// what this means
NSString *string;
end edit
I would not be using NSUserDefaults this way. You have already parsed JSON into an archiveable object (NSMutableArray). In viewDidLoad, you should probably try doing something like:
-(void)viewDidLoad
{
// Load the array from a plist file
self.dataYouNeed = [NSMutableArray arrayWithContentsOfFile:#"someFileName.plist"];
// If we got back nil, that file didn't exist, so call 'reloadMain',
// do your parsing there THEN SAVE to a plist using:
//
// [myArray writeToFile:#"someFileName.plist"]
//
if(self.dataYouNeed == nil) [self reloadMain];
// Then do the exact same thing when you try to persist your selection...
// aka do not store a CSV string, just store an Array, and call writeToFile:
// when you want to save, and arrayWithContentsOfFile when you want to read
// it back in
}
On top of that, depending on where your data is coming from, I would move all of your data out of the JSON files and set it up in a plist, then you can ditch all of your parsing code.... :). Basically I am saying this is all a little too complicated for such a simple task, make your own life easier.
edit
You may have an issue with not using 'self.string', simply referring to 'string' is dangerous, you are creating a new reference everytime. This is most likely creating a memory leak. (ARC is not magic, it can not handle ALL memory management for you)
edit
Ok, so re-reading your code, I noticed a few things.
1. Why do you store your CSV string in the 'string' instance var?
This is somewhat redundant. At no point do you ever read from this variable without having set it in the few lines of code before. It should just be an NSString declared with in the scope of the method.
2. Are you expecting '_selectedItems' to have retained your reference to the 'selectedCategories' array on your AppDelegate?
You can not make this assumption, especially without having made a #property declaration. ARC does not know how to handle it and will probably be releasing the reference when you leave the view. The more likely possibility is that you are creating a memory leak every time you set that variable. You can also not guarantee that viewDidLoad will be called again to reset the reference. You should probably be setting this in viewWillAppear.
3. Which NSMutableArray are you experiencing a nil reference to?
If it is '_selectedItems', consider #2. If it is 'categorySelected', this is also probably being released when this view disappears. If this is really what you are trying to persist, then why are you not populating it from the viewDidAppear method. The only thing you do in viewDidAppear is set the 'string' variable (which is never actually read from, like #1 says). Did you mean to set 'categorySelected' here? I believe you meant to get your list from NSUserDefaults, then populate 'categorySelected' using that string's componentsSeparatedByString: method, which returns an array
Every time a user goes to some other view and comes back then
self.categorySelected = [[NSMutableArray alloc] init]; gets executed resulting it to an empty array.
First serialize the array when leaving the view:
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:#[#"1",#"2",#"3"]];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:#"myarray"];
[[NSUserDefaults standardUserDefaults] synchronize];
Then deserialize it when you are back to that view:
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:#"myarray"];
NSArray *myarray = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSLog(#"MYARRAY %#", myarray);
Note: if key is not found then init a new array.
Can you try to change 'retain' to 'strong' here?
#property (nonatomic, retain) NSMutableArray *categorySelected;
I think the issue is, you are setting the selectedItems array in viewDidLoad method. Probably the viewDidLoad is working once.
Just add the following line in your viewWillAppear method:
_selectedItems = [(AppDelegate *)[[UIApplication sharedApplication] delegate] selectedCategories];
Related
In my application I have an UITextView, which is populated at runtime when I insert new data via an Alert Controller. To print this use data:
NSArray * textfields = alertController.textFields;
UITextField * urlfield = textfields [0];
UITextField * titlefield = textfields [1];
UITextField * categoryfield = textfields [2];
FeedInfo * newFeed = [[FeedInfo alloc] init];
NewFeed.feedURL = urlfield.text;
NewFeed.feedTitle = titlefield.text;
NewFeed.feedCategory = categoryfield.text;
[Self.feedArray addObject: newFeed];
[Self.tableView reloadData];
Where feedArray is a defined class model:
#interface FeedInfo : NSObject
#property (nonatomic,strong) NSString *feedURL;
#property (nonatomic,strong) NSString *feedTitle;
#property (nonatomic,strong) NSString *feedCategory;
#end
Next, I print the data from feedArray inside the IUTableView with:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell" forIndexPath:indexPath];
FeedInfo *feedToShow = [self.feedArray objectAtIndex:indexPath.row];
cell.textLabel.text = feedToShow.feedTitle;
cell.detailTextLabel.text = feedToShow.feedCategory;
return cell;
OK. Up to here everything works fine.
Now the problem is that before I quit the application, I want to save all the cells in the UITableView, because when the re-opening must still have all the inserted data.
I was thinking of saving all the feedArra array (1st class of this topic) by inserting this piece of code after [self.feedArray addObject: newFeed]; and before [self.tableView reloadData];
NSUserDefaults * userDefaults = [NSUserDefaults standardUserDefaults
[UserDefaults setObject: self.feedArray forKey: #"feedArray"];
[userDefaults synchronize];
Within the viewDidLoad method instead:
NSUserDefaults * userDefaults = [NSUserDefaults standardUserDefaults];
Array = [userDefaults objectForKey: # "feedArray"];
And in cellForRowAtIndexPath:
FeedInfo *feedToShow = [array objectAtIndex:indexPath.row];
cell.textLabel.text = feedToShow.feedTitle;
cell.detailTextLabel.text = feedToShow.feedCategory;
But it throws an Exception:
RSSReader[47834:2470261] [User Defaults] Attempt to set a
non-property-list object ("<FeedInfo: 0x600000235200>" ) as an NSUserDefaults/CFPreferences value for key feedArray 2017-07-18 11:00:13.652
RSSReader[47834:2470261] *** Terminating app due to uncaught exception
'NSInvalidArgumentException', reason: 'Attempt to insert non-property
list object (
"<FeedInfo: 0x600000235200>" ) for key feedArray'
You model (Feed) should confirm to NSCoding protocol to Archive and UnArchive the data.
FeedInfo.h
#interface FeedInfo : NSObject <NSCoding>
#property (nonatomic,strong) NSString *feedURL;
#property (nonatomic,strong) NSString *feedTitle;
#property (nonatomic,strong) NSString *feedCategory;
#end
FeedInfo.m
#implementation FeedInfo
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super init];
if (!self) {
return nil;
}
self.feedURL = [aDecoder decodeObjectForKey:#"feed_url"];
self.feedTitle = [aDecoder decodeObjectForKey:#"feed_title"];
self.feedCategory = [aDecoder decodeObjectForKey:#"feed_category"];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:_feedURL forKey:#"feed_url"];
[coder encodeObject:_feedTitle forKey:#"feed_title"];
[coder encodeObject:_feedCategory forKey:#"feed_category"];
}
#end
Store to Userdefault
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:self.feedArray];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:#"feedArray"];
Retrive from UserDefault
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:#"feedArray"];
NSArray *feedArray = [NSKeyedUnarchiver unarchiveObjectWithData:data];
NSUserDefault support these classes :NSNumber(Integer、Float、Double),NSString,NSDate,NSArray,NSDictionary,BOOL;
FeedInfo is a custom class , and you cannot put it in the NSUserDefault;
You should turn the FeedInfo object to NSData , then add it;
NSData * newData = [NSKeyedArchiver archivedDataWithRootObject:newFeed];
[Self.feedArray addObject: newData];
You can you this code. my code is help you
Save NSMutableArray in NSUserDefaults
- (void)saveList:(NSMutableArray *)arr
{
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:arr];
[[NSUserDefaults standardUserDefaults] setObject:data forKey:ZIPCODE_LIST];
[[NSUserDefaults standardUserDefaults]synchronize];
}
Get NSMutableArray from NSUserDefaults
- (NSMutableArray *)getList
{
NSData *data = [[NSUserDefaults standardUserDefaults] objectForKey:ZIPCODE_LIST];
NSMutableArray *arr = (NSMutableArray *)[NSKeyedUnarchiver unarchiveObjectWithData: data];
return arr;
}
Hi everyone I would like to ask I want to show JSON data and customize the position for image and text in Table View.
how can I do like that? please advice me.
You can take a look at the Image Url.
for image 1 it is the result as I've parsing from JSON.
Image 1
for image 2 it is my goal.
Image 2
ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController <UITableViewDataSource,UITableViewDelegate>
#property (retain, nonatomic) IBOutlet UITableView *myTableView;
#end
ViewController.m
#import "ViewController.h"
#interface ViewController ()
{
NSMutableArray *myObject;
// A dictionary object
NSDictionary *dict;
// Define keys
NSString *galleryid;
NSString *name;
NSString *titlename;
NSString *thumbnail;
}
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// Define keys
galleryid = #"GalleryID";
name = #"Name";
titlename = #"TitleName";
thumbnail = #"Thumbnail";
// Create array to hold dictionaries
myObject = [[NSMutableArray alloc] init];
NSData *jsonData = [NSData dataWithContentsOfURL:
[NSURL URLWithString:#"MY_JSON_URL"]];
id jsonObjects = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingMutableContainers error:nil];
// values in foreach loop
for (NSDictionary *dataDict in jsonObjects) {
NSString *strGalleryID = [dataDict objectForKey:#"GalleryID"];
NSString *strName = [dataDict objectForKey:#"Name"];
NSString *strTitleName = [dataDict objectForKey:#"TitleName"];
NSString *strThumbnail = [dataDict objectForKey:#"Thumbnail"];
dict = [NSDictionary dictionaryWithObjectsAndKeys:
strGalleryID, galleryid,
strName, name,
strTitleName, titlename,
strThumbnail, thumbnail,
nil];
[myObject addObject:dict];
}
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return myObject.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
// Use the default cell style.
cell = [[[UITableViewCell alloc] initWithStyle : UITableViewCellStyleSubtitle
reuseIdentifier : CellIdentifier] autorelease];
}
NSDictionary *tmpDict = [myObject objectAtIndex:indexPath.row];
NSURL *url = [NSURL URLWithString:[tmpDict objectForKey:thumbnail]];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *img = [[UIImage alloc] initWithData:data];
cell.imageView.image = img;
cell.textLabel.text = [tmpDict objectForKey:name];
cell.detailTextLabel.text= [tmpDict objectForKey:titlename];
//[tmpDict objectForKey:memberid]
//[tmpDict objectForKey:name]
//[tmpDict objectForKey:titlename]
//[tmpDict objectForKey:thumbnail]
return cell;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)dealloc {
[_myTableView release];
[super dealloc];
}
#end
It will be easy if creating custom TableviewCell (subclass to UITableViewCell) to display the array of objects you have, than using UITableViewCell to display as you desired.
Create custom UITableviewCell with xib or with storyboard and create properties as you needed and give the position and styling for the cell elements.
Bind data for the cells in your cellForRowAtIndexPath method.
For more information, refer: this and this for more info
So I have a class "WishListItem", a TableViewController and a ViewController. I'm having difficulties in saving chunk of data and retrieving it from TableViewController? How to do this effectively?
Here's my ViewController which has a prepareForSegue that stores data to my WishListItem class.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
NSDate *myDate = self.targetDatePicker.date;
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"MMM d, YYYY"];
NSString *prettyVersion = [dateFormat stringFromDate:myDate];
if (sender != self.addWishListButton) return;
if (self.wishTextField.text.length > 0) {
self.wishItem = [[WishlistItem alloc] init];
self.wishItem.wishlistItem = self.wishTextField.text;
self.wishItem.descWishItem = self.descTextField.text;
self.wishItem.targetDate = prettyVersion;
}
}
WishListItem.h :
#interface WishlistItem : NSObject
#property NSString *wishlistItem;
#property NSString *descWishItem;
#property NSString *targetDate;
#end
ViewController.h :
#interface JLSViewController : UIViewController
#property (strong, nonatomic) IBOutlet UIBarButtonItem *addWishListButton;
#property (strong, nonatomic) IBOutlet UITextField *wishTextField;
#property (strong, nonatomic) IBOutlet UITextField *descTextField;
#property (strong, nonatomic) IBOutlet UIDatePicker *targetDatePicker;
#property WishlistItem *wishItem;
#end
From here. I want to save it every time I add a wishItem. How would I store MULTIPLE ENTRIES? and at the same time retrieve those entries in my TableViewController?
Please let me know if I need to provide more info. TIA.
The easiest and the rude way of doing it is storing the data in NSUserDefaults
+(void)userDefaultsSetObject:(id)userObject forKey:(NSString *)userKey
{
NSUserDefaults *userDefaults=[NSUserDefaults standardUserDefaults];
[userDefaults setObject:userObject forKey:userKey];
[userDefaults synchronize];
}
/**
* This method helps to get values from NSUserDefaults
*/
+(id)userDefaultsGetObjectForKey:(NSString *)userKey
{
NSUserDefaults *userDefaults=[NSUserDefaults standardUserDefaults];
return [userDefaults objectForKey:userKey];
}
Use the above two functions to save and retrieve data from NSUserDefaults. So coming to your problem here, add the wishlistitem objects to an array and set it in NSUserDefaults. Add this code to your tableviewcontroller class, and have a datasource array as a global variable.
NSMutableArray *dataSourceArray;
In ViewDidLoad of the tableviewcontroller class add this,
NSMutableArray *wishlistItems;
if([self userDefaultsGetObjectForKey:#"WishListItems"]==nil)
{
wishlistItems = [NSMutableArray array];
}
else
{
wishlistItems = [self userDefaultsGetObjectForKey:#"WishListItems"];
}
dataSourceArray = [[NSMutableArray alloc] initWithArray:wishlistItems];
And in the tableViewDelegateMethod, try this:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return [dataSourceArray count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:#"cell"];
if(cell == nil)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
}
WishlistItem *wishListObject = [dataSourceArray objectAtIndex:indexPath.row];
[cell.textLabel setText:wishListObject.wishlistItem];
[cell.detailTextLabel setText:wishListObject.descWishItem];
return cell;
}
So every time you add something do this,
- (NSMutableArray*)wishListAdded:(WishlistItem*)wishList
{
NSMutableArray *wishlistItems;
if([self userDefaultsGetObjectForKey:#"WishListItems"]==nil)
{
wishlistItems = [NSMutableArray array];
[wishlistItems addObject: wishItem];
[self userDefaultsSetObject:wishlistItems forKey:#"WishListItems"];
}
else
{
wishlistItems = [self userDefaultsGetObjectForKey:#"WishListItems"];
[wishlistItems addObject:yourWishListObject];
[self userDefaultsSetObject:wishlistItems forKey:#"WishListItems"];
}
return wishlistItems;
}
To save your array state use this ie in view controller
NSString *valueToSave = #"someValue";
[[NSUserDefaults standardUserDefaults] setObject:valueToSave forKey:#"preferenceName"];
[[NSUserDefaults standardUserDefaults] synchronize];
use this where you want to retrieve the variable ie. intableViewController
NSString *savedValue = [[NSUserDefaults standardUserDefaults]
stringForKey:#"preferenceName"];
You can use mutablearrays for saving multiple objects of your wishlist class and then store that array in the userdefaults and retrieve it as per your needs.
Any changes made to the a particular object in the index of the array needs to be updated in the user default as well, else you will end up getting the same result set which you have stored in the first place.
I think you need to create the #property WishlistItem *wishItem; object in TableViewController .h file and save all the data while in segue method. So this is how your implementation will look like
in TableViewController.h
#import "WishlistItem.h"
#interface SomeTableViewController : UITableViewController
#property WishlistItem *wishItem
in JLSViewController.m
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
NSDate *myDate = self.targetDatePicker.date;
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"MMM d, YYYY"];
NSString *prettyVersion = [dateFormat stringFromDate:myDate];
if (sender != self.addWishListButton) return;
if (self.wishTextField.text.length > 0) {
SomeTableViewController *tableViewController = [segue destinationViewController];
tableViewController.wishItem = [[WishlistItem alloc] init];
tableViewController.wishItem.wishlistItem = self.wishTextField.text;
tableViewController.wishItem.descWishItem = self.descTextField.text;
tableViewController.wishItem.targetDate = prettyVersion;
}
}
in TableViewController.m
you can populate the data in tableview using self.wishItem.wishlistItem etc.
if you want to save large data then consider using Core Data, also NSDateFormatter is expensive operation so you can refer this link for some improving that.
There are severals ways of saving data and retrieving them:
1. Saving data to NSUserDefaults (how Anonymous suggested):
With NSUserDefaults you can save objects from the following class types:
NSData, NSString, NSNumber, NSDate, NSArray, NSDictionary
try to take a look at the documentation, UserDefaults is simple to understand and use.
NSUserDefaults Doc
2. Saving data to Property List file (plist)
It is kind of the same like UserDefaults (it can store the same data type) difference is you write your data in a document.
But: Don't keep it in the Document folder unless it is appropriate for storage in iCloud, says Apple; Reference
3. Save data to a data base
this will be another way to store your data...
At the end it all depends how complex will be the data you want to save, are we talking about strings and numbers or images NSUserNotification etc
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
NSDate *myDate = self.targetDatePicker.date;
NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
[dateFormat setDateFormat:#"MMM d, YYYY"];
NSString *prettyVersion = [dateFormat stringFromDate:myDate];
if (sender != self.addWishListButton) return;
if (self.wishTextField.text.length > 0)
{
NSMutableDictionary *dict = [[NSMutableDictionary alloc]init];
[dict setObject:self.wishTextField.text forKey:#"wishTextField"];
[dict setObject:self.descTextField.text forKey:#"descTextField"];
[dict setObject:prettyVersion forKey:#"prettyVersion"];
NSMutableArray *array = (NSMutableArray*)[userDefaults objectForKey:#"myWishList"];
//check if array is not null i`m not sure if it will give you error if it is null
// if null NSMutableArray *array= [NSMutableArray alloc]init]; else
[array addObject:dict];
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[userDefaults setObject:array forKey:#"myWishList"];
}
}
i think this should work for saving. For loading data in your:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath // methode
//add this
NSMutableArray *array = (NSMutableArray*)[userDefaults objectForKey:#"myWishList"];
// this will return all your wishlist items
NSMutableDictionary *dict = [array objectAtIndex:indexPath.row];
// this will give you the data you saved in the particular place
cell.textLabel.text = [dict objectForKey:#"wishTextField"];//this will be the name of the whishlist
I think this should do
I am working on an app for a game server company and part of the app requires the user to see a list of his or her game servers and whether or not they are online, offline, how many players on them, the server name, etc. This data is all found in a PHP file hosted on the web updated from a MySQL database which when viewed, outputs JSON.
Using the code below, this doesn't seem to work. I load the view and right away get a "Thread 1: signal SIGABRT" error on the line with NSDictionary *myServer = [servers objectAtIndex:indexPath.row];. When removing indexPath.row and replacing it with either a 0 or 1, the data is displayed on the UITableView in my Storyboard, except it is displayed 4 times in a row and only for that entry in the JSON file (either 0 or 1). I can't keep it at a fixed number as the client might have 100 servers, or just 5 servers which is why I need something like indexPath.row. Below, I also attached exactly what the JSON looks like when given from the server and accessed directly from the app's code
I'd really appreciate it if someone could please let me know what the problem is and propose a solution unique to my situation to get rid of this SIGABRT error and once we do, make sure it doesn't show 4 times in the TableView like it is now.
My header file:
#import <UIKit/UIKit.h>
#import "ServerDetailViewController.h"
#interface SecondViewController : UITableViewController {
IBOutlet UITableView *mainTableView;
NSDictionary *news;
NSMutableData *data;
}
#property (weak, nonatomic) IBOutlet UIBarButtonItem *refreshServersButton;
- (IBAction)refreshServers:(id)sender;
#end
My main file:
#import "SecondViewController.h"
#interface SecondViewController ()
#end
#implementation SecondViewController
- (void)viewDidLoad
{
[super viewDidLoad];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSURL *url = [NSURL URLWithString:#"REDACTED"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
data = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)theData
{
[data appendData:theData];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
news = [NSJSONSerialization JSONObjectWithData:data options:nil error:nil];
[mainTableView reloadData];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
UIAlertView *errorView = [[UIAlertView alloc] initWithTitle:#"Error" message:#"Unable to load server list. Make sure you are connect to either 3G or Wi-Fi or try again later." delegate:nil cancelButtonTitle:#"Dismiss" otherButtonTitles:nil, nil];
[errorView show];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
}
- (int)numberOfSectionsInTableView:(UITableView *)tableView
{
return 1;
}
- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [news count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UIColor *colorGreen = [UIColor colorWithRed:91.0f/255.0f green:170.0f/255.0f blue:101.0f/255.0f alpha:1.0f];
UIColor *colorRed = [UIColor redColor];
static NSString *CellIdentifier = #"MainCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
UILabel *serverName = (UILabel *)[cell viewWithTag:100];
UILabel *serverPlayers = (UILabel *)[cell viewWithTag:101];
UILabel *serverStatus = (UILabel *)[cell viewWithTag:102];
UILabel *serverOfflineName = (UILabel *)[cell viewWithTag:103];
serverPlayers.textColor = [UIColor grayColor];
NSDictionary *resultDict = [news objectForKey:#"result"];
NSArray *servers = [resultDict objectForKey:#"servers"];
NSDictionary *myServer = [servers objectAtIndex:indexPath.row];
NSString *titleOfServer = [myServer objectForKey:#"title"];
NSNumber *statusOfServer = [NSNumber numberWithInt:[[myServer objectForKey:#"status"] intValue]];
NSNumber *playersOnServer = [NSNumber numberWithInt:[[myServer objectForKey:#"players"] intValue]];
if ([[statusOfServer stringValue] isEqualToString:#"0"]) {
serverName.text = #"";
serverOfflineName.text = titleOfServer;
serverStatus.textColor = colorRed;
serverStatus.text = #"OFFLINE";
serverPlayers.text = #"";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
} else if ([[statusOfServer stringValue] isEqualToString:#"1"]) {
serverName.text = titleOfServer;
serverOfflineName.text = #"";
serverStatus.textColor = colorGreen;
serverStatus.text = #"ONLINE";
serverPlayers.text = [playersOnServer stringValue];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
} else if ([[statusOfServer stringValue] isEqualToString:#"2"]) {
serverName.text = #"";
serverOfflineName.text = titleOfServer;
serverStatus.textColor = [UIColor blueColor];
serverStatus.text = #"BUSY";
serverPlayers.text = #"";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
} else if ([[statusOfServer stringValue] isEqualToString:#"3"]) {
serverName.text = #"";
serverOfflineName.text = titleOfServer;
serverStatus.textColor = [UIColor grayColor];
serverStatus.text = #"SUSPENDED";
serverPlayers.text = #"";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
} else if ([[statusOfServer stringValue] isEqualToString:#"-1"]) {
serverName.text = #"";
serverOfflineName.text = titleOfServer;
serverStatus.textColor = [UIColor orangeColor];
serverStatus.text = #"CRITICAL ERROR";
serverPlayers.text = #"";
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}
return cell;
}
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
ServerDetailViewController *detail = [self.storyboard instantiateViewControllerWithIdentifier:#"detail"];
[self.navigationController pushViewController:detail animated:YES];
}
- (IBAction)refreshServers:(id)sender {
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSURL *url = [NSURL URLWithString:#"REDACTED"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
#end
JSON code from server: {"status":"OK","error":"","debug":"2 server(s)","result":{"servers":[{"id":1,"title":"Test","players":0,"slots":10,"status":3},{"id":2,"title":"Creative Spawn","players":0,"slots":5,"status":-1}]}}
From your code, this looks like the source of error.(However, I didn't read the whole thing.)
- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [news count]; //counted number of items in your whole json object.
}
and in your cellForRowAtIndexPath:(NSIndexPath *)indexPath
NSDictionary *resultDict = [news objectForKey:#"result"];
NSArray *servers = [resultDict objectForKey:#"servers"];
// you used a different array(an item of the whole json array).
// Since news object has more items than servers, it caused an out of bound here.
NSDictionary *myServer = [servers objectAtIndex:indexPath.row];
Try doing following to your code
- (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSDictionary *resultDict = [news objectForKey:#"result"];
NSArray *servers = [resultDict objectForKey:#"servers"];
return [servers count]; //counted number of items in your whole json object.
}
TL;DR
The reason for the crash, you're using the news.count as the number of rows in the table, yet referencing the indexPath.row in the servers array (which it isn't guaranteed to be).
There's a few things here:
Firstly, this is not a very proficient way of networking, since you support iOS5, I would suggest using the following method (or something similar) :
[NSURLConnection sendAsynchronousRequest:theRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSString *dataString = [[NSString alloc] initWithBytes:[data bytes] length:[[data bytes] length] encoding:NSUTF8StringEncoding];
}];
Secondly, I would strongly recommend the MVC model, managing data is not something for a controller - as someone has mentioned, the code at the moment isn't as easy reading as it could be (as well as maintaining it!).
Thirdly, I would recommend you employ more defensive coding, below are the points I've spotted while reading through:
news, well rather [NSJSONSerialization JSONObjectWithData:data options:nil error:nil];,, isn't guaranteed to return a dictionary at all; although it is treated as such
The reason for the crash, you're using the news.count as the number of rows in the table, yet referencing the indexPath.row in the servers array (which it isn't guaranteed to be).
If I were you, I'd probably start with simplifying the networking, followed by created a simple model (for example Server), have the model parse the JSON that's relevant to it. I'd go so far as to include a static method in your Server model, say 'retrieveServers', that returns an NSArray of Server objects.
That way, all your controller is doing is:
[self setNews:[Server retrieveServers]];
[_tableView reloadData];
Rather than having a lot of irrelevant code in your controller - this will increase maintainability and readability.
If you were so inclined, you could take another step, and provide custom accessors, rather than referencing the members directly via the model, for example:
Server *currentServer = nil;
if( self.news.count > indexPath.row ) {
currentServer = [_news objectAtIndex:indexPath.row];
}
[serverPlayers setText:(currentServer ? [currentServer getPlayers] : [Server defaultPlayerValue]]
The code above is being safe with checking that the array has at least the same number of elements in it as we need, and secondly, when assigning the value to the table cell (which may be re-used, and so needs to be set to sane values for every possible branch of execution). The advantages of doing the above: readability, centralised default value, maintainability.
I apologise if this seems rather overkill (TL;DR added ;/), trying to provide some pointers, which would aid in debugging if you see no other reason to employ the tips above.
I am downloading and parsing JSON objects to build a "news feed" to populate a UITableView. The very last line of code I have in the connectionDidFinishLoading delegate method is:
[tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
However, my break points in the - (UITableViewCell *)tableView:(UITableView *)mytableView cellForRowAtIndexPath:(NSIndexPath *)indexPath method are not hit. (They are hit when the app first launches)
So for whatever reason even though I am calling the reloadData on the main thread; it doesn't appear to be firing. I tried just [tableView reloadData] and that did not work.
Here is my connectionDidFinishLoading method:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
//NSString *responseString = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
NSArray *publicTimeline = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:nil];
NSUInteger newsStreamCount = [publicTimeline count];
// Initialize the collection and the dictionary
newsItemManager.newsItemCollection = [[NSMutableArray alloc] initWithCapacity:newsStreamCount];
NSMutableArray *dictOfNewsItems = [[NSMutableArray alloc] initWithCapacity:newsStreamCount];
// From the JSON object, parse out the individual news item JSON objects and
// put them into a dictionary.
for (int i = 0; i < newsStreamCount; i++)
{
NSDictionary *item = [publicTimeline objectAtIndex:i];
[dictOfNewsItems addObject:item];
}
// For each news item JSON object, extract out the information we need for our
// news manager class
for (int i = 0; i < newsStreamCount; i++)
{
NSString *userName = [[dictOfNewsItems objectAtIndex:i] valueForKey:#"Title"];
NSString *message = [[dictOfNewsItems objectAtIndex:i] valueForKey:#"Content"];
NSString *imgUrl = [[dictOfNewsItems objectAtIndex:i] valueForKey:#"https://si0.twimg.com/logo_normal.jpg"];
NewsItem *newsItem = [[NewsItem alloc] initWithBasicInfo:userName :message :imgUrl];
[newsItemManager.newsItemCollection addObject:newsItem];
}
[tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
}
Here is my header file:
#import <UIKit/UIKit.h>
#interface NewsViewController : UITableViewController
#property (nonatomic, retain) NSMutableArray* newsItemArray;
#property (nonatomic, retain) NSMutableData *responseData;
#property (nonatomic, retain) IBOutlet UITableView *tableView;
#end
Here is my implementation:
- (void)viewDidLoad
{
[super viewDidLoad];
// Uncomment the following line to preserve selection between presentations.
// self.clearsSelectionOnViewWillAppear = NO;
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem;
self.title = #"News";
//self.newsItemArray = [[NSMutableArray alloc] initWithObjects:#"One", #"Two", #"Three", nil];
tableView.rowHeight = 75.0;
responseData = [NSMutableData data];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://blah.com/api/news"]];
(void)[[NSURLConnection alloc] initWithRequest:request delegate:self];
}
Thanks,
Flea
If your connectionDidFinish... method is inside your tableViewController class, maybe you just need
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:NO];
The overall problem was my newsItemManager.newsItemCollection was not being initialized properly and was returning null the entire time, thus when the UITableView was trying to load data; there was nothing to load.
I thought I had checked for this but one of those problems of staring at the computer all day and missing the obvious.