Search code examples
iosswiftnotificationsrealm

Realm Object-Level Notifications inside UICollectionViewCell's


I have a collection view with Album objects. An Albumcould be favorited or unfavorited by tapping a button inside of the cell. The buttons image changes depending on if the Album is favorited or not. This part is easy and works.

The problem I have is:

  • If you select a cell, a new view controller appears with the Album object in the cell. Within this view controller, you can favorite or unfavorite the Album object. Now when I dismiss this view controller, the button in the cell is "not" updated based on Album's isFavorite property.

I think the solution is to use Realm's Object-Level Notifications inside the UICollectionViewCell's. So that when you favorite/unfavorite an Album in different view controllers, when you come back to the collection view, the button is up to date. But I have no idea how to add and remove the notifications - like where to add/remove and where to update based on the notification?

Note: Please do not say to use collectionView.reloadData().

This is what I have so far (notice the comment: var notificationToken: NotificationToken? // Is this where I add the notification for Realm?):

class Album: Object {
    dynamic var title = ""
    dynamic var isFavorite = false

    convenience init(json: [String: Any]) throws {
        self.init()

        guard let title = json["title"] as? String else {
            throw SerializationError.invalidJSON("Album")
        }

        self.title = title
    }
}

protocol AlbumCollectionViewCellDelegate: class {
    func didTapFavoriteButton(_ favoriteButton: UIButton, album: Album)
}

class AlbumCollectionViewCell: UICollectionViewCell {
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var favoriteButton: UIButton!

    weak var delegate: AlbumCollectionViewCellDelegate?
    var album: Album!

    // HELP
    var notificationToken: NotificationToken? // Is this where I add the notification for Realm?

    @IBAction func didTapFavoriteButton(_ sender: UIButton) {
        delegate?.didTapFavoriteButton(sender, album: album)
    }

    func configure(with album: Album) {
        titleLabel.text = album.title
        favoriteButton.isSelected = album.isFavorite
        self.album = album
    }
}

class FavoritesListViewController: UIViewController, AlbumCollectionViewCellDelegate {

    // MARK: - AlbumCollectionViewCellDelegate
    func didTapFavoriteButton(_ favoriteButton: UIButton, album: Album) {
        favoriteButton.isSelected = album.isFavorite
    }
}

Any thoughts?


Solution

  • I know you said not to mention reloadData(), but if the view controller was guaranteed to be off screen and would be called back onto the screen, I would instead just call reloadData() (or even just reload​Items(at:​ [Index​Path]) passing in the index paths of the visible items) into the viewWillAppear(animated:) method of the view controller.

    It would be much easier than managing sets of notification tokens, especially if you're not even using notification tokens when the view controller is on screen to begin with. :)

    That being said, if you want to avoid manually updating the view, when it's onscreen and offscreen and rely on notifications all the time, then yes, Realm's object-level notifications would be the most appropriate feature to use, and yes, adding the logic to the collection view cell subclass would be the most appropriate. :)

    class AlbumCollectionViewCell: UICollectionViewCell {
        @IBOutlet weak var titleLabel: UILabel!
        @IBOutlet weak var favoriteButton: UIButton!
    
        weak var delegate: AlbumCollectionViewCellDelegate?
        var album: Album!
    
        var notificationToken: NotificationToken? 
    
        @IBAction func didTapFavoriteButton(_ sender: UIButton) {
            delegate?.didTapFavoriteButton(sender, album: album)
        }
    
        override func prepareForReuse() {
            notificationToken.stop()
            notificationToken = nil
        }
    
        func configure(with album: Album) {
            titleLabel.text = album.title
            favoriteButton.isSelected = album.isFavorite
            self.album = album
    
            notificationToken = self.album.addNotificationBlock { change in
                switch change {
                case .change(let properties):
                    for property in properties {
                        if property.name == "isFavorite" {
                            self.favoriteButton.isSelected = self.album.isFavorite
                        }
                    }
                }
            }
        }
    }