iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 5
1

前言

這篇開始會進入實作「標籤輸入」的系列,我已經建立一個基礎的程式架構,簡單來說新增兩個 UIViewController,也就是兩個介面,第一個介面是之後我們顯示「帳」的列表,第二個介面是我們的主角「快速記帳」。

請參考 GitHub

基於這個程式架構,我們接下來會一一實作「標籤輸入」的程式。

實作 UICollectionViewCell

首先我們先實作稍後會用到的兩種 UICollectionViewCell。

  • TagCollectionViewCell:顯示標籤的 UICollectionViewCell
  • TagTextFieldCollectionViewCell:新增標籤的 UICollectionViewCell

TagCollectionViewCell

先簡單的在裡面放一個 UILabel,我們之後會一起調整內容顯示的邏輯。

class TagCollectionViewCell: UICollectionViewCell {
    lazy var label: UILabel = {
        return UILabel()
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)

        addSubview(label: label)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func addSubview(label: UILabel) {
        contentView.addSubview(label)

        let margin = contentView.layoutMarginsGuide

        label.translatesAutoresizingMaskIntoConstraints = false
        label.leftAnchor.constraint(equalTo: margin.leftAnchor).isActive = true
        label.rightAnchor.constraint(equalTo: margin.rightAnchor).isActive = true
        label.topAnchor.constraint(equalTo: margin.topAnchor).isActive = true
        label.bottomAnchor.constraint(equalTo: margin.bottomAnchor).isActive = true
    }
}

TagTextFieldCollectionViewCell

一樣先在裡面放一個 UITextField,之後再一起調整排版的邏輯。

須注意的是,我們要監控 UITextField 按下 Return 的事件,代表使用者輸入完標籤後,確定要建立該標籤。

因此,我們需要先定義一個 Delegate Protocol:

protocol TagTextFieldDelegate {
    func didAdd(tag: String)
}

接著實作 TagTextFieldCollectionViewCell:

class TagTextFieldCollectionViewCell: UICollectionViewCell {
    lazy var textField: UITextField = {
        var textField = UITextField()
        textField.delegate = self
        return textField
    }()

    var delegate: TagTextFieldDelegate?

    override init(frame: CGRect) {
        super.init(frame: frame)

        addSubview(textField: textField)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func addSubview(textField: UITextField) {
        contentView.addSubview(textField)

        let margin = contentView.layoutMarginsGuide

        textField.translatesAutoresizingMaskIntoConstraints = false
        textField.leftAnchor.constraint(equalTo: margin.leftAnchor).isActive = true
        textField.rightAnchor.constraint(equalTo: margin.rightAnchor).isActive = true
        textField.topAnchor.constraint(equalTo: margin.topAnchor).isActive = true
        textField.bottomAnchor.constraint(equalTo: margin.bottomAnchor).isActive = true
    }
}

只要 UITextField 內有文字,並按下 Return 鍵後,我們就把目前 UITextField 內的文字取出來,當做標籤丟給 TagTextFieldDelegate,如下:

extension TagTextFieldCollectionViewCell: UITextFieldDelegate {
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        guard let text = textField.text, !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else {
            return false
        }

        delegate?.didAdd(tag: text.trimmingCharacters(in: .whitespacesAndNewlines))

        textField.text = ""

        return true
    }
}

稍後我們就可以在 UIViewController 利用這個 Delegate,取得使用者建立的標籤。

QuickCreateViewController:用來快速新增「帳」的介面

首先我們先在介面上加入 UICollectionView,給個大概的高度,先填上背景色方便我們辨識,並註冊前面實作的兩種 UICollectionViewCell:

class QuickCreateViewController: UIViewController {
    let tagCollectionView: UICollectionView = UICollectionView(frame: CGRect.zero, collectionViewLayout: UICollectionViewFlowLayout())
    var tags: [String] = []

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = MMColor.white

        addSubview(tagCollectionView: tagCollectionView)
    }

    private func addSubview(tagCollectionView: UICollectionView) {
        view.addSubview(tagCollectionView)
        tagCollectionView.translatesAutoresizingMaskIntoConstraints = false
        tagCollectionView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
        tagCollectionView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
        tagCollectionView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor).isActive = true
        tagCollectionView.heightAnchor.constraint(equalToConstant: 44 * 3).isActive = true
        tagCollectionView.backgroundColor = MMColor.black
        tagCollectionView.dataSource = self
        tagCollectionView.delegate = self
        tagCollectionView.register(TagCollectionViewCell.self, forCellWithReuseIdentifier: NSStringFromClass(TagCollectionViewCell.self))
        tagCollectionView.register(TagTextFieldCollectionViewCell.self, forCellWithReuseIdentifier: NSStringFromClass(TagTextFieldCollectionViewCell.self))
    }
}

並且先簡單的加上顯示 UICollectionViewCell 的邏輯:

  1. 用 TagCollectionViewCell 顯示目前新增的標籤
  2. 在最後加上 TagTextFieldCollectionViewCell 方便使用者新增新的標籤

如下:

extension QuickCreateViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return tags.count + 1
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        if indexPath.row == tags.count {
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(TagTextFieldCollectionViewCell.self), for: indexPath) as? TagTextFieldCollectionViewCell else {
                fatalError()
            }

            cell.backgroundColor = MMColor.red
            cell.delegate = self

            return cell
        } else {
            guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NSStringFromClass(TagCollectionViewCell.self), for: indexPath) as? TagCollectionViewCell else {
                fatalError()
            }

            cell.backgroundColor = MMColor.white
            cell.label.text = tags[indexPath.row]

            return cell
        }
    }
}

接著加上 TagTextFieldDelegate 的實作,一但使用者在輸入框內按下 Return,我們取得標籤後,加入到陣列裡,並請 UICollectionView 重新讀取資料:

extension QuickCreateViewController: TagTextFieldDelegate {
    func didAdd(tag: String) {
        tags.append(tag)
        tagCollectionView.reloadData()
    }
}

最後,我們需要監控 UICollectionView 重新顯示 Cell 的事件,並在 UICollectionView 重新顯示資料時,再把 TagTextFieldCollectionViewCell 裡面的 UITextField 重新設為 First Responder,否則每次使用者新增完一個 Tag,並重新顯示 UICollectionView 時,鍵盤都會隱藏起來,就不能快速地連續新增標籤。

extension QuickCreateViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
        (cell as? TagTextFieldCollectionViewCell)?.textField.becomeFirstResponder()
    }
}

至此,完整程式碼請看 GitHub

下一篇我們將針對 UICollectionView 調整排版。

備註:有些地方用了 lazy var,但其實沒必要,之後預計會有一篇重構,到時候再來處理這些誤用。


上一篇
Money Mom - 標籤輸入
下一篇
Money Mom - 實作標籤輸入 Part 2
系列文
iOS 三十天上架記帳 APP30

1 則留言

0
海綿寶寶
iT邦超人 1 級 ‧ 2017-12-24 10:26:29

做個實驗一下
似乎 force refresh 頁面個幾次之後就會有高亮了

class TagCollectionViewCell: UICollectionViewCell {
    lazy var label: UILabel = {
        return UILabel()
    }()

    override init(frame: CGRect) {
        super.init(frame: frame)

        addSubview(label: label)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    private func addSubview(label: UILabel) {
        contentView.addSubview(label)

        let margin = contentView.layoutMarginsGuide

        label.translatesAutoresizingMaskIntoConstraints = false
        label.leftAnchor.constraint(equalTo: margin.leftAnchor).isActive = true
        label.rightAnchor.constraint(equalTo: margin.rightAnchor).isActive = true
        label.topAnchor.constraint(equalTo: margin.topAnchor).isActive = true
        label.bottomAnchor.constraint(equalTo: margin.bottomAnchor).isActive = true
    }
}

我強制重整還是沒有欸

我要留言

立即登入留言