Part 1 時我們實作了標籤輸入的基本功能,但是我們的每個標籤,因為沒有給 UICollectionViewCell 寬度,所以被擠壓到剩下一個小方塊,原因是因為 UICollectionViewFlowLayout 預設每一個 Cell 的大小為 50x50。
但是我們理想的畫面,應該是每個標籤應該要有足夠空間顯示內容,並從左邊開始往右排版,盡可能塞滿寬度,但又保有固定的間距,如下圖所示:
實作上,我們可以給標籤足夠寬度顯示,然後限制其最大寬度,避免因為標籤字數太多破壞排版,因此每個標籤排版須符合下列規則:
首先,我們要調整每個 UICollectionViewCell 的長寬,我們可以實作 sizeForItemAt 告訴 UICollectionView 我們的 Cell 有多大,如下:
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if indexPath.row == tags.count {
// 最後的輸入框先固定大小(之後會調整)
return CGSize(width: 100, height: 50)
} else {
let cell = TagCollectionViewCell()
cell.label.text = tags[indexPath.row]
cell.label.sizeToFit()
// 標籤寬度則是內文加上左右兩邊的 Margin
// 過長時,限制其最大寬度為螢幕的一半
return CGSize(width: min(cell.label.frame.width + cell.layoutMargins.right + cell.layoutMargins.left, tagCollectionView.frame.width / 2), height: 50);
}
}
我們依據 Cell 的類型給不同的寬度,在此我們先給最後的輸入框固定長寬,稍後會針對這個元件處理,因為每次使用者打字,輸入框的寬度都必須跟著調整。
而標籤的部分,我們透過計算 TagCollectionViewCell 的標籤文字大小,加上標籤的左右 Margin,得到標籤的寬度,但是要特別注意的是,如果標籤太寬,就要自動縮成螢幕寬的一半(在此也就是 UICollectionView 的一半寬)。
標籤過長時,限制標籤的寬度後,理所當然標籤的文字會被切斷,在這邊我們可以將標籤的文字設定成 byTruncatingMiddle,也就是將文字的中段用「...」取代,我們可以將這個設定加入到 TagCollectionViewCell,如下:
private func addSubview(label: UILabel) {
contentView.addSubview(label)
let margin = contentView.layoutMarginsGuide
// ...略
// 將文字中斷以「...」取代
label.lineBreakMode = .byTruncatingMiddle
}
前段我們新增了計算長寬的邏輯後,我們可以發現在新增標籤時,長寬已經可以自動幫我們計算,並適時地換行,但是換行造成了一個問題,UICollectionView 不會自動幫我們捲動到最下面,也因此我們最後的輸入框,會被往下擠然後消失在畫面中,使用者就不知道自己打什麼字,還得自己手動往下捲才能繼續輸入,相當麻煩!
因此,我們必須在新標籤建立後,也就是可能造成換行的情形時,請 UICollectionView 幫我們捲動到最後的輸入框,以便使用者持續地新增標籤。
我們可以在 QuickCreateViewController 監控標籤新增的 didAdd(tag:) 方法內加入這段程式,如下:
extension QuickCreateViewController: TagTextFieldDelegate {
func didAdd(tag: String) {
tags.append(tag)
tagCollectionView.reloadData()
// 請 UICollectionView 幫我們捲動到最後的輸入框
tagCollectionView.scrollToItem(at: IndexPath(item: tags.count, section: 0), at: .bottom, animated: true)
}
}
備註:捲動的部分,在後面有發現一些問題,之後會專門針對這個問題寫一篇。
至此,我們已經完成大部分 UICollectionView 介面的邏輯了,可是 UICollectionView 預設會幫我們盡量地平均分佈這些 Cell,如下圖所示:
可是我們希望所有標籤要靠左,並保持固定的距離,直到寬度不夠時,才自動換行。因此,我們需要調整一下 UICollectionViewFlowLayout 的排版邏輯,所以我們必須繼承 UICollectionViewFlowLayout 進行一些調整。
首先,我們先建立一個 UICollectionViewFlowLayout 的小孩,如下:
class TagCollectionViewFlowLayout: UICollectionViewFlowLayout {
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributes = super.layoutAttributesForElements(in: rect)
// 稍後會針對 attributes 調整
return attributes
}
}
透過 layoutAttributesForElements(in:) 這個方法,我們可以調整 UICollectionView 的排版方式(為什麼?),調整的概念是:「先產生原生 UICollectionViewFlowLayout 的排版,然後調整 x 軸的位置,使其靠左」,如下所示:
override func layoutAttributesForElements(in rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
let attributes = super.layoutAttributesForElements(in: rect)
var leftMargin = sectionInset.left
var maxY: CGFloat = -1.0
attributes?.forEach { layoutAttribute in
if layoutAttribute.frame.origin.y >= maxY {
leftMargin = sectionInset.left
}
layoutAttribute.frame.origin.x = leftMargin
leftMargin += layoutAttribute.frame.width + minimumInteritemSpacing
maxY = max(layoutAttribute.frame.maxY, maxY)
}
return attributes
}
最後,再請 UICollectionView 使用我們修改後的 Layout,如下:
let tagCollectionView: UICollectionView = {
return UICollectionView(frame: CGRect.zero, collectionViewLayout: TagCollectionViewFlowLayout())
}()
最後,經過本次調整,我們的標籤輸入從原本的:
進化成:
程式碼:GitHub
下一篇會針對最後「紅色」那塊,也就是標籤輸入框做調整,目的是讓輸入框的寬度可以隨著輸入的字調整,這樣就不會造成最後明明還有位置,紅色卻還是被往下擠的狀況。
請問如何在tableView Cell內放入collectionView
在
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "DraftNameCollectionViewCell", for: indexPath) as! DraftNameCollectionViewCell
}
使用Cell時會報錯
libc++abi.dylib: terminating with uncaught exception of type NSException
Thread 1: Exception: "-[NSConcreteValue setSizeHasBeenSet:]: unrecognized selector sent to instance 0x6000015bd640"