mnort9
mnort9

Reputation: 1820

Reuse single UICollectionViewCell in multiple collection views

I have multiple collection views that need to use the same prototype cells. Right now, I'm duplicating the prototype cells in each collection view in storyboard (as seen below). This is not very DRY and becomes a pain when making storyboard changes to the cells.

Is it possible to define the cell in one place in storyboard?

enter image description here

Alternatively I tried using only 1 collection view, but I couldn't get the 3 column layout where cells stack and scroll vertically, so I moved to 3 collections views.

Upvotes: 3

Views: 3231

Answers (2)

Alexander
Alexander

Reputation: 63379

I built a cool/useful abstraction over the trick Robert J. Clegg's shoed above.

I cut/pasted all my NSTableViewCells into a new XIB, which I called SharedTableCellViews.xib. To keep track of its contents, I made an accompanying SharedTableCellViews struct, as such:

import AppKit

/// Models the multiple NSTableCellViews contained within SharedTableCellViews.nib, which can be defined centrally, and reused
/// between multiple `NSTableView`s or `NSOutlineView`s (which are just a subclass thereof)
struct SharedTableCellViews {
    typealias ID = NSUserInterfaceItemIdentifier

    static let shared = SharedTableCellViews()

    static let nibName = "SharedTableCellViews"

    static let textWithIcon   = ID(rawValue: "NameAndIconTableCellView")
    static let numericText    = ID(rawValue: "NumericTextTableCellView")
    static let normalText     = ID(rawValue: "TextTableCellView")
    static let monospacedText = ID(rawValue: "MonoSpacedTextTableCellView")

    static let allIDs = [
        textWithIcon,
        numericText,
        normalText,
        monospacedText,
    ]

    let nib = NSNib(nibNamed: nibName, bundle: nil)

    func register(viewIDs: [NSUserInterfaceItemIdentifier] = allIDs, forUseBy tableView: NSTableView) {
        for viewID in viewsIDs {
            precondition(SharedTableCellViews.allIDs.contains(viewID), "The nib name \(viewID) " +
                "is not one of the viewIDs managed by \(SharedTableCellViews.self).")

            tableView.register(self.nib, forIdentifier: viewID)
        }
    }
}

The register(viewIDs:forUseBy:) method allows me to only need a single line from my view controller to set up a table:

SharedTableCellViews.shared.register(forUseBy: self.someTableView)

The same nib object (SharedTableCellViews.shared.nib) is reused by all tables, for every reuse identifier. The key is to set the identifiers of each NSTableCellViews within the one xib file.

This also acts nicely as a central namespace to store all my NSUserInterfaceItemIdentifier, which I could directly use with NSTableView.makeView(withIdentifier:owner:)s.

Everything here is for MacOS/AppKit, but should be equally applicable to iOS/UIKit with just a few NS -> UI renamings.

Upvotes: 0

Robert J. Clegg
Robert J. Clegg

Reputation: 7370

Yes this is possible. I never design cells (UITableView or UICollectionView cells ) in the storyboard. Chances are, you'll need to reuse them as is the case here.

To begin with, create a new view (File > New > File) and select the View option from the User Interface section. It doesn’t matter which device family you choose, so give it a name (I’m calling mine NibCell). It will open up in the canvas – the first job is to delete the existing view, and drag in a Collection View Cell from the Object Browser.

You would also create a subclass of UICollectionViewCell class and set it to the class of the xib file in interface builder. Once you have created the xib and wired up the elements to the custom class - you need to register it like so in your class that has the UICollectionView in it (don't forget to import it the custom cell class first!)

UINib *cellNib = [UINib nibWithNibName:@"NibCell" bundle:nil];
[self.collectionView registerNib:cellNib forCellWithReuseIdentifier:@"cvCell"];

And you would use it like so:

-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {

    static NSString *cellIdentifier = @"cvCell";

    CVCell *cell = (CVCell *)[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
    //Setup the cell for use 
    return cell;

}

And thats generally all there is to it. Hope this helps!

Upvotes: 6

Related Questions