This article is translated from the NSCollectionView Tutorial of Raywenderlich.com. It has been consulted with the other website, and can be translated up to 10 articles. I hope that if you have English reading ability, you’d better give credit first and then read the English original. After all, no matter Xcode, or official documents, or all kinds of cutting-edge information is only in English version. To sum up, this translation version is for reference only, declined to reprint.
Basic MacOS application development tutorial 1: Basic MacOS application development tutorial 1: Basic MacOS application development tutorial 2: Basic MacOS application development tutorial
Update: This NSCollectionView tutorial has been updated by Gabriel Miro to Xcode 8 and Swift 3.
Collection View is the best way to display a series of identical types of data. The Mac’s built-in Finder and Photos use it: a Collection View that shows all your files and Photos.
The NSCollectionView, first introduced in OS X 10.5, is a convenient way to lay out a set of items of the same size and display them in a Scroll View that can be moved.
In OS X 10.11 El Capitan, the NSCollectionView has been fully updated with reference to the UICollectionView on iOS.
MacOS 10.12 Sierra gives it the ability to “unpack partitions” (like in the Finder) and to “fix titles”, narrowing the gap even further.
In this introductory NSCollectionView tutorial, you will create an app called SlideMagic, which is a grid-like image viewing app for you.
This tutorial assumes that you already have a basic understanding of MacOS app development. If you haven’t, Raywenderlich.com offers a number of great MacOS development tutorials that you should check out first.
And, of course, my own zero-based MacOS application development tutorial series
Ready to start
SlidesMagic is a simple photo browser. It’s cool, but don’t accidentally delete photos from your Mac just because it’s so cool. ~
The app pulls all the images from a folder and displays them in a very elegant Collection View. The finished app looks like this:
Download the startup code for this project, compile and run it:
At this point, the app looks like an empty window, but the startup code includes some “hidden features” that are the foundation for what later becomes a photo browser.
When SlidesMagic starts, it automatically loads all the images in the Desktop Pictures directory on the system, and the names of these files are visible in Xcode’s console output.
The output list in the console shows that the Model loading logic code in the startup code is working properly. You can go to File → Open Another Folder in this app… Open another directory in the menu.
Start code
The starter code provides some code that is not directly related to Collection Views.
Model
- Imagefile.swift: Used to describe an ImageFile
- ImageDirectoryLoader. Swift: used to load the image from the hard drive out of the Helper classes
Controller
The app has two main controllers:
-
WindowController. Swift:
-
windowDidLoad()
: Sets the size of the main window on the left side of the screen; -
openAnotherFolder(_:)
: provides a standard “open” dialog box for users to select folders;
-
-
ViewController. Swift:
-
viewDidLoad()
Open the Desktop Pictures directory as the default directory.
-
Behind the scenes of Collection View
The NSCollectionView is the main story today, and it will display a number of items with the help of several key components.
layout
NSCollectionViewLayout: This specifies the layout of CollectionView. It is an abstract class from which all real classes representing the CollectionView layout are inherited.
NSCollectionViewFlowLayout: provides a flexible grid layout. This layout works for most apps.
NSCollectionViewGridLayout: in order to compatible with OS X 10.11 and previous versions retain the layout, the app is not recommended for new creation.
Section vs. IndexPath: The former allows you to divide an item into sections, each containing an ordered set of items. Each item is associated with an index that is an instance of IndexPath consisting of a pair of integers (section, item). By default, the Collection View will still have a section when you don’t need to partition an item.
Collection View Item
Like many other Cocoa frameworks, the Items in Collection View follow the MVC design pattern.
Model and View: The contents of this item come from the Model’s data object. Each individual object displays itself by creating its own View in the Collection View. The structure of these views is defined by a separate NIB file with a.xib file extension.
Controller: The NIB file mentioned above is a subclass of NSViewController managed by NSCollectionViewItem. It is responsible for communicating with the Model object and controlling the display of the Collection View. Typically, you’ll write a subclass of NSCollectionViewItem. When you need different types of Item, you need to define a different subclass for each branch and create a Nib.
Additional View
To display additional information in the Collection View that is different from the normal Item, you need an additional Item.
The most obvious example is the partition title and footnote.
Data Source and Delegates Collection View
-
NSCollectionViewDataSource
: Populate the Collection View with items and additional items. -
NSCollectionViewDelegate
: Handles drag-and-drop related events, as well as selection status and highlighting. -
NSCollectionViewDelegateFlowLayout
: Allows you to customize your grid view.
Note: There are two ways to populate a Collection View: the data source and the Cocoa binding. This tutorial will use data sources.
Create a Collection View
Open the Main storyboard. Go to the control library and drag a Collection View into the View Controller Scene.
Note: You may have noticed that the Interface Builder adds three Views instead of one. This is because the Collection View is embedded in a Scroll View, which in turn has a Clip View subview. Each of these views is different, so when you are asked to select a Collection View in this tutorial, be sure not to choose a Scroll View or a Clip View by mistake.
Resize the Bordered Scroll View so that it fills all the space in its superview. Then select Editor → Resolve Auto Layout Issues → Add Missing Constraints from the Xcode menu bar to Add Auto Layout Constraints.
You need to add an Outlet to the ViewController to access the Collection View on the screen. Open ViewController.swift and add the following code to the definition of the ViewController class:
@IBOutlet weak var collectionView: NSCollectionView!
Open Main.storyboard and select the View Controller in the View Controller Scene.
Open the Connection Inspector, find the CollectionView in the Outlets section, and drag the small circle next to it onto the Collection View in the canvas.
Adjust the layout of the Collection View
You now have two options: set the main layout properties in Interface Builder, or write them manually in your code.
In the case of SlidesMagic, we chose to write the code manually.
Open ViewController.swift and add these methods to the ViewController:
fileprivate func configureCollectionView() { // 1 let flowLayout = NSCollectionViewFlowLayout() flowLayout.itemSize = NsSize (width: 160.0, height: 140.0) flowLayout. sectionInSet = edGeInSets (top: 10.0, left: 20.0, bottom: 10.0, right: 20.0) flowLayout. MinimumInteritemSpacing = 20.0 flowLayout. MinimumLineSpacing = 20.0 collectionView. CollectionViewLayout = flowLayout // 2 view.wantsLayer = true // 3 collectionView.layer? .backgroundColor = NSColor.black.cgColor }
What this code does is:
- To create a
NSCollectionViewFlowLayout
, configures its base properties, and setsNSCollectionView
的collectionViewLayout
; - In general,
NSCollectionView
Is layer based, so you need to put it in the superview’swantsLayer
Set totrue
; - Set the background color of the Collection View to black.
You’ll need to call this method when the load attempt is complete, so insert it at the end of viewDidLoad() :
configureCollectionView()
Compile and run:
At this point, your Collection View has a black background and the layout is configured.
Create a Collection View Item
First you need to create a subclass of NSCollectionViewItem and display the data in the Model.
Click File → New → File… on the Xcode menu bar. , select MacOS → Source → Cocoa Class and click Next.
Enter CollectionViewItem for Class and NSCollectionViewItem for Subclass and check Also create XIB for user interface.
Click Next, then select Controllers in the Group dialog box, and click Create.
Open CollectionViewItem. Swift and replace all contents in it with:
import Cocoa class CollectionViewItem: NSCollectionViewItem { // 1 var imageFile: ImageFile? { didSet { guard isViewLoaded else { return } if let imageFile = imageFile { imageView? .image = imageFile.thumbnail textField? .stringValue = imageFile.fileName } else { imageView? .image = nil textField? .stringValue = "" } } } // 2 override func viewDidLoad() { super.viewDidLoad() view.wantsLayer = true view.layer? .backgroundColor = NSColor.lightGray.cgColor } }
What this code does is:
- Defines the
imageFile
Property to access the Model object that needs to be presented. When youimageFile
Property is assigned to itsdidSet
The property viewer sets the Image and Label of the item; - Changes the background color of the Item’s View.
Add Control to the View
You checked “Also create a XIB” on CollectionViewItem. Swift. To clear up the file, Drag the CollectionViewItem. Xib into the Resources group under Main.storyboard.
The View in the NIB file is the root View that each item is displayed in. You need to add an Image View to display the Image and a Label to display the file name.
Open CollectionViewItem. Xib and add a new Nsimageview:
- Drag an Image View from the control library to a View on the canvas.
- Click the Pin button in the Auto Layout toolbar to set its constraints;
- Set its TOP, LEADING, AND TRAILING constraints to 0 and BOTTOM to 30. Click Update Frames: Items of New Constraints and then click Add 4 Constraints.
Let’s add another Label:
- Drag a Label from the control library to the canvas below the Image View;
- Click the Pin button in the Auto Layout toolbar and set its Top, Bottom, Leading and Trailing constraints to 0. Click Update Frames: Items of New Constraints and then click Add 4 Constraints.
Select Label and set the following properties in the property inspector:
- Set Alignment to center
- Set the Text Color to white
- Set Line Break to Truncate Tail
Adds CollectionViewItem to the NIB and connects to Outlets
Although the File’s Owner of the NIB File is now CollectionViewItem, it is still just a placeholder. When the NIB file is instantiated, it will also need an instance of the “real” NSCollectionViewItem.
Drag a CollectionViewItem from the control library into the document outline, select it, and set its Class to CollectionViewItem in the identity inspector.
In xib, you need to connect the View hierarchy to an Outlet of CollectionViewItem. In CollectionViewItem. Xib:
- Select the Collection View Item and go to the Connections Inspector;
- the
view
Let’s drag an outlet toDocument outlineIn theViewOn; - In the same way, take
imageView
和textField
Outlet to connect toDocument outlineIn theImage View 和 LabelIn the.
Fill the Collection View
You need to implement the data source protocol for Collection View. In human terms:
- How many partitions are there in the Collection?
- How many items does each partition have?
- Which Item does an Index Path correspond to?
Open ViewController.swift and add these extensions at the end of the file:
extension ViewController : NSCollectionViewDataSource {
// 1
func numberOfSections(in collectionView: NSCollectionView) -> Int {
return imageDirectoryLoader.numberOfSections
}
// 2
func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return imageDirectoryLoader.numberOfItemsInSection(section)
}
// 3
func collectionView(_ itemForRepresentedObjectAtcollectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
// 4
let item = collectionView.makeItem(withIdentifier: "CollectionViewItem", for: indexPath)
guard let collectionViewItem = item as? CollectionViewItem else {return item}
// 5
let imageFile = imageDirectoryLoader.imageFileForIndexPath(indexPath)
collectionViewItem.imageFile = imageFile
return item
}
}
- If your app doesn’t need partitioning, then you can remove this method because one partition is enough.
- This is two
NSCollectionViewDataSource
One of the methods that the protocol must implement, where you need to return the number of items held by a partition; - Another method that has to be implemented, and this method is going to be for one
indexPath
Returns an item; - This method instantiates an item from the nib. The name of the item is
identifier
Argument, which attempts to reuse an item based on the type of the item needed, or creates a new item if no item is available for reuse. - According to the
IndexPath
Get the Model object and set the contents of the Image and Label.
Note: Collection View has the ability to loop through items that have already been generated to reduce memory stress if the data source becomes too large. Items that are removed from the interface are items that are reused.
Setting the data source
Next I need to define the data source:
Open Main.storyboard and select Collection View.
Open theConnection checkerIn theOutletsFound in sectiondataSource.Drag theIt’s next to the small circle toDocument outlineIn theView ControllerOn.
Compile and run, and your Collection View should now display images from the Desktop Pictures directory:
Ha ha ha, toss about half a day is worth ✌️ ~
troubleshooting
If you can’t see any images yet, you might be missing a few small details:
- Did you set up all the connections correctly in the connection checker?
- You set the
dataSource
Do you have an Outlet of? - Did you apply the correct custom classes in the identity checker?
- You add the top layer
NSCollectionViewItem
And sets its class toCollectionViewItem
Yet? -
makeItemWithIdentifier
In theidentifier
Is the value of the parameter the same as the name of the nib?
The items are reloaded when the Model changes
To display images from Another directory, click File → Open Another Folder… on the app’s menu bar. , and then select a directory that holds images in JPG or PNG format.
At this point, however, nothing seems to have changed in the window, which still shows the images from the Desktop Pictures directory. Even though the console in Xcode has already printed out the names of the files in the new directory.
You need to call the Collection View’s reloadData() method to refresh its items.
Open the ViewController. Swift and add the code to loadDataForNewFolderWithUrl (_) method:
collectionView.reloadData()
Compile and run it, and you should now see the correct photos displayed in the window.
Add the partition
The SlidesMagic app can now do some pretty amazing things, but let’s go one step further and add partitions to the Collection View.
First, you need to add a check box at the bottom of the main view that allows you to toggle whether grouping is enabled or not.
Open main. storyboard, and check the constraint on Scroll View in the document outline, and change its Constant to 30 in the size inspector.
This will elevate the Collection View a bit, making room for the checkboxes.
Now drag a Check Box Button from the control library into the space below the Collection View in the canvas, select it, set its Title to Show Sections in the Property Inspector, and State to OFF.
Next, click the Pin button to update its Auto Layout constraints: Top set to 8, Leading set to 20. Then click Update Frames: Items of New Constraints and Add 2 Constraints
Compile and run, and the bottom of your app should now look something like this:
When you click this check box, your app needs to change the appearance of the Collection View.
Open ViewController.swift and add this code at the end of the ViewController class:
@IBAction func showHideSections(sender: NSButton) {
let show = sender.state
// 1
imageDirectoryLoader.singleSectionMode = (show == NSOffState)
// 2
imageDirectoryLoader.setupDataForUrls(nil)
// 3
collectionView.reloadData()
}
This code will:
- Toggle single/multiple groups according to checkbox status;
- Adjust the Model to the currently selected schema, which is passed at this time
nil
The parameter skips the image loading — the images are still the same after all, just the layout has changed; - The Model has changed, so you need to refresh the data.
If you’re curious about the rules by which images are grouped, look for a SectionLengthArray in ImageDirectoryLoader. This array contains a number that sets the maximum number of items that can be placed in each group. This array is randomly generated and is only used for demonstration purposes.
Now, open main.storyboard. Drag Show Sections to the View Controller while holding down the Control⌃ key in the document outline. Click ShowhideSections: in the black window that pops up. You can check to see if your connection is successful in the connection checker.
Compile and run, and check Show Sections to see the layout change.
To better differentiate between partitions, open ViewController.swift and edit the sectionInset property in the configureCollectionView() method.
Put this line in:
FlowLayout. sectionInSet = EdGeInSets (top: 10.0, left: 20.0, bottom: 10.0, right: 20.0)
Replace it with this:
FlowLayout. sectionInSet = EdGeInSets (top: 30.0, left: 20.0, bottom: 30.0, right: 20.0)
Compile and run it, and check Show Sections to see that the partitions are already separated.
Add a partition title
Another way to distinguish the boundaries of each partition is to add a title or footnote for each partition.
You need a custom NSView class and implement the corresponding data source method to add a title to the Collection View
To create a title, go to the Xcode menu bar and click File → New → File… . Select MacOS → User Interface → View, and click Next.
Type the file name headerview.xib and select Resources for Group.
Click on the Create.
Open HeaderView.xib and select Custom View. In the Size Inspector, set Width to 500 and Height to 40.
Drag a Label from the Object Library to the left side of the Custom View. Open the property inspector, set its Title to Section Number, and Font Size to 16.
Drag another Label to the right side of the Custom View. Set its Title to Image Count and Alignment to Right.
Select the Section Number Label, click the Pin button, and set its Top constraint to 12 and Leading constraint to 20. Click Update Frames: Items of New Constraints and Add 2 Constraints.
Next, set the Image Count Label’s Top constraint to 11 and the Trailing constraint to 20. Don’t forget to click Update Frames: Items of New Constraints and Add 2 Constraints.
Our title should now look something like this:
Now that our header UI is ready, we need to create a subclass for it.
In Xcode’s menu bar, click File → New → File… . Select MacOS → Source → Cocoa Class, and click Next. Set its class name to HeaderView and let it inherit from NSView, click Next, and select Views in Group. Click on the Create.
Open headerview. swift and replace everything inside with:
// 1 @IBOutlet weak var sectionTitle: NSTextField! @IBOutlet weak var imageCount: NSTextField! // 2 Override func draw(_ dirtyRct: NSRct) {super.draw(dirtyRct) NSColor(calibratedWhite: 0.8, alpha: 0.5) {super.draw(dirtyRct) NSColor(calibratedWhite: 0.8, alpha: 0.5); 0.8). The set () NSRectFillUsingOperation (dirtyRect, NSCompositingOperation sourceOver)}
Here’s what the code does:
- Set the outlets you need to connect to NIB elements;
- Draw a grey background.
To connect an Outlet to a Label, open HeaderView.xib and select Custom View. In the Identity Inspector, set the Class to HeaderView.
In the document outline View, hold down the Control⌃ key while clicking the Header View. In the black window that pops up, drag ImageCount to connect to an outlet on Images Count.
Do the same for the second Label, dragging the sectionTitle onto the Section Number Label in the canvas.
Implement data source and proxy methods
Your title is fully ready to on the battlefield, you need to implement collectionView (_ : viewForSupplementaryElementOfKind: at:), the title passed to View the Collection View:
Open the ViewController. Swift and add these methods to NSCollectionViewDataSource extension of:
func collectionView(_ collectionView: NSCollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> NSView {
// 1
let view = collectionView.makeSupplementaryView(ofKind: NSCollectionElementKindSectionHeader, withIdentifier: "HeaderView", for: indexPath) as! HeaderView
// 2
view.sectionTitle.stringValue = "Section \(indexPath.section)"
let numberOfItemsInSection = imageDirectoryLoader.numberOfItemsInSection(indexPath.section)
view.imageCount.stringValue = "\(numberOfItemsInSection) image files"
return view
}
The Collection View calls this method when it needs the data source and sets the title for each partition. This method does this:
- call
makeSupplementaryViewOfKind(_:withIdentifier:for:)
To instantiate a name from a NIB filewithIdentifier
的HeaderView
Object; - Set the values of each Label.
The ViewController. Swift, finally, add the NSCollectionViewDelegateFlowLayout extension:
extension ViewController : NSCollectionViewDelegateFlowLayout {
func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> NSSize {
return imageDirectoryLoader.singleSectionMode ? NSZeroSize : NSSize(width: 1000, height: 40)
}
}
This method is not necessary, but you must do so if you need to set the title, because the Flow Layout proxy requires you to provide the size of the title for each partition.
If you do not implement this method, you will not see the headings because they are all size 0. In addition, it ignores the width you specify and sets the width of the header to the width of the Collection View.
In this example, when the Collection View has only one partition, this method returns a header size of 0, otherwise it will return 40.
Used for the Collection of NSCollectionViewDelegateFlowLayout View, you need to connect ViewController to NSCollectionView delegate.
Open Main.storyboard and select Collection View. Open the connection inspector and find the delegate in the Outlets section. Drag the dot next to it onto the View Controller in the document outline.
Compile and run it, and check Show Sections to see the titles that separate the partitions:
Fixed title
MacOS 10.12 new joined the two attributes: the NSCollectionViewFlowLayout sectionHeadersPinToVisibleBounds and sectionFootersPinToVisibleBounds.
When sectionHeadersPinToVisibleBounds set to true, the top of the title of the partition will be fixed at the top, not removed from the interface. As you scroll down, the next title will push it away. This effect is commonly called “sticky headers” or “floating headers.”
The sectionFootersPinToVisibleBounds is set to true will fix the footnote at the bottom.
Open ViewController.swift and add this method at the bottom of the configureCollectionView() method:
flowLayout.sectionHeadersPinToVisibleBounds = true
Compile and run it, check Show Sections and scroll down a bit. You can see that the first section has some images removed from the screen, but the title is still fixed at the top:
Note: if your app needs to support OS X 10.11 or older, you need to rewrite layoutAttributesForElements (in fixed title:) method to “manual”. You can check out the Advanced Collection Views in OS X Tutorial.
Collection View selection function
To show the selected status of an item, you need to set a white border. Items that are not selected will not show this border.
First, you need to make our Collection View selectable. Open Main.storyboard, select Collection View and select Selectable in the Properties Inspector.
Checking “Selectable” enables selection, meaning you can select an item by clicking on it. If you click on another item, it will deselect the previous item and select the new item.
When you select an item:
- It’s
IndexPath
It’s going to be added toNSCollectionView
的selectionIndexPaths
Properties; - It’s
isSelected
Property will be set totrue
.
Open the CollectionViewItem. Swift. Append at the end of viewDidLoad() method:
// 1 view.layer? .borderColor = NSColor.white.cgColor // 2 view.layer? . BorderWidth = 0.0
This code:
- Set a white border;
- the
borderWidth
Set to0.0
To make sure the border is not visible — that is, not selected.
To change the borderWidth every time isSelected is set, we need to add this code to the CollectionViewItem class:
override var isSelected: Bool { didSet { view.layer? .borderWidth = isSelected ? 5.0:0.0}}
Every time isSelected changes, the didSet will set the width of the border based on the new value.
Compile and run. Click on an item to select it, and you’ll see a border appear around it. Hahaha, amazing ✨!
What should we do next?
Click here to download the finished SlideMagic.
In this introductory NSCollectionView tutorial, you learned how to create your first CollectionView, the intricacies of the data source API, and how to handle partitioning. You’ve learned a lot here, but this is just the beginning. Collection View has a lot of features to explore. There’s a lot to explore here:
- Build a “data-source-free” Collection View using Cocoa’s data binding
- Different types of items
- Append and remove items
- Custom Layout
- Drag and drop
- animation
- Modify the
NSCollectionViewFlowLayout
- Collapsing a partition (new feature for MacOS 10.12 Sierra)
You can be in our “NSCollectionView advanced tutorial” (the original |) learn more.