Giter Site home page Giter Site logo

Comments (7)

lkzhao avatar lkzhao commented on July 24, 2024 1

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.

lkzhao avatar lkzhao commented on July 24, 2024

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.

hiroshihorie avatar hiroshihorie commented on July 24, 2024

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.

lkzhao avatar lkzhao commented on July 24, 2024

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.

lkzhao avatar lkzhao commented on July 24, 2024

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.

hiroshihorie avatar hiroshihorie commented on July 24, 2024

I just learned autoclosure, delayed execution, clever.
I have implemented the dequeuing by Type #39

from collectionkit.

hiroshihorie avatar hiroshihorie commented on July 24, 2024

Thanks @lkzhao , for both implementing and letting me know.
I will eventually migrate to your design.

from collectionkit.

Related Issues (20)

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.