Juanpe / SkeletonView

☠️ An elegant way to show users that something is happening and also prepare them to which contents they are awaiting
MIT License
12.59k stars 1.11k forks source link

Support of iOS 13 DiffableDataSource #279

Open macistador opened 4 years ago

macistador commented 4 years ago

Hi

Do you plan to support iOS 13 new UICollectionViewDiffableDataSource ?

Thanks

seanperez29 commented 4 years ago

I am also very curious if support for this is coming.

Semty commented 4 years ago

It's strange that there has not been any answer on the issue since March. @Juanpe, could you, please, response

adelbios commented 4 years ago

@Juanpe hello ?! Please response on this issue

tbaranes commented 4 years ago

SkeletonView is currently working with DiffableDataSource when setting false to reloadDataAfter when hiding skeleton view. I did nothing special beside that in order to have a working skeleton tableview

Semty commented 4 years ago

@tbaranes Hi! What do you mean by "is currently working"? As far as I know, you need to implement SkeletonCollectionViewDataSource to make it work with UICollectionView

tbaranes commented 4 years ago

Exactly, you need to inherit from UITableViewDiffableDataSource and conforms to SkeletonTableViewDataSource. In my case I was using a tableview, but I think it will be the same for a collection view.

class MyDataSource: UITableViewDiffableDataSource<Int, Model> {

    init(tableView: UITableView) {
        super.init(tableView: tableView) { tableView, indexPath, model in
           // ...
        }
    }

}

// MARK: - SkeletonCollectionViewDataSource

extension MyDataSource: SkeletonTableViewDataSource {

    func collectionSkeletonView(_ skeletonView: UITableView, cellIdentifierForRowAt indexPath: IndexPath) -> ReusableCellIdentifier {
        "ReusableIdentifier"
    }

}

Doing this will make skeleton working but as said before when hiding the skeleton you will need to set reloadDataAfter to false, otherwise the skeleton won't be hidden:

if showSkeleton {
   tableView.showAnimatedSkeleton()
} else {
   tableView.hideSkeleton(reloadDataAfter: false)
}
daihovey commented 3 years ago

@tbaranes Is there anything more to your example? I have the same code but the collectionSkeletonView function is never called

ndraiman commented 3 years ago

@tbaranes solution doesn't work for me. I tried on v1.21.0

MobiOS-21 commented 2 years ago

also does not work with collections version 1.25.2

dsk1306 commented 2 years ago

I've tried @tbaranes's solution but I keep getting this error

*** Assertion failure in -[UITableView _performDiffableUpdate:], UITableView.m:19143
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException',
reason: 'Attempted to apply updates to a table view from a UITableViewDiffableDataSource, but the table view's dataSource is not the UITableViewDiffableDataSource.
Table view: <
  UITableView: 0x7f7d7d121400;
  frame = (0 174.333; 390 521.333);
  clipsToBounds = YES;
  alpha = 0;
  userInteractionEnabled = NO;
  gestureRecognizers = <NSArray: 0x600000635bf0>;
  animations = { opacity=<CABasicAnimation: 0x600000838a80>;
  transition=<CATransition: 0x600000838ac0>; };
  layer = <CALayer: 0x600000817900>;
  contentOffset: {0, 0};
  contentSize: {390, 422.33333333333331};
  adjustedContentInset: {0, 0, 0, 0};
  dataSource: <SkeletonView.SkeletonCollectionDataSource: 0x600001326340>
>'

so I guess it doesn't really work. Does anyone know another way to make SkeletonView work with diffable data sources?

tomcheung commented 2 years ago

@dsk1306 It looks like SkeletonView will swap to its own datasource for showing skeleton, so you need to hideSkeleton() before you call .apply() on DiffableDataSource

Juanpe commented 2 years ago

Hi guys! I see there is a workaround, right? Please, could you provide an example code or gist with this workaround to help other users with the same problem? Thanks

tomcheung commented 2 years ago

Here is the testing code, base on @tbaranes solution

import UIKit
import SkeletonView

class ItemCell: UICollectionViewCell {
    let titleLabel = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)

        contentView.addSubview(titleLabel)
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            titleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
            titleLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor)
        ])

        isSkeletonable = true
        contentView.isSkeletonable = true
        titleLabel.isSkeletonable = true
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

class DiffableDataSourceViewController: UIViewController {
    let collectionViewLayout = UICollectionViewCompositionalLayout.list(using: .init(appearance: .plain))

    lazy var collectionView: UICollectionView = {
        return UICollectionView(frame: self.view.bounds, collectionViewLayout: collectionViewLayout)
    }()

    lazy var datasource = CollectionViewSkeletonDiffableDataSource<String, Int>(collectionView: self.collectionView) { collectionView, indexPath, item in
        let cell = collectionView.dequeueConfiguredReusableCell(using: self.cellRegistration, for: indexPath, item: item)
        cell.backgroundColor = UIColor(hue: CGFloat(item % 10) / 10, saturation: 0.5, brightness: 0.7, alpha: 1)

        return cell
    }

    let cellRegistration = UICollectionView.CellRegistration<ItemCell, Int> { cell, IndexPath, value in
        cell.titleLabel.text = String(value)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(collectionView)
        collectionView.frame = view.frame
        collectionView.register(ItemCell.self, forCellWithReuseIdentifier: "cell")

        collectionView.isSkeletonable = true
        collectionView.dataSource = datasource
        collectionView.showSkeleton()

        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            var ss = NSDiffableDataSourceSnapshot<String, Int>()
            ss.appendSections(["Section 1", "Section 2", "Section 3"])

            ss.appendItems(Array((1..<10)), toSection: "Section 1")
            ss.appendItems(Array((11..<20)), toSection: "Section 2")
            ss.appendItems(Array((21..<30)), toSection: "Section 3")

            self.collectionView.hideSkeleton()
            self.datasource.apply(ss)
        }
    }
}

class CollectionViewSkeletonDiffableDataSource<Section: Hashable, Item: Hashable>: UICollectionViewDiffableDataSource<Section, Item>, SkeletonCollectionViewDataSource {

    var cellIdentifier: String = "cell"

    func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier {
        return cellIdentifier
    }

    func collectionSkeletonView(_ skeletonView: UICollectionView, prepareCellForSkeleton cell: UICollectionViewCell, at indexPath: IndexPath) {
        (cell as? ItemCell)?.titleLabel.text = "XXX"
    }

    func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 50
    }
}
tsangaris commented 2 years ago

Hi guys! I see there is a workaround, right? Please, could you provide an example code or gist with this workaround to help other users with the same problem? Thanks

Hi there Juanpe, there should be a dedicated section in the Readme where it states how to use it with DiffableDataSource as this package is very helpful and versatile!

Thanks!

Juanpe commented 2 years ago

Hi guys! I see there is a workaround, right? Please, could you provide an example code or gist with this workaround to help other users with the same problem? Thanks

Hi there Juanpe, there should be a dedicated section in the Readme where it states how to use it with DiffableDataSource as this package is very helpful and versatile!

Thanks!

Hi @tsangaris 👋🏼 Yeah, I think it's a good idea. Would you like to do it? If not, I will add it ASAP 👍🏼

tsangaris commented 2 years ago

Hi guys! I see there is a workaround, right? Please, could you provide an example code or gist with this workaround to help other users with the same problem? Thanks

Hi there Juanpe, there should be a dedicated section in the Readme where it states how to use it with DiffableDataSource as this package is very helpful and versatile! Thanks!

Hi @tsangaris 👋🏼 Yeah, I think it's a good idea. Would you like to do it? If not, I will add it ASAP 👍🏼

@Juanpe I just switched to DiffableDatasource, so I will need some time to learn how to use it and proceed with Skeleton views. I am just working proactively, so I need to make sure I can switch the skeleton views as well.

I am not sure how long its going to take, but if i have something ready i will let you know. Or if you have something that can help us, please post it :wink:

tsangaris commented 2 years ago

Here is the testing code, base on @tbaranes solution

import UIKit
import SkeletonView

class ItemCell: UICollectionViewCell {
    let titleLabel = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)

        contentView.addSubview(titleLabel)
        titleLabel.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            titleLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor),
            titleLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor)
        ])

        isSkeletonable = true
        contentView.isSkeletonable = true
        titleLabel.isSkeletonable = true
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

class DiffableDataSourceViewController: UIViewController {
    let collectionViewLayout = UICollectionViewCompositionalLayout.list(using: .init(appearance: .plain))

    lazy var collectionView: UICollectionView = {
        return UICollectionView(frame: self.view.bounds, collectionViewLayout: collectionViewLayout)
    }()

    lazy var datasource = CollectionViewSkeletonDiffableDataSource<String, Int>(collectionView: self.collectionView) { collectionView, indexPath, item in
        let cell = collectionView.dequeueConfiguredReusableCell(using: self.cellRegistration, for: indexPath, item: item)
        cell.backgroundColor = UIColor(hue: CGFloat(item % 10) / 10, saturation: 0.5, brightness: 0.7, alpha: 1)

        return cell
    }

    let cellRegistration = UICollectionView.CellRegistration<ItemCell, Int> { cell, IndexPath, value in
        cell.titleLabel.text = String(value)
    }

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(collectionView)
        collectionView.frame = view.frame
        collectionView.register(ItemCell.self, forCellWithReuseIdentifier: "cell")

        collectionView.isSkeletonable = true
        collectionView.dataSource = datasource
        collectionView.showSkeleton()

        DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
            var ss = NSDiffableDataSourceSnapshot<String, Int>()
            ss.appendSections(["Section 1", "Section 2", "Section 3"])

            ss.appendItems(Array((1..<10)), toSection: "Section 1")
            ss.appendItems(Array((11..<20)), toSection: "Section 2")
            ss.appendItems(Array((21..<30)), toSection: "Section 3")

            self.collectionView.hideSkeleton()
            self.datasource.apply(ss)
        }
    }
}

class CollectionViewSkeletonDiffableDataSource<Section: Hashable, Item: Hashable>: UICollectionViewDiffableDataSource<Section, Item>, SkeletonCollectionViewDataSource {

    var cellIdentifier: String = "cell"

    func collectionSkeletonView(_ skeletonView: UICollectionView, cellIdentifierForItemAt indexPath: IndexPath) -> ReusableCellIdentifier {
        return cellIdentifier
    }

    func collectionSkeletonView(_ skeletonView: UICollectionView, prepareCellForSkeleton cell: UICollectionViewCell, at indexPath: IndexPath) {
        (cell as? ItemCell)?.titleLabel.text = "XXX"
    }

    func collectionSkeletonView(_ skeletonView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return 50
    }
}

@tomcheung thank you for taking the time to write this example. Just a question: What if I want to show a different number of skeleton items in each section? For example:

"Section 1": show 4 skeleton items "Section 2": show 6 skeleton items "Section 3": show 1 skeleton item

Also, what if I want each section to have a different layout of skeleton item?

Is there a way I can access the dataSource's snapshot inside the CollectionViewSkeletonDiffableDataSource class?

Thank you in advance.