New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
How to mix data/object types #38
Comments
To support this, right now you have to implement a custom view provider and make the generic 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. |
Hello lkzhao, thanks for your reply. Your projects are great, I also use your Hero library. 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. |
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 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? |
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. |
I just learned autoclosure, delayed execution, clever. |
In v2.0, there is a 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
) |
Thanks @lkzhao , for both implementing and letting me know. |
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
}
The text was updated successfully, but these errors were encountered: