I'm not too sure if I should of made another question for this or expanded on the last question, please correct me if I wasn't meant to.
I've currently got this code working:
if ([ myArray containsObject:#"Object1" ] && [ myArray containsObject:#"Object 2"]) {
return YES;
}
else {
return NO;
}
What I'm needing to do is modify this so it iterates through an array and accesses an Objects property value. For example:
if (myArray contains obj.ID 1 & 2) {
return YES
}
else{
return NO;
}
Any suggestions on what I should look at? I've been at this for a couple of hours and tried different permutations with no luck.
Thank you!
You can use -indexOfObjectPassingTest: to check if an object with a particular attribute value is in your array. The method returns either the object's index if it is found or NSNotFound if not.
Thus, assuming your objects are e.g. NSDictionaries and they have NSNumbers as IDs, you could do something like this:
if([myArray indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [[obj objectForKey:#"ID"] intValue]==1;
}]!=NSNotFound && [myArray indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [[obj objectForKey:#"ID"] intValue]==2;
}]!=NSNotFound)
{
//Array contains objects
}
If you only want the first object, you can use -indexOfObjectPassingTest: as mpramat says. If you want all the objects in the array that match your criteria, use indexesOfObjectsPassingTest:.
It takes a block as a parameter. The block evaluates each object and returns YES or know to let the method know if that object should be part of the set of objects that pass the test.
Related
for(int i=0;i<[serviceNamesFilterArray count];i++){
NSLog(#"state : %#", [serviceNamesFilterArray objectAtIndex:i]);
NSString *str = [serviceNamesFilterArray objectAtIndex:i];
if (tag_id == [serviceNamesFilterArray indexOfObject:str] ) {
// filterButtonArray = serviceNamesFilterArray;
[filterButtonArray addObject:str];
NSLog(#"%#",filterButtonArray);
}
}
I want to access index of serviceNamesFilterArray. How can i access index's of my array so that i can compare it with integer tag_id?
Even Objective-C provides smarter filter APIs than a loop.
index will contain the index of the object in the array matching tag_id
NSInteger index = [self.serviceNamesFilterArray indexOfObjectPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return (NSString *)obj.integerValue == tag_id;
}];
you can compare i value with your tag_id as follows:
for(int i=0;i<[serviceNamesFilterArray count];i++) {
NSLog(#"state : %#", [serviceNamesFilterArray objectAtIndex:i]);
NSString *str = [serviceNamesFilterArray objectAtIndex:i];
if (tag_id == i) {
//perform your logic here
}
}
you can use the enumerateObjectsUsingBlock method, like
[serviceNamesFilterArray enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
// ...
}];
Preamble: you are using [array objectAtIndex:index] in your code, while this was the way to index historically in modern Objective-C you simply write array[index]. If you are learning Obj-C from a book/website you might want to look for a newer text.
It is unclear what you are asking let’s see what we can figure out.
You start with a loop:
for(int i=0;i<[serviceNamesFilterArray count];i++)
Here i is going to be used as an index into the array serviceNamesFilterArray. Inside the loop you then access the object at index i (updating your code as above):
NSString *str = serviceNamesFilterArray[i];
and having obtained the object at index i you ask what is the index of that object:
[serviceNamesFilterArray indexOfObject:str]
There are two possible answer here:
i – this is the most obvious answer and will be the result if there are no duplicates in serviceNamesFilterArray. It will be the answer as you just obtained str from index i of the array.
j where j < i – this will be the answer if the array contains duplicates and the same string is found at indices j and i. This result happens because indexOfObject: returns the first index at which the object occurs with the array.
The most likely result seems to be (1) in your case (guessing you do not have duplicate “service names”). In this case your conditional is equivalent to:
if (tag_id == i) {
[filterButtonArray addObject:str];
}
However if this is your intention then the loop is completely unnecessary as your code is equivalent to:
NSString *str = serviceNamesFilterArray[tag_id];
[filterButtonArray addObject:str];
If the serviceNamesFilterArray does contain duplicates then your code as written may add the string at index tag_id multiple times to filterButtonArray or it may add it no times – we'll leave figuring out why as an exercise, and we doubt this is your intention anyway.
At the time of writing #vadian has made a different guess as to your aim. Their solution finds the index, if any, where the string value if interpreted as an integer is equal to the value of tag_id (an integer). If that is your aim then #vadian’s solution provides it.
Of course both our and #vadian’s guesses might be wrong at to what your aim is. If so you can edit the question to explain, or delete it and ask a new one instead – given this question has at the time of writing 3 answers already deletion in this case might be better to reduce future confusion when people read the (revised) question and (outdated) answers.
HTH
Maybe you just need to judge whether the tag_id is less than count of serviceNamesFilterArray, then you can get the value by tag_id directlly.
if (tag_id < [serviceNamesFilterArray count]){
NSString *str = [serviceNamesFilterArray objectAtIndex:tag_id];
// other logic here
}
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (condition) {
[array removeObject:obj];
}
}];
sometimes it worked,and sometimes it crashed,why?
Imagine how you would write the code for -enumerateObjectsUsingBlock: if you were working at Apple and asked to add this. It would probably look something like:
-(void) enumerateObjectsUsingBlock: (void(^)(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop))myBlock
{
NSUInteger numItems = [self count];
BOOL stop = false;
for( NSUInteger x = 0; x < numItems && stop == false; x++ )
{
myBlock( [self objectAtIndex: x], x, &stop );
}
}
Now if your block calls removeObject:, that means that the array:
0: A
1: B
2: C
3: D
After myBlock( A, 0 ) changes to:
0: B
1: C
2: D
Now x++ gets executed, so next call is myBlock( C, 1 ) -- already you see that the 'B' is now skipped, and the item originally at index 2 is deleted second (instead of the one at index 1). Once we have deleted that, we loop again and the array looks like this:
0: B
1: D
So when -enumerateObjectsUsingBlock: tries to delete the item at index 2, it runs off the end of the array, and you get a crash.
In short, the documentation for -enumerateObjectsUsingBlock: doesn't say anywhere that you may modify the array while you're iterating it, and there is no way for the block to tell the looping code in -enumerateObjectsUsingBlock: that it just deleted an object, so you can't rely on that working.
(You can try this out yourself ... rename this version of enumerateObjectsUsingBlock: to myEnumerateObjectsUsingBlock:, declare it in a category on NSArray, and step through it in the debugger with a program like: [myArray myEnumerateObjectsUsingBlock: ^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop){ [myArray removeObject: obj]; }];)
If you expect you'll be deleting items from the array, there are several workarounds. One, you can make a copy of the array, loop over that, and delete objects from the original array. Another option is to iterate backwards over the array, which means that the indexes of earlier items won't change (try modifying the version of -enumerateObjectsUsingBlock: above and watch what happens in the debugger).
Yet another approach is to write your own method that filters the array. You give it a block that is expected to return YES if you are to keep the object, NO if you shouldn't. Then you loop over the original array, call the block on each item, and create a new array, to which you add all objects for which the block returns YES.
This is unsafe way. I don't know well internal execution of this language, btw I think when you remove the object, then the size of array decreases, and it would be error when you reach the last element of array.
I think in enumeration block, you are not allowed to change array. it is same issue on other languages.
You can get further information in this url.
http://ronnqvi.st/modifying-while-enumerating-done-right/
After deletion, your objects are not proper. so make copy as:
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (condition) {
int counter = [array indexOfObject:obj];
[array removeObjectAtIndex:counter];
}
}];
I have a CKReferenceList for a list of employee, something may look like this
(
"<CKReference: 0xa5bc930; 83a97165-2635-4bda-a7eb-fabf5a725bed:(_defaultZone:__defaultOwner__)>",
"<CKReference: 0xa50a2e0; 9B7F4269-D8BA-4CE9-9BCF-AD2047B73EB5:(_defaultZone:__defaultOwner__)>"
)
Then I have a another CKReference which points to a employee record. Which might look like this
"<CKReference: 0xa5bc330; 83a97165-2635-4bda-a7eb-fabf5a725bed:(_defaultZone:__defaultOwner__)>"
Notice that the reference of this specific employee is in the employee list, they have the same reference name, namely " 83a97165-2635-4bda-a7eb-fabf5a725bed".
When I tried to remove using the code below
NSMutableArray<CKReference*>* employeeList = [[NSMutableArray alloc]initWithArray:self.employeeList];
if(employeeList != nil){
[employeeList removeObject:employeeReference];
}
This won't remove the reference from the list even though the reference name is equal(the same happens to contain method).
I think the the removeObject uses the isEqual: method as a criteria, so when comparing two CKReference, they are actually comparing the address, but not based on the string name.
Should I just overwrite the isEqual implementation to provide my own in order to remove a given CKReference from a list of CKReference.
Or is there any alternative approach for this?
You can call indexOfObjectPassingTest: and compare whatever you need:
NSMutableArray <CKReference*>* employeeList = [[NSMutableArray alloc] init];
NSUInteger index = [employeeList indexOfObjectPassingTest:^BOOL(CKReference * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
return [obj.recordID.recordName isEqualToString:recordNameToRemove];
}];
if (index != NSNotFound) {
[employeeList removeObjectAtIndex:index];
}
Sorry guys, this problem I am running into is pretty trivial. I just can't wrap my head around it so hope someone can help me. Your help is really appreciated. I am getting JSON data through NSURLConnectDelegate with a web API. I get something like this back:
(
{
id = 340
name = Vicent },
{
id = 339
name = Johny },
{
id = 338
name = Eric }
)
and I save it in a NSMutableArray as a global variable. Now, I have a NSSet of "ids". For example:
{
340, 339
}
In the numberOfRowsInSection, I return the set's count. I am trying to load only the ids in the NSSet from the array with the data saved from the webAPI, so I do something like this in cellForRowIndexPath:
for (NSNumber *num in [set allObjects]) {
NSString *newString = [[savedArray objectAtIndex:indexPath.row]
NSString *new = [num stringValue];
if ([new isEqual:newString]) {
}}
How can I just populate the ids I want?
The JSON makes it look like you have an array of dictionaries, which is a reasonable data structure to use as the data source for a table view.
It sounds like you're trying to filter your array to only include the items that are in your set. Is that right?
If so, you could write code that would create a new array containing the subset of your array elements who's ID is also in your set. There are at least a half-dozen ways to do that. One fairly simple approach would be to use the NSArray method indexesOfObjectsPassingTest. You'd pass that method a block of code that would check each array element to see if it's id object was in your set.
That would give you an NSIndexSet with the indexes of the items in your array who's ID are in your set. Then you could use the NSArray method objectsAtIndexes to get an array of only the objects that are also in the set. Something like this (Assuming that your array of dictionaries is called savedArray and your set is called allObjects:
//get the indexes of items in the array savedArray who's id appears in the set allObjects
NSIndexSet *indexes = [savedArray indexesOfObjectsPassingTest:
^(NSDictionary *obj,
NSUInteger idx,
BOOL *stop)
{
return [allObjects member: obj[#"id"]] != nil;
}
];
//Now build an (immutable) array of just the objects who's ID are in the set
NSArray *subArray = [savedArray objectsAtIndexes: indexes];
The array subArray created above is immutable. If you need a mutable array you would need to make a mutable copy, which is a one-line change.
Disclaimer: I still struggle a little with block syntax, so the above might not be exactly correct, but it gives you the general idea.
Im trying to interpret this block code:
SCENARIO 1
NSIndexSet* indexes = [[self.orderItems allKeys] indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
IODItem* key = obj;
return [searchItem.name isEqualToString:key.name] && searchItem.price == key.price;
}];
Ok so I get allKeys of the self.orderItems dictionary and pass them as id obj to the block {}. Inside the block, that obj is assigned to an IODItem *key. Then both the call isEqualToString returns a BOOL as does the comparator ==. This makes sense to me because the block is of return type BOOL. So how does that fill an NSIndexSet of indexes?
SCENARIO 2
NSArray* keys = [[self.orderItems allKeys] sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
IODItem* item1 = (IODItem*)obj1;
IODItem* item2 = (IODItem*)obj2;
return [item1.name compare:item2.name];
}];
So I get allKeys again for that dictionary. I then sortArrayUsingComparator and I pass in the keys as obj1 & obj2? This is confusing. obj1 & obj2 are just keys in the [self.orderItems allKeys]-array?
Then I take those 2 objs and assign each to a different IDOItem. Then I actually return the items1 & 2? This is confusing again. I thought I was filling in an NSArray *keys. Why am i returning 2 things?
SCENARIO 3
// 3 - Enumerate items and add item name and quantity to description
[keys enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
IODItem* item = (IODItem*)obj;
NSNumber* quantity = (NSNumber*)[self.orderItems objectForKey:item];
[orderDescription appendFormat:#"%# x%#\n", item.name, quantity];
}];
im enumerating through the keys array (which is gotten from SCENARIO 2 actually) and using the key obj and assigning it to an IODItem item. But here is where i got lost...I take the objectForKey item and use it as a quantity? If objectForKey returns the object paired to that key, and the key "item" refers to the id obj, then that id obj is a key from the sorted keys array. So its a key, not a value! Isnt it?
It's not too confusing if you think of blocks as a method with arguments and a return type. That first method is just iterating through the entire set of keys and determining if they pass the test you created or not. If your block returns 'YES' then it is added to the new index set. If the block returns 'NO' then it is ignored. The resulting index set will refer to a complete set of keys that passed the test.
The second method is a way for you to perform a custom sort on an array. Depending on which object should be closer to the beginning of the array, you either return NSOrderedAscending, NSOrderedSame or NSOrderedDescending. This could change based on the criteria you have for the sort. What you have basically done is called the compare: method on the first object's 'name' property. Depending on what data type this is (I'm assuming it's an NSString), your block will call the compare: method from NSString. If it was an NSNumber or another class, it would call that class's compare: method, etc. etc. The compare: method also returns an NSComparisonResult (one of the three options listed above).
Added Scenario 3:
For scenario 3 while enumerating through the 'keys', each 'id obj' is a key in the dictionary, NOT an IODItem as you've coded it. To get the applicable IODItem, you will probably need to do something like this (I'm assuming self.orderItems refers to an NSDictionary object):
__block NSMutableString *orderDescription;
__block NSNumber *quantity;
[keys enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
NSString* currentKey = (NSString*)obj;
IODItem *item = [self.orderItems objectForKey:currentKey];
quantity = item.quantity;
[orderDescription appendFormat:#"%# x%#\n", item.name, quantity];
}];