I'm setting N items in an MPMediaItemsCollection, where some items aren't unique (the collection represents a playlist, where the same song might appear twice).
- (void)setLastSongWithItemCollection:(MPMediaItemCollection *)itemCollection
{
[_musicPlayer setQueueWithItemCollection: itemCollection];
NSLog(#"itemCollection count %u", itemCollection.count);
NSLog(#"itemCollection lastObject index: %u", [itemCollection.items indexOfObject:itemCollection.items.lastObject]);
_musicPlayer.nowPlayingItem = [[itemCollection items] lastObject];
}
In one example, I'm generating itemCollection with 4 songs, where the last song is the first one repeated. If I attempt to get the last object in the list, _musicPlayer will always play the first item.
The first NSLog prints "itemCollection count 4" clearly indicating there are four items in the items array. However, the second NSLog prints "itemCollection lastObject index:0", indicating that items.lastObject does not return the object and the last index, but rather the first one where that same media item occurs.
Can anyone shed some light on this?
CORRECTION: As pointed out, indexOfObject: is not a valid test for returning the index of a repeated object
Still, _musicPlayer.nowPlayingItem = [[itemCollection items] lastObject] sets the first instance identical to lastObject to the media player. It seems the nowPlayingItem setter calling indexOfObject: on itemCollection. Workarounds?
Follow-up
It seems there is no way to select an index in the queue of an MPMediaPlayer, and setNowPlayingItem: calls indexOfObject: To find a selected media item. The result is that indexOfNowPlayingItem will return the index of the first instance identical to nowPlayingItem in the queue. In my case, this affects the visual representation of the queue that I have in my app (it's a scrollview with a panel for each song). It's possible that MPMediaItemCollection was not intended for use with playlists where MPMediaItems can occur more than once. I will fill a bug report in the interest of getting more information on the subject.
This really has nothing to do with MPMediaItemsCollection specifically. Rather, it's just the behavior of NSArray.
As the documentation for -[NSArray indexOfObject:] states, it "returns the lowest index whose corresponding array value is equal to a given object." In this case, equality is determined by sending each object an -isEqual: message.
So, if you have an array:
NSArray *array = #[#"A", #"B", #"C", #"A"];
You'll see the following:
[array indexOfObject:#"A"] // returns 0
[array indexOfObject:array[3]] // also returns 0
[array indexOfObject:[array lastObject]] // also returns 0
Essentially, -[NSArray indexOfObject] looks like this:
- (void)indexOfObject:(id)object
{
for (NSUInteger i=0; i<[self count]; i++) {
if ([self[i] isEqual:object]) return i;
}
return NSNotFound;
}
If you want to know the indexes for all matching objects in the array, use -[NSArray indexesOfObjectsPassingTest:]
NSArray *array = #[#"A", #"B", #"C", #"A"];
NSIndexSet *indexes = [array indexesOfObjectsPassingTest:^BOOL(id obj, NSUInteger idx, BOOL *stop) {
return [obj isEqual:#"A"];
}];
NSLog(#"Indexes: %#", indexes);
>>Indexes: <NSIndexSet: 0x7fbc9b40b170>[number of indexes: 2 (in 2 ranges), indexes: (0 3)]
If you really need to know which index should be associated with your use of a non-unique item in the playlist, you'll have to keep track of that yourself by storing it when you retrieve the media item in the first place.
Related
I have multiple NSDictionaries inside an NSMutableArray. A short hand example is below. comparisonChart is the NSMutableArray.
NSDictionary *dict1 = #{
#"Tube": #"10/0",
#"Dress":#"3"
};
[self.comparisonChart setValue:dict1 forKey#"0"];
// key 0 as i wish to use numeric indexes, comparisonChart is mutable array
When i wish to extract the value for a key i've tried:
[self.comparisonChart valueForKey:[NSString stringWithFormat:#"%ld", value]]
//where value is numeric value e.g 0
However this returns null. ive also tried objectForKey:value with the same result.
How do i go about this?
update:
[self.comparisonChart insertObject:dict23 atIndex:1];
NSLog(#"chart: %#", [self.comparisonChart objectAtIndex:1]);
Output: chart: (null) // why is this?
If self.comparisonChart is a NSMutableArray then you add the NSDictionary like so:
[self.comparisonChart addObject: dict1];
or you may specify the index like so:
[self.comparisonChart insertObject: dict1 atIndex:desiredIndex];
To retrieve the NSDictionary object you must call:
[self.comparisonChart objectAtIndex:indexNumber];
You need to use objectForKey for one, you should also be using subscripts instead;
NSDictionary *dict1 = #{#"Tube": #"10/0",
#"Dress":#"3"};
NSMutableDictionary *comparisonChart = [NSMutableDictionary new];
comparisonChart[#"0"] = dict1;
NSLog(#"My value: %#", comparisonChart[#"0"]);
Here's the code copy and pasted from AppDelegate and working:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSDictionary *dict1 = #{#"Tube": #"10/0",
#"Dress":#"3"};
NSMutableDictionary *comparisonChart = [NSMutableDictionary new];
comparisonChart[#"0"] = dict1;
NSLog(#"My value: %#", comparisonChart[#"0"]);
return YES;
}
Edit:
To append on the question above please ensure the index being inserted is valid per the documentation below:
Apple documentation
- (void)insertObject:(id)anObject
atIndex:(NSUInteger)index
Parameters
anObject
The object to add to the array's content. This value must not be nil.
IMPORTANT
Raises an NSInvalidArgumentException if anObject is nil.
index
The index in the array at which to insert anObject. This value must not be greater than the count of elements in the array.
IMPORTANT
Raises an NSRangeException if index is greater than the number of elements in the array.
Discussion
If index is already occupied, the objects at index and beyond are shifted by adding 1 to their indices to make room.
Note that NSArray objects are not like C arrays. That is, even though you specify a size when you create an array, the specified size
is regarded as a “hint”; the actual size of the array is still 0. This
means that you cannot insert an object at an index greater than the
current count of an array. For example, if an array contains two
objects, its size is 2, so you can add objects at indices 0, 1, or 2.
Index 3 is illegal and out of bounds; if you try to add an object at
index 3 (when the size of the array is 2), NSMutableArray raises an
exception.
Import Statement
import Foundation
Availability
Available in iOS 2.0 and later.
I have array of objects and some objects in it have the same value(for example user's guid).
I want find all object with same guide and remove all of then rather then first.
What is the best way to do it?
You can use the NSMUtableArray's removeObject method. Notice that your object should implement the isEqual method appropriately.
[NSMutableArray removeObject]
as per the description:
This method uses indexOfObject: to locate matches and then removes
them by using removeObjectAtIndex:. Thus, matches are determined on
the basis of an object’s response to the isEqual: message. If the
array does not contain anObject, the method has no effect (although it
does incur the overhead of searching the contents).
So, first of all you array need to be mutable NSMutableArray, then the process is:
consider the actual object;
check if is present another object equal to this in the other objects;
if yes, delete the equal objects include the actual.
-
NSMutableArray *arr = [NSMutableArray arrayWithArray:#[#1, #2, #3, #2, #5, #3]];
for(int i=0; i<[arr count]; i++) {
id obj = arr[i];
if([arr indexOfObject:obj inRange:NSMakeRange(i+1, [arr count]-i-1)] != NSNotFound) {
[arr removeObject:obj inRange:NSMakeRange(i, [arr count]-i)];
i--;
}
}
I have created simple XCTest to test distinctUnionOfObjects. All the test cases are passing except one which is isKindOfClass (Last XCTAssertTrue). Any idea why it's changing the class when you do distinctUnionOfObjects.
- (void)testUsersPredicate
{
NSArray *usersBeforePredicate = [[self userData] users];
XCTAssertEqual([usersBeforePredicate count] , 34u, #"We need 34");
XCTAssertTrue([[usersBeforePredicate lastObject] isKindOfClass:[ICEUsersModelObject class]], #"Object is not ICEUsersModelObject class");
NSString *distinctUsersKeyPath = [NSString stringWithFormat:#"#distinctUnionOfObjects.%#", #"userName"];
NSArray* usersAfterPredicate = [usersBeforePredicate valueForKeyPath:distinctUsersKeyPath];
XCTAssertEqual([usersAfterPredicate count] , 30u, #"We need 30");
XCTAssertTrue([[usersAfterPredicate lastObject] isKindOfClass:[ICEUsersModelObject class]], #"Object is not ICEUsersModelObject class");
}
As the right key path on your distinctUnionOfObjects is userName, the -valueForKeyPath: call will return an NSArray of distinct userNames (not user objects).
From Apple's KVC Programming Guide:
The #distinctUnionOfObjects operator returns an array containing the distinct objects in the property specified by the key path to the right of the operator.
Change the last test case to check for [NSString class] and it should pass.
Alternatives
Using equality:
If the userNameproperty is supposed to serve as a unique identifier, you could enforce that by overriding -isEqual: and -hashon the user object to reflect this:
- (BOOL)isEqual:(id)object {
return ([object isKindOfClass:self.class] && [object.userName isEqual:self.userName]);
}
- (NSUInteger)hash {
return self.userName.hash;
}
This can benefit your overall model design and opens up a lot of additional options, like this one that obtains a collection of distinct users reg. userName in one line - NSSet is very fast when used for this:
NSArray *uniqueUsers = [[NSSet setWithArray:users] allObjects];
Note: I re-used the hashing function of NSString for the user hash, which has a subtle pitfall; -[NSString hash] only guarantees uniqueness for strings of up to 96 characters! This is not in the docs and took me almost a day to track down in production code. (see Apple's implementation of CFString.c - search for __CFStrHashCharacters)
Using NSPredicate:
Here's a, let's say, 'creative' solution that uses a predicate. However, some kind of iteration is needed, because the predicate condition would otherwise have to be a function of its own result:
NSMutableArray *__uniqueUsers = [NSMutableArray array];
[[users valueForKeyPath:#"#distinctUnionOfObjects.userName"] enumerateObjectsUsingBlock:^(id name, NSUInteger idx, BOOL *stop) {
NSArray *uniqueUser = [users filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id user, NSDictionary *bindings) {
return [user.userName isEqual:name];
}]];
if (uniqueUser.count > 0)
[__uniqueUsers addObject:uniqueUser.lastObject];
}];
NSArray *uniqueUsers = [NSArray arrayWithArray:__uniqueUsers];
It obtains a collection of unique userNames, iterates over it, selects exactly one user for each name and adds that to the output array.
You test has no sense. You can't use distinct in this way because as #mvanellen told you, you should change the class in NSString due the fact that you are searching for distinct username.
You instead are trying to get the list of objects considering distinct username, but it is conceptually wrong. You should try to get the list of the username if you want, but not of the entire objects.
Consider to have in your array:
ITEM 1: aav / otherValue1
ITEM 2: aav / otherValue2
ITEM 3: matteo / otherValue3
for sure the function would return ITEM 3, but being a distinct query, which from ITEM 1 and ITEM 2 should it takes?
Think about this ;)
This question already has answers here:
How do I sort an NSMutableArray with custom objects in it?
(27 answers)
Closed 9 years ago.
Hopefully someone can help.
I'm adding multiple objects to a NSMutableArray and I need to sort the order based on the first element which will always be a number.
However I'm unsure how to do this?
For example:
NSMutableArray *array = [[NSMutableArray alloc] init];
NSArray *object = [NSArray arrayWithObjects: #"1",#"Test",#"Test"];
[array addObject:object];
Thanks
If your array always contains other arrays, and the first element of the innermost array is always a string containing a number, you could use the NSMutableArray method sortUsingComparator to sort your array:
[array sortUsingComparator: ^(NSArray* obj1, NSArray* obj2)
{
int value1 = [obj1[0] integerValue];
int value2 = [obj2[0] integerValue];
if (value1==value2)
return NSOrderedSame;
else if (value1 < value2)
return NSOrderedAscending;
else
return NSOrderedDescending;
}
];
In the sortUsingComparator family of methods, you supply a block of code that the sort method uses to compare pairs of objects in your array. The block uses the standard typedef NSComparator, which takes 2 objects as parameters and returns a value of type NSComparisonResult.
The code above will probably crash if all the objects in your array are not arrays of strings. (Actually it would work if the first element of each component array was an NSNumber, since NSNumber also responds to the integerValue message.)
If you are going to use this code in a very controlled environment where you can be sure that the data you are sorting is well-formed, it should work as written. If there is any chance that the objects in the array would be of a different type, or be empty, or that their first element would not respond to the integerValue messages, then you should add error checking code.
If you sort your array alphanumerically, the object #"1" will appear before any words. Keep in mind though that #"1" in your code above is a string, not a number.
As to how to sort an array, look into [NSArray sortedArrayUsingComparator:] and similar methods.
I have my array unique that is my main array and my array kind. I need to check that only 1 value of kind is present in the array unique. Then if there is more than 1 value of the array kind in unique I need to unset all values but the first one used in the array.
The further i got to achieve this is with the following code but I can not store the indexpath of the found object to do a later comparison. xcode says "bad receiver type nsinteger"
could anyone help me to achieve this?
kind = #[#"#Routine",#"#Exercise",#"#Username"];
NSMutableArray *uniqueKind = [NSMutableArray array];
for (NSString* obj in kind) {
if ( [unique containsObject:obj] ) {
NSInteger i = [unique indexOfObject:obj];
[uniqueKind addObject: [i intValue]];
}
}
An NSInteger is like an int, so you can't send it a message ([i intValue]). Also, you can't add an NSInteger to an array without making it an NSNumber or some other object type. You can do it like this:
NSInteger i = [unique indexOfObject:obj];
[uniqueKind addObject: [NSNumber numberWithInteger:i]];
Also (without understanding what you're doing) you might want to use an NSSet instead of an array. And you can combine a couple of calls:
NSUInteger i = [unique indexOfObject:obj];
if ( i != NSNotFound ) {
[uniqueKind addObject:[NSNumber numberWithInteger:i]];
}
I'm not sure if it would solve your problem, but have you considered using sets (or mutable variation) instead of arrays? They ensure uniqueness and they allow you to check for intersection/containment. See the NSSet class reference.
You have to add objects to the NSMutableArray, not the actual intValue. Try converting teh integer to a NSNumber first.
[uniqueKind addObject: [NSNumber numberWithInt:i]];
instead.
(edited )