Newbie Questions,
I have 3 Class, 3 of them are subclass from NSOBject.
Collection Class, have 2 properties, masterSong as NSMutableSet (strong, nonatomic) and listOfPlaylists as NSMutableArray (strong, nonatomic)
Playlist Class
Have 2 properties, playListName as NSString (copy, nonatomic) and songList as NSMutableArray (strong, nonatomic)
3 . Song Class
Have 4 properties: title,artist,album, playtime as NSString (copy, nonatomic).
masterSong will contain Song object, and listOfPlaylist will contain Playlist Object.
while songList only store reference to Song Object.
I want to create removeSong method for Collection Class by looking up Song for title,artist,or album inside masterSong.
if looking up find 1, it will return NSSet, returned NSSet will take place as parameter in minusSet: method to remove the song from masterSong and all Playlist.songList.
but, I can't figure out how to write down NSPredicate syntax to filter out .title or .album or .artist from masterSong which contain Song Object.
here is what I got so far, for Collection.m
- (void) removeSong: (NSString *)zSong{
//remove from reference playlist
NSSet *targets = [self lookUpTitle:zSong];
if ([targets count]>0) {
[self.masterSongs minusSet:targets];
for (Playlist *playlist in listOfPlaylists) {
// -all objects converts the set into array.
[playlist.songList removeObjectsInArray:[targets allObjects]];
}
}
else
;
}
Xcode throw exception when executing lookUpTitle method by saying that Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Can't look for value (title:Whole point of no return ,artist: Cafe Bleu ,album: CBSB ,playtime: 3:56) in string (What Do You Know); value is not a string
- (NSSet *) lookUpTitle: (NSString *)aName {
NSString *filters = @"%K CONTAINS [cd] %@";
NSPredicate *filter = [NSPredicate predicateWithFormat:filters, @"title", aName];
NSSet *result = [masterSongs filteredSetUsingPredicate:filter];
if ([result count] == 0) {
NSLog(@"not found");
return nil;
}
else{
return result;
}
}
I know that the main problem is at lookUpTitle method
NSPredicate *filter = [NSPredicate predicateWithFormat:filters, @"title", aName];
NSSet *result = [masterSongs filteredSetUsingPredicate:filter];
the filter is filtering in a wrong way, while it should filtering NSString object (title, artist, or album) but masterSong is containing Song Object, how do I access title,artist,album in Song Object placed inside masterSong with NSPredicate?
any other effective way for this condition?
Predicate Programming Guide only show example set/array containing string
I found other thread which has similar topic, but block is quite confusing to read, and still won't work in my case.
Edit : additions to the partial code (Class.m method) as requested by responder #1 . here are some method in .m files related to my questions
Main.m
#import <Foundation/Foundation.h>
#import "MusicCollection.h"
#import "Playlist.h"
#import "Song.h"
int main(int argc, const char * argv[])
{
@autoreleasepool {
// insert code here...
// NSLog(@"Hello, World!");
//create Songs
Song *aSong = [[Song alloc]initWithTitle:@"Whole point of no return" withArtist:@"Cafe Bleu" withAlbum:@"CBSB" withPlaytime:@"3:56"];
Song *bSong = [[Song alloc]initWithTitle:@"Council Meetin'" withArtist:@"Cafe Bleu" withAlbum:@"CBSB" withPlaytime:@"4:00"];
Song *cSong = [[Song alloc]initWithTitle:@"Hayate" withArtist:@"Spitz" withAlbum:@"Indigo Chiheisen" withPlaytime:@"4:21"];
Song *dSong = [[Song alloc]initWithTitle:@"I have a Dreams" withArtist:@"WestLife" withAlbum:@"Season" withPlaytime:@"4:11"];
Song *eSong = [[Song alloc]initWithTitle:@"What Do You Know" withArtist:@"David Choi" withAlbum:@"Tomorrow" withPlaytime:@"3:46"];
//create playList
Playlist *playlistA = [[Playlist alloc]initWithName:@"Playlist A"];
Playlist *playListB = [[Playlist alloc]initWithName:@"Playlist B"];
//store Song A & B to Playlist A and Song A,B,C to playlist B
[playlistA addSong:aSong];
[playlistA addSong:bSong];
[playListB addSong:aSong];
[playListB addSong:bSong];
[playListB addSong:cSong];
// [playListB removeSong:eSong];
//Creating Master Collection
MusicCollection *myCollection = [[MusicCollection alloc]initWithName:@"Library"];
[myCollection addPlaylist:playlistA];
[myCollection addPlaylist:playListB];
[myCollection addSong:eSong];
[myCollection addSong:dSong];
[myCollection removePlaylist:playListB];
[myCollection removeSong:aSong];
NSSet *container2 = [myCollection lookUpTitle:@"What"];
NSLog(@"%@",container2);
// NSLog(@"%@",myCollection);
}
return 0;
}
Collection.m
-(instancetype) initWithName: (NSString *)aName{
self = [super init];
if (self) {
self.name = [NSMutableString stringWithString:aName];
self.listOfPlaylists = [NSMutableArray array];
self.masterSongs = [NSMutableSet set];
}
return self;
}
-(instancetype) init{
return [self initWithName:@""];
}
-(void) addPlaylist: (Playlist *)aPlayList{
if ([listOfPlaylists containsObject:aPlayList]==YES) {
}
else
[listOfPlaylists addObject:aPlayList];
}
-(void) removePlaylist: (Playlist *)aPlayList{
if ([listOfPlaylists containsObject:aPlayList]) {
[listOfPlaylists removeObjectIdenticalTo:aPlayList];
}
else{
;
}
}
- (void) displaySong{
NSLog(@"displaying all song in Collection");
NSLog(@" %@",self.masterSongs);
}
- (void) addSong :(Song *)aSong{
if (![masterSongs containsObject:aSong]) {
[masterSongs addObject:aSong];
}
}
- (void) removeSong: (NSString *)zSong{
//remove from reference playlist
NSSet *targets = [self lookUpTitle:zSong];
if ([targets count]>0) {
[self.masterSongs minusSet:targets];
for (Playlist *playlist in listOfPlaylists) {
// -all objects converts the set into array.
[playlist.songList removeObjectsInArray:[targets allObjects]];
}
}
else
;
}
- (NSSet *) lookUpTitle: (NSString *)aName {
NSString *filters = @"%K CONTAINS [cd] %@";
NSPredicate *filter = [NSPredicate predicateWithFormat:filters, @"title", aName];
NSSet *result = [masterSongs filteredSetUsingPredicate:filter];
if ([result count] == 0) {
NSLog(@"not found");
return nil;
}
else{
return result;
}
}
Playlist.m
- (void) addSong :(Song *)aSong{
if (![songList containsObject:aSong]) {
[songList addObject:aSong];
}
}
- (void) removeSong: (Song *)aSong{
if ([songList containsObject:aSong]){
[self.songList removeObjectIdenticalTo:aSong];
}
}
- (instancetype) initWithName: (NSString *)aPLName{
self = [super init];
if (self) {
self.songList = [NSMutableArray array];
self.playlistName = aPLName;
}
return self;
}
- (instancetype)init{
return [self initWithName:@""];
}
Song.m
- (instancetype) initWithTitle: (NSString *)aTitle withArtist: (NSString *)anArtist withAlbum: (NSString *)aAlbum withPlaytime: (NSString *)playingTime{
self = [super init];
if (self) {
self.artist = [NSMutableString stringWithString:anArtist];
self.album = [NSMutableString stringWithString:aAlbum];
self.title = [NSMutableString stringWithString:aTitle];
self.playtime = [NSMutableString stringWithString:playingTime];
}
return self;
}
-(instancetype) init{
return [self initWithTitle:@"null" withArtist:@"null" withAlbum:@"null" withPlaytime:@"null"];
}
You have overloaded the meaning of
-removeSong:
and it has caused you confusion. InPlaylist
,-removeSong:
takes aSong
instance, but inCollection
,-removeSong:
takes aNSString
instance for the song title.The problem is you pass an instance of a
Song
to the version which expects anNSString
.Should either be
or
I changed the name of the second version of the method to clearly show what the expected parameter is. When you want to use the second version, you would pass the message
[myCollection removeSongWithTitle:aSong.title]
You don't need to use
-predicateWithBlock
, it should be sufficient to use the following predicate in your caseEDIT
Jeffery Thomas is right, the problem is in this line - in fact the compiler gives a warning
you're passing to the
-removeSong:
a Song object, not the song's title. If you want to pass to-removeSong:
aSong
object changes the method signature from- (void)removeSong:(NSString *)songTitle
to-(void)removeSong:(Song*)song
and change accordingly the method implementation to pass to the predicate the song's title which is aNSString
From now on, please do not ignore compiler warnings to avoid passing days debugging this kind of subtle errors.