A data-driven UICollectionView framework for building fast and flexible lists.

Overview

Build Status Coverage Status Pods Version Platforms Carthage Compatible


A data-driven UICollectionView framework for building fast and flexible lists.

Main Features
🙅 Never call performBatchUpdates(_:, completion:) or reloadData() again
🏠 Better architecture with reusable cells and components
🔠 Create collections with multiple data types
🔑 Decoupled diffing algorithm
Fully unit tested
🔍 Customize your diffing behavior for your models
📱 Simply UICollectionView at its core
🚀 Extendable API
🐦 Written in Objective-C with full Swift interop support

IGListKit is built and maintained with ❤️ by Instagram engineering. We use the open source version master branch in the Instagram app.

Multilingual translation

Chinese README

Requirements

  • Xcode 9.0+
  • iOS 9.0+
  • tvOS 9.0+
  • macOS 10.11+ (diffing algorithm components only)
  • Interoperability with Swift 3.0+

Installation

CocoaPods

The preferred installation method is with CocoaPods. Add the following to your Podfile:

pod 'IGListKit', '~> 4.0.0'

Carthage

For Carthage, add the following to your Cartfile:

github "Instagram/IGListKit" ~> 4.0.0

Swift Package Manager

For Swift Package Manager:

To integrate using Xcode:

File -> Swift Packages -> Add Package Dependency

Enter package URL: https://github.com/Instagram/IGListKit, and select the latest release.

For advanced usage, see our Installation Guide.

Getting Started

$ git clone https://github.com/Instagram/IGListKit.git
$ cd IGListKit/
$ ./scripts/setup.sh

Documentation

You can find the docs here. Documentation is generated with jazzy and hosted on GitHub-Pages.

To regenerate docs, run ./scripts/build_docs.sh from the root directory in the repo.

Vision

For the long-term goals and "vision" of IGListKit, please read our Vision doc.

Contributing

Please see the CONTRIBUTING file for how to help. At Instagram, we sync the open source version of IGListKit daily, so we're always testing the latest changes. But that requires all changes be thoroughly tested and follow our style guide.

We have a set of starter tasks that are great for beginners to jump in on and start contributing.

License

IGListKit is MIT-licensed.

The files in the /Examples/ directory are licensed under a separate license as specified in each file. Documentation is licensed CC-BY-4.0.

Comments
  • Create a flow layout for IGList

    Create a flow layout for IGList

    Create a flow layout for IGList that does not newline sections #3

    • [x] Minimum inter-item spacing
    • [x] Minimum line spacing
    • [x] Constant item size with constant layout time
    • [x] Update layout on insert/delete/move
    • [x] Unit Test
    CLA Signed 
    opened by zhubofei 95
  • [WIP][RFC] Add Swift-bridge subspec

    [WIP][RFC] Add Swift-bridge subspec

    ⚠️ WIP ⚠️

    (and will be for a while)

    RFC: IGListKit Swift bridge

    Goals

    • Allow Swift structs to be used with IGListKit core mechanics
      • Diffing, IGListAdapter data source and APIs, IGListBindingSectionController
    • Remove need to guard and fatalError() for optional values on IGListSectionController
    • Reduce complexity of IGListAdapterDataSource and IGListBindingSectionControllerDataSource
      • Convert data source methods into a tuple of data and a section-controller generation function
      • Explicit mapping between data and section controller

    Proposal

    Naming

    I'm not sold on naming yet, but I've started exploring ListSwift* as a prefix for all Swift-bridge systems. Thoughts?

    Diffing & structs

    Add three new protocols:

    • ListSwiftIdentifiable to uniquely identify data
      • Return an Int for easier interop w/ Hashable (below)
      • analogous to diffIdentifier()
    • ListSwiftEquatable to compare data values
      • analogous to isEqual(toDiffable object:)
    • ListSwiftDiffable that is just a union of identifiable and equatable
      • Maybe this could be omitted w/ Swift 4 unions, e.g. ListSwiftIdentifiable & ListSwiftEquatable

    Can provide default conformance to Hashable and Equatable via:

    public extension Hashable {
        var identifier: Int {
            return hashValue
        }
    }
    
    public extension Equatable {
        func isEqual(to object: ListSwiftEquatable) -> Bool {
            guard let object = object as? Self else { return false }
            return self == object
        }
    }
    
    • This lets any struct that conforms to Hashable and Equatable automatically satisfy ListSwiftDiffable
    • Doesn't require you to use hash+equal

    An example struct conformance would look like this:

    struct Person: Hashable, Equatable {
        let name: String
        let age: Int
    
        var hashValue: Int {
            return name.hashValue ^ age
        }
    
        static func ==(lhs: CustomValue, rhs: CustomValue) -> Bool {
            return lhs.name == rhs.name
                && lhs.age == rhs.age
        }
    }
    extension Person: ListSwiftDiffable { }
    
    • [ ] Is everyone ok with returning an Int for the identifier instead of NSObjectProtocol?
    • [ ] Are we ok w/ the default hash + equality mapping?
    • [ ] Would anyone prefer we explicitly require conformance to Hashable and Equatable?

    Boxing

    Provide an internal-only box object that bridges ListSwiftDiffable and ListDiffable. Should be as simple as:

    internal final class ListDiffableBox: ListDiffable {
    
        internal let value: ListSwiftDiffable
    
        init(value: ListSwiftDiffable) {
            self.value = value
        }
    
        // MARK: ListDiffable
    
        func diffIdentifier() -> NSObjectProtocol {
            return value.identifier as NSObjectProtocol
        }
    
        func isEqual(toDiffableObject object: ListDiffable?) -> Bool {
            if self === object { return true }
            guard let object = object as? ListDiffableBox else { return false }
            return value.isEqual(to: object.value)
        }
    
    }
    

    Use this to interact w/ all Objective-C systems (ListAdapter, ListSectionController, etc)

    • [ ] Performance could be a concern w/ allocating tons of boxes. Maybe we have a global reuse pool for boxes?

    Data Source

    ListAdapterDataSource has 3 required functions:

    • Return an array of ListDiffable objects
    • Given a ListDiffable object, return a `ListSectionController
    • Return an optional UIView for empty state

    I have 2 big issues w/ this design in Swift:

    • Empty view isn't always needed, boilerplate
    • Mapping an object to a section controller is extremely error-prone b/c we cannot rely on the compiler to catch anything
      • Frequently have to if object is Person { return PersonSectionController() } which explodes in complexity quickly
      • Easy to miss during refactors
      • Can add new data to the objects: [ListDiffable] return value and forget to add the section controller pairing

    I propose we slim this down into a single method that returns a tuple with:

    • A data conforming to ListSwiftDiffable
    • A function that returns a new instance of ListSectionController
      • Function b/c we don't create a section controller when one already exists

    This required function would look like:

    public protocol ListSwiftAdapterDataSource: class {
      func values(adapter: ListSwiftAdapter) -> [ 
        (
          ListSwiftDiffable, 
          (ListSwiftDiffable) -> (ListSectionController)
        )
      ]
    }
    

    And an example implementation would be as simple as:

    func values(adapter: ListSwiftAdapter) -> [(ListSwiftDiffable, (ListSwiftDiffable) -> (ListSectionController))] {
        let values: [Person] = // ... get from somewhere
        return values.map { ($0, { _ in PersonSectionController() }) }
    }
    

    Notice the explicit pairing of Person with PersonSectionController.

    • [ ] Any concerns about returning this type of mapping?
    • [ ] The bridge adapter will need to take care to release the closure once executed. Very easy to retain too much stuff in there which could grow unbounded if we're not draining.

    More TODO...

    Relevant issues

    #35, #146, #871

    CLA Signed 
    opened by rnystrom 70
  • Update .travis.yml and lint.sh, try to fix #1060 and lint errors #trivial

    Update .travis.yml and lint.sh, try to fix #1060 and lint errors #trivial

    project:

    • fix file target membership issues in framework targets and test targets
    • fix private/internal header imports, which shouldn't be <IGListKit/ apparently
    • fix static analyzer errors

    travis:

    • always install latest swiftlint
    • ~~don't cache bundler, attempts to fix #1060~~
    • remove markdown link check

    swiftlint:

    • make script non-failing if any version of swiftlint is installed
    • warning if incorrect version is installed
    • fail if not installed
    • remove scripts/generate_ci_yaml.rb, we can just set the config file path directly
    CLA Signed ready for fb-import 
    opened by jessesquires 68
  • Proper Implementation

    Proper Implementation

    [So I have been attempting to implement IGListKit For a couple days now I believe my implementation is correct. I'm using it currently to load comments when a user clicks on a post. I'm not 100% sure if I have implemented it right so this is less of an issue and more of a verification in the hopes that my code has no issue.

    This is my comments controller

    import UIKit
    import IGListKit
    import Firebase
    
    
     class NewCommentsViewController: UIViewController, UITextFieldDelegate {
    //array of comments which will be loaded by a service function
    var comments = [CommentGrabbed]()
    var messagesRef: DatabaseReference?
    var bottomConstraint: NSLayoutConstraint?
    public var eventKey = ""
    //This creates a lazily-initialized variable for the IGListAdapter. The initializer requires three parameters:
    //1 updater is an object conforming to IGListUpdatingDelegate, which handles row and section updates. IGListAdapterUpdater is a default implementation that is suitable for your usage.
    //2 viewController is a UIViewController that houses the adapter. This view controller is later used for navigating to other view controllers.
    //3 workingRangeSize is the size of the working range, which allows you to prepare content for sections just outside of the visible frame.](url)
    
    lazy var adapter: ListAdapter = {
        return ListAdapter(updater: ListAdapterUpdater(), viewController: self)
    }()
    
    
    // 1 IGListKit uses IGListCollectionView, which is a subclass of UICollectionView, which patches some functionality and prevents others.
    let collectionView: UICollectionView = {
        // 2 This starts with a zero-sized rect since the view isn’t created yet. It uses the UICollectionViewFlowLayout just as the ClassicFeedViewController did.
        let view = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout())
        // 3 The background color is set to white
        view.backgroundColor = UIColor.white
        return view
    }()
    
    //will fetch the comments from the database and append them to an array
    fileprivate func fetchComments(){
        messagesRef = Database.database().reference().child("Comments").child(eventKey)
        print(eventKey)
        print(comments.count)
        messagesRef?.observe(.childAdded, with: { (snapshot) in
            print(snapshot)
            guard let commentDictionary = snapshot.value as? [String: Any] else{
                return
            }
            print(commentDictionary)
            guard let uid = commentDictionary["uid"] as? String else{
                return
            }
            UserService.show(forUID: uid, completion: { (user) in
                if let user = user {
                    var commentFetched = CommentGrabbed(user: user, dictionary: commentDictionary)
                    commentFetched.commentID = snapshot.key
                    let filteredArr = self.comments.filter { (comment) -> Bool in
                        return comment.commentID == commentFetched.commentID
                    }
                    if filteredArr.count == 0 {
                        self.comments.append(commentFetched)
                    }
                    print(self.comments)
                    self.adapter.performUpdates(animated: true)
                }
                self.comments.sort(by: { (comment1, comment2) -> Bool in
                    return comment1.creationDate.compare(comment2.creationDate) == .orderedAscending
                })
                self.comments.forEach({ (comments) in
                })
            })
        }, withCancel: { (error) in
            print("Failed to observe comments")
        })
        
        //first lets fetch comments for current event
    }
    
    lazy var submitButton : UIButton = {
        let submitButton = UIButton(type: .system)
        submitButton.setTitle("Submit", for: .normal)
        submitButton.setTitleColor(.black, for: .normal)
        submitButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 14)
        submitButton.addTarget(self, action: #selector(handleSubmit), for: .touchUpInside)
        submitButton.isEnabled = false
        return submitButton
    }()
    
    //allows you to gain access to the input accessory view that each view controller has for inputting text
    lazy var containerView: UIView = {
        let containerView = UIView()
        containerView.backgroundColor = .white
        containerView.addSubview(self.submitButton)
        self.submitButton.anchor(top: containerView.topAnchor, left: nil, bottom: containerView.bottomAnchor, right: containerView.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 12, width: 50, height: 0)
        
        containerView.addSubview(self.commentTextField)
        self.commentTextField.anchor(top: containerView.topAnchor, left: containerView.leftAnchor, bottom: containerView.bottomAnchor, right: self.submitButton.leftAnchor, paddingTop: 0, paddingLeft: 12, paddingBottom: 0, paddingRight: 180, width: 0, height: 0)
        self.commentTextField.delegate = self
        let lineSeparatorView = UIView()
        lineSeparatorView.backgroundColor = UIColor.rgb(red: 230, green: 230, blue: 230)
        containerView.addSubview(lineSeparatorView)
        lineSeparatorView.anchor(top: containerView.topAnchor, left: containerView.leftAnchor, bottom: nil, right: containerView.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0.5)
        
        return containerView
    }()
    
    lazy var commentTextField: UITextField = {
        let textField = UITextField()
        textField.placeholder = "Add a comment"
        textField.delegate = self
        textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
        return textField
    }()
    
    func textFieldDidChange(_ textField: UITextField) {
        let isCommentValid = commentTextField.text?.characters.count ?? 0 > 0
        if isCommentValid {
            submitButton.isEnabled = true
        }else{
            submitButton.isEnabled = false
        }
    }
    
    func handleSubmit(){
        guard let comment = commentTextField.text, comment.characters.count > 0 else{
            return
        }
        let userText = Comments(content: comment, uid: User.current.uid, profilePic: User.current.profilePic!)
        sendMessage(userText)
        // will remove text after entered
        self.commentTextField.text = nil
    }
    
    func flagButtonTapped (from cell: CommentCell){
        guard let indexPath = collectionView.indexPath(for: cell) else { return }
        
        // 2
        let comment = comments[indexPath.item]
        _ = comment.uid
        
        // 3
        let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
        
        // 4
        if comment.uid != User.current.uid {
            let flagAction = UIAlertAction(title: "Report as Inappropriate", style: .default) { _ in
                ChatService.flag(comment)
                
                let okAlert = UIAlertController(title: nil, message: "The post has been flagged.", preferredStyle: .alert)
                okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
                self.present(okAlert, animated: true)
            }
            let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
            alertController.addAction(cancelAction)
            alertController.addAction(flagAction)
        }else{
            let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
            let deleteAction = UIAlertAction(title: "Delete Comment", style: .default, handler: { _ in
                ChatService.deleteComment(comment, self.eventKey)
                let okAlert = UIAlertController(title: nil, message: "Comment Has Been Deleted", preferredStyle: .alert)
                okAlert.addAction(UIAlertAction(title: "Ok", style: .default))
                self.present(okAlert, animated: true)
                self.adapter.performUpdates(animated: true)
                
            })
            alertController.addAction(cancelAction)
            alertController.addAction(deleteAction)
            
        }
        present(alertController, animated: true, completion: nil)
        
    }
    
    func handleKeyboardNotification(notification: NSNotification){
        if let userinfo = notification.userInfo{
            
            let keyboardFrame = (userinfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).cgRectValue
            self.bottomConstraint?.constant = -(keyboardFrame.height)
            
            let isKeyboardShowing = notification.name == NSNotification.Name.UIKeyboardWillShow
            self.bottomConstraint?.constant = isKeyboardShowing ? -(keyboardFrame.height) : 0
            
            UIView.animate(withDuration: 0, delay: 0, options: UIViewAnimationOptions.curveEaseOut, animations: {
                self.view.layoutIfNeeded()
            }, completion: { (completion) in
                if self.comments.count > 0  && isKeyboardShowing{
                    let indexPath = NSIndexPath(item: self.comments.count-1, section: 0)
                    self.collectionView.scrollToItem(at: indexPath as IndexPath, at: .top, animated: true)
                }
            })
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(collectionView)
        collectionView.addSubview(containerView)
        collectionView.alwaysBounceVertical = true
        view.addConstraintsWithFormatt("H:|[v0]|", views: containerView)
        view.addConstraintsWithFormatt("V:[v0(48)]", views: containerView)
        bottomConstraint = NSLayoutConstraint(item: containerView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0)
        view.addConstraint(bottomConstraint!)
        adapter.collectionView = collectionView
        adapter.dataSource = self
        NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillShow, object: nil)
        
        NotificationCenter.default.addObserver(self, selector: #selector(handleKeyboardNotification), name: NSNotification.Name.UIKeyboardWillHide, object: nil)
        collectionView.register(CommentCell.self, forCellWithReuseIdentifier: "CommentCell")
        fetchComments()
        // Do any additional setup after loading the view.
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        tabBarController?.tabBar.isHidden = true
        submitButton.isUserInteractionEnabled = true
        
    }
    //viewDidLayoutSubviews() is overridden, setting the collectionView frame to match the view bounds.
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        collectionView.frame = view.bounds
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    

    }

     extension NewCommentsViewController: ListAdapterDataSource {
    // 1 objects(for:) returns an array of data objects that should show up in the collection view. loader.entries is provided here as it contains the journal entries.
    func objects(for listAdapter: ListAdapter) -> [ListDiffable] {
        print("comments = \(comments)")
        return comments
    }
    
    // 2 For each data object, listAdapter(_:sectionControllerFor:) must return a new instance of a section controller. For now you’re returning a plain IGListSectionController to appease the compiler — in a moment, you’ll modify this to return a custom journal section controller.
    func listAdapter(_ listAdapter: ListAdapter, sectionControllerFor object: Any) -> ListSectionController {
        //the comment section controller will be placed here but we don't have it yet so this will be a placeholder
        return CommentsSectionController()
    }
    
    // 3 emptyView(for:) returns a view that should be displayed when the list is empty. NASA is in a bit of a time crunch, so they didn’t budget for this feature.
    func emptyView(for listAdapter: ListAdapter) -> UIView? {
        let view = UIView()
        view.backgroundColor = UIColor.white
        return view
    }
    

    }

    extension NewCommentsViewController { func sendMessage(_ message: Comments) { ChatService.sendMessage(message, eventKey: eventKey)

    }
    

    }

    This is my section controller

     import UIKit
     import IGListKit
    import Foundation
    
     class CommentsSectionController: ListSectionController {
     var comment: CommentGrabbed?
         override init() {
             super.init()
             inset = UIEdgeInsets(top: 0, left: 0, bottom: 15, right: 0)
         }
         // MARK: IGListSectionController Overrides
         override func numberOfItems() -> Int {
             return 1
         } 
    
    override func sizeForItem(at index: Int) -> CGSize {
        let frame = CGRect(x: 0, y: 0, width: collectionContext!.containerSize.width, height: 50)
        let dummyCell = CommentCell(frame: frame)
         dummyCell.comment = comment
        dummyCell.layoutIfNeeded()
        let targetSize =  CGSize(width: collectionContext!.containerSize.width, height: 55)
        let estimatedSize = dummyCell.systemLayoutSizeFitting(targetSize)
        let height = max(40+8+8, estimatedSize.height)
        return  CGSize(width: collectionContext!.containerSize.width, height: height)
        
    }
    
    override var minimumLineSpacing: CGFloat {
        get {
            return 0.0
        }
        set {
            self.minimumLineSpacing = 0.0
        }
    }
    
    override func cellForItem(at index: Int) -> UICollectionViewCell {
        guard let cell = collectionContext?.dequeueReusableCell(of: CommentCell.self, for: self, at: index) as? CommentCell else {
            fatalError()
        }
        print(comment)
        
        cell.comment = comment
        return cell
    }
    override func didUpdate(to object: Any) {
        comment = object as! CommentGrabbed
    }
    override func didSelectItem(at index: Int){
        
        
    }
    

    } ](url) Thanks in advance

    ](url)

    question 
    opened by Smiller193 52
  • Added Subspec for Diffing

    Added Subspec for Diffing

    Changes in this pull request

    So the main Podspec file now has two Subspecs, Diffing and for lack of better name Default. Diffing is purely the files within the common directories (used for Diffing, if in the future we support MacOS NSCollectionView then this may need changing). Default depends on Diffing but also adds all the non-common files.

    To use it via CocoaPods, nothing changes. You import IGListKit and you will only have access to the files in the pod you use. (So if you use IGListKit/Diffing in your Podfile, then you will only get access to the diffing files). If you do a manual installation, or I assume via Carthage, then you will need to import the correct header file (either IGListDiffKit.h or IGListKit.h depending on what you want).

    Turns out CocoaPods creates it's own umbrella header (unless you tell it otherwise) meaning our ones are unused by it.

    Just to confirm IGListKit in your Podfile will give you everything, only if you add /Diffing will you "opt-out" of the extra functionality. MacOS users can use which ever one they choose, and will get the same functionality (at this point in time)

    Do I need to update changelog for this? Leaving myself a few TODOs once we've discussed this

    • [x] Create new guide in Guides/ explaining this usage

    • [x] Run pod install in the Examples/ dir

    • Closes #365 (Hopefully)

    • Closes #53

    Pull request checklist

    • [x] All tests pass. Demo project builds and runs.
    • [x] I added tests, an experiment, or detailed why my change isn't tested.
    • [x] I added an entry to the CHANGELOG.md for any breaking changes, enhancements, or bug fixes.
    • [x] I have reviewed the contributing guide
    CLA Signed 
    opened by Sherlouk 40
  • Interactive Reordering

    Interactive Reordering

    I had a desire for interactive reordering in a personal project, so here's a first attempt at adding support in IGListKit.

    I figured I might as well get a WIP PR up for comments before I continue further as there are a few aspects to interactive reordering that don't interplay perfectly with IGListKit.

    As discussed in #291, I went after two prime use cases:

    1. Moving items amongst a section
    2. Rearranging whole sections

    I also "disabled" moving items between sections by having those moves revert, to mimic interactive reordering cancellation as closely as possible.

    You can see both in the Mixed Data example. Grid items can be moved within a section, while users can be moved to reorder whole sections. But trying to move a grid item out of a grid or a user item into a grid will auto-revert. The revert animation isn't as tight as it should be. It may be more desirable to disable the animation - though you lose the visual cue.

    There is a also a new example, ReorderableViewController, that demonstrates 2 in its pure form (likely the most desired use case), where all sections are reorderable single rows.

    Happy to take feedback -- this is my first experience working on IGListKit, so I would expect there to be gaps. (Ex. I haven't used IGListStackedSectionController, and its tests failed as I hadn't implemented reordering delegates for it. Those are simply stubbed out for now.)

    Changes in this pull request

    Issue fixed: #291

    Checklist

    • [x] All tests pass. Demo project builds and runs.
    • [x] I added tests, an experiment, or detailed why my change isn't tested.
    • [x] I added an entry to the CHANGELOG.md for any breaking changes, enhancements, or bug fixes.
    • [x] I have reviewed the contributing guide

    Additional TODO

    • [x] Proper support in IGListStackedSectionController
    enhancement CLA Signed 
    opened by jverdi 39
  • Auto diffing section controller

    Auto diffing section controller

    Started work on the plane to get this moving since #418 is up and ready to land. We'll likely need to spend some time fleshing out the API of this, and I think I'll split it up into a couple different PRs once ready for review. Putting this up now to get early feedback.

    What is this?

    This adds an auto-diffing section controller as outlined in #38. There are several key parts:

    • Subclass a new section controller IGListAutoSectionController (naming wip)
    • Connect a data source
    • Implement the data source methods that do 3 things:
      • Given a top-level object, transform it into an array of diffable view models
      • Given a view model, return a cell
      • Given a view model, return a size for a cell
    • A new protocol for the cell IGListBindable so that we can control when the cell is updated w/ the view model.
      • The most important part of this is that it unlocks moving and reloading a cell, which you can't do w/ UICollectionView

    TODO

    • [x] Unit test reloadObjects:
    • [x] Add didSelectItemAtIndex: support
    • [x] Verify all headers use FB copyright
    • [x] Create PR for IGListAdapterUpdater changes
      • [x] Include fix + tests for #288
    • [ ] ~~Create PR for re-entrant updates~~
      • ~~e.g. calling performBatchAnimated:updates:completion: within an in-progress update block should execute immediately~~ doing this here
    • [x] Create PR for new section controller w/ unit tests
    • [x] Create example
    • [x] Add header docs
    • [x] Optimize test coverage

    IGListAdapter

    I had to make some changes to IGListAdapterUpdater in order for this to work. The biggest challenge is when the section controller receives a new object, it has to split it up into view models and then apply changes.

    However, didUpdateToObject: will be called inside performBatchUpdates:, so we need to diff and fire off the changes on the collection view immediately. A few issues:

    • You couldn't use -[IGListCollectionContext performBatchAnimated:updates:completion:] inside didUpdateToObject: and have it update immediately (async updates)
    • There are other ways that didUpdateToObject: gets called, so we can't just insert/delete/move
    • We're planning to remove non-batched updates entirely in #392

    Diffing top-level models

    One super important piece to getting this right is having top-level (the ones given to IGListAdapter and handed to the SC in didUpdateToObject: always equal itself, even if its values change. I solved this in the unit tests w/ an object that implements diffing like:

    @interface IGTestObject: NSObject <IGListDiffable>
    @property NSString *key;
    @end
    
    @implementation IGTestObject
    - (id)diffIdentifier {
      return self.key;
    }
    
    - (BOOL)isEqualToDiffableObject:(id)object {
      return [self.key isEqual:[object key]];
    }
    @end
    

    If you use a traditional isEqualToDiffableObject: (where you check all props/values) you'll end up reloading the section, avoiding all the nice cell animations.

    I started making a stock object that does this, something like IGListAutoObject, but its so simple that it's kind of annoying. It's essentially a model box that will probably end up more confusing than having to learn how to diff w/ the auto SC.

    Would love thoughts here.

    Selection

    didSelectItemAtIndex: is totally broken. Options are:

    • Add a new method on the data source protocol
      • Gross b/c that's another empty method for a lot of folks
    • Add another delegate protocol you can optionally wire up

    We've discussed splitting off didSelectItemAtIndex: into a selectionDelegate (like the display/scroll/etc delegate) in #184 and elsewhere. That might help, though the view models array is private, so we'd have to expose that or create another delegate API.

    How can I help?

    Since this is super early, please hold on nit picks, formatting, etc. I'll work on that as we go along. But there are some big things I'd love your thoughts on:

    • The name IGListAutoSectionController. Anything better out there? I thought IGListDiffableSectionController but idk
    • I'm using a data source to split the object into view models, return cells, and handle sizes. I started off passing the SC blocks on init that did the transform (sort of like IGListSingleSectionController). I thought the API was kind of gross though, and you have to subclass the SC anyways.
    • IGListBindable is great, but you can't just use the view model to config the cell. For instance, how do you make the SC the cell delegate? That's why I'm leaving the method to still return a cell, but we will call bind before returning it. Is this ok?
    • Sample project now or later?
    • Let me know if you see some glaring Swift interop issues. I haven't tried it yet.
    • Throw all the crazy test-case scenarios you got at me!
    CLA Signed 
    opened by rnystrom 35
  • Add option to maintain scroll position when performing updates.

    Add option to maintain scroll position when performing updates.

    Internally, we discussed adding this to the API, as it's likely a common use case.

    We've built this for IG Direct, where we keep the scroll position while loading previous messages.

    Proposed addition:

    // IGListAdapter
    
    - (void)performUpdatesAnimated:(BOOL)animated 
               fixedScrollPosition:(BOOL)fixedScrollPosition
                        completion:(nullable IGListUpdaterCompletion)completion;
    

    I'm leaning toward adding this method, not replacing:

    - (void)performUpdatesAnimated:(BOOL)animated completion:(nullable IGListUpdaterCompletion)completion;
    

    The existing performUpdatesAnimated: would call the new one, passing NO for fixedScrollPosition:

    Thoughts?

    enhancement 
    opened by jessesquires 35
  • [Swift] reference type and value type conformances to IGListDiffable

    [Swift] reference type and value type conformances to IGListDiffable

    The handy NSObject+IGListDiffable category doesn't really help with Swift objects like String and Int. This seems a little tricky because String is a struct but is convertible to an NSString. Doing that conversion gives us the NSObject<IGListDiffable> conformance, but that's kind of lame. I wonder if there's a way we can get this to work a little better?

    For example, the Swift 3.0 compiler wont allow this:

    let letters: [IGListDiffable] = ["a", "b", "c"]
    

    But you can get this to work a few ways:

    let strings: [NSString]  = ["a", "b", "c"]
    let letters = strings as [IGListDiffable]
    // or
    let letters = ["a" as IGListDiffable, "b" as IGListDiffable, "c" as IGListDiffable]
    // or
    let letters: [IGListDiffable] = ["a" as NSString, "b" as NSString, "c" as NSString]
    

    Also adding an extension to String isn't easy either, since String is a struct and IGListDiffable is an Objective-C protocol...

    Note that this also stunts using IGListDiffable with Swift structs

    enhancement wontfix 
    opened by rnystrom 35
  • Add Swiftlint #trivial

    Add Swiftlint #trivial

    Replaces #642

    Issue ref: #394

    Integrates SwiftLint into DangerBot. You may want to append a ?w=1 to the end of the files-changed url to exclude whitespace-only changes.

    • [ ] All tests pass. Demo project builds and runs.
    • [ ] I added tests, an experiment, or detailed why my change isn't tested.
    • [ ] I added an entry to the CHANGELOG.md for any breaking changes, enhancements, or bug fixes.
    • [ ] I have reviewed the contributing guide

    #trivial

    CLA Signed 
    opened by Iron-Ham 34
  • Add incremental move reporting

    Add incremental move reporting

    Changes in this pull request

    Added incremental move reporting, as mentioned in #337. Diffs with incremental moves are necessary for NSTableView, and can be used to apply changes to collections.

    Pull request checklist

    • [x] All tests pass. Demo project builds and runs.
    • [x] I added tests, an experiment, or detailed why my change isn't tested.
    • [x] I have reviewed the contributing guide
    CLA Signed 
    opened by antons 34
  • Refresh without Animation

    Refresh without Animation

    New issue checklist

    • [ ] I have reviewed the README and documentation
    • [ ] I have searched existing issues and this is not a duplicate
    • [ ] I have attempted to reproduce the issue and include an example project.

    General information

    • IGListKit version:
    • iOS version(s):
    • CocoaPods/Carthage version:
    • Xcode version:
    • Devices/Simulators affected:
    • Reproducible in the demo project? (Yes/No):
    • Related issues:

    Debug information

    # Please include debug logs using the following lldb command:
    po [IGListDebugger dump]
    

    image image image image

    And logs image

    opened by Snail-hash 1
  • Xcode 14 + SPM integration = no SwiftUI previews

    Xcode 14 + SPM integration = no SwiftUI previews

    New issue checklist

    General information

    • IGListKit version: commit cf7f78e28a1c100c38c28858a16af9b1bb46a690
    • iOS version(s): 16.0
    • CocoaPods/Carthage version: SPM
    • Xcode version: 14.0
    • Devices/Simulators affected: SwiftUI previews
    • Reproducible in the demo project? Yes

    If the latest commit on IGListKit main is added to your project via SPM, SwiftUI previews will fail with the following error:

    HumanReadableSwiftError
    
    SettingsError: noExecutablePath(<IDESwiftPackageStaticLibraryProductBuildable:ObjectIdentifier(0x0000600024d25260):'IGListKit'>)
    

    Seems related:

    https://developer.apple.com/forums/thread/707569

    opened by RamblinWreck77 0
  • Scroll to top on iOS 15 devices has glitch or a behaviour different in the 'main' branch respect to '4.0.0' version for expanded items.

    Scroll to top on iOS 15 devices has glitch or a behaviour different in the 'main' branch respect to '4.0.0' version for expanded items.

    New issue checklist

    • [x] I have reviewed the README and documentation
    • [x] I have searched existing issues and this is not a duplicate
    • [x] I have attempted to reproduce the issue and include an example project.

    General information

    • IGListKit version: main branch
    • iOS version(s): iOS 15
    • CocoaPods: 1.11.3
    • Xcode version: 13.4.1
    • Devices/Simulators affected: iOS 15 powered devices/simulators
    • Reproducible in the demo project? (Yes/No): YES
    • Related issues: none

    Debug information

    As a workaround you could disable batchContext.reload(self) on iOS 15, loosing animation updates.

    override func didSelectItem(at index: Int) {
        collectionContext?.performBatch(
          animated: true,
          updates: { [weak self] batchContext in
            guard let self = self else { return }
            self.expanded.toggle()
            if #available(iOS 15, *) {
              // do nothing
            } else {
              batchContext.reload(self)
            }
          },
          completion: { [weak self] succeded in
            guard let self = self else { return }
            self.collectionContext?.scroll(
                to: self,
                at: index,
                scrollPosition: .top,
                animated: true
            )
          })
      }
    

    Marslink_Final.zip Screen Recording 2022-07-05 at 09.05.33.zip

    different_behaviour

    opened by shadowsheep1 0
  • Crash IGListAdapter performUpdatesAnimated:completion:

    Crash IGListAdapter performUpdatesAnimated:completion:

    New issue checklist

    General information

    • IGListKit version: 4.0.0
    • iOS version(s): iOS 14.6.0
    • Xcode version: 13.2.1
    • Devices/Simulators affected: iPhone 12 Pro, iPhone 6s Plus
    • Reproducible in the demo project? (Yes/No): No
    • Related issues: https://github.com/Instagram/IGListKit/issues/1287

    Debug information

    Hello. I'm working with a showcase of collection cells in my project. Firebase crashlytics shows me this crash on real users. This crash itself has not yet been able to reproduce. The crash occurs in the line:

        private func updateCollectionView(animation: Bool = true, completion: ((Bool) -> Void)? = nil) {
            self.adapter.performUpdates(animated: animation, completion: completion)
        }
    
    
    Fatal Exception: NSInternalInconsistencyException
    0  CoreFoundation                 0x9270c __exceptionPreprocess
    1  libobjc.A.dylib                0x14f04 objc_exception_throw
    2  Foundation                     0x124c60 -[NSMutableDictionary(NSMutableDictionary) initWithContentsOfFile:]
    3  IGListKit                      0x7928 (Missing UUID 773d79ad3e88310681855f42b4dbdeb0)
    4  MTSTV Fairplay                 0x1fdcb4c HomeSceneViewController.updateCollectionView(animation:completion:) + 263 (HomeSceneViewController.swift:263)
    5  MTSTV Fairplay                 0x1fe0528 HomeSceneViewController.displayAddedObjects(with:) + 493 (HomeSceneViewController.swift:493)
    6  MTSTV Fairplay                 0x1fe49f0 protocol witness for HomeSceneDisplayLogic.displayAddedObjects(with:) in conformance HomeSceneViewController (<compiler-generated>)
    7  MTSTV Fairplay                 0x2ee58 HomeScenePresenter.didLoadNextPage(with:) + 412 (HomeScenePresenter.swift:412)
    8  MTSTV Fairplay                 0x32e20 protocol witness for HomeScenePresentationLogic.didLoadNextPage(with:) in conformance HomeScenePresenter (<compiler-generated>)
    9  MTSTV Fairplay                 0x1b9ba74 closure #1 in HomeSceneInteractor.configurePagingManager() + 136 (HomeSceneInteractor.swift:136)
    10 MTSTV Fairplay                 0x1824444 PagingManagerImplementation.didReceiveList(with:receivePagingObject:responce:) + 126 (PagingManagerImplementation.swift:126)
    11 MTSTV Fairplay                 0x18240f0 closure #1 in PagingManagerImplementation.loadList(offset:count:) + 102 (PagingManagerImplementation.swift:102)
    12 MTSTV Fairplay                 0xfe26e4 PageReceivingWorker.obtainVideoSectionsList(with:count:queue:completion:) + 62 (PageReceivingWorker.swift:62)
    13 MTSTV Fairplay                 0xfe4614 PageReceivingWorker.loadList(with:count:completion:) + 259 (PageReceivingWorker.swift:259)
    14 MTSTV Fairplay                 0xfe463c protocol witness for PagingListProtocol.loadList(with:count:completion:) in conformance PageReceivingWorker (<compiler-generated>)
    15 MTSTV Fairplay                 0x1823f0c PagingManagerImplementation.loadList(offset:count:) + 95 (PagingManagerImplementation.swift:95)
    16 MTSTV Fairplay                 0x1823a74 PagingManagerImplementation.needLoadList(withParameters:) + 89 (PagingManagerImplementation.swift:89)
    17 MTSTV Fairplay                 0x1823978 PagingManagerImplementation.loadNextPage(withParameters:) + 59 (PagingManagerImplementation.swift:59)
    18 MTSTV Fairplay                 0x18236b4 PagingManagerImplementation.reload(withParameters:) + 55 (PagingManagerImplementation.swift:55)
    19 MTSTV Fairplay                 0x18246a4 protocol witness for PagingManager.reload(withParameters:) in conformance PagingManagerImplementation (<compiler-generated>)
    20 MTSTV Fairplay                 0x1b9e620 closure #1 in HomeSceneInteractor.refreshAllData(with:) + 255 (HomeSceneInteractor.swift:255)
    21 MTSTV Fairplay                 0x14fd8 thunk for @escaping @callee_guaranteed () -> () (<compiler-generated>)
    22 libdispatch.dylib              0x63094 _dispatch_call_block_and_release
    23 libdispatch.dylib              0x64094 _dispatch_client_callout
    24 libdispatch.dylib              0x13cb8 _dispatch_root_queue_drain
    25 libdispatch.dylib              0x14398 _dispatch_worker_thread2
    26 libsystem_pthread.dylib        0x1dd4 _pthread_wqthread
    27 libsystem_pthread.dylib        0x193c start_wqthread
    
    opened by IgorNikiforovV 0
  • minimumInteritemSpacing does not work with ListSectionController

    minimumInteritemSpacing does not work with ListSectionController

    I create horizontal collection view inside the vertical collection view by using ListSectionController. When I set the value to minimumInteritemSpacing, it does not affect, but it works when I set the inset = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: cellSpacing). What's wrong with it? And selecting the item, the callback below always return index = 0 func didSelectItem(at index: Int) BTW: Anyone knows how to set minimumLineSpacing for ListSingleSectionController?

    opened by congpt-xamarindev 0
Releases(4.0.0)
  • 4.0.0(Nov 21, 2019)

    4.0.0

    4.0.0 is here! 🎉🎊🥳

    This version has a number of improvements we've been working on for the last year. We're excited for everyone get their hands on the new improvements and features. Thanks so much to all who contributed code, submitted issues, and contributed to discussions around this release!

    Breaking Changes

    • Added Swift annotation name to IGListAdapterDelegate which removes IG prefix. The new name for Swift clients is ListAdapterDelegate. Andrea Antonioni (#1116)

    • Remove support for iOS 8 Ian Perry (#1381)

    • IGListKit has been split into IGListKit and IGListDiffKit for Xcode and Carthage builds. Cocoapods continues to use an all-inclusive IGListKit podspec. Nate Stedman (#1377)

    • Remove coalescanceTime from IGListAdapterUpdate, since it increase crash rate. Zhisheng Huang (2f76e8c)

    • All IGListBindingSectionControllerSelectionDelegate methods are now required. Bofei Zhu (#1186)

    • Renamed [IGListAdapterUpdatingDelegate listAdapterUpdater:willPerformBatchUpdatesWithCollectionView:] to [IGListAdapterUpdatingDelegate listAdapterUpdater:willPerformBatchUpdatesWithCollectionView:fromObjects:toObjects:listIndexSetResult:] to include more supporting info on updated objects. Jeremy Cohen (b200dda)

    • Renamed [IGListAdapterUpdatingDelegatelistAdapterUpdater:collectionView:willCrashWithException:fromObjects:toObjects:updates:] to [ IGListAdapterUpdatingDelegatelistAdapterUpdater:collectionView:willCrashWithException:fromObjects:toObjects:diffResult:updates:] to include diff result info. Zhisheng Huang (039e77e)

    • Remove IGListStackedSectionController. Hanton Yang (#1355)

    Enhancements

    • Added IGListCollectionScrollingTraits for exposing UICollectionView scrolling traits to section controllers via IGListCollectionContext. Adam Stern (b4c8ea1)

    • IGListBindingSectionController no longer asserts when reloading the entire section. A warning message is now logged if the entire section is going to be reloaded. Jeff Bailey (#1213)

    • Added preferItemReloadsForSectionReloads in IGListAdapterUpdater so that the item updates are invoked with the proper collectionView animation, instead of using the delete+insert section operation when the number of items is unchanged. Zhisheng Huang (f699ea0)

    • Created IGListAdapterPerformanceDelegate for IGListAdapter to be able to measure how long some operations take across all section controllers. For example, how long it takes to dequeue a cell. Maxime Ollivier (4662454)

    • Update CocoaPods integration to use the CocoaPods specs CDN Koen Punt (#1386)

    • Remove useless system version code Kinarobin (#1386)

    Fixes

    • Fixed bug with layouts inconsistency in updateAnimated:completion of IGListBindingSectionController. Qinghua Hong (#1285)

    • Fixed bug with -[IGListAdapter scrollToObject:supplementaryKinds:scrollDirection:scrollPosition:animated:] where the content inset(bottom/right) of the collection view was incorrectly being applied to the final offset and was inconsistent with the content inset(top/left) of the collection view being applied. Qinghua Hong (#1284)

    • Fixed crash when the data source is nil before calling -[IGListAdapterUpdater performUpdateWithCollectionViewBlock:fromObjects:toObjectsBlock:animated:objectTransitionBlock:completion:]. Zhisheng Huang (6cdd112)

    • Experimental fix to get the UICollectionView for batch updating immediately before applying the update. Ryan Nystrom (583efb9)

    • Fixed bug with IGListDiff.mm where arrays of NSIndexPath, instead of NSIndexPath, were incorrectly set as objects for the IndexPathMaps. Bofei Zhu (#1205)

    • [IGListAdapterUpdater performBatchUpdatesWithCollectionViewBlock:] and [IGListAdapterUpdater performReloadDataWithCollectionViewBlock:] clean state and run completion blocks if their UICollectionView is nil. Brandon Darin (290d592)

    • Ensuring view models with duplicate diff identifiers are removed when view models are first requested by IGListBindingSectionController Adam Stern (a1ee4c1)

    • Fixed [IGListAdapterUpdater reloadItemInCollectionView:fromIndexPath:toIndexPath:] does not call delegate when not inside a batch update. Bofei Zhu (#1211)

    • Log instead of assert for duplicate diff identifiers to make code testable. Adam Stern (bee2178)

    • Removed nibName argument from IGListReusableViewIdentifier. Trung Duc (#1223)

    • Fixed crash when using -[IGListCollectionContext dequeueReusableCellOfClass:withReuseIdentifier:forSectionController:atIndex:] Jeremy Lawrence (3b19cfb)

    • Added missing method override to IGListBindingSectionController that updates the internal viewModels array after moving a cell. Dennis Müller (#1262)

    • Fixed logic flaw in [IGListCollectionViewLayout shouldInvalidateLayoutForBoundsChange:]. Allen Hsu (#1236)

    • Fixed crash when calling [UICollectionView layoutAttributesForSupplementaryElementOfKind...] with IGListCollectionViewLayout and the section controller doesn't actually return a supplementary view Maxime Ollivier (cddb297)

    • Added IGListExperimentAvoidLayoutOnScrollToObject to avoid creating off-screen cells when calling [IGListAdapter scrollToObject ...]. Maxime Ollivier (6faddd9)

    • Added IGListExperimentFixIndexPathImbalance to test fixing a crash when inserting and deleting the same NSIndexPath multiple times. Maxime Ollivier (7824698)

    Source code(tar.gz)
    Source code(zip)
  • 3.4.0(May 3, 2018)

  • 3.3.0(Apr 23, 2018)

    3.3.0

    Enhancements

    • Add support for UICollectionView's interactive reordering in iOS 9+. Updates include -[IGListSectionController canMoveItemAtIndex:] to enable the behavior, -[IGListSectionController moveObjectFromIndex:toIndex:] called when items within a section controller were moved through reordering, -[IGListAdapterDataSource listAdapter:moveObject:from:to] called when section controllers themselves were reordered (only possible when all section controllers contain exactly 1 object), and -[IGListUpdatingDelegate moveSectionInCollectionView:fromIndex:toIndex] to enable custom updaters to conform to the reordering behavior. The update also includes two new examples ReorderableSectionController and ReorderableStackedViewController to demonstrate how to enable interactive reordering in your client app. Jared Verdi (#976)

    • 5x improvement to diffing performance when result is only inserts or deletes. Ryan Nystrom (afd2d29)

    • Can always show sticky header although section data is empty. Marcus Wu (#1129)

    • Added -[IGListCollectionContext dequeueReusableCellOfClass:withReuseIdentifier:forSectionController:atIndex:] to allow for registering cells of the same class with different reuse identifiers. Jeremy Lawrence (f47753e)

    Fixes

    • Fixed Xcode 9.3 build errors. Sho Ikeda (#1143)

    • Copy objects when retrieving from datasource to prevent modification of models in binding section controller. Kashish Goel (#1109)

    • Fixed footer is sticky when stickyHeader is true aelam (#1094)

    • Updated IGListCollectionViewLayout to rely on layoutAttributesClass instead of vanilla UICollectionViewLayoutAttributes Cole Potrocky #1135

    • -[IGListSectionController didSelectItemAtIndex:] is now called when a scrollViewDelegate or collectionViewDelegate is set. Ryan Nystrom (#1108)

    • Fixed binding section controllers failing to update their cells when the section controller's section changes. Chrisna Aing (#1144)

    Source code(tar.gz)
    Source code(zip)
  • 3.2.0(Feb 7, 2018)

    Enhancements

    • Added -[IGListSectionController didHighlightItemAtIndex:] and -[IGListSectionController didUnhighlightItemAtIndex:] APIs to support UICollectionView cell highlighting. Kevin Delannoy (#933)

    • Added -didDeselectSectionController:withObject: to IGListSingleSectionControllerDelegate Darren Clark (#954)

    • Added a new listener API to be notified when IGListAdapter finishes updating. Add listeners via -[IGListAdapter addUpdateListener:] with objects conforming to the new IGListAdapterUpdateListener protocol. Ryan Nystrom (5cf01cc)

    • Updated project settings for iOS 11. Ryan Nystrom (#942)

    • Added support UICollectionElementKindSectionFooter for IGListCollectionViewLayout. Igor Vasilenko (#1017)

    • Added experiment to make -[IGListAdapter visibleSectionControllers:] a bit faster. Maxime Ollivier (82a2a2e)

    • Added support -[UIScrollView adjustedContentInset] for iOS 11. Guoyin Li (#1020)

    • Added new transitionDelegate API to give IGListSectionControllers control to customize initial and final UICollectionViewLayoutAttributes. Includes automatic integration with IGListCollectionViewLayout. Sue Suhan Ma (26924ec)

    • Reordered position of intercepted selector in IGListAdapterProxy's isInterceptedSelector method to reduce overall consumption of compare. zhongwuzw (#1055)

    • Made IGListTransitionDelegate inherited from NSObject. Igor Vasilenko (#1075)

    Fixes

    • Duplicate objects for initial data source setup filtered out. Mikhail Vashlyaev (#993

    • Weakly reference the UICollectionView in coalescence so that it can be released if the rest of system is destroyed. Ryan Nystrom (d322c2e)

    • Fix bug with -[IGListAdapter scrollToObject:supplementaryKinds:scrollDirection:scrollPosition:animated:] where the content inset of the collection view was incorrectly being applied to the final offset. Ryan Nystrom (b2860c3)

    • Avoid crash when invalidating the layout while inside `-[UICollectionView performBatchUpdates:completion:]. Ryan Nystrom (d9a89c9)

    • Duplicate view models in IGListBindingSectionController gets filtered out. Weyert de Boer (#916)

    • Check object type on lookup to prevent crossing types if different objects collide with their identifiers. Ryan Nystrom (296baf5)

    Source code(tar.gz)
    Source code(zip)
  • 3.1.1(Aug 31, 2017)

  • 3.1.0(Aug 24, 2017)

    This release closes the 3.1.0 milestone.

    Enhancements

    • Added debug descriptions for 'IGListBindingSectionController' when printing to lldb via po [IGListDebugger dump]. Candance Smith (#856)

    Fixes

    • Prevent a crash when update queued immediately after item batch update. Ryan Nystrom (3dc6060)

    • Return correct -[IGListAdapter visibleSectionControllers] when section has no items, but has supplementary views. Mani Ghasemlou (#643)

    • Call [CATransaction commit] before calling completion block in IGListAdapterUpdater to prevent animation issues. Maxime Ollivier (6f946b2)

    • Fix scrollToObject:supplementaryKinds:... not scrolling when section is empty but does have supplymentary views. Gulam Moledina (#808)

    • Better support for non-top positions in scrollToObject: API. Gulam Moledina (#861)

    Enhancements

    • Added -[IGListSectionController didDeselectItemAtIndex:] API to support default UICollectionView cell deselection. Ryan Nystrom (6540f96)

    • Added -[IGListCollectionContext selectItemAtIndex:] Select an item through IGListCollectionContext like -[IGListCollectionContext deselectItemAtIndex:]. Marvin Nazari (#874)

    • Added horizontal scrolling support to IGListCollectionViewLayout. Peter Edmonston (#857)

    • Added support for scrollViewDidEndDecelerating to IGListAdapter. Phil Larson (#899)

    • Automatically disable [UICollectionView isPrefetchingEnabled] when setting a collection view on an adapter. Ryan Nystrom (#889)

    Source code(tar.gz)
    Source code(zip)
  • 3.0.0(May 12, 2017)

    This release closes the 3.0.0 milestone.

    Breaking Changes

    • Added Swift annotation names which remove IG prefixes from class names, C functions, and other APIs. Note, this only affects Swift clients. Robert Payne (#593)

    Example:

    // OLD
    class MySectionController : IGListSectionController { ... }
    
    // NEW
    class MySectionController : ListSectionController { ... }
    
    // OLD
    IGListDiff([], [], .equality)
    
    // NEW
    ListDiff(oldArray: [], newArray: [], .equality)
    
    
    • Updated didSelect delegate call in IGListSingleSectionControllerDelegate to include object. Sherlouk (#397)
    // OLD
    - (void)didSelectSingleSectionController:(IGListSingleSectionController *)sectionController;
    
    // NEW
    - (void)didSelectSectionController:(IGListSingleSectionController *)sectionController
                            withObject:(id)object;
    
    • IGListUpdatingDelegate now conforms to NSObject, bringing it in line with other framework protocols. Adlai Holler (#435)

    • Changed hasChanges methods in IGListIndexPathResult and IGListIndexSetResult to read-only properties. Bofei Zhu (#453)

    • Replaced IGListGridCollectionViewLayout with IGListCollectionViewLayout. Ryan Nystrom (#482, #450)

    • Renamed IGListAdapterUpdaterDelegate method to listAdapterUpdater:didPerformBatchUpdates:collectionView:. Vincent Peng (#491)

    • Moved section controller mutations to IGListBatchContext, provided as a parameter when calling -performBatchAnimated:updates:completion on a section controller's collectionContext. All updates (insert, delete, reload item/section controller) must now be done inside a batch update block. Ryan Nystrom (a15ea08)

    // OLD
    [self.collectionContext performBatchAnimated:YES updates:^{
      self.expanded = YES;
      [self.collectionContext insertInSectionController:self atIndexes:[NSIndexSet indexSetWithIndex:1]];
    } completion:nil];
    
    // NEW
    [self.collectionContext performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
      self.expanded = YES;
      [batchContext insertInSectionController:self atIndexes:[NSIndexSet indexSetWithIndex:1]];
    } completion:nil];
    
    // OLD
    [self.collectionContext reloadSectionController:self];
    
    // NEW
    [self.collectionContext performBatchAnimated:YES updates:^(id<IGListBatchContext> batchContext) {
      [batchContext reloadSectionController:self];
    } completion:nil];
    
    • -[IGListCollectionContext containerSize] no longer accounts for the content inset of the collection view when returning a size. If you require that behavior, you can now use -[IGListCollectionContext insetContainerSize]. Ryan Nystrom (623ff2a)

    • IGListCollectionView has been completely removed in favor of using plain old UICollectionView. See discussion at #409 for details. Jesse Squires (2284ce3)

    • IGListBatchUpdateData replaced its NSSet properties with NSArray instead. Ryan Nystrom (#616)

    • IGListUpdatingDelegate now requires method -reloadItemInCollectionView:fromIndexPath:toIndexPath: to handle reloading cells between index paths. Ryan Nystrom (#657)

    • -[IGListCollectionContext sectionForSectionController:] has been removed and replaced with the NSInteger sectionIndex property on IGListSectionController. Andrew Monshizadeh #671

    Enhancements

    • Added an initializer on IGListAdapter that does not take a workingRangeSize and defaults it to 0. BasThomas (#686)

    • Added -[IGListAdapter visibleCellsForObject:] API. Sherlouk (#442)

    • Added -[IGListAdapter sectionControllerForSection:] API. Adlai-Holler (#477)

    • You can now manually move items (cells) within a section controller, ex: [self.collectionContext moveInSectionController:self fromIndex:0 toIndex:1]. Ryan Nystrom (#418)

    • Invalidate the layout of a section controller and control the transition with UIView animation APIs. Ryan Nystrom (#499)

    • Added -[IGListAdapter visibleIndexPathsForSectionController:] API. Malecks (#465)

    • Added IGListBindingSectionController which automatically binds view models to cells and animates updates at the cell level. Ryan Nystrom (#494)

    • Added IGListGenericSectionController to take advantage of Objective-C (and Swift) generics and automatically store strongly-typed references to the object powering your section controller. Ryan Nystrom (301f147)

    • Added a debug option for IGListKit that you can print to lldb via po [IGListDebugger dump]. Ryan Nystrom (#617)

    Fixes

    • Gracefully handle a nil section controller returned by an IGListAdapterDataSource. Ryan Nystrom (#488)

    • Fix bug where emptyView's hidden status is not updated after the number of items is changed with insertInSectionController:atIndexes: or related methods. Peter Edmonston (#395)

    • Fix bug where IGListStackedSectionController's children need to know numberOrItems before didUpdate is called. (#348)

    • Fix bug where -[UICollectionViewCell ig_setStackedSectionControllerIndex:] should use OBJC_ASSOCIATION_COPY_NONATOMIC for NSNumber. PhilCai (#424)

    • Fix potential bug with suppressing animations (by passing NO) during -[IGListAdapter performUpdatesAnimated: completion:] where user would see UI glitches/flashing. Jesse Squires (019c990)

    • Fix bug where scroll position would be incorrect in call to -[IGListAdapter scrollToObject:supplementaryKinds:scrollDirection:scrollPosition:animated: with scrollDirection/scrollPosition of UICollectionViewScrollDirectionVertical/UICollectionViewScrollPositionCenteredVertically or UICollectionViewScrollDirectionHorizontal/UICollectionViewScrollPositionCenteredHorizontally and with a collection view with nonzero contentInset. David Yamnitsky (5cc0fcd)

    • Fix a crash when reusing collection views between embedded IGListAdapters. Ryan Nystrom (#517)

    • Only collect batch updates when explicitly inside the batch update block, execute them otherwise. Fixes dropped updates. Ryan Nystrom (#494)

    • Remove objects that return nil diff identifiers before updating. Ryan Nystrom (af984ca)

    • Fix a potential crash when a section is moved and deleted at the same time. Ryan Nystrom (#577)

    • Prevent section controllers and supplementary sources from returning negative sizes that crash UICollectionViewFlowLayout. Ryan Nystrom (#583)

    • Add nullability annotations to a few more headers. Adlai Holler (#626)

    • Fix a crash when inserting or deleting from the same index within the same batch-update application. Ryan Nystrom (#616)

    • IGListSectionType protocol was removed and its methods were absorted into the IGListSectionController base class with default implementations. Ryan Nystrom (3102852)

    • When setting the collection view on IGListAdapter, its layout is now properly invalidated. Jesse Squires (#677)

    • Fixes a bug when reusing UICollectionViews with multiple IGListAdapters in an embedded environment that would accidentally nil the collectionView property of another adapter. Ryan Nystrom (#721)

    • Fixes a bug where maintaining a reference to a section controller but not the list adapter in an async block could lead to calling -[IGListAdapter sectionForSectionController:] (or checking -[IGListSectionController sectionIndex]) and receiving an incorrect value. With the adapter check the value would be 0 because the adapter was nil and for the section controller property the value would be the last set index value. Andrew Monshizadeh (#709)

    Source code(tar.gz)
    Source code(zip)
  • 2.1.0(Jan 4, 2017)

    This release closes the 2.1.0 milestone.

    Enhancements

    Fixes

    • Avoid UICollectionView crashes when queueing a reload and insert/delete on the same item as well as reloading an item in a section that is animating. Ryan Nystrom (#325)
    • Prevent adapter data source from deallocating after queueing an update. Ryan Nystrom (4cc91a2)
    • Fix out-of-bounds bug when child section controllers in a stack remove cells. Ryan Nystrom (#358)
    • Fix a grid layout bug when item has full-width and iter-item spacing is not zero. Bofei Zhu (#361)
    Source code(tar.gz)
    Source code(zip)
  • 2.0.0(Dec 9, 2016)

    This release closes the 2.0.0 milestone. We've increased test coverage to 97%. Thanks to the 27 contributors who helped with this release!

    You can find a migration guide here to assist with migrating between 1.0 and 2.0.

    Breaking Changes

    • Diff result method on IGListIndexPathResult changed. -resultWithUpdatedMovesAsDeleteInserts was removed and replaced with -resultForBatchUpdates (b5aa5e3)
    // OLD
    - (IGListIndexPathResult *)resultWithUpdatedMovesAsDeleteInserts;
    
    // NEW
    - (IGListIndexPathResult *)resultForBatchUpdates;
    
    • IGListDiffable equality method changed from isEqual: to isEqualToDiffableObject: (ab890fc)
    • The default NSObject<IGListDiffable> category was removed and replaced with NSString<IGListDiffable> and NSNumber<IGListDiffable> categories. All other models will need to conform to IGListDiffable. (3947600)
    • Added support for specifying an end position when scrolling. Bofei Zhu (#196). The IGListAdapter scrolling method changed:
    // OLD
    - (void)scrollToObject:(id)object
        supplementaryKinds:(nullable NSArray<NSString *> *)supplementaryKinds
           scrollDirection:(UICollectionViewScrollDirection)scrollDirection
                  animated:(BOOL)animated;
    
    // NEW
    - (void)scrollToObject:(id)object
        supplementaryKinds:(nullable NSArray<NSString *> *)supplementaryKinds
           scrollDirection:(UICollectionViewScrollDirection)scrollDirection
            scrollPosition:(UICollectionViewScrollPosition)scrollPosition
                  animated:(BOOL)animated;
    

    Enhancements

    • Added support for supplementaryViews created from nibs. Rawlinxx (#90)
    • Added support for cells created from nibs. Sven Bacia (#56)
    • Added an additional initializer for IGListSingleSectionController to be able to support single sections created from nibs. An example can be found here. (#56)
    - (instancetype)initWithNibName:(NSString *)nibName
                             bundle:(nullable NSBundle *)bundle
                     configureBlock:(IGListSingleSectionCellConfigureBlock)configureBlock
                          sizeBlock:(IGListSingleSectionCellSizeBlock)sizeBlock;
    
    • Added -isFirstSection and -isLastSection APIs to IGListSectionController (316fbe2)
    • Added support for cells and supplementaryViews created from storyboard. There's a new required method on the IGListCollectionContext protocol to do this. Bofei Zhu (#92)
    // IGListCollectionContext
    - (__kindof UICollectionViewCell *)dequeueReusableCellFromStoryboardWithIdentifier:(NSString *)identifier
                                                                  forSectionController:(IGListSectionController<IGListSectionType> *)sectionController
                                                                               atIndex:(NSInteger)index;
    
    // IGListCollectionContext
    - (void)scrollToSectionController:(IGListSectionController<IGListSectionType> *)sectionController
                              atIndex:(NSInteger)index
                       scrollPosition:(UICollectionViewScrollPosition)scrollPosition
                             animated:(BOOL)animated;
    

    Fixes

    • Fixed -[IGListAdapter reloadDataWithCompletion:] not returning early when collectionView or dataSource is nil and completion is nil. Ben Asher (#51)
    • Prevent UICollectionView bug when accessing a cell during working range updates. Ryan Nystrom (#216)
    • Skip reloading for objects that are not found when calling -[IGListAdapter reloadObjects:]. Ryan Nystrom (ca15e29)
    • Fixes a crash when a reload is queued for an object that is deleted in the same runloop turn. Ryan Nystrom (7c3d499)
    • Fixed a bug where IGListStackSectionController would only set its supplementary source once. Ryan Nystrom (#286)
    • Fixed a bug where IGListStackSectionController passed the wrong section controller for will-drag scroll events. Ryan Nystrom (#286)
    • Fixed a crash when deselecting a cell through a child section controller in an IGListStackSectionController. Ryan Nystrom (#295)

    Documentation

    Source code(tar.gz)
    Source code(zip)
  • 1.0.0(Oct 12, 2016)

    Our First production release 🎉

    Take a look at the documentation, add IGListKit to your Podfile, or take a pass at some of our starter tasks.

    We're launching at version 1.0 because of how widely used the framework is here at Instagram, but looking forward to enhancing and extending the framework even further. 🚀

    Source code(tar.gz)
    Source code(zip)
💾 🔜📱 Type-safe data-driven CollectionView, TableView Framework. (We can also use ASCollectionNode)

⚠️ The latest updates is this PR. It changes the difference algorithm to DifferenceKit. DataSources ?? ?? ?? Type-safe data-driven List-UI Framework.

Muukii 563 Dec 16, 2022
A generic small reusable components for data source implementation for UITableView/UICollectionView in Swift.

GenericDataSource A generic small reusable components for data source implementation for UITableView/UICollectionView written in Swift. Features Basic

null 132 Sep 8, 2021
Conv smart represent UICollectionView data structure more than UIKit.

Conv Conv smart represent UICollectionView data structure more than UIKit. Easy definition for UICollectionView DataSource and Delegate methods. And C

bannzai 157 Nov 25, 2022
Conv smart represent UICollectionView data structure more than UIKit.

Conv Conv smart represent UICollectionView data structure more than UIKit. Easy definition for UICollectionView DataSource and Delegate methods. And C

bannzai 155 May 12, 2022
An iOS drop-in UITableView, UICollectionView and UIScrollView superclass category for showing a customizable floating button on top of it.

MEVFloatingButton An iOS drop-in UITableView, UICollectionView, UIScrollView superclass category for showing a customizable floating button on top of

Manuel Escrig 298 Jul 17, 2022
Automates prefetching of content in UITableView and UICollectionView

Automates preheating (prefetching) of content in UITableView and UICollectionView. Deprecated on iOS 10. This library is similar to UITableViewDataSou

Alexander Grebenyuk 633 Sep 16, 2022
Netflix and App Store like UITableView with UICollectionView, written in pure Swift 4.2

GLTableCollectionView Branch Status master develop What it is GLTableCollectionView is a ready to use UITableViewController with a UICollectionView fo

Giulio 708 Nov 17, 2022
Incremental update tool to UITableView and UICollectionView

EditDistance is one of the incremental update tool for UITableView and UICollectionView. The followings show how this library update UI. They generate

Kazuhiro Hayashi 90 Jun 9, 2022
Collapse and expand UICollectionView sections with one method call.

This library provides a custom UICollectionView that allows to expand and collapse sections. Provides a simple API to manage collection view appearanc

Touchlane 172 Dec 26, 2022
Protocol-oriented UICollectionView management, powered by generics and associated types.

DTCollectionViewManager Features Powerful mapping system between data models and cells, headers and footers Automatic datasource and interface synchro

Denys Telezhkin 308 Jan 6, 2023
Cellmodel-driven collectionview manager

Sapporo [] (https://github.com/Carthage/Carthage) cellmodel-driven collectionview manager Features Easy to manage your sections and cells (reset/inser

Le Van Nghia 245 Sep 20, 2022
UICollectionView layout for presenting of the overlapping cells.

StickyCollectionView UICollectionView layout for presenting of the overlapping cells. Objective-C version here Checkout demo Overview Installation Man

Bogdan Matveev 325 Oct 11, 2022
Reimagining UICollectionView

CollectionKit Reimagining UICollectionView A modern Swift framework for building composable data-driven collection view. Migration Guide v2.0 Features

SoySauceLab 4.3k Dec 27, 2022
ZHTCView - UITableview & UICollectionView

ZHTCView 这是一个使用Block替换代理的UITableview & UICollectionView。 使用方法如下: - (DSTableView *)tableView { if (!_tableView) { _tableView = DSTableView.

黑酒一 0 Jan 10, 2022
CollectionView - UICollectionView using UICollectionViewCompositionalLayout

CollectionView UICollectionView using UICollectionViewCompositionalLayout create

null 0 Jan 11, 2022
CollectionViewSegmentedControl - Scrollable UISegmentedControl built using a UICollectionView

CollectionViewSegmentedControl Installation CocoaPods Download CocoaPods Run 'Po

James Sedlacek 7 Nov 24, 2022
A modest attempt to port UICollectionView to SwiftUI.

LazyCollectionView A modest attempt to port UICollectionView to SwiftUI. Table of Contents Description Requirements Installation Usage Components Impr

Unsplash 109 Dec 27, 2022
PJFDataSource is a small library that provides a simple, clean architecture for your app to manage its data sources while providing a consistent user interface for common content states (i.e. loading, loaded, empty, and error).

PJFDataSource PJFDataSource is a small library that provides a simple, clean architecture for your app to manage its data sources while providing a co

Square 88 Jun 30, 2022
This component allows for the transfer of data items between collection views through drag and drop

Drag and Drop Collection Views Written for Swift 4.0, it is an implementation of Dragging and Dropping data across multiple UICollectionViews. Try it

Michael Michailidis 508 Dec 19, 2022