Search code examples
iosswiftkey-value-observing

How to update collection view with KVO?


I want to show progress in collection view cell text label. I use this code to do it. Code from observer works and I see print progress in debug but I can't see this in cell. Why?

KVO variant (not working)

I get progress inside the observer, but the cell text is not updated. Why?

var nameObservation: NSKeyValueObservation?
@objc dynamic var progress = 0.0


func createDataSource() {
    dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) { collectionView, indexPath, item in
        switch self.sections[indexPath.section].identifier {
        case "carouselCell":
            let cell = self.configure(CarouselCell.self, with: item, for: indexPath)
                self.nameObservation = self.observe(\.progress, options: .new) { vc, change in
                    cell.title.text = "\(self.progress)"
                }
            return cell
        default: return self.configure(CarouselCell.self, with: item, for: indexPath)
        }
    }
}

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    _ = Timer.scheduledTimer(withTimeInterval: 0.10, repeats: true) { timer in
        guard self.progress <= 1.0 else {
            timer.invalidate()
            self.progress = 0.0
            return
        }
    }
}

Notification variant (not working).

I'm also trying to solve this problem using notifications. And my collection disappears after clicking

var nameObservation: NSKeyValueObservation?
        
    @objc func createDataSource() {
        dataSource = UICollectionViewDiffableDataSource<Section, Item>(collectionView: collectionView) { collectionView, indexPath, item in
            switch self.sections[indexPath.section].identifier {
            case "carouselCell":
                let cell = self.configure(CarouselCell.self, with: item, for: indexPath)
                        cell.title.text = "\(self.progressA)"
                        print(self.progressA)
                return cell
            default: return self.configure(CarouselCell.self, with: item, for: indexPath)
            }
        }
    }
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        
        NotificationCenter.default.addObserver(
            self, selector: #selector(self.createDataSource),
            name: Notification.Name(rawValue: "sound-CarouselController"), object: nil)
                
        _ = Timer.scheduledTimer(withTimeInterval: 0.10, repeats: true) { timer in
            guard self.progressA <= 1.0 else {
                timer.invalidate()
                self.progressA = 0.0
                return
            }
            self.progressA += 0.01
            NotificationCenter.default.post(name: Notification.Name(rawValue: "sound-CarouselController"), object: nil)

        }
                
    }

Solution

  • I find it extremely challenging to animate things in cells, syncing them and so on.

    You can see some old QA about it. https://stackoverflow.com/a/58239612/294884 Synchronise all animations on collection-view cells etc - no idea what's going on in those.

    the only honest-to-God solution we have ever found

    1. Make a singleton that entirely handles all your timer(s), progress etc.

    2. So for example there might be some "progress", lets say it is "progress of download 7B" ...

    3. During any frame, anyone at all who wants to, can, get that current value, from the singleton

    4. And that's it. In your cells (or anywhere whatsoever) just draw the value in question (whether a bar or whatever it is) each frame to the value you read from the singleton

    So you just completely forget about the madness of UIKit's various animation paradigms.

    Just use CADisplayLink and draw everything as you wish - based only on the values in one central singleton.

    Don't forget that (of course) cells are constantly changing as you scroll. ie the cell that the user thinks of as "cell 91" is of course displayed by DIFFERENT changing cells as you scroll up and down, hopefully you completely understand this basic paradigm of how recycler views work.

    Note that you don't have to bother with the insanity of notifications, subscriptions etc.

    It's one of those things that is much, much easier once you make the jump to using normal frame based drawing (CADisplayLink).

    When you're testing this you can put the animation anywhere at all, on a normal view, in a popup, in an alert, in cells, in tab bars, whatever. The "cell problems" become nonexistent.