addObjects adds only last object to NSMutableArray - ios

I am making an Schedule class and trying to output all events in a loop in a ViewController. I have created an Event class and Schedule class. I am looping thorough NSMutableArray to get the text all of all the events. My problem is that when I am in the addNewEvent method in Schedule.m file I overwrite my NSMutable array with the last object I am adding through addObject. My goal is to add all the event objects one by one into the array and display their text. It would also be nice if I could set separate IDs for the event objects. I understand that I probably need to loop through something in the addNewEvent method when I am adding objects to the mutable array, but I don't know through what. I also thought that maybe I am initializing the NSMutableArray (which is a property of the Schedule class) in the wrong place, but it gives an error if I move the initializer anywhere else.
Could you please help me solve this problem. Any help will be much appreciated!
My Code is below. I have two classes (Event & Schedule) and a ViewController file.
Event.h:
#interface Event : NSObject
#property int eventId;
#property NSString * eventTitle;
#property NSString * eventDescription;
#property NSDate * eventDate;
-(void) logEvent;
-(NSString*) getEventText;
#end
Event.m:
#import "Event.h"
#implementation Event
-(NSString*) getEventText {
NSString * eventText1 = [NSString stringWithFormat: #"\n\nEvent ID: %d.\n", _eventId];
NSString * eventText2 = [eventText1 stringByAppendingFormat: #"Title: %#.\n", _eventTitle];
NSString * eventText3 = [eventText2 stringByAppendingFormat:#"Description: %#.\n", _eventDescription];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateStyle = NSDateFormatterFullStyle;
dateFormatter.timeStyle = NSDateFormatterShortStyle;
NSString * eventText = [eventText3 stringByAppendingFormat:#"Date: %#.\n\n", [dateFormatter stringFromDate:_eventDate]];
return eventText;
}
#end
Schedule.h:
#interface Schedule : NSObject
#property int idIndex;
#property NSMutableArray * scheduledEvents;
-(NSString*) getAllEventText;
-(void)addNewEventWithTitle:(NSString *)Title Description:(NSString *)Description andDate:(NSDate *)Date;
#end
Schedule.m:
#import "Schedule.h"
#import "Event.h"
#implementation Schedule
-(void)addNewEventWithTitle:(NSString *)Title Description:(NSString *)Description andDate:(NSDate *)Date {
Event * event = [[Event alloc] init];
_idIndex = 1;
[event setEventTitle:Title];
[event setEventDescription:Description];
[event setEventDate:Date];
[event setEventId:_idIndex];
NSLog(#"%#", [event getEventText]);
_scheduledEvents = [[NSMutableArray alloc] init];
[_scheduledEvents addObject: event];
NSLog(#"%lu", [_scheduledEvents count]);
}
-(NSString*) getAllEventText {
NSString * allEventText;
// loop through NSMutableArray calling each Event's getEventText method using a for loop
for (int i=0; i<[_scheduledEvents count]; i++) {
allEventText = [NSString stringWithFormat:#"Event %d: %#\n", i+1, [_scheduledEvents[i] getEventText] ];
NSLog(#"%#", [_scheduledEvents[i] getEventText]);
}
// return concatenated string
return allEventText;
}
#end
ViewController.m:
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
Schedule * mySchedule = [[Schedule alloc] init];
[mySchedule addNewEventWithTitle:#"My Birthday" Description:#"My most cherished birthday" andDate:[NSDate dateWithTimeIntervalSinceReferenceDate:484830000]];
[mySchedule addNewEventWithTitle:#"Meeting with the Client" Description:#"My most important meeting" andDate:[NSDate dateWithTimeIntervalSinceReferenceDate:481302000]];
[mySchedule addNewEventWithTitle:#"Appointment with Family Doctor" Description:#"My most urgent appointment" andDate:[NSDate dateWithTimeIntervalSinceReferenceDate:480270000]];
[_outputTextView setText:[mySchedule getAllEventText]];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end

You are re-initializing scheduledEvents everytime addNewEventWithTitle:Description:andDate is called.
You have to move _scheduledEvents = [[NSMutableArray alloc] init]; in your viewDidLoad method.

The problem is this line:
-(void)addNewEventWithTitle:(NSString *)Title Description:(NSString *)Description andDate:(NSDate *)Date {
// ...
_scheduledEvents = [[NSMutableArray alloc] init]; // <--
// ...
}
By definition, if _scheduledEvents already has any value (an existing array), that line destroys that value and replaces it with a new empty array. So the result of running your addNewEvent... method will always be that _scheduledEvents ends up consisting of just this one event.

SOLVED: the problem was in the getAllEventText method.
The allEventText string was constantly replacing its own content in the for loop in the method previously.
I had to change the NSString to NSMutableString and use the append method to add to the existing string, and not replace its content.
So I changed the method as follows and it works now:
-(NSString*) getAllEventText {
NSMutableString* allEventText = [NSMutableString stringWithCapacity:150];
// loop through NSMutableArray calling each Event's getEventText method using a for loop
for (int i=0; i<[_scheduledEvents count]; i++) {
[allEventText appendFormat:#"Event %d: %#\n", i+1, [_scheduledEvents[i] getEventText]];
NSLog(#"%#", [_scheduledEvents[i] getEventText]);
}
// return concatenated string
return allEventText;
}
Also big thanks to metronic and matt for suggesting that I need to move the MSMutableArray initializer away from the addNewEvent method!!

Related

Objective-C: Selectively combine two arrays

I am aware of how normal NSArray concatenation works in Objective-C. This is not that question.
I have data that is being incrementally updated from a web service. My object has the following class definition (with a lot removed):
// NoteTemplate.h
#import <UIKit/UIKit.h>
#interface NoteTemplate
#property (copy, nonatomic) NSString *objectId;
I am caching a list of these on-device and checking at launch to see if there are any new or updated NoteTemplate objects in my database to load. So, I end up with two arrays:
NSArray <NoteTemplate *> *oldArray
NSArray <NoteTemplate *> *newArray
If there are no updates, then all I need to do is simply concatenate the two arrays together and that's that.
If there are updates, however, I want to combine the two arrays, but whenever there is a common objectId, the item in newArray should take precedence over the item in oldArray.
Thus far, I am brute-forcing it like this:
- (void)updateNoteTemplatesWithArray:(NSArray *)newTemplates {
NSArray *oldTemplates = [self getNoteTemplates];
NSMutableArray *combined = [NSMutableArray arrayWithArray:newTemplates];
for (NoteTemplate *noteTemplate in oldTemplates) {
NSArray *matches = [combined filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id blockTemplate, NSDictionary<NSString *,id> *bindings) {
return [((NoteTemplate *)blockTemplate).objectId isEqualToString:noteTemplate.objectId];
}]];
if (matches.count == 0) {
[combined addObject:noteTemplate];
}
}
[self setNoteTemplates:[combined copy]];
}
Is there a more optimized way to do this? I can't see that this will affect performance at all, so perhaps an optimization is unnecessary. Still, this approach feels hacky and way over-engineered.
To extend #Larme's suggestion with Set usage you can try the following approach:
#interface NoteTemplate: NSObject
#property (copy, nonatomic) NSString *objectId;
#property (copy, nonatomic) NSString *text;
- (instancetype)initWithObjectId:(NSString *)objectId text:(NSString *)text;
#end
#implementation NoteTemplate
- (instancetype)initWithObjectId:(NSString *)objectId text:(NSString *)text {
self = [super init];
if (self != nil) {
_objectId = objectId;
_text = text;
}
return self;
}
- (BOOL)isEqual:(id)object {
return [self.objectId isEqualToString:[object objectId]];
}
#end
And the usage code:
NoteTemplate *nt1 = [[NoteTemplate alloc] initWithObjectId:#"1" text:#"old set"];
NoteTemplate *nt2 = [[NoteTemplate alloc] initWithObjectId:#"2" text:#"old set"];
NoteTemplate *nt3 = [[NoteTemplate alloc] initWithObjectId:#"1" text:#"new set"];
NoteTemplate *nt4 = [[NoteTemplate alloc] initWithObjectId:#"3" text:#"new set"];
NSSet <NoteTemplate *> *oldSet = [NSSet setWithObjects:nt1, nt2, nil];
NSSet <NoteTemplate *> *newSet = [NSSet setWithObjects:nt3, nt4, nil];
NSMutableSet <NoteTemplate *> *mergedSet = [newSet mutableCopy];
[mergedSet unionSet:oldSet];
for (NoteTemplate *note in mergedSet) {
NSLog(#"Set item %# %#", note.objectId, note.text);
}
After executing this code you'll see in the log:
Set item 3 new set
Set item 1 new set
Set item 2 old set
I assume that's what you were looking for.
I don't know if I'd call this elegant but it's a less brutish approach. Instead of filtering combined at every pass through the loop, get all the new IDs in advance and check the ID list in the loop.
NSMutableArray <NoteTemplate *> *combined = [NSMutableArray arrayWithArray:newTemplates];
NSArray <NSString *> *newTemplateIds = [newTemplates valueForKey:#"objectId"];
for (NoteTemplate *oldTemplate in oldTemplates) {
if (![newTemplateIds containsObject:oldTemplate.objectId]) {
[combined addObject:oldTemplate];
}
}

How can I ensure value is in the custom class property at the NSMutableArray?

I create the custom class name with FileModel.
FileModel.h
#import <Foundation/Foundation.h>
#interface FileModel : NSObject
#property (nonatomic, copy) NSString *fileName;
#property (nonatomic, copy) NSString *fileType;
#property (nonatomic, strong) NSDate *editDate;
#property (nonatomic, assign) NSInteger fileSize;
#end
I want to compare the particular string with the fileName.
I create the sample like below .m
- (void)viewDidLoad {
[super viewDidLoad];
NSArray *fileSampleName = [[NSArray alloc] initWithObjects:#"apple.png",#"banana.png",#"cherry.png",#"durian.png",#"grape.png",#"avocado.png", nil];
NSMutableArray *fileData = [NSMutableArray new];
FileModel *fileModel = nil;
for( NSInteger i = 0 ; i < fileSampleName.count ; i++){
fileModel = [FileModel new];
fileModel.fileName = [fileSampleName objectAtIndex:i];
fileModel.fileType = #"photo";
fileModel.fileSize = 0;
fileModel.editDate = [NSDate new];
[fileData addObject:fileModel];
}
// fileData's fileName containsObject #"grape" or not?
}
NSArray has containsObject method.
But How can I check the #"grape" is containsObject using fileData at the custom class property filename?
I known using for loop compare one by one.
Did they have other method to check like containsObject?
--- edit---
I try to using indexOfObjectPassingTest method , But the result always is 1.
BOOL result = [fileData indexOfObjectPassingTest:^ BOOL (id tr,NSUInteger index, BOOL *te){
FileModel *fileModel = (FileModel*)tr;
if([#"orange" isEqualToString: fileModel.fileName]){
*te = YES;
return YES;
}else{
return NO;
}}];
NSLog(#"result:%#",#(result)); // it always return 1
Why? thank you very much.
Take a look that NSArray class reference in Xcode. One method you could use is indexOfObjectPassingTest. There are number of related methods depending on your needs. All take a block that's used to test objects to see if they meet whatever criteria you want. In your case you'd test the fileName string.
So you'd pass in a closure that compared the fileName property of each object to your desired filename.

iOS - JSONModel with dynamic key

I am calling a web service which returns dictionary to render the graph. Dictionary structure is
{"1":0,"2":0,"8":0,"9":2,"10":3,"11":0,"12":0}
The problem is keys are dynamic values like 1,2,3 etc which indicates month. Is it possible to represent this in JsonModel?
See you cant create properties at run time as per response structure. But we can smartly use pre-defined things and achieve this. Do following steps:
Create one model class. So your MyCustomModel.h file will look like this
#import <Foundation/Foundation.h>
#interface MyCustomModel : NSObject
#property (nonatomic, retain) NSString * myCustomKey;
#property (nonatomic, retain) NSString * myCustomValue;
#end
This will be your MyCustomModel.m file
#import "MyCustomModel.h"
#implementation MyCustomModel
#synthesize myCustomKey, myCustomValue;
-(id)init {
self = [super init];
myCustomKey = #"";
myCustomValue = #"";
return self;
}
#end
Now lets suppose {"1":0,"2":0,"8":0,"9":2,"10":3,"11":0,"12":0} is NSDictionary and lets say its name is dictionaryResponse
Now do this stuffs:
NSArray *responseKeys = [[NSArray alloc]init];
responseKeys = [dictionaryResponse allKeys];
So your responseKeys will have all keys of response like ["1","2","8","9","10","11","12",]
Now you can iterate loop and create NSMutableArray of model objects as
NSMutableArray *arrayMonthList = [[NSMutableArray alloc]init];
for (int i = 0; i < responseKeys.count; i++) {
MyCustomModel *myModelObject = [[MyCustomModel alloc]init];
myModelObject.myCustomKey = [NSString stringWithFormat:#"%#",[responseKeys objectAtIndex:i]];
myModelObject.myCustomValue = [dictionaryResponse valueForKey:[NSString stringWithFormat:#"%#",[responseKeys objectAtIndex:i]]];
[arrayMonthList addObject:myModelObject];
}
Now arrayMonthList will consist of objects of type MyCustomModel
So you can use it and parse it. Even you can use it to show on UITableView. Following code is written to print values of model properties, you can customise at your expected level.
for (int i = 0; i < arrayMonthList.count; i++) {
MyCustomModel *myModelObject = [arrayMonthList objectAtIndex:i];
NSLog(#"Month is %# and its value is %#",myModelObject.myCustomKey,myModelObject.myCustomValue);
}

Hash Table Code Explanation

I found this implementation of a hash table written in objective-c. I can follow almost all of it, but am struggling to understand how exactly the -(id) init function works. This is the method in the HashTable.m file with 3 lines (I repasted it below right after the question). Could someone explain what exactly it is doing? I included some of the other relevant code although for the most part I think I can follow the rest. Despite that I'm unclear as to the specifics of the init method. Thanks
-(id)init
{
self =[super init];
self.othercontainer = [[NSMutableArray alloc]init];
return self;
}
HashTable.h
#import <Foundation/Foundation.h>
#interface HashTable : NSObject
#property(nonatomic) NSMutableArray* othercontainer;
-(id)objectForKey:(NSString*)name;
-(void)setObject:(id)object forKey:(NSString*)name;
-(id)init;
#end
HashTable.m
#import "HashTable.h"
#import "Person.h"
#implementation HashTable
-(id)init
{
self =[super init];
self.othercontainer = [[NSMutableArray alloc]init];
return self;
}
-(id)objectForKey:(NSString*)name
{
Person* tempPerson = nil;
for (id item in self.othercontainer)
{
NSString* tempName = [((Person*)item) name];
if ([tempName isEqualToString:name])
{
tempPerson = item;
break;
}
}
return tempPerson;
}
-(void)setObject:(id)object forKey:(NSString*)name
{
[self.othercontainer addObject:object];
}
#end
Part of ViewController.m
NSData *data;
NSFileHandle *fh;
NSString *inBoundFile = #"/Users/user/Desktop/names.txt";
NSString *fileString;
fh = [NSFileHandle fileHandleForReadingAtPath:inBoundFile];
data = [fh readDataToEndOfFile];
fileString = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
NSArray *PersonArray = [fileString componentsSeparatedByString:#"\n"];
self.container = [[HashTable alloc]init];
for (int x= 0; PersonArray.count > x ;x++) {
NSArray* tempNameandAddress = [PersonArray[x] componentsSeparatedByString:#" "];
Person *personA = [[Person alloc]init]; //could be other ways of defining an instance of an object
personA.name = tempNameandAddress[0];
personA.address = tempNameandAddress[1];
if ([self.container objectForKey:personA.name] == nil)
[self.container setObject:personA forKey:personA.name];
else
NSLog(#"%# already exists \n",personA.name);
}
This is simply an almost right common init.
self is set to the object returned by the superclass init.
Then they miss one proper step.
The next step should be if (self) { ...additional setup... }
Basically only creating ivars/properties if self as returned from super init is not nil.
If self is nil at that point you would normally just bypass additional code and go straight to return self. (Returning nil)
The next line is just creating the NSMutableArray ivar for the othercontainer property.
This is also not quite right.
In init, this is when you should use the synthesized ivar directly.
_othercontainer = [[NSMutableArray alloc] init];
Nothing special here.

How to replace an object in an NSMutableArray at a given index with a new object

I have an NSMutableArray object (retained, synthesized as all) that is initiated just fine and I can easily add objects to it using the addObject: method. But if I want to replace an object at a certain index with a new one in that NSMutableArray, it doesn't work.
For example:
ClassA.h:
#interface ClassA : NSObject {
NSMutableArray *list;
}
#property (nonatomic, copy, readwrite) NSMutableArray *list;
#end
ClassA.m:
#import "ClassA.h"
#implementation ClassA
#synthesize list;
- (id)init
{
[super init];
NSMutableArray *localList = [[NSMutableArray alloc] init];
self.list = localList;
[localList release];
//Add initial data
[list addObject:#"Hello "];
[list addObject:#"World"];
}
// Custom set accessor to ensure the new list is mutable
- (void)setList:(NSMutableArray *)newList
{
if (list != newList)
{
[list release];
list = [newList mutableCopy];
}
}
-(void)updateTitle:(NSString *)newTitle:(NSString *)theIndex
{
int i = [theIndex intValue]-1;
[self.list replaceObjectAtIndex:i withObject:newTitle];
NSLog((NSString *)[self.list objectAtIndex:i]); // gives the correct output
}
However, the change remains true only inside the method. from any other method, the
NSLog((NSString *)[self.list objectAtIndex:i]);
gives the same old value.
How can I actually get the old object replaced with the new one at a specific index so that the change can be noticed from within any other method as well.
I even modified the method like this, but the result is the same:
-(void)updateTitle:(NSString *)newTitle:(NSString *)theIndex
{
int i = [theIndex intValue]-1;
NSMutableArray *localList = [[NSMutableArray alloc] init];
localList = [localList mutableCopy];
for(int j = 0; j < [list count]; j++)
{
if(j == i)
{
[localList addObject:newTitle];
NSLog(#"j == 1");
NSLog([NSString stringWithFormat:#"%d", j]);
}
else
{
[localList addObject:(NSString *)[self.list objectAtIndex:j]];
}
}
[self.list release];
//self.list = [localList mutableCopy];
[self setList:localList];
[localList release];
}
Please help out guys :)
This does the trick:
[myMutableArray replaceObjectAtIndex:index withObject:newObject];
OK, there are a few bits of confusion here.
You don't need to take a mutableCopy of a newly created NSMutableArray to make it mutable. It's already mutable -- the clue is in the name. You only need to do that in the setter if you want the property to have copy semantics (which you've set, and may have good reason for, of course). But you certainly wouldn't need to do it as shown in your updated updateTitle code, and doing so leaks localList.
Also, you're mixing together property access via self.list and direct use of list in the same method. This is not invalid, but it's bad practice, because it means whatever other stuff the accessor methods do is being randomly bypassed. It's common for properties like this to do everything through self except in the accessors themselves, or in dealloc, and possibly in init (opinions seem to differ on this), where you would access the ivar directly.
Also, never call [self.list release] -- the property accessor doesn't give its caller ownership. Doing this will end in tears, mark my words.
None of this answers the real question, which is why is your change disappearing. The original updateTitle code does not explain this as far as I can see -- it should work. So I suspect that somewhere else you are calling self.list = theOriginalList and hence undoing your change.
Update:
Just for the sake of argument, I'm going to post what I think the code you posted is probably meant to look like. I've preserved your use of a string to pass the index to updateTitle, but I'd like to point out that doing it this way is wrong. It's a number, you should pass it as such. Even if the number comes from a text field or something, that's the caller's concern; the class interface should specify a number. Similarly the apparent change from 1-based to 0-based indexing. Please do not do this sort of thing implicitly, it is a recipe for weeping and gnashing of teeth.
ClassA.h:
#import <Cocoa/Cocoa.h>
#interface ClassA : NSObject
{
NSMutableArray* list;
}
- (void) setList:(NSMutableArray*)newList;
- (void) updateTitle:(NSString*)newTitle forIndex:(NSString*)theIndex;
#property (nonatomic, copy, readwrite) NSMutableArray* list;
#end
ClassA.m:
#import "ClassA.h"
#implementation ClassA
#synthesize list;
- (id) init
{
if ( self = [super init] )
{
list = [[NSMutableArray alloc] init];
[list addObject:#"Hello "];
[list addObject:#"World"];
}
return self;
}
- (void) setList:(NSMutableArray*) newList
{
if ( list != newList )
{
[list release];
list = [newList mutableCopy];
}
}
- (void) updateTitle:(NSString*)newTitle forIndex:(NSString*)theIndex
{
int i = [theIndex intValue] - 1;
[self.list replaceObjectAtIndex:i withObject:newTitle];
}
- (void) dealloc
{
[list release];
[super dealloc];
}
#end
This cleans up various issues, but note that updateTitle is mostly the same. If you drop all this in and the change still doesn't survive, you are definitely resetting list somewhere.
A more straight answer would be:
self.list[i] = newTitle;
This just works like
[self.list replaceObjectAtIndex:i withObject:newTitle];
Look at this line:
#property (nonatomic, copy, readwrite) NSMutableArray *list;
The copy means that whenever you access self.list, you don't get the "_list" instance variable of your object, but a copy of that list. If you write [self.list replaceObjectAtIndex... ] you replace an object in that copy of your list; the original _list is unchanged. Just use
#property (nonatomic, strong, readwrite) NSMutableArray *list;
And to avoid confusion, remove the "list" instance variable and the #synthesize statement, then use _list to access the instance variable.
For Swift you could try:
//if you have indexPath
self.readArray.removeAtIndex((indexPath?.row)!)
self.readArray.insert(tempDict, atIndex: (indexPath?.row)!)
//tempDict is NSDictionary object.
Finally Got Some Perfect Code,
let DuplicateArray: NSArray = array
let DuplicateMutableArray: NSMutableArray = []
DuplicateMutableArray.addObjectsFromArray(DuplicateArray as [AnyObject])
var dic = (DuplicateMutableArray[0] as! [NSObject : AnyObject])
dic["is_married"] = "false"
DuplicateMutableArray[self.SelectedIndexPath] = dic
array = []
array = (DuplicateMutableArray.copy() as? NSArray)!
//Output Will Be Like
array = [
{
"name": "Kavin",
"Age": 25,
"is_married": "false"
},
{
"name": "Kumar",
"Age": 25,
"is_married": "false"
}
]

Resources