Ch23 Silver Challenge - Is the Segmented Control the issue? Or the new Predicate Function?

I set up a UISegmented Control to toggle between All Photos and Favorites in my PhotosViewController.swift and set up a new Predicate function to fetch my Favorites.

But when I run the app, nothing happens when I toggle (I only marked a handful of photos as Favorites).

PhotosViewController.swift:

   // for Silver Challenge:
@objc func photoTypeChanged(_ segControl: UISegmentedControl) {
    switch segControl.selectedSegmentIndex {
    case 0:
        store.fetchInterestingPhotos {
        (photosResult) -> Void in
            self.updateDataSource()
        }
    default:
        store.fetchAllFavorites {
        (photosResult) -> Void in
            self.updateDataSource()
        }
        
    }
}

PhotoStore.swift:

// Silver Challenge
func fetchAllFavorites(completion: @escaping (Result<[Photo], Error>) -> Void) {
    let fetchRequest: NSFetchRequest<Photo> = Photo.fetchRequest()
    let sortByDateTaken = NSSortDescriptor(key: #keyPath(Photo.dateTaken), ascending: true)
    
    
    fetchRequest.predicate = NSPredicate(format: "isFavorite == true")
    fetchRequest.sortDescriptors = [sortByDateTaken]
    
    let viewContext = persistentContainer.viewContext
    viewContext.perform {
        do {
            let allPhotos = try viewContext.fetch(fetchRequest)
            completion(.success(allPhotos))
        } catch {
            completion(.failure(error))
        }
    }
}

Is there something wrong with my predicate function? I tried to adapt what I found from this Apple resource:

[https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/FetchingObjects.html#//apple_ref/doc/uid/TP40001075-CH6-SW1](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreData/FetchingObjects.html#//apple_ref/doc/uid/TP40001075-CH6-SW1)

Did you check to see if the photoTypeChanged function is being called at all, by using the debugger or by printing a message?

@objc func photoTypeChanged(_ segControl: UISegmentedControl) {
    print ("--> \(type (of:self)): \(#function)");

    switch segControl.selectedSegmentIndex {
    ...
}

When you set up the predicate shouldn’t it read:

fetchRequest.predicate = NSPredicate(format: “photo.isFavorite == true”)

Well, for starters, I needed to add a new variable and a function to respond to the toggle:

@IBOutlet weak var segmentedControl: UISegmentedControl! 

// for Silver Challenge:
@objc func photoTypeChanged(_ segControl: UISegmentedControl) {
    switch segControl.selectedSegmentIndex {
    case 0:
        store.fetchInterestingPhotos {
        (photosResult) -> Void in
            self.updateDataSource()
        }
        print("Fetched all photos.")
    default:
        store.fetchAllFavorites {
        (photosResult) -> Void in
            self.updateDataSource()
        }
        print("Fetched favorites")
        
    }
}

But after I added these items, the app crashes before loading the photo set, pointing to updateDataSource():

My debug log doesn’t elaborate on the error. Additionally, I don’t see an entry in my log identifying which set of photos it’s displaying.

Why would the app crash at this point when all I did was add and declare a UISegmentedControl?

@GPeteN – Thanks, I made that adjustment.

Still, the Build succeeded, but the app still crashed in the same spot that I identified in my previous post.

Well all the photo attributes are optionals except for isFavorite which is a Bool. Is there some place where you are not unwrapping an optional?

I resorted to Optional chaining for my segmented control here:
@IBOutlet weak var segmentedControl: UISegmentedControl? // Silver challenge

segmentedControl?.addTarget( **self** , action: **#selector** (photoTypeChanged( **_** :)), for: .valueChanged)

Current fetchAllFavorites() function:

func fetchAllFavorites(completion: @escaping (Result<[Photo], Error>) -> Void) {
    let fetchRequest: NSFetchRequest<Photo> = Photo.fetchRequest()
    let sortByDateTaken = NSSortDescriptor(key: #keyPath(Photo.dateTaken), ascending: true)
    
    
    fetchRequest.predicate = NSPredicate(format: "Photo.isFavorite == true")
    fetchRequest.sortDescriptors = [sortByDateTaken]
    
    let viewContext = persistentContainer.viewContext
    viewContext.perform {
        do {
            let allPhotos = try viewContext.fetch(fetchRequest)
            completion(.success(allPhotos))
        } catch {
            completion(.failure(error))
        }
    }
}

But when I switched to Favorites, the app crashed with these log items:

Fetched favorites (console log message acknowledging the toggle to Favorites...)

2020-08-19 18:40:38.582259-0400 Photorama[53423:917810] [error] error: SQLCore dispatchRequest: exception handling request: <NSSQLFetchRequestContext: 0x600002769260> , keypath Photo.isFavorite not found in entity <NSSQLEntity Photo id=1> with userInfo of (null)
CoreData: error: SQLCore dispatchRequest: exception handling request: <NSSQLFetchRequestContext: 0x600002769260> , keypath Photo.isFavorite not found in entity <NSSQLEntity Photo id=1> with userInfo of (null)
2020-08-19 18:40:38.586057-0400 Photorama[53423:917810] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'keypath Photo.isFavorite not found in entity <NSSQLEntity Photo id=1>'

Is my predicate correct?

fetchRequest.predicate = NSPredicate(format: "Photo.isFavorite == true")

If I invoke either Photo the Class or ‘photo’ the Object, the app crashes in the same way.

If I use only ‘isFavorite == true’, I still see all photos when I toggle.

When you look at the data model is one of the attributes “isFavorite”? It looks like that piece isn’t working.

If I understand NSPredicate correctly, you don’t say “Photo.isFavorite == true”, you just say “isFavorite == true”. Since the predicate is operating on a container of objects, you don’t need to specify the class name - that’s a given based on the container being searched. You just specify the attribute name.

On the plus side, those runtime errors show that the search is taking place so that’s one thing you don’t need to worry about.

Finally, I think the problem may be that you’re using “==” in the format string. I’ve seen a couple places showing format strings for NSPredicate using “=” rather than “==”. This one, for example. So maybe try “isFavorite = true” and see if that produces better results.

OK, I just did an experiment. I ran the program & marked a few photos as favorites, then I added your predicate to PhotoStore.fetchAllPhotos to only return favorited photos (changing the attribute name to the one I used) & re-ran the program, and it worked. Didn’t matter whether I used “=” or “==”.

So I think the problem is not with your comparison, it’s somewhere else.

[Edited to add: I should have mentioned that I only got part-way through this challenge. I added code to allow favoriting a photo, but did not get around to the second part to provide a way to only display favorited photos. That’s why I did the experiment I mentioned.]

Did you remember to regenerate your NSManagedObject subclass files (p450) after adding the isFavorite attribute? (I think this is what GPeteN was getting at in their last post.)

In PhotoStore.processPhotoRequest, did you remember to initialize isFavorite to false when copying flickerPhoto to photo?

JonAlt,

I ran the same experiment (I switched to ‘=’ and took ‘Photo.’ out) and saw my Favorites.

I did regenerate the NSManagedObject subclass files.

And I added an initialization for isFavorite in processPhotoRequest():

var photo: Photo!
                context.performAndWait {
                    photo = Photo(context: context)
                    photo.title = flickrPhoto.title
                    photo.photoID = flickrPhoto.photoID
                    photo.remoteURL = flickrPhoto.remoteURL
                    photo.dateTaken = flickrPhoto.dateTaken
                    photo.isFavorite = false
                }
                return photo

Same result as before, however.

I figured it out. The problem is in your photoTypeChanged function. When the Favorites option is selected on the segmented control, you call store.fetchAllFavorites, but when that completes your closure calls self.updateDataSource() which in turn calls store.fetchAllPhotos, undoing the favorite filtering you just did.

You need to remove the fetchInterestingPhotos/fetchAllFavorites calls from photoTypeChanges and just have it call updateDataSource. Let updateDataSource decide which set of photos to get. Or make a copy of updateDataSource that fetches favorited photos & call that when you want to display favorites (that’s what I did). You’ll have some duplicate code in the two functions, but you can refactor to get rid of that.

I ended up copying updateDataSource() too, Jon. Thanks as always.

I have now (finally!) finished this book.

1 Like

Thanks for sharing good information
vmware

@Caps5150 finish the book you say ? What about Accessibility chapter lol.
Anyways, I think you might have over engineered this. Check out my simple solution here: