Creating NSMutableArray of objects - ios

I'm having trouble adding objects to an NSMutableArray. I clearly place 2 objects in the typeList array, but the count only shows up as 1. What am I doing wrong?
content.h
#interface TBContentModel : NSObject
+(NSMutableArray*)typeList;
+(void)setTypeList:(NSMutableArray*)str;
content.m
static NSMutableArray *typeList = nil;
#implementation TBContentModel
- (id) init {
self = [super init];
if (self) {
typeList = [NSMutableArray array];
}
return self;
}
contentviewcontroller.m
#implementation TBViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *jsonString = #"[{\"Content\":268,\"type\":\"text\"},{\"Content\":65,\"type\":\"number\"}]";
NSData *data = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSMutableArray *array = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
for (NSMutableDictionary *dictionary in array)
{
TBContentModel *test = [[TBContentModel alloc] init];
test.type = dictionary[#"type"];
[[TBContentModel typeList] addObject:test];
NSLog(#"%#", test.type);
}
}
- (IBAction)tapButton:(id)sender {
NSLog(#"%d", [TBContentModel.typeList count]); // always shows 1
}

You are re-creating your static typeList object everytime you allocate and initialize a new TBContentModel object.
Make the following changes:
static NSMutableArray *typeList = nil;
static dispatch_once_t once;
+ (NSMutableArray*)typeList {
dispatch_once(&once, ^{
typeList = [NSMutableArray array];
});
return typeList;
}
And remove the following line from your init method:
typeList = [NSMutableArray array];

Related

Objective-C - Firebase retrieving data from database and populate in table

Database structure
I have a Firebase database setup (please refer to the picture).
I have a "FeedViewController" to display the contents of each post in the database. A user may post one or more posts.
When retrieving these posts from the Firebase snapshot and storing them onto a dictionary, I find that this dictionary's values are not accessible outside of the Firebase's observeEventType function.
My idea was to retrieve these key-value pairs, store them onto a NSObject custom class object (Post *post) and use this object to load the table view for my "FeedViewController". Inside the observeEventType function, I am able to access the object's values, but outside, I'm not. As a result, I don't know how to use these values to populate the table view in my FeedViewController. I understand that this observeEventType function is an asynchronous callback, but I don't know how to access the values of the object and populate my table. I don't have a clue what the dispatch_async(dispatch_get_main_queue() function is doing here. Any help would be highly appreciated. Thanks!
FeedViewController.m
#import "FeedViewController.h"
#import "Post.h"
#import "BackgroundLayer.h"
#import "SimpleTableCell.h"
#import "FBSDKCoreKit/FBSDKCoreKit.h"
#import "FBSDKLoginKit/FBSDKLoginKit.h"
#import "FBSDKCoreKit/FBSDKGraphRequest.h"
#import Firebase;
#import FirebaseAuth;
#import FirebaseStorage;
#import FirebaseDatabase;
#interface FeedViewController()
#property (strong, nonatomic) Post *post;
#end
#implementation FeedViewController
-(void) viewDidLoad {
[super viewDidLoad];
_ref = [[FIRDatabase database] reference];
self.post = [[Post alloc] init];
/*
_idArr = [[NSMutableArray alloc] init];
_postDict = [[NSMutableDictionary alloc] init];
_idDict = [[NSMutableDictionary alloc] init];
_postID = [[NSMutableArray alloc] init];
_userName = [[NSMutableArray alloc] init];
_placeName = [[NSMutableArray alloc] init];
_addressLine1 = [[NSMutableArray alloc] init];
_addressLine2 = [[NSMutableArray alloc] init];
_ratings = [[NSMutableArray alloc] init];
_desc = [[NSMutableArray alloc] init];
_userEmail = [[NSMutableArray alloc] init];
_userIDArray = [[NSMutableArray alloc] init];
*/
[self fetchData];
NSLog(#"Emails: %#", _post.userID);
}
-(void) viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
CAGradientLayer *bgLayer = [BackgroundLayer blueGradient];
bgLayer.frame = self.view.bounds;
[self.view.layer insertSublayer:bgLayer atIndex:0];
FIRUser *user = [FIRAuth auth].currentUser;
if (user != nil)
{
//fbFirstName.text = user.displayName;
//fbEmail.text = user.email;
NSURL *photoUrl = user.photoURL;
NSString *userID = user.uid;
//NSString *uploadPath = [userID stringByAppendingString:#"/profile_pic.jpg"];
//NSData *data = [NSData dataWithContentsOfURL:photoUrl];
//ProfilePic.image = [UIImage imageWithData:data];
FIRStorage *storage = [FIRStorage storage];
FIRStorageReference *storageRef = [storage referenceForURL:#"gs://foodsteps-cee33.appspot.com"];
NSString *access_token = [[NSUserDefaults standardUserDefaults] objectForKey:#"fb_token"];
FBSDKGraphRequest *friendList = [[FBSDKGraphRequest alloc]
initWithGraphPath:#"me?fields=friends"
parameters:nil
tokenString: access_token
version:nil
HTTPMethod:#"GET"];
[friendList startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection,
id result,
NSError *error) {
if(error == nil)
{
//NSLog(#"%#", result);
NSDictionary *dictionary = (NSDictionary *)result;
NSDictionary *dict = [dictionary objectForKey:#"friends"];
_idArray = [[NSMutableArray alloc] init];
for(int i = 0; i < [[dict objectForKey:#"data"] count]; i++) {
[_idArray addObject:[[[dict objectForKey:#"data"] objectAtIndex:i] valueForKey:#"id"]];
}
//NSLog(#"%#", idArray);
}
else {
NSLog(#"%#",error);
}
}];
}
}
-(void) fetchData {
_refHandle = [[_ref child:#"users"] observeEventType:FIRDataEventTypeValue
withBlock:^(FIRDataSnapshot * _Nonnull snapshot)
{
NSDictionary *postDict = snapshot.value;
NSLog(#"%#", postDict);
for( NSString *aKey in [postDict allKeys] )
{
// do something like a log:
_post.userID = aKey;
}
//_post.
//[_post setValuesForKeysWithDictionary:postDict];
[self.tableView reloadData];
}];
NSLog(#"Emails: %#", _post.userID);
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
-(void) viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[[_ref child:#"users"] removeObserverWithHandle:_refHandle];
}
#end
Post.m
#import "Post.h"
#implementation Post
- (instancetype)init {
return [self initWithUid:#""
andPostid:#""
andUsername:#""
andDesc:#""
andRatings:#""
andPlacename:#""
andAddressLine1:#""
andAddressLine2:#""
andEmail:#""];
}
- (instancetype)initWithUid:(NSString *)userID
andPostid:(NSString *)postID
andUsername: (NSString *)userName
andDesc:(NSString *)desc
andRatings:(NSString *)ratings
andPlacename: (NSString *)placeName
andAddressLine1: (NSString *)addressLine1
andAddressLine2: (NSString *)addressLine2
andEmail: (NSString *)userEmail {
self = [super init];
if(self) {
self.userID = userID;
self.postID = postID;
self.userName = userName;
self.desc = desc;
self.ratings = ratings;
self.placeName = placeName;
self.addressLine1 = addressLine1;
self.addressLine2 = addressLine2;
self.userEmail = userEmail;
}
return self;
}
#end
Your approach is what I tried to do initially. But I had problems accessing it in cellforrowatindexpath. the thing that works for me is.
- (void)configureDatabase :(NSUInteger)postsAmount{
_ref = [[FIRDatabase database] reference];
// Listen for new messages in the Firebase database
_refHandle = [[[[_ref child:#"posts"]queryOrderedByKey] queryLimitedToLast:postsAmount]observeEventType:FIRDataEventTypeChildAdded withBlock:^(FIRDataSnapshot *snapshot) {
[_posts insertObject:snapshot atIndex:0];
}];
}
Then in viewdidappear
[self configureDatabase:_numberOfPosts];
then lastly
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
FIRDataSnapshot *postsSnapshot = _posts[indexPath.section];
NSDictionary *post = postsSnapshot.value;
//use key values to create your views.
}
also include
#property (strong, nonatomic) NSMutableArray<FIRDataSnapshot *> *posts;
What this does is queries firebase for your values and receives snapshots. those snapshots are then placed in your _posts array and then you can access them in other methods.

NSInternalInconsistencyException for NSMutableArray

I'm trying to add objects to an NSMutableArray but it keeps giving me this error.:
NSInternalInconsistencyException', reason: '-[__NSCFArray insertObject:atIndex:]: mutating method sent to immutable object
I have researched this problem, and I'm not doing anything wrong that past people have done, so I have no idea what's wrong. Here is my code:
Group.h
#property (strong, nonatomic) NSString *custom_desc;
#property (strong, nonatomic) NSMutableArray *attributes; //I define the array as mutable
Group.m
#import "Group.h"
#implementation Group
-(id)init
{
self = [super init];
if(self)
{
//do your object initialization here
self.attributes = [NSMutableArray array]; //I initialize the array to be a NSMutableArray
}
return self;
}
#end
GroupBuilder.m
#import "GroupBuilder.h"
#import "Group.h"
#implementation GroupBuilder
+ (NSArray *)groupsFromJSON:(NSData *)objectNotation error:(NSError **)error
{
NSError *localError = nil;
NSDictionary *parsedObject = [NSJSONSerialization JSONObjectWithData:objectNotation options:0 error:&localError];
if (localError != nil) {
*error = localError;
return nil;
}
NSMutableArray *groups = [[NSMutableArray alloc] init];
NSDictionary *results = [parsedObject objectForKey:#"result"];
NSArray *items = results[#"items" ];
for (NSDictionary *groupDic in items) {
Group *group = [[Group alloc] init];
for (NSString *key in groupDic) {
if ([group respondsToSelector:NSSelectorFromString(key)]) {
[group setValue:[groupDic valueForKey:key] forKey:key];
}
}
[groups addObject:group];
}
for(NSInteger i = 0; i < items.count; i++) {
//NSLog(#"%#", [[items objectAtIndex:i] objectForKey:#"attributes"]);
NSMutableArray *att = [[items objectAtIndex:i] objectForKey:#"attributes"]; //this returns a NSArray object understandable
Group *g = [groups objectAtIndex:i];
[g.attributes addObjectsFromArray:[att mutableCopy]]; //I use mutable copy here so that i'm adding objects from a NSMutableArray and not an NSArray
}
return groups;
}
#end
Use options:NSJSONReadingMutableContainers on your NSJSONSerialization call.
Then all the dictionaries and arrays it creates will be mutable.
According to the error message you are trying to insert an object into an instance of NSArray, not NSMutableArray.
I think it is here:
NSMutableArray *att = [[items objectAtIndex:i] objectForKey:#"attrib`enter code here`utes"]; //this returns a NSArray object understandable
Items is fetched from JSON and therefore not mutable. You can configure JSONSerialization in a way that it creates mutable objects, but how exactly I don't know out of the top of my head. Check the references on how to do that or make a mutable copy:
NSMutableArray *att = [[items objectAtIndex:i] objectForKey:#"attributes"] mutableCopy];
Next try, considering your replies to the first attempt:
#import "Group.h"
#implementation Group
-(NSMutableArray*)attributes
{
return [[super attributes] mutableCopy];
}
#end

How to convert NSDictionary to custom object

I have a json object:
#interface Order : NSObject
#property (nonatomic, retain) NSString *OrderId;
#property (nonatomic, retain) NSString *Title;
#property (nonatomic, retain) NSString *Weight;
- (NSMutableDictionary *)toNSDictionary;
...
- (NSMutableDictionary *)toNSDictionary
{
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
[dictionary setValue:self.OrderId forKey:#"OrderId"];
[dictionary setValue:self.Title forKey:#"Title"];
[dictionary setValue:self.Weight forKey:#"Weight"];
return dictionary;
}
In string this is:
{
"Title" : "test",
"Weight" : "32",
"OrderId" : "55"
}
I get string JSON with code:
NSMutableDictionary* str = [o toNSDictionary];
NSError *writeError = nil;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:str options:NSJSONWritingPrettyPrinted error:&writeError];
NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
Now I need to create and map object from JSON string:
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError *e;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData options:nil error:&e];
This returns me filled NSDictionary.
What should I do to get object from this dictionary?
Add a new initWithDictionary: method to Order:
- (instancetype)initWithDictionary:(NSDictionary*)dictionary {
if (self = [super init]) {
self.OrderId = dictionary[#"OrderId"];
self.Title = dictionary[#"Title"];
self.Weight = dictionary[#"Weight"];
}
return self;
}
Don't forget to add initWithDictionary's signature to Order.h file
In the method where you get JSON:
NSData *jsonData = [jsonString dataUsingEncoding:NSUTF8StringEncoding];
NSError *e;
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:jsonData options:nil error:&e];
Order *order = [[Order alloc] initWithDictionary:dict];
If the property names on your object match the keys in the JSON string you can do the following:
To map the JSON string to your Object you need to convert the string into a NSDictionary first and then you can use a method on NSObject that uses Key-Value Coding to set each property.
NSError *error = nil;
NSData *jsonData = ...; // e.g. [myJSONString dataUsingEncoding:NSUTF8Encoding];
NSDictionary *jsonDictionary = [NSJSONSerialization JSONObjectWithData:jsonData options:NSJSONReadingOptionsAllowFragments error:&error];
MyObject *object = [[MyObject alloc] init];
[object setValuesForKeysWithDictionary:jsonDictionary];
If the keys do not match you can override the instance method of NSObject -[NSObject valueForUndefinedKey:] in your object class.
To map you Object to JSON you can use the Objective-C runtime to do it automatically. The following works with any NSObject subclass:
#import <objc/runtime.h>
- (NSDictionary *)dictionaryValue
{
NSMutableArray *propertyKeys = [NSMutableArray array];
Class currentClass = self.class;
while ([currentClass superclass]) { // avoid printing NSObject's attributes
unsigned int outCount, i;
objc_property_t *properties = class_copyPropertyList(currentClass, &outCount);
for (i = 0; i < outCount; i++) {
objc_property_t property = properties[i];
const char *propName = property_getName(property);
if (propName) {
NSString *propertyName = [NSString stringWithUTF8String:propName];
[propertyKeys addObject:propertyName];
}
}
free(properties);
currentClass = [currentClass superclass];
}
return [self dictionaryWithValuesForKeys:propertyKeys];
}
Assuming that your properties names and the dictionary keys are the same, you can use this function to convert any object
- (void) setObject:(id) object ValuesFromDictionary:(NSDictionary *) dictionary
{
for (NSString *fieldName in dictionary) {
[object setValue:[dictionary objectForKey:fieldName] forKey:fieldName];
}
}
this will be more convenient for you :
- (instancetype)initWithDictionary:(NSDictionary*)dictionary {
if (self = [super init]) {
[self setValuesForKeysWithDictionary:dictionary];}
return self;
}
The perfect way to do this is by using a library for serialization/deserialization
many libraries are available but one i like is
JagPropertyConverter
https://github.com/jagill/JAGPropertyConverter
it can convert your Custom object into NSDictionary and vice versa
even it support to convert dictionary or array or any custom object within your object (i.e Composition)
JAGPropertyConverter *converter = [[JAGPropertyConverter alloc]init];
converter.classesToConvert = [NSSet setWithObjects:[Order class], nil];
#interface Order : NSObject
#property (nonatomic, retain) NSString *OrderId;
#property (nonatomic, retain) NSString *Title;
#property (nonatomic, retain) NSString *Weight;
#end
//For Dictionary to Object (AS IN YOUR CASE)
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
[dictionary setValue:self.OrderId forKey:#"OrderId"];
[dictionary setValue:self.Title forKey:#"Title"];
[dictionary setValue:self.Weight forKey:#"Weight"];
Order *order = [[Order alloc]init];
[converter setPropertiesOf:order fromDictionary:dictionary];
//For Object to Dictionary
Order *order = [[Order alloc]init];
order.OrderId = #"10";
order.Title = #"Title;
order.Weight = #"Weight";
NSDictionary *dictPerson = [converter convertToDictionary:person];
Define your custom class inherits from "AutoBindObject". Declare properties which has the same name with keys in NSDictionary. Then call method:
[customObject loadFromDictionary:dic];
Actually, we can customize class to map different property names to keys in dictionary. Beside that, we can bind nested objects.
Please have a look to this demo. The usage is easy:
https://github.com/caohuuloc/AutoBindObject

How to initialize a dictionary from NSString to NSArray

I am trying to create a dictionary (Not sure whether it should be NSDictionary or NSMutableDictionary) from NSString to an array (Not sure whether it should be NSArray or NSMutableArray).
property:
#property(nonatomic, readonly) NSMutableDictionary * categories;
implementation:
#synthesize categories = _categories;
- (NSMutableDictionary *)categories{
if(! _categories) {
for(PFObject * each in self.products) {
NSString * currentcategory = [each valueForKey:#"subtitle"];
NSArray * currentlist = [_categories objectForKey:currentcategory];
if(! currentlist) {
currentlist = [[NSArray alloc] init];
}
NSMutableArray * newArray = [currentlist mutableCopy];
[newArray addObject:each];
NSArray * newlist = [NSArray arrayWithArray:newArray];
[_categories setObject:newlist forKey:currentcategory];
}
}
NSLog(#"After constructor the value of the dictionary is %d", [_categories count]);
return _categories;
}
From the debug NSLog I realize that the dictionary is empty after the construction. What is wrong here and how shall I change it?
After code line
if(! _categories) {
add
_categories = [NSMutableDictionary new];
If you did not initialize _category array somewhere in code then.
you must instantiate it inside
if(!_categories)
Your NSMutableArray _categories instance is not allocated and initialized yet.
To create instance of NSMutableArray just add
_categories = [NSMutableArray arrayWithCapacity:0];

NSMutableArray Add and Save Objects

New to iOS and am stuck on one issue in regards to adding objects in NSMutable Array and displaying the array on another view within the App. The data displays fine on other view in TableView, but when I add another item to the Array (using code below), it just replaces what was there, not adding to the array.
- (void) postArray {
tableData = [[NSMutableArray alloc] initWithCapacity:10];
tableData = [[NSMutableArray alloc] initWithObjects: nil];
[tableData addObject:favShot]; }
-(NSString *) saveFilePath {
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory, NSUserDomainMask, TRUE);
return [[path objectAtIndex:0] stringByAppendingPathComponent:#"savefile.plist"]; }
- (void) viewWillDisappear:(BOOL)animated: (UIApplication *) application {
NSArray *values = [[NSArray alloc] initWithObjects:tableData, nil];
[values writeToFile:[self saveFilePath] atomically: TRUE]; }
- (void)viewDidLoad {
tableData = [[NSMutableArray alloc] initWithObjects: nil];
NSString *myPath = [self saveFilePath];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:myPath];
if (fileExists)
{
NSArray *values = [[NSArray alloc] initWithContentsOfFile:myPath];
tableData = [values mutable copy];
}
UIApplication *myApp = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:myApp];
[super viewDidLoad]; }
Thank you.
Every time you call postArray you're creating an instance of NSMutableArray, then throwing it away (leaking it if you aren't using ARC). Then you're creating another instance of NSMutableArray. Then you're adding an object (favShot) to that second instance.
Next time you call postArray it's going to throw away your old array and create 2 new ones.
What you want to do is create the tableData instance when you create the controller instance, or when the view loads. Then, don't set tableData = ... as that will discard the old instance. Just add and remove objects.
edit
- (void) postArray {
[tableData addObject:favShot];
}
- (NSString *)saveFilePath {
NSArray *path = NSSearchPathForDirectoriesInDomains(NSDocumentationDirectory, NSUserDomainMask, TRUE);
return [[path objectAtIndex:0] stringByAppendingPathComponent:#"savefile.plist"];
}
- (void) viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[tableData writeToFile:[self saveFilePath] atomically: TRUE];
}
- (void)viewDidLoad {
NSString *myPath = [self saveFilePath];
BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:myPath];
if (fileExists)
{
tableData = [[NSMutableArray alloc] initWithContentsOfFile:myPath];
} else {
tableData = [[NSMutableArray alloc] initWithObjects: nil];
}
UIApplication *myApp = [UIApplication sharedApplication];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationDidEnterBackground:)
name:UIApplicationDidEnterBackgroundNotification
object:myApp];
[super viewDidLoad];
}
Try
- (void) viewWillDisappear:(BOOL)animated: (UIApplication *) application
{
NSString *filePath = [self saveFilePath];
NSMutableArray *savedArray = [NSMutableArray arrayWithContentsOfFile:filePath];
if (!savedArray) savedArray = [NSMutableArray array];
[savedArray addObject:tableData];
[savedArray writeToFile:filePath atomically:YES];
}

Resources