Issue #980
NSCollectionView, available since macOS 10.5+, is a good choice to present a list of content. Let’s make a SwiftUI wrapper for NSCollectionView
with diffable data source and compositional layout
Use NSViewControllerRepresentable
First, let’s create a SwiftUI view that will represent the macOS view controller. We’ll use the NSViewControllerRepresentable
protocol to bridge SwiftUI and AppKit.
import SwiftUI
import AppKit
struct CollectionView: NSViewControllerRepresentable {
func makeNSViewController(context: Context) -> CollectionViewController {
return CollectionViewController()
}
func updateNSViewController(_ nsViewController: CollectionViewController, context: Context) {
nsViewController.update()
}
}
In this code, the CollectionView
struct acts as a wrapper around CollectionViewController
, our main view controller where all the action happens.
Creating the View Controller
Next, we’ll create the CollectionViewController
, which is responsible for displaying the list of UUIDs
.
final class CollectionViewController: NSViewController {
typealias Item = String
private var scrollView: NSScrollView!
private var collectionView: NSCollectionView!
private var dataSource: NSCollectionViewDiffableDataSource<Int, Item>!
Make it scrollable
We want our list to be scrollable, so we embed the NSCollectionView
inside an NSScrollView
. This allows users to scroll through the list if it’s too long to fit on the screen.
override func viewDidLoad() {
super.viewDidLoad()
// Set up ScrollView
scrollView = NSScrollView()
scrollView.hasVerticalScroller = true
view.addSubview(scrollView)
scrollView.constrainEdges(to: view)
// Set up CollectionView
collectionView = NSCollectionView()
collectionView.backgroundColors = [.clear]
scrollView.documentView = collectionView
// Set up Compositional Layout
collectionView.collectionViewLayout = createCompositionalLayout()
// Register Cell
collectionView.register(CollectionViewItem.self, forItemWithIdentifier: NSUserInterfaceItemIdentifier("UUIDItem"))
// Set up Data Source
dataSource = NSCollectionViewDiffableDataSource(
collectionView: collectionView,
itemProvider: { collectionView, indexPath, item in
let cell = collectionView.makeItem(withIdentifier: NSUserInterfaceItemIdentifier("UUIDItem"), for: indexPath) as! CollectionViewItem
cell.textField?.stringValue = item
return cell
}
)
// Populate data
update()
}
Here’s what’s happening:
- NSScrollView: We create a scroll view and add it to the main view.
- NSCollectionView: This is where our list items will be displayed. We set it as the documentView of the scroll view.
- Compositional Layout: We set up a simple layout to arrange the items in the collection view.
Creating the Compositional Layout
The compositional layout allows us to create flexible and complex layouts easily. In this case, we’re keeping it simple with a vertical list.
func createCompositionalLayout() -> NSCollectionViewCompositionalLayout {
return NSCollectionViewCompositionalLayout { (sectionIndex, layoutEnvironment) -> NSCollectionLayoutSection? in
// Define item size
let itemSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .absolute(30)
)
let item = NSCollectionLayoutItem(layoutSize: itemSize)
// Define group size and layout
let groupSize = NSCollectionLayoutSize(
widthDimension: .fractionalWidth(1.0),
heightDimension: .absolute(30)
)
let group = NSCollectionLayoutGroup.vertical(layoutSize: groupSize, subitems: [item])
group.interItemSpacing = .fixed(10)
// Define section
let section = NSCollectionLayoutSection(group: group)
section.interGroupSpacing = 10
section.contentInsets = NSDirectionalEdgeInsets(top: 10, leading: 10, bottom: 10, trailing: 10)
return section
}
}
This layout creates a simple vertical list where each item takes up the full width of the collection view and has a height of 30 points.
Adding Data to the Collection View
Finally, we need to populate our collection view with data. In this example, we’re generating 1,000 random UUID strings.
func update() {
var snapshot = NSDiffableDataSourceSnapshot<Int, Item>()
snapshot.appendSections([0])
let uuidStrings = (0..<1000).map { _ in UUID().uuidString }
snapshot.appendItems(uuidStrings, toSection: 0)
dataSource.apply(snapshot)
}
We create an array of 1,000 random UUID strings. Then we use a snapshot to manage the data in the collection view. This allows us to efficiently update the view when the data changes.
Customizing the Collection View Item
We need to create a custom NSCollectionViewItem
to display each UUID string.
class CollectionViewItem: NSCollectionViewItem {
override func loadView() {
self.view = NSView()
let textField = NSTextField(labelWithString: "")
textField.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(textField)
NSLayoutConstraint.activate([
textField.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 10),
textField.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -10),
textField.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
self.textField = textField
}
}
And that’s it! You’ve built a macOS app that displays a scrollable list of 1,000 random UUID strings using Swift, AppKit, and NSCollectionView
. This project demonstrates how to use modern techniques like compositional layouts and NSDiffableDataSource to create a dynamic and efficient user interface