Comments (7)
In v2.0, there is a ComposedViewSource
that allows you to give a viewSourceSelector
function to determine the actual view source matching a particular type of data. The casting is done automatically for you.
let labelViewSource = ClosureViewSource(viewUpdater: {
(view: UILabel, data: LabelObj, at: Int) in
view.font = data.font
view.text = data.text
})
let imageViewSource = ClosureViewSource(viewUpdater: {
(view: UIImageView, data: ImageObj, at: Int) in
view.image = data.image
})
let dataSource = ArrayDataSource(data: [
LabelObj(text: "This is a text object"),
LabelObj(text: "This is another text object"),
ImageObj(image: UIImage(named: "someImage"))
])
let viewSource = ComposedViewSource(viewSourceSelector: { data in
if data is LabelObj {
return labelViewSource
} else if data is ImageObj {
return imageViewSource
} else {
fatalError("Unsupported data type: \(data)")
}
})
let provider = BasicProvider(
dataSource: dataSource,
viewSource: viewSource
)
from collectionkit.
To support this, right now you have to implement a custom view provider and make the generic ViewType
to be UIView
.
It will look something like:
enum SettingsDataType {
case textAndSwitch(text: String)
case textAndSlider(text: String)
case longText(text: String)
}
class SettingsViewProvider: CollectionViewProvider<SettingsDataType, UIView> {
override func update(view: UIView, data: SettingsDataType, index: Int) {
switch data {
case .textAndSwitch(let text):
(view as? TextAndSwitchView)?.text = text
case .textAndSlider(let text):
(view as? TextAndSliderView)?.text = text
case .longText(let text):
(view as? LongTextView)?.text = text
}
}
override func view(data: SettingsDataType, index: Int) -> UIView {
let view: UIView
switch data {
case .textAndSwitch(let text):
view = reuseManager.dequeue(TextAndSwitchView())
case .textAndSlider(let text):
view = reuseManager.dequeue(TextAndSliderView())
case .longText(let text):
view = reuseManager.dequeue(LongTextView())
}
update(view: view, data: data, index: index)
return view
}
}
provider = CollectionProvider(data: [.textAndSwitch(text: "ABC")], viewProvider: SettingsViewProvider())
This is definitely something that needs some improvement. with this workaround we lost the view type information and cannot benefit from swift's generic.
It would be best if we can utilize swift generics and allow the compiler to guarantee view types. I will think about it more. let me know if you have other ideas.
from collectionkit.
Hello lkzhao, thanks for your reply. Your projects are great, I also use your Hero library.
Although not the cleanest, for now, I will use the following design (for a single column collection)
class BaseObj {
// Object responsible for specifying view
var view: BaseView.Type { return BaseView.self }
var insets = UIEdgeInsets(top: 10, left: 15, bottom: 10, right: 15)
}
class BaseView: UIView {
weak var data: AnyObject?
func commonInit() { }
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func set(_ data: AnyObject?) {
self.data = data
}
// View responsible for specifying height
class func height(data: AnyObject?, width: CGFloat) -> CGFloat {
return 100
}
}
class BaseViewProvider: CollectionViewProvider<BaseObj, BaseView> {
override func update(view: BaseView, data: BaseObj, index: Int) {
view.set(data)
}
override func view(data: BaseObj, index: Int) -> BaseView {
let view = reuseManager.dequeue(data.view.init())
update(view: view, data: data, index: index)
return view
}
}
let BaseSizeProvider: CollectionSizeProvider<BaseObj> = { (index, data, collectionSize) in
let height = data.view.height(data: data, width: collectionSize.width);
return CGSize(width: collectionSize.width, height: height)
}
// MARK : Label
class LabelObj: BaseObj {
override var view: BaseView.Type { return LabelView.self }
var text: String
var font = UIFont.systemFont(ofSize: 16)
init(text: String) { self.text = text }
}
class LabelView: BaseView {
var label = UILabel()
override func commonInit() {
backgroundColor = .clouds
label.numberOfLines = 0
label.textAlignment = .center
addSubview(label)
}
override func layoutSubviews() {
super.layoutSubviews()
if let data = data as? LabelObj {
label.frame = UIEdgeInsetsInsetRect(bounds, data.insets)
}
}
override func set(_ data: AnyObject?) {
super.set(data)
if let data = data as? LabelObj {
label.font = data.font
label.text = data.text
}
}
override class func height(data: AnyObject?, width: CGFloat) -> CGFloat {
if let data = data as? LabelObj {
let horizontalInsets = data.insets.left + data.insets.right
let verticalInsets = data.insets.top + data.insets.bottom
return data.text.height(width: width - horizontalInsets, font: data.font) + verticalInsets
}
return 100
}
}
// ... Create more Obj/View pairs ...
extension String {
func height(width: CGFloat, font : UIFont)->CGFloat {
let paragraph = NSMutableParagraphStyle()
paragraph.lineBreakMode = .byWordWrapping
return self.boundingRect(
with: CGSize(width: width, height: CGFloat.greatestFiniteMagnitude),
options: .usesLineFragmentOrigin,
attributes: [.font: font, .paragraphStyle: paragraph],
context: nil).height
}
}
let dataProvider = ArrayDataProvider<BaseObj>(data: [
LabelObj(text: "This is a text object"),
LabelObj(text: "This is some long text, This is some long text, This is some long text, This is some long text, This is some long text, This is some long text, This is some long text."),
])
let provider = CollectionProvider<BaseObj, BaseView>(
dataProvider: dataProvider,
viewProvider: BaseViewProvider(),
sizeProvider: BaseSizeProvider
)
This design requires casting of data in the View class which is ugly, any ideas to avoid this ?
if let data = data as? LabelObj {
}
Another questions is that the reuseManager.dequeue requires the view to be instantiated, resulting the view to be initialized every time. Isn't it better to pass the Type and let the reuseManager decide when to instantiate the view ? Sorry if I'm missing something.
I'm new to swift so please let me know if you have better ideas.
from collectionkit.
reuseManager.dequeue
uses Swift's autoclosure so that the code is not actually executed if there is a reusable cell. see https://github.com/SoySauceLab/CollectionKit/blob/master/Sources/Other/CollectionReuseViewManager.swift#L35
For your case, It doesn't reuse because the compiler doesn't know what kind of cell you are trying to dequeue and it will probably try to dequeue a base UIView
instead of LabelView
. The reuseManager won't be able to reuse the LabelView
that you have queued before.
But like you said we should allow user to pass in a view type to dequeue a cell.
If you have the time do you want to take a look at adding that extra dequeue method for the reuseManager?
from collectionkit.
The casting is not easily avoidable with the current design. But I'm thinking about giving CollectionProvider the ability to take in multiple ViewProvider and a mapper function that determines which ViewProvider to use for which type of data.
from collectionkit.
I just learned autoclosure, delayed execution, clever.
I have implemented the dequeuing by Type #39
from collectionkit.
Thanks @lkzhao , for both implementing and letting me know.
I will eventually migrate to your design.
from collectionkit.
Related Issues (20)
- Plan for RxSwift Support?
- Is there an oc version?
- Some one make swipe to delete?
- how scroll to item? HOT 1
- To centered items into collection view
- How to create Context Menu for cells?
- How to animate a Collection View from another Collection View? HOT 1
- [Bug] Changing mode cause duplications
- How to keep scroll position ? HOT 2
- Control a section view of ComposedHeaderProvider?
- Support RTL Directions
- Using CollectionView Cell with IBOutlets built using XIB , crashes with nil HOT 1
- build faild with error
- multi-view cast view type error
- How to add background view to BasicProvider?
- Question?
- How to achieve vertical waterfall flow effect HOT 1
- horizontal scrolling? HOT 1
- SPM Support?
- About ReloadData
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from collectionkit.