[译] NSCollectionView 入门教程
本文翻譯自 raywenderlich.com 的 NSCollectionView Tutorial,已咨詢對方網(wǎng)站,可至多翻譯 10 篇文章。
希望各位有英語閱讀能力的話,還是 先打賞 然后去閱讀英文原吧,畢竟無論是 Xcode,抑或是官方的文檔,還是各種最前沿的資訊都只有英文版本。
綜上,此翻譯版本僅供參考,謝絕轉(zhuǎn)載。
相關(guān)鏈接:
NSCollectionView 進階教程:原文 / 譯文(翻譯中)
零基礎 macOS 應用開發(fā)教程(一):原文 / 譯文
更新信息: 此 NSCollectionView 教程已由 Gabriel Miro 更新至 Xcode 8 和 Swift 3.
Collection View 是顯示一系列相同類型數(shù)據(jù)的最佳方式。Mac 中自帶的 Finder 和 Photos 就是使用了它:通過一個 Collection View 來展示所有的文件和圖片。
NSCollectionView 最早在 OS X 10.5 被推出,它可以非常方便地布局一組具有相同大小的 item,并把它們展示在一個可以滑動的 Scroll View 中。
在 OS X 10.11 El Capitan 中,參照 iOS 上的 UICollectionView,NSCollectionView 被全面進行了升級。
macOS 10.12 Sierra 則給予了它「收起分區(qū)」(就像 Finder 里那樣)和「固定標題」兩項新功能,使得它和 iOS 的差距進一步減小了。
在這個 NSCollectionView 的入門教程中,你將會創(chuàng)造一個叫 SlideMagic 的 app,它是一個只屬于你的網(wǎng)格狀的圖片瀏覽 app。
這個教程假定你已經(jīng)基本了解過了 macOS app 的開發(fā),如果你還不曾了解過,raywenderlich.com 上提供了很多很棒的 macOS 開發(fā)教程,你可以先去看看那些。
當然還有我自己翻的《零基礎 macOS 應用開發(fā)教程》系列
準備開始
你將會編寫的 SlidesMagic app 是一個簡單的圖片瀏覽器,它很酷,但是,可別因為它太酷了而一不小心在把玩的時候把自己 Mac 上的照片刪了哦?~
這個 app 會從獲取一個文件夾里的所有圖片,然后用一個極其優(yōu)雅的 Collection View 來把它們顯示出來。完成了的 app 長這樣:
下載這個項目的起步代碼,編譯并運行:
此時,這個 app 看起來只是一個空蕩蕩的窗口,但這些起步代碼包含了一些「隱藏功能」,這是后面使它成為一個圖片瀏覽器的基礎。
SlidesMagic 啟動的時候,會自動加載系統(tǒng)中 Desktop Pictures 目錄下的所有圖片,在 Xcode 的控制臺輸出中,我們可以看到這些文件的名字。
控制臺中輸出的列表表明,起步代碼中 Model 的加載邏輯代碼已經(jīng)可以正常工作了,你可以在這個 app 的 File → Open Another Folder… 菜單中打開另一個目錄。
起步代碼
起步代碼提供了一些與 Collection Views 無直接關(guān)聯(lián)的代碼。
Model
- ImageFile.swift: 用于描述一個圖片文件
- ImageDirectoryLoader.swift: 用來把圖片從硬盤中加載出來的 Helper 類
Controller
這個 app 擁有兩個主要的 Controller:
-
WindowController.swift:
- windowDidLoad():在左半邊的屏幕上設置主窗口的大小;
- openAnotherFolder(_:):提供一個標準的「打開」對話框來供用戶選擇文件夾;
-
ViewController.swift:
- viewDidLoad() 打開 Desktop Pictures 目錄作為默認目錄。
Collection View 幕后探秘
NSCollectionView 是今天的主角,它將會在幾個關(guān)鍵的組成部分的幫助下,顯示許多 item。
布局
NSCollectionViewLayout:明確了 Collection View 的布局方式,它是一個抽象的類,所有用來表示 CollectionView 布局的實類都繼承自它。
NSCollectionViewFlowLayout:提供了一個靈活的網(wǎng)格狀的布局。對于絕大多數(shù) app,這種布局方式都適用。
NSCollectionViewGridLayout:為了兼容 OS X 10.11 和以前的版本所保留的布局方式,對于新創(chuàng)建的 app 不推薦使用。
Section 和 IndexPath:前者允許你把 item 分成若干個 section(分區(qū)),每個 section 包含了一組有序的 item。每個 item 都和一個索引相關(guān)聯(lián),這個索引是一個由一對整數(shù)(section,item)構(gòu)成的 IndexPath 實例。默認情況下,當你不需要給 item 分區(qū)時,這個 Collection View 仍然會擁有一個 section。
Collection View Item
就像其他許多 Cocoa 框架一樣,Collection View 中的 item 也遵守著 MVC 設計模式。
Model 和 View:這個 item 的內(nèi)容來自 Model 的數(shù)據(jù)對象。每個單獨的對象都通過在 Collection View 中創(chuàng)建自己的 View 來把自己顯示出來。這些 View 的結(jié)構(gòu)由一個單獨的 nib 文件(文件擴展名是 .xib)來定義。
Controller:上面提到的 nib 文件是一個由 NSCollectionViewItem 管理的 NSViewController 的子類。它負責與 Model 對象進行通信并控制 Collection View 的顯示。通常情況下,你會編寫一個 NSCollectionViewItem 的子類。當你需要不同類型的 item 的時候,你需要為每個分支定義一個不同的子類,并創(chuàng)建一個 nib。
額外的 View
要在 Collection View 中顯示不同于普通 item 額外的信息,你需要額外的 item。
最直觀的例子就是分區(qū)的標題和腳注。
Collection View 的數(shù)據(jù)源和代理(Data Source and Delegates)
- NSCollectionViewDataSource:用 item 和額外的 item 來填充 Collection View。
- NSCollectionViewDelegate:處理拖放相關(guān)的事件,以及選中狀態(tài)和高亮。
- NSCollectionViewDelegateFlowLayout:允許你自定義你的網(wǎng)格視圖。
注意:填充一個 Collection View 的方法有二:數(shù)據(jù)源和 Cocoa 綁定。這個教程將會使用數(shù)據(jù)源。
創(chuàng)建 Collection View
打開 Main.storyboard。前往控件庫,向 View Controller Scene 中拖動一個 Collection View。
注意:你或許注意到了,Interface Builder 為我們添加了三個 View,而不是一個。這是因為 Collection View 是嵌入在一個 Scroll View 中的,而后者又自帶一個 Clip View 子視圖。這仨視圖各不相同,因此當本教程需要你選擇 Collection View 的時候,切記不要錯選了 Scroll View 或 Clip View。
調(diào)整 Bordered Scroll View 的大小,使它填滿它的父視圖的所有空間。然后選擇 Xcode 菜單欄上的 Editor → Resolve Auto Layout Issues → Add Missing Constraints 來添加 Auto Layout 約束條件。
你需要在 ViewController 中添加一個 Outlet 來訪問界面上的 Collection View。打開 ViewController.swift,在 ViewController 類的定義中添加以下代碼:
@IBOutlet weak var collectionView: NSCollectionView!打開 Main.storyboard,并選擇 View Controller Scene 中的 View Controller。
打開連接檢查器,在 Outlets 部分中找到 collectionView,拖動它旁邊的小圓圈到畫布中的 Collection View 上。
調(diào)整 Collection View 的布局
你現(xiàn)在有兩種選擇:在 Interface Builder 中設置好主要的布局屬性,或者在代碼中手動編寫。
在 SlidesMagic 這個項目中,我們選擇手動編寫代碼。
打開 ViewController.swift,把這些方法添加到 ViewController 中:
fileprivate func configureCollectionView() {// 1let 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.0flowLayout.minimumLineSpacing = 20.0collectionView.collectionViewLayout = flowLayout// 2view.wantsLayer = true// 3collectionView.layer?.backgroundColor = NSColor.black.cgColor}這些代碼的作用是:
你需要在試圖加載完成時調(diào)用這個方法,所以在 viewDidLoad() 方法的最后插入:
configureCollectionView()編譯并運行:
此時,你的 Collection View 已經(jīng)擁有了一個黑色的背景,并配置好了布局。
創(chuàng)建一個 Collection View Item
先在你需要創(chuàng)建一個 NSCollectionViewItem 的子類并把 Model 里的數(shù)據(jù)們顯示出來。
點擊 Xcode 菜單欄上的 File → New → File…,選擇 macOS → Source → Cocoa Class 并點擊 Next。
把 Class 填寫 CollectionViewItem,Subclass of 填寫 NSCollectionViewItem,并勾選 Also create XIB for user interface。
點擊 Next,然后在對話框中的 Group 中選擇 Controllers,并點擊 Create。
打開 CollectionViewItem.swift,把里邊的內(nèi)容全部替換為:
import Cocoa class CollectionViewItem: NSCollectionViewItem {// 1var imageFile: ImageFile? {didSet {guard isViewLoaded else { return }if let imageFile = imageFile {imageView?.image = imageFile.thumbnailtextField?.stringValue = imageFile.fileName} else {imageView?.image = niltextField?.stringValue = ""}}}// 2override func viewDidLoad() {super.viewDidLoad()view.wantsLayer = trueview.layer?.backgroundColor = NSColor.lightGray.cgColor} }這些代碼的功能是:
向 View 中添加 Control
你在 CollectionViewItem.swift 時勾選了「Also create a XIB(同時創(chuàng)建一個 XIB)」,為了更清楚地整理文件,把 CollectionViewItem.xib 拖動到 Main.storyboard 下方的 Resources 分組中。
Nib 文件中的 View 就是每個 item 所顯示出來的根視圖,你需要添加一個 Image View 來顯示圖片,以及一個 Label 來顯示文件名。
打開 CollectionViewItem.xib,添加一個 NSImageView:
再來添加一個 Label:
選中 Label,在屬性檢查器中設置如下屬性:
向 Nib 中添加 CollectionViewItem 并連接 Outlets
盡管 Nib 文件的 File’s Owner 現(xiàn)在是 CollectionViewItem,它還只是一個占位符。當 Nib 文件被實例化時,它還會需要一個「真正的」NSCollectionViewItem 的實例。
從控件庫中拖動一個 Collection View Item 到文檔大綱中,選中它,在 身份檢查器中把它的 Class 設置為 CollectionViewItem。
在 xib 中,你需要把 View 的層次關(guān)系連接到 CollectionViewItem 的 Outlet 中,在 CollectionViewItem.xib 中:
填充 Collection View
你需要實現(xiàn) Collection View 的數(shù)據(jù)源協(xié)議,說人話就是:
打開 ViewController.swift 并在文件的末尾添加這些擴展代碼:
extension ViewController : NSCollectionViewDataSource {// 1func numberOfSections(in collectionView: NSCollectionView) -> Int {return imageDirectoryLoader.numberOfSections}// 2func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {return imageDirectoryLoader.numberOfItemsInSection(section)}// 3func collectionView(_ itemForRepresentedObjectAtcollectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {// 4let item = collectionView.makeItem(withIdentifier: "CollectionViewItem", for: indexPath)guard let collectionViewItem = item as? CollectionViewItem else {return item}// 5let imageFile = imageDirectoryLoader.imageFileForIndexPath(indexPath)collectionViewItem.imageFile = imageFilereturn item}}注意: Collection View 具有一項能力:循環(huán)使用已經(jīng)生成了的 Item,以此來減輕數(shù)據(jù)源過大時的內(nèi)存壓力。從界面上移出去的 item 就是被重復使用的 item。
設置數(shù)據(jù)源
接下來我發(fā)需要定義數(shù)據(jù)源:
打開 Main.storyboard,選中 Collection View。
打開連接檢查器,在 Outlets 部分中找到 dataSource,拖動它旁邊的小圓圈到文檔大綱里的 View Controller 上。
編譯并運行,你的 Collection View 現(xiàn)在應該能顯示 Desktop Pictures 目錄中的圖片了:
哈哈哈,折騰了半天都是值得的??~
故障排除
如果你還看不見任何圖片,你可能遺漏了一些小細節(jié):
Model 發(fā)生改變時重新加載 Item 們
要顯示另一個目錄中的圖片,你可以在 app 的菜單欄上點擊 File → Open Another Folder…,然后選擇一個存有 JPG 或 PNG 格式的圖片的目錄。
但時此時窗口中的東西似乎什么變化都沒有,還是顯示著 Desktop Pictures 目錄中的圖片。盡管 Xcode 里的控制臺中已經(jīng)打印出了新目錄里的文件名稱。
你需要調(diào)用 Collection View 的 reloadData() 方法來刷新它的 item。
打開 ViewController.swift 并把這些代碼添加到 loadDataForNewFolderWithUrl(_:) 方法中:
collectionView.reloadData()編譯并運行,現(xiàn)在你應該能看到窗口中已經(jīng)能顯示正確的照片了。
添加分區(qū)
SlidesMagic app 現(xiàn)在已經(jīng)可以做一些很神奇的事兒了,但我們要更進一步 —— 為 Collection View 加入分區(qū)。
首先,你需要在主視圖的最底部加入一個復選框,來允許你切換是否啟用分組。
打開 Main.storyboard,然后在文檔大綱中選中 Scroll View 的約束條件,在尺寸檢查器中吧它的 Constant 修改為 30.
這會把 Collection View 抬高一些些,騰出地方來放置復選框。
現(xiàn)在,從控件庫中拖動一個 Check Box Button到畫布中 Collection View 下方的空間里,選中它,在屬性檢查器中把它的 Title 設置為 Show Sections,然后把 State 設置為 Off。
接下來,點擊 Pin 按鈕更新它的 Auto Layout 約束條件:Top 設置為 8,Leading 設置為 20。然后點擊 Update Frames: Items of New Constraints 和 Add 2 Constraints
編譯并運行,現(xiàn)在 app 的底部看起來應該是這樣的:
當你點擊這個復選框的時候,你的 app 需要改變 Collection View 的外觀。
打開 ViewController.swift 在 ViewController 類的最后添加這些代碼:
@IBAction func showHideSections(sender: NSButton) {let show = sender.state// 1imageDirectoryLoader.singleSectionMode = (show == NSOffState)// 2imageDirectoryLoader.setupDataForUrls(nil)// 3collectionView.reloadData()}這些代碼會:
如果你好奇圖片是按照是按照什么規(guī)則進行分組的,在 ImageDirectoryLoader 中找到 sectionLengthArray,這個數(shù)組里的數(shù)字設置了各個分組的里最多可以容納多少個 item。這個數(shù)組是隨機生成的,只是用來用作演示。
現(xiàn)在,打開 Main.storyboard。在文檔大綱中按住 Control? 鍵的同時把 Show Sections 拖動到 View Controller 上。在彈出的黑色窗口中點擊 showHideSections:。你可以在連接檢查器里查看你是否連接成功了。
編譯并運行,勾選 Show Sections 來查看布局的變化。
為了更好地區(qū)分各個分區(qū),打開 ViewController.swift,編輯 configureCollectionView() 方法里的 sectionInset 屬性。
把這一行:
flowLayout.sectionInset = EdgeInsets(top: 10.0, left: 20.0, bottom: 10.0, right: 20.0)替換成這個:
flowLayout.sectionInset = EdgeInsets(top: 30.0, left: 20.0, bottom: 30.0, right: 20.0)編譯并運行,勾選 Show Sections,可以看到各個分區(qū)之間已經(jīng)有了分隔。
添加分區(qū)標題
另一種區(qū)分各個分區(qū)邊界的方法是為每個分區(qū)添加一個標題或腳注。
你需要一個自定義的 NSView 類,并實現(xiàn)相應的數(shù)據(jù)源方法來為 Collection View 添加一個標題
要創(chuàng)建一個標題,在 Xcode 的菜單欄點擊 File → New → File…。選擇 macOS → User Interface → View,并點擊 Next。
文件名輸入 HeaderView.xib,Group 選擇 Resources。
點擊 Create。
打開 HeaderView.xib 并選中 Custom View。在尺寸檢查器中把 Width 設置為 500,Height 設置為 40。
從 Object Library 拖動一個 Label 到 Custom View 的左半邊。打開屬性檢查器,設置它的 Title 為 Section Number,設置 Font Size 為 16。
再拖動一個 Label 到 Custom View 的右半邊。設置它的 Title 為 Image Count,設置 Alignment 為 Right。
選中 Section Number Label,點擊 Pin 按鈕,設置它的 Top 約束為 12,Leading 約束為 20。點擊 Update Frames: Items of New Constraints 和 Add 2 Constraints。
接下來,設置 Image Count Label 的 Top 約束為 11,Trailing 約束為 20,別忘了點擊 Update Frames: Items of New Constraints 和 Add 2 Constraints。
現(xiàn)在我們的標題應該看起來像這樣:
現(xiàn)在我們的標題 UI 已經(jīng)準備好了,我們還需要為它創(chuàng)建一個子類。
在 Xcode 的菜單欄點擊 File → New → File…。選擇 macOS → Source → Cocoa Class,并點擊 Next。把它的類名設置為 HeaderView,并讓它繼承自 NSView,點擊 Next,并在 Group 中選擇 Views。點擊 Create。
打開 HeaderView.swift 然后把里邊的所有內(nèi)容替換為:
// 1 @IBOutlet weak var sectionTitle: NSTextField! @IBOutlet weak var imageCount: NSTextField!// 2 override func draw(_ dirtyRect: NSRect) {super.draw(dirtyRect)NSColor(calibratedWhite: 0.8 , alpha: 0.8).set()NSRectFillUsingOperation(dirtyRect, NSCompositingOperation.sourceOver) }這里的代碼做了這些事兒:
要把 outlet 連接至 Label,打開 HeaderView.xib 并選中 Custom View。在 Identity Inspector 中把 Class 設置為 HeaderView。
在文檔大綱視圖中,按住 Control? 鍵的同時點擊 Header View。在彈出的黑色窗口中,拖動 imageCount 到 Images Count 上來連接 outlet。
對第二個 Label 進行同樣的操作,拖動 sectionTitle 到畫布中的 Section Number Label 上。
實現(xiàn)數(shù)據(jù)源和代理方法
你的標題已經(jīng)完全準備好上戰(zhàn)場了,你需要實現(xiàn) collectionView(_:viewForSupplementaryElementOfKind:at:),把這個標題視圖傳遞給 Collection View:
打開 ViewController.swift 并把這些方法添加到 NSCollectionViewDataSource extension 中:
func collectionView(_ collectionView: NSCollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> NSView {// 1let view = collectionView.makeSupplementaryView(ofKind: NSCollectionElementKindSectionHeader, withIdentifier: "HeaderView", for: indexPath) as! HeaderView// 2view.sectionTitle.stringValue = "Section \(indexPath.section)"let numberOfItemsInSection = imageDirectoryLoader.numberOfItemsInSection(indexPath.section)view.imageCount.stringValue = "\(numberOfItemsInSection) image files"return view }Collection View 會在它需要數(shù)據(jù)源的時候調(diào)用這個方法,并為每個分區(qū)設置標題。這個方法做了這些:
在 ViewController.swift 的最后,添加這個 NSCollectionViewDelegateFlowLayout 擴展:
extension ViewController : NSCollectionViewDelegateFlowLayout {func collectionView(_ collectionView: NSCollectionView, layout collectionViewLayout: NSCollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> NSSize {return imageDirectoryLoader.singleSectionMode ? NSZeroSize : NSSize(width: 1000, height: 40)} }上面這個方法其實是不是必須的,但當你需要設置標題的時候就必須寫上了,因為 Flow Layout(流式布局)的代理需要你提供各個分區(qū)的標題的大小。
如果沒有實現(xiàn)這個方法,你將看不到標題,因為它們的尺寸都是 0。此外,它還會忽略你指定的寬度,而是把標題的寬度設置為 Collection View 的寬度。
在這個例子中,當 Collection View 只有一個分區(qū)的時候,這個方法返回的標題尺寸是 0,否則他會返回 40.
對于使用了 NSCollectionViewDelegateFlowLayout 的 Collection View,你需要把 ViewController 連接到 NSCollectionView 的 delegate。
打開 Main.storyboard 并選中 Collection View。打開連接檢查器,在 Outlets 部分中找到 delegate。拖動他旁邊的小圓點到文檔大綱中的 View Controller 上。
編譯并運行,勾選 Show Sections,可以看到一個個標題把分區(qū)區(qū)分開來:
固定標題
macOS 10.12 中的 NSCollectionViewFlowLayout 新加入了兩個屬性:sectionHeadersPinToVisibleBounds 和 sectionFootersPinToVisibleBounds。
當 sectionHeadersPinToVisibleBounds 設置為 true,最上端的分區(qū)的標題將會固定在頂端,而不會移出界面以外。當你繼續(xù)向下滾動時,下一個標題會把它頂走。這種效果一般被稱為「sticky headers(固定標題)」或「floating headers(浮動標題)」。
把 sectionFootersPinToVisibleBounds 設置為 true 則會把腳注固定在底部。
打開 ViewController.swift,在 configureCollectionView() 方法的底部加入這個方法:
flowLayout.sectionHeadersPinToVisibleBounds = true編譯并運行,勾選 Show Sections 并向下滾動一些,你可以看到第一個區(qū)域已經(jīng)有一些圖片被移出屏幕了,但標題還是固定在頂部:
注意:如果你的 app 需要支持 OS X 10.11 或更老的版本,你需要通過重寫 layoutAttributesForElements(in:) 方法來「手動」實現(xiàn)固定標題。你可以查看[Advanced Collection Views in OS X Tutorial]這篇教程(我正在翻譯~)。
Collection View 的選擇功能
為了顯示一個 item 的被選中狀態(tài),你需要設置一個白色的邊框,沒有被選中的項目將不會顯示這個邊框。
首先,你需要讓我們的 Collection View 支持選中。打開 Main.storyboard,選中 Collection View 并在屬性檢查器里,勾選 Selectable。
勾選 Selectable 開啟了選擇功能,意味著你可以通過點擊一個 item 來選中它。如果你點擊另一個 item,將會取消選擇之前的那個 item 并選中新的 item。
當你選中一個 item:
打開 CollectionViewItem.swift。在 viewDidLoad() 方法的最后追加:
// 1 view.layer?.borderColor = NSColor.white.cgColor // 2 view.layer?.borderWidth = 0.0這段代碼:
要在每次 isSelected 被設置時改變 borderWidth,我們需要把這些代碼添加到 CollectionViewItem 類中:
override var isSelected: Bool {didSet {view.layer?.borderWidth = isSelected ? 5.0 : 0.0}}每次 isSelected 發(fā)生了改變,didSet 將會根據(jù)新的值來設置邊框的寬度。
編譯并運行。點擊一個項目來選中它,你將會看見它周圍出現(xiàn)了邊框。哈哈哈,神奇?!
下一步該做些啥?
點擊這里下載最終完成了的 SlideMagic。
在這個 NSCollectionView 入門教程中,你了解了如何創(chuàng)建你的第一個 Collection View,了解了錯綜復雜的數(shù)據(jù)源 API 和如何處理分區(qū)。至此你已經(jīng)學到了很多,但其實這僅僅是個開始,Collection View 還有很多功能等待你去發(fā)掘。這里有很多值得去探索的東西:
- 通過 Cocoa 的數(shù)據(jù)綁定構(gòu)建「免數(shù)據(jù)源」的 Collection View
- 不同類型的 Item
- 追加和移除 Item
- 自定義布局
- 拖放手勢(Drag and drop)
- 動畫
- 修改 NSCollectionViewFlowLayout
- 收起某個分區(qū)(macOS 10.12 Sierra 的新功能)
你可以在我們的《NSCollectionView 進階教程》(原文|譯文)中了解更多。
《新程序員》:云原生和全面數(shù)字化實踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的[译] NSCollectionView 入门教程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Swift 4正式发布,新功能概览
- 下一篇: 《HiBlogs》重写笔记[1]--从D