Coder Social home page Coder Social logo

collectionkit's Introduction

CollectionKit

Reimagining UICollectionView

A modern Swift framework for building composable data-driven collection view.

Carthage compatible Version License Build Status codecov Xcode 8.2+ iOS 8.0+ Swift 3.0+ Slack

Migration Guide

v2.0

Features

  • Rewritten UICollectionView on top of UIScrollView.
  • Automatically diff data changes and update UI.
  • Superb performance through cell reuse, batched reload, visible-only diff, & the use of swift value types.
  • Builtin layout & animation systems specifically built for collections.
  • Composable sections with independent layout.
  • Strong type checking powered by Swift Generics.

Install

# CocoaPods
pod "CollectionKit"

# Carthage
github "SoySauceLab/CollectionKit"

Getting Started

To start using CollectionKit, use CollectionView in place of UICollectionView. CollectionView is CollectionKit's alternative to UICollectionView. You give it a Provider object that tells CollectionView how to display a collection.

The simpliest way to construct a provider is by using BasicProvider class.

BasicProvider

To build a BasicProvider, here is what you need:

  • DataSource
    • an object that supplies data to the BasicProvider.
  • ViewSource
    • an object that maps each data into a view, and update the view accordingly
  • SizeSource
    • an function that gives the size for each cell.

It sounds complicated, but it really isn't. Here is a short example demostrating how it all works.

let dataSource = ArrayDataSource(data: [1, 2, 3, 4])
let viewSource = ClosureViewSource(viewUpdater: { (view: UILabel, data: Int, index: Int) in
  view.backgroundColor = .red
  view.text = "\(data)"
})
let sizeSource = { (index: Int, data: Int, collectionSize: CGSize) -> CGSize in
  return CGSize(width: 50, height: 50)
}
let provider = BasicProvider(
  dataSource: dataSource,
  viewSource: viewSource,
  sizeSource: sizeSource
)

//lastly assign this provider to the collectionView to display the content
collectionView.provider = provider

Note that we used ArrayDataSource & ClosureViewSource here. These two classes are built-in to CollectionKit, and should be able to serve most jobs. You can implement other dataSource and viewSource as well. Imagine implementing a NetworkDataSource in your project, that retrives json data and parse into swift objects.

Reload

It is easy to update the CollectionView with new data.

dataSource.data = [7, 8, 9]

This will trigger an update of the CollectionView that is served by this dataSource.

Note that append and other array mutating methods will also work.

dataSource.data.append(10)
dataSource.data.append(11)
dataSource.data.append(12)

We updated the array three times in this example. Each update is triggering a reload. You might be thinking that this is very computational intensive, but it isn't. CollectionKit is smart enough to only update once per layout cycle. It will wait until the next layout cycle to actually reload.

After executing the 3 lines above, CollectionView will still show [7, 8, 9]. But once the current run loop cycle is completed, CollectionView will update immediately. Your user won't notice any lag from this process.

To trigger an update immediately, you can call collectionView.reloadData() or provider.reloadData() or dataSource.reloadData().

To make collectionView reload on the next layout cycle, you can call collectionView.setNeedsReload() or provider.setNeedsReload() or dataSource.setNeedsReload(). You might already noticed, once you update the array inside ArrayDataSource, it is basically calling setNeedsReload() for you.

Note that if you assign an array to the dataSource and later update that array instead. It won't actually update the CollectionView

var a = [1, 2 ,3]
dataSource.data = a
a.append(5) // won't trigger an update be cause dataSource.data & a is now two different array.
a = [4 ,5 ,6] // also won't trigger an update

Layout

Up to this point, the collection is still a bit ugly to look at. Every cell is left aligned and doesn't have space in between. You might want the views to be evenly spaced out, or you might want to add some spacing in between items or lines.

These can be achieved with Layout objects. Here is an example.

provider.layout = FlowLayout(spacing: 10, justifyContent: .center)

FlowLayout is a Layout class that it built-in to CollectionKit. There are many more built-in layouts including WaterfallLayout & RowLayout. You can also easily create your own layout.

FlowLayout is basically a better UICollectionViewFlowLayout that aligns items in row by row fashion. It supports lineSpacing, interitemSpacing, alignContent, alignItems, & justifyContent.

Every layout also supports inset(by:) and transposed() methods.

inset(by:) adds an outer padding to the layout and return the result layout as InsetLayout.

let inset = UIEdgeInset(top: 10, left: 10, bottom: 10, right: 10)
provider.layout = FlowLayout(spacing: 10).inset(by: inset)

transposed() converts a vertical layout into a horizontal layout or vice-versa. It returns the original layout wrapped inside a TransposedLayout

provider.layout = FlowLayout(spacing: 10).transposed()

You can also use them together like

let inset = UIEdgeInset(top: 10, left: 10, bottom: 10, right: 10)
provider.layout = FlowLayout(spacing: 10).transposed().inset(by: inset)

There can be a lot to talk about with Layouts. We will create more tutorial later to teach you how to create your own layout and show you some advance usages. In the mean time, feel free to dive in the source code. I promise you it is not complecated at all.

Composing (ComposedProvider)

The best feature of CollectionKit, is that you can freely combine providers together into multiple sections within one CollectionView. And it is REALLY EASY to do so.

let finalProvider = ComposedProvider(sections: [provider1, provider2, provider3])

collectionView.provider = finalProvider

To update individual sections, just update its own dataSource.

provider2DataSource.data = [2]

You can also live update sections around.

finalProvider.sections = [provider2, provider3, provider1]

Or add more to it.

finalProvider.sections.append(provider4)

You can even put ComposedProvider into another ComposedProvider no problem.

let trulyFinalProvider = ComposedProvider(sections: [finalProvider, provider5])

collectionView.provider = trulyFinalProvider

How cool is that!

Animation

CollectionKit offers a animation system which allows you to create fancy animations and adjust how cells are displayed.

Here are some examples of custom animators that is included in the example project. They can be used in combination with any layout. Here we are using a transposed waterfall layout.

Wobble Edge Shrink Zoom

Animator can also perform animations when a cell is added/moved/deleted. Here is an example showing a 3d scale animation with a cascading effect.

It is easy to use an Animator. You can assign it to providers, cells, or to entire CollectionView.

// apply to the entire CollectionView
collectionView.animator = ScaleAnimator()

// apply to a single section, will override CollectionView's animator
provider.animator = FadeAnimator()

// apply to a single view, will take priority over all other animators
view.collectionAnimator = WobbleAnimator()

Note: that in order to use WobbleAnimator, you have to include pod "CollectionKit/WobbleAnimator" subspec to your podfile.

Please checkout the example project to see many of these examples in action.

Questions? Want to contribute?

Join our public Slack!

collectionkit's People

Contributors

codacy-badger avatar dishcool avatar fabezi avatar humblehacker avatar jindulys avatar lkzhao avatar narsail avatar tylerian avatar yonaskolb avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

collectionkit's Issues

WaterfallLayout vertical bounce bug?

I test WaterfallLayout and WobblePresenter. With transposed() option, scroll work well but when remove transposed(), scroll at top and bottom not good (bounce of scrollView).
I attached my code. Please check for me, whether i do something wrong or not. Thanks
My Examples.zip

problem when UICollectionView/UITableView wrapped into a SimpleViewProvider

Hi. It's really a nice framework.

I got some problems when I wrap UICollectionView/UITableView into a SimpleViewProvider.

In my viewController:

import UIKit

class TableTestVC: UITableViewController {
    let data = [1, 2, 3, 4, 5, 6, 7]
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.data.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "celllll")
        cell.textLabel?.text = "\(self.data[indexPath.row])"
        return cell
    }
    
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        NSLog("Select TableViewCell")
    } 
}

Provider:

import UIKit
import CollectionKit

open class ControllerViewProvider: SimpleViewProvider {
    public var contentView: UIView {
        return view(at: 0)
    }
    
    private var contentVC: UIViewController?
    
    public init(identifier: String? = nil, controllerType: UIViewController.Type, height: CGFloat, insets: UIEdgeInsets = .zero) {
        contentVC = controllerType.init()
        let content = contentVC!.view!
        super.init(identifier: identifier, views: [content], sizeStrategy: (.fill, .absolute(height)), layout: insets == .zero ? FlowLayout() : FlowLayout().inset(by: insets))
    }
}

And then in my controller to show the CollectionView:

override func viewDidLoad() {
        super.viewDidLoad()
        
        self.provider = ComposedProvider(sections: [
            space(64),
            LabelProvider(text: "Label 1234567", font: .systemFont(ofSize: 20)),
            space(20),
            ButtonProvider(identifier: "HIBUTTON", text: "Button Test", font: .systemFont(ofSize: 24), color: .blue, insets: UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16)),
            space(20),
            ControllerViewProvider(identifier: "Kuangren", controllerType: TableTestVC.self, height: 180.0, insets: .zero),
            space(20)
        ])
    }

tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) can not be trigged when I tap the table row.

And when I have a long long long long press on the table row, it will reach the delegate methods tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath).

It may stupid to use any UIScrollView in CollectionView, but I want to reuse some pods for UICollectionView or UITableView, such as FSPagerView.

Is it not recommended to wrap any UIScrollView into SimpleViewProvider?
Or am I using it in a wrong way?

Thanks.

DidSelectItem

Hi,

is there a way to detect when an item is selected?
Like in collectionView didSelectItemAtIndexPath etc.?

Greetings

Reuse cells

Hello!
When user scroll every cell are creating again. My cells creating from xib, so it too expensive to create every cells again when scroll.
Is there any built-in layout or something else, so my cells will reuse, not create again?
Thanks!

How to implement Infinity scroll ?

Hello guys, this framework seems to be cool. I have a few questions.

  1. In the CollectionKitExample there is an example "ArticleExample" can you guys tell me if its possible to add infinite scrolling? if yes how?
  2. Is this framework production ready?

No Prefetch? Scroll performance issues

Hi . First great work with CollectionKit . I Love it but I have huge performance issues with it. And noticed some strange things.

There are no prefetch and no preload of views(cells) . running simple print() debug I can see that the views are created just as they become visible . That can't be right . That creates huge performance issues when scrolling . I need to be able to draw the view graphics before it enters the visibleframe.

Looking at for example Texture by Facebook https://github.com/texturegroup/texture .. they preload not only one but 2-3 cells/views/nodes before becoming visible in order to get 60 fps scroll

There are no control of how many view I would like to preload / prefetch

I really really like this kit but I can't use it because of this ... and I really would not like to rewrite all of my code ...

Module isn't visible

After building with carthage and linking framework, module still invisible in storyboard.
Am I doing smth. wrong?

Proposal: add WobbleAnimator to the framework

Wobbling animation is extensively used in apps, at least more than the scale animation, although WobbleAnimator can be added by dragging code from the example, it makes sense to add this animator to the framework, since there are only 2 animators available as of yet.

Can't run the project

Can you please explain in steps how to run the project using cocoapods or with direct download ?

1.I download the project zip and open it
2.opened CollectionKit.xcodeproj
3.build "CollectionKit"
4. now what ?

I'm objective-c developer and will be nice to run the samples in simple step.
Thanks.

AutoSize and Sticky headers questions

Hi,
quick questions:

  • are sticky headers (like section headers in UITableView) supported? will it be possible to write a customProvider for them?
  • Is it possible to size the CollectionView based on the children size? Let's say that I want a collectionview as wide as the widest child, will be possible to achieve this result?

Thanks

Proposal: class based SizeSource

The problem

In the current state of CollectionKit, SizeSource is defined as a function/closure that takes (Int, Data, CGSize) and returns CGSize. It is simple to use but it presents some problems.

  1. function/closure definition only include types and doesn't include parameter names.
public typealias SizeSource<Data> = (Int, Data, CGSize) -> CGSize
  • Without comment or previous knowledge, no body knows what these parameter are. We cannot benefit from Swift and OC's named parameter feature.
  1. While writing a SizeSource inside BasicProvider's initializer, it is easy to forget the what are the parameters and what is order of these parameter.

  2. DataSource, ViewSource, and Layout are class based, while sizeSource is not. there is a inconsistency.

  3. Hard for function based SizeSource to store value. We might want a size source that can cache sizes for each data.

  4. Hard to have CollectionKit provide built-in SizeProvider.

  • With function type, built-in SizeSource will be lying around as some function like imageSizeSource.
    We cannot provide parameters to these built-in SizeSource. and they will be hard to find, and hard to document as well.

  • Would love to have something like ImageSizeSource(contentMode: .aspectFit) or AutoLayoutSizeSource(dummyView: MyCell()) included in the repo.

  1. SimpleViewProvider currently implements its own sizing strategy which is pretty bad. It is another system that developer have to learn. It is easy to write but it doesn't have the flexibility of SizeSource. The tuple also has the problem of not displaying the parameter name when it is used.
    We should make SimpleViewProvider support SizeSource but at the same time, provide some built-in SizeSource so that developers don't have to write a closure every time they use it. Best of both world!

Proposal

Therefore, here I propose that we change SizeSource into a abstract base class of some sort.

class SizeSource<Data> {
  // override point for subclass
  func size(index: Int, data: Data, collectionSize: CGSize) -> CGSize {}
}

We will also provide a ClosureSizeSource similar to ClosureViewSource.

We will also implement some default SizeSource.

class ImageSizeSource: SizeSource<UIImage> {
  // ...
}

// provide a dummy cell that use autolayout to calculate size for each cell
class AutoLayoutSizeSource: SizeSource<Data> {
  // ...
}

// cache sizes for each cell based on their identifier
class CacheSizeSource<Data>: SizeSource<Data> {
  // ..
}

SimpleViewSizeSource(widthStrategy: ViewSizeStrategy, heightStrategy: ViewSizeStrategy)

With this change, we can also change SimpleViewProvider to use SizeSource.

Large Navbars

Is the SizeProvider called for every data object in the Data Provider when the Collection View Size is changing? I have around 1300 items in my data and when using the Large Navigationbars the scrolling is seriously stuttering.

I checked what the source of the stuttering might be and I found that the sizeProvider of every cell is called every time when the size of the collectionView changes. That might be the culprit.

See the Example:
21zmub

In the Example HorizontalGalleryViewController won't save scroll position

In the example if you scroll horizontal gallery to left or right and scroll the whole view to down, and back scroll to top again the horizontal gallery back to zero offset.

ArticleExampleViewController, AnimatorExampleViewController and others do the same!

Is there a way for the scroll views (horizontal & vertical) in the HorizontalGalleryViewController to keep their scroll position?

Cannot create BasicProvider!

I just use the example of readme but I've got this error "Cannot convert value of type 'ArrayDataSource' to expected argument type 'DataSource<_>'" on line 17.

class CategoriesViewController: UIViewController {
    let collectionView = CollectionView()
    let dataSource = ArrayDataSource(data: ["1","2", "3", "4"])
    
    let viewSource = ClosureViewSource { (view: RoundedCardWrapperView, data: String, index: Int) in
        view.cardView.titleLabel.text = data
        view.cardView.subtitleLabel.text = nil
        view.cardView.imageView.image = UIImage(named: data)
    }
    let sizeSource  = { (index: Int, data: Int, collectionSize: CGSize) -> CGSize in
        return CGSize(width: collectionSize.width, height: collectionSize.width + 20)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let provider = BasicProvider(
            dataSource: dataSource,
            viewSource: viewSource,
            sizeSource: sizeSource
        )
        
        //lastly assign this provider to the collectionView to display the content
        collectionView.provider = provider
    }
}

How can I make "section" header?

Hello! Thanks for the wonderful framework! I looked at the demo project and read the documentation, but I did not find the mention of "section" headers anywhere. I understand that on providers they replace sections (and do much more), but is it possible to do "section" header out of the box? Thank you.

WaterfallLayout can't set this layout?

I set provider's layout with this code:
provider.layout = WaterfallLayout<UIImage>(columns: 2, spacing: 0.0)
I set columns = 2, then I provide imagesize
`func imageSizeProvider(at: Int, data: UIImage, collectionSize: CGSize) -> CGSize {

if at == 0 {
return CGSize(width: collectionSize.width, height: 150)
}
else if at == 1 {
return CGSize(width: collectionSize.width/2, height: 150)
}
else {
return CGSize(width: collectionSize.width/2, height: 150)
}
}`

I want get this layout display ,

image

I find this waterfalllayout, must demand equal width through set column = 2?

My question is, what's wrong I use, or has a better solution for this?

Thanks.

All providers update after changed datasource in one provider

Hello!
Let's imagine we have ComposedProvider:
collectionView.provider = ComposedProvider(sections: [firstProvider, secondProvider])
The issue is when we've updated dataSource in firstProvider, like:
firstProviderDataSource.data = [newData]
viewUpdater and sizeSource are called for secondProvider too.
So everytime when we update data in some dataSource, all other providers will recalculate their sizeSource..
Is it right behavior?
Thanks!

Xcode 10, Swift 4.2. 'inset(by insets: UIEdgeInsets) -> CGRect'

'In the latest version of iOS toolchain, this utility func is no longer needed, because the same function was added to the standard library. If you try to automatically convert to the latest swift version, then UIEdgeInsetsInsetRect converts to 'inset(by insets:' and this leads to infinite recursive func.

i just quickly create full workaround, using "Non-trivial Podfile in Artsy/Eigen" as a base. If some one needs, just append this code in your Podfile

require 'fileutils'

def edit_pod_file(file, old_code, new_code)
	code = File.read(file)
	if code.include?(old_code)
		FileUtils.chmod("+w", file)
		File.write(file, code.sub(old_code, new_code))
	end
end

post_install do |installer|
	stringToRemove = 'func inset(by insets: UIEdgeInsets) -> CGRect {\n' +
					'  return UIEdgeInsetsInsetRect(self, insets)\n' + '}\n'
	edit_pod_file("Pods/CollectionKit/Sources/Extensions/Util.swift", stringToRemove, '')
end

How can I dynamically control the height of the view on each controller?

  let examples: [(String, UIViewController.Type)] = [
    ("Horizontal Gallery", HorizontalGalleryViewController.self),
    ("Grid", GridViewController.self),
    ("Articles", ArticleExampleViewController.self),
    ("Reload", ReloadDataViewController.self),
    ("Reload Animation", ReloadAnimationViewController.self),
    ("Header", HeaderExampleViewController.self),
    ("Chat", MessagesViewController.self),
    ("Animators", AnimatorExampleViewController.self)
  ]

  override func viewDidLoad() {
    super.viewDidLoad()

    let examplesSection = BasicProvider(
      dataSource: ArrayDataSource(data: examples),
      viewSource: ClosureViewSource(viewUpdater: {
        (view: ExampleView, data: (String, UIViewController.Type), at: Int) in
        view.populate(title: data.0, contentViewControllerType: data.1)
      }),
      sizeSource: { (index, data, size) -> CGSize in
        // I want to control the height of each item here. If the data changes, it will change here by collectionView reloadData.
      },
      layout: FlowLayout(lineSpacing: 30).inset(by: bodyInset)
      )

    provider = ComposedProvider(sections: [
      space(100),
      LabelProvider(text: "CollectionKit", font: .boldSystemFont(ofSize: 38), insets: headerInset),
      LabelProvider(text: "A modern swift framework for building reusable collection view components.", font: .systemFont(ofSize: 20), insets: bodyInset),
      space(30),
      LabelProvider(text: "Examples", font: .boldSystemFont(ofSize: 30), insets: headerInset),
      examplesSection,
      space(30)
    ])
  }

Dynamic height in sizeProvider

I have a view that consists of a label for a title, a label for a subtitle and a textfield. Since I don't know of the lenght of title + subtitle, it is possible for them to have multiple lines.
This is the view:

`
import Foundation
import UIKit

class GenericSingleLineInputView: GenericView<String> {

var viewModel: GenericSingleLineInputViewModel?

private let title: UILabel = {
   let title = UILabel()
    title.font = Config.Font.text
    title.textColor = Config.Color.text
    title.backgroundColor = UIColor.red
    title.numberOfLines = 0
    return title
}()

private let subtitle: UILabel = {
    let title = UILabel()
    title.font = Config.Font.smallText
    title.textColor = Config.Color.text
    title.backgroundColor = UIColor.yellow
    title.numberOfLines = 0
    return title
}()

private let textfield: UITextField = {
   let textfield = UITextField()
    textfield.addTarget(self, action: #selector(inputValueChanged), for: .valueChanged)
    textfield.backgroundColor = UIColor.blue
    return textfield
}()

override func didLoad() {
    addSubview(title)
    addSubview(subtitle)
    addSubview(textfield)
    self.backgroundColor = UIColor.green
    title.snp.makeConstraints { (make) in
        make.top.equalTo(self).offset(12)
        make.left.equalTo(self)
        make.right.equalTo(self)
        make.bottom.equalTo(subtitle.snp.top)
    }
    
    subtitle.snp.makeConstraints { (make) in
        make.left.equalTo(self)
        make.right.equalTo(self)
        make.bottom.equalTo(textfield.snp.top)
    }
    
    textfield.snp.makeConstraints { (make) in
        make.height.equalTo(30)
        make.left.equalTo(self)
        make.right.equalTo(self)
        make.bottom.equalTo(self).offset(-12)
    }
}

@objc func inputValueChanged() {
    if let text = textfield.text {
        viewModel?.valueChanged(text)
    }
}

func setup(with viewModel: GenericSingleLineInputViewModel) {
    self.viewModel = viewModel
    title.text = viewModel.title
    subtitle.text = viewModel.subtitle
    textfield.text = viewModel.value
}

}
`

Now I know to implement the sizeProvider property to set the size. I've tried it that way:

`
let provider1 = CollectionProvider(dataProvider: CreateBudgetAddBudgetDataProvider(), viewProvider: CreateBudgetAddBudgetProvider())

    provider1.sizeProvider = {index, viewModel, size in
        let view = provider1.view(at: index)
        log.debug(view.sizeThatFits(CGSize(width: size.width, height: CGFloat.greatestFiniteMagnitude)))
        return CGSize(width: size.width, height: 100)
    }

    collectionView.provider = CollectionComposer(
        provider1
    )

`

However, this does not work, since
let view = provider1.view(at: index) log.debug(view.sizeThatFits(CGSize(width: size.width, height: CGFloat.greatestFiniteMagnitude)))
will always return zero.

And this are the provider classes:
`
class CreateBudgetAddBudgetProvider: CollectionViewProvider<BudgetViewModel, UIView> {

override func update(view: UIView, data: BudgetViewModel, index: Int) {
    if let view = view as? GenericSingleLineInputView, let viewModel = data as? GenericSingleLineInputViewModel {
        view.setup(with: viewModel)
    }
}

override func view(data: BudgetViewModel, index: Int) -> UIView {
    let view: UIView
    
    if data is GenericSingleLineInputViewModel {
        view = reuseManager.dequeue(GenericSingleLineInputView())
    }
    else {
        view = UIView()
    }
    update(view: view, data: data, index: index)
    
    return view
}

}

class CreateBudgetAddBudgetDataProvider: CollectionDataProvider {
var data: [BudgetViewModel] = []

override init() {
    super.init()
    data.append(GenericSingleLineInputViewModel(title: "Name",valueChanged: { (value) in
        
    }, subtitle: "Hier könne Ihr name stehen"))
}

override var numberOfItems: Int {
    get {
        return data.count
    }
}

override func data(at: Int) -> BudgetViewModel {
    return data[at]
}

}
`

edit: I'm sorry for the code formatting, can't get it to work 😅

Views that have wrong positionning

Hello,

So I'm pretty much new to swift and still learning a few things.
Tried to use the CollectionKit framework because it looked really nice.
I have an issue though, not too sure why, but I suppose I'm doing something wrong?

Trying to have a nice CollectionKit with UIButton so I mostly took back the code from the example and tweaked it to behave the way I want. The problem is that the UIViews are weirdly positioned.

Would any of you have any idea on the why?

Here is my code:

func sizeProvider(at: Int, data: UIButton, collectionSize: CGSize) -> CGSize {
    var size = data.frame.size
    if size.width > collectionSize.width {
        size.height /= size.width / collectionSize.width
        size.width = collectionSize.width
    }
    if size.height > collectionSize.height {
        size.width /= size.height / collectionSize.height
        size.height = collectionSize.height
    }
    return size
}

class GenericCollectionView {
    class func createForButton(frame: CGRect, buttons: [UIButton]) -> CollectionView {
        let collectionView = CollectionView(frame: frame)
        collectionView.contentInset = UIEdgeInsetsMake(10, 10, 10, 10)
        let provider = CollectionProvider(
            data: buttons,
            viewUpdater: { (view: UIView, data: UIButton, index: Int) in
                view.addSubview(data)
        })

        let visibleFrameInsets = UIEdgeInsets(top: 0, left: -100, bottom: 0, right: -100)
        provider.layout = WaterfallLayout<UIButton>(columns: 2).transposed().insetVisibleFrame(by: visibleFrameInsets)
        provider.sizeProvider = sizeProvider
        collectionView.provider = provider
        return collectionView
    }
}

collectionView is screen's height / 2 and placed in the centre.
And here is what it looks like, the views are like off-centred:

screen shot 2018-04-12 at 17 35 22

Oh and on a sidenote the buttons are not clickable.

Remove and add animation doesn't work

Hello!
Don't understand why, but in your example when cell is adding or removing we can see the animation properly, but in my project removing the latest cell, even if I call removeFirst() :

collectionView.provider = BasicProvider(
            dataSource: dataSource,
            viewSource: ClosureViewSource(viewGenerator: { (data, index) -> UILabel in
                let cell = UILabel()
                cell.text = data.name
                cell.backgroundColor = .yellow
                return cell
            }, viewUpdater: { (view: UILabel, data: Match, at: Int) in
                view.text = data.name
            }),
            sizeSource: sizeSource,
            layout:  FlowLayout(lineSpacing: 10,justifyContent: .center),
            animator: WobbleAnimator(),
            tapHandler: { [weak self] context in
                self?.dataSource.data.remove(at: context.index)
            }
        )

Video:
http://dropmefiles.com/KrXuP

Project Not working

I downloaded entire project and copied Examples folder to new project. then I setup pod and run the project but got 11 errors. Tried to do by some quick suggestions but still not working.

How to mix data/object types

In a realistic case where the collection requires many different data/object types, how to implement this ?
For example a settings page may require TextAndSwitchView, TextAndSliderView, LongTextView and so on..

if there was something like...
provider.viewClassForData = { dataObj in
return dataObj.viewClass
}

header layout

let headers: [String?] = [
            nil, nil, "推荐视频", "热门直播", "推荐音频", "热门图文"
        ]

let headerComposedProvider = ComposedHeaderProvider(
            identifier: nil,
            layout: FlowLayout().inset(by: UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)),
            headerViewSource: ClosureViewSource(
                viewUpdater: { (view: UIButton, data, index) in
                    view.backgroundColor = UIColor.white
                    if index < headers.count {
                        if let x = headers[index] {
                            view.setTitle(x, for: .normal)
                            view.setTitleColor(UIColor.darkGray, for: .normal)
                            view.contentHorizontalAlignment = .left
                        }
                     }
            }),
            headerSizeSource: { (index, data, size) -> CGSize in
                if index < headers.count {
                    if let _ = headers[index] {
                        return CGSize(width: size.width, height: 40)
                    }
                 }
                 return CGSize.zero
            },
            sections: sections)
        
        headerComposedProvider.isSticky = false
        self.provider = headerComposedProvider

The layout: FlowLayout().inset(by: UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8)) is for header?

I set this layout, but it affects the sections' layout.

And any way to pass dataSource to header?

BTW: justifyContent, alignItems, alignContent in Layout, any documents?

Thanks.

Drag and drop support

I did not find any mention of "drag and drop" support. Any thoughts or best practices how to implement it with CollectionKit?

Visible frame inset doesn't work as expected

@lkzhao Hello!
I have ComposedProvider with 3 providers(1 provider = 1 cells) with RowLayout have size == view.frame.width. I have only 3 cells and want they be displayed always, so I use this code:

 let visibleFrameInsets = UIEdgeInsets(top: 0, left: -view.frame.width * 5, bottom: 0, right: -view.frame.width * 5)
matchProvider = ComposedProvider(layout: RowLayout().insetVisibleFrame(by: visibleFrameInsets),
                                         sections: [infoProvider])
.....
            matchProvider.sections.append(videoProvider)
            matchProvider.sections.append(webViewProvider)

Those providers don't have their own layout.
But only 2 cells are initialized and Third isn't. Although I multiplied view.frame.width by 5 and visibleFrameInsets is right (-1875 left and right).
What's wrong? Seems it should work. But there is no changes between -view.frame.width and -view.frame.width * 5.

Scrolling on tvOS

Hey, great library!
I know tvOS isn't officially supported (I just made a PR to add it to Carthage and Cocoapods #54), but it would be nice to get it fully working.

At the moment if you make a view focusable, you can scroll through the collection. But if you scroll off screen, the scroll view jumps back to top of the scrollview. This is maybe because the focus engine tries to automatically scroll to the focused view which may be reused and now in a different place?

In addition to this it would be great to have a focusHandler similar to the tapHandler

CollectionView doesn't clear sometimes

Hello!
I've tried to create dummyCells and showing it, for example, first 5 sec and then clear collectionView and show real cells. But when I set new data some cells didn't remove from collectionView even I set empty array to dataSource:

var dummyModels: [Model] = []
        for _ in 0..<20 {
            let model = Model(object: [:])
            dummyModels.append(model)
        }
        dataSource.data = dummyModels

and after sometime call:
dataSource.data = matches
And on the screen I can see 2 empty cells and bellow them real cells:
http://joxi.ru/D2P6pJspD1XQr3
http://joxi.ru/xAe8abupz6aY2y

If I call

dataSource.data.removeAll()

On the screen I can see 2 cells. I've debugged and dataSource.data.count == 0.

Search Bar

Thanks for a good library!
I wanted to ask how best to add Search Bar?

Example out of date...

The examples in the README refer to dataSource.layout which should now be provider.layout I think.

Refresh control

Thank you for a wonderful library!
I was wondering what is the best way to add a refresh control at the top of CollectionView?

3D cascading animator

Is there an example of how to achieve the 3D cascading animator shown in the readme? It looks like exactly what I need!

Scroll lag when updating dataSource

Hello!
First of all thanks for this great lib!
I have a dataSource:

let  dataSource = ArrayDataSource(data: self.presenter.matches) / / [Match]

My data is updating every x seconds and I need to update my collection view with the new data. I'm doing like this:

    func display(_ matches: [Match]) {
        dataSource.data = matches
    }

matches.count is about 100.
So seems it's not good way to do like this, because when display method is calling, scrolling begins to lag.

So how can I update my dataSource then?
Thanks!

Enhance performence with DataProvider

Maybe the class like ArrayDataProvider should use ContiguousArray instead of Array. When
Element type is a class or @objc protocoll, it would be more predictable performance.

Xcode 10, update by Carthage - Task failed with exit code 65:

Carthage:
...
This usually indicates that project itself failed to compile. Please check the xcodebuild log for more details:
...

SpaceProvider.swift:14:10: error: Identifier Name Violation: Enum element name should only contain alphanumeric characters: 'absolute(_:)' (identifier_name)

SimpleViewProvider.swift:16:10: error: Identifier Name Violation: Enum element name should only contain alphanumeric characters: 'absolute(_:)' (identifier_name)

How can I solve this?

DataSource of any type?

let deviceProvider = BasicProvider(
dataSource: ArrayDataSource(data: deviceEntities),
...
)

where deviceEntities is a list of some CoreData entities. When I do this, the compiler said Cannot convert value of type '[CoreDataDevice]' to expected argument type '[_]'.

Does it only support String and Int type?

Build error / Swiftlint

Hi, I've cloned this commit and there're some build errors due to swiftlint.

  1. Sources/Layout/SimpleLayout.swift:12:3: error: Identifier Name Violation: Variable name should only contain alphanumeric characters: '_contentSize' (identifier_name)

  2. /Sources/Addon/SpaceCollectionProvider.swift:20:3: error: Identifier Name Violation: Variable name should only contain alphanumeric characters: '_contentSize' (identifier_name)

HorizontalGalleryViewController Not Reloading

I'm trying to update the HorizontalGalleryViewController in your examples, like ReloadDataViewController.

However, the view is not updating the new image.

I'm using the code below.

//
// HorizontalGalleryViewController.swift
// CollectionKitExample
//
// Created by Luke Zhao on 2016-06-14.
// Copyright © 2016 lkzhao. All rights reserved.
//

import UIKit
import CollectionKit

func imageSizeProvider(at: Int, data: UIImage, collectionSize: CGSize) -> CGSize {
var imageSize = data.size
if imageSize.width > collectionSize.width {
imageSize.height /= imageSize.width/collectionSize.width
imageSize.width = collectionSize.width
}
if imageSize.height > collectionSize.height {
imageSize.width /= imageSize.height/collectionSize.height
imageSize.height = collectionSize.height
}
return imageSize
}

class HorizontalGalleryViewController: CollectionViewController {

let addButton02: UIButton = {
let button = UIButton()
button.setTitle("+", for: .normal)
button.titleLabel?.font = .boldSystemFont(ofSize: 20)
button.backgroundColor = UIColor(hue: 0.6, saturation: 0.68, brightness: 0.98, alpha: 1)
button.layer.shadowColor = UIColor.black.cgColor
button.layer.shadowOffset = CGSize(width: 0, height: -12)
button.layer.shadowRadius = 10
button.layer.shadowOpacity = 0.1
return button
}()

override func viewDidLoad() {
super.viewDidLoad()
addButton02.addTarget(self, action: #selector(add02), for: .touchUpInside)
view.addSubview(addButton02)
collectionView.contentInset = UIEdgeInsetsMake(10, 10, 10, 10)
let provider = CollectionProvider(data: testImages, viewGenerator: { (data, index) -> UIImageView in
let view = UIImageView()
view.layer.cornerRadius = 5
view.clipsToBounds = true
return view
}, viewUpdater: { (view: UIImageView, data: UIImage, at: Int) in
view.image = data
})
provider.layout = WaterfallLayout(columns: 2).transposed()
provider.sizeProvider = imageSizeProvider
provider.presenter = WobblePresenter()
self.provider = provider
}

override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let viewWidth = view.bounds.width
let viewHeight = view.bounds.height
addButton02.frame = CGRect(x: 0, y: viewHeight - 44, width: viewWidth, height: 44)
collectionView.frame = CGRect(x: 0, y: 0, width: viewWidth, height: viewHeight - 44)
}

@objc func add02() {
let test = UIImage(named: "6")!
testImages.append(test)
collectionView.reloadData()
print(testImages)
}

}

ComposedHeaderProvider tapHandler out of range crash

Managed to crash ComposedHeaderProvider tapHandler on line (ComposedHeaderProvider #135) let context = TapContext(view: view as! HeaderView, index: at, section: sections[at]) Index out of range.

Attached example project below. Crash happens if you try tap Title 4 or above.
CollectionCrash.zip

Have I done something wrong or possibly a bug?

Thanks

How could I set a select or tap style for cell or any interactionable style

Thanks for the wonderful framework!
How could I add any selection style?

It seems to have no interaction style for select or tap.
Even when I used a UIButton in my viewsource, it has no state change style interaction.
How could I use it to enable the default button click interaction style?

How could I make a background color or shadow change when I click the cell or any style change when I click a UIControl?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.