Iglistkit: Airport style effect / Upload progress indicator

Created on 26 Jun 2017  路  5Comments  路  Source: Instagram/IGListKit

So far I am really enjoying IGListKit but at the moment I am stuck how to implement a UI element using it. Admitting, even with plain UICollectionView. 馃槄 I am not sure what the best practices are to get this going. I am trying to get a similar effect going as you can see when you use Airport and upload a file from like your Mac to an iPad. You have a progress indicator while the file gets uploaded.

I was wondering what the best approach would be to implement this? Currently, I was doing it like listed below only I am getting stuck how to update the cell.

Airdrop screenshot

  1. Create a cell which can show the progress
  2. Create a view model Item(id: String, name: String, progress: Int)
  3. Create an operation class which does the heavy lifting
  4. Create a section controller which each of these cells which has a reference to the view model and instantiates the operation class when the cell gets selected
  5. Keep track of the upload progress (using didSendBodyData?)
  6. The operation class will store the progress and then I make a map against the view model's diffIdentifier and the operation
  7. Each time, the section controller gets recreated or new cell is needed. I can do something like self.viewController?.getProgressForModel(self.object)

Only the problem I am having when the cell / section controller doesn't need to recreated as the collection view is not being scrolled. How can I force an update to the view model so the progress indicator can updated? Do I need to continuously create a new view model for each the progress of the operation changes? And implement the isEqual like this:

    func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
        guard self !== object else { return true }
        guard let object = object as? User else { return false }
        return id == object.id && name == object.name && progress == object.progress
    }

What would be the best approach to do this? I would be happy to make an example out of this once I have this all going happily 馃憤

question

Most helpful comment

Yup tons of ways to do this. You could use plain ol' delegates:

Delegate + system setup

protocol ProgressDelegate: class {
  func didChangeProgress(_ emitter: ProgressEmitter, from: CGFloat, to: CGFloat)
}
class ProgressEmitter {
  weak var delegate: ProgressDelegate? = nil
  private var progress: CGFloat = 0

  public func update(progress: progress) {
    delegate?.didChangeProgress(self, from: self.progress, to: progress)
    self.progress = progress
  }
}

Then have a NSURLSessionDataDelegate tell the ProgressEmitter about progress changes. Setup your section controller to respond to progress updates:

class MySectionController: ListSectionController, ProgressDelegate {
  func didChangeProgress(_ emitter: ProgressEmitter, from: CGFloat, to: CGFloat) {
    guard let cell = collectionContext?.cellForItem(at: 0, sectionController: self) as? ProgressCell
      else { return }
    cell.animate(from: from, to: to)
  }
}

And connect your SC to the emitter in the VC or something

func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
  let controller = MySectionController()
  self.emitter.delegate = controller
  return controller
}

You can also make your emitter hold an array of weak ref listeners like this.

Going to close this out since its basically been answered. Hope this helps!

All 5 comments

Do I need to continuously create a new view model for each the progress of the operation changes?

Infinite number of ways of doing this, I have one :)

I am using signals to avoid too much knowledge about this in my technology stack. Meaning the viewModel has a signal that wraps the progress. This signal is then bound to the progress indication and gets updated as download progress happens.

I understand this only partly answers your question, but I wanted to mention (as I have done) it is possible to separate this part from the sections/viewModel creation/re-creation.

@heumn What do you mean with a signal here? Do you mean a closure?

RxSwift, Bond, ReactiveCocoa etc. Also here it seems like our options are infinite :)

I will have a look what those are. I prefer to use vanilla Swift when possible 鈽猴笍

Yup tons of ways to do this. You could use plain ol' delegates:

Delegate + system setup

protocol ProgressDelegate: class {
  func didChangeProgress(_ emitter: ProgressEmitter, from: CGFloat, to: CGFloat)
}
class ProgressEmitter {
  weak var delegate: ProgressDelegate? = nil
  private var progress: CGFloat = 0

  public func update(progress: progress) {
    delegate?.didChangeProgress(self, from: self.progress, to: progress)
    self.progress = progress
  }
}

Then have a NSURLSessionDataDelegate tell the ProgressEmitter about progress changes. Setup your section controller to respond to progress updates:

class MySectionController: ListSectionController, ProgressDelegate {
  func didChangeProgress(_ emitter: ProgressEmitter, from: CGFloat, to: CGFloat) {
    guard let cell = collectionContext?.cellForItem(at: 0, sectionController: self) as? ProgressCell
      else { return }
    cell.animate(from: from, to: to)
  }
}

And connect your SC to the emitter in the VC or something

func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
  let controller = MySectionController()
  self.emitter.delegate = controller
  return controller
}

You can also make your emitter hold an array of weak ref listeners like this.

Going to close this out since its basically been answered. Hope this helps!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

andreamazz picture andreamazz  路  16Comments

rnystrom picture rnystrom  路  34Comments

heumn picture heumn  路  27Comments

cikpis picture cikpis  路  16Comments

jessesquires picture jessesquires  路  16Comments