本文翻譯自 Self-sizing UITextView in a UITableView using Auto Layout (like Reminders.app)
現(xiàn)在,很多開發(fā)者自己寫計算文字尺寸谬墙,但這樣會錯誤地把代碼搞的太過復(fù)雜。不要這樣做替梨!如果正確設(shè)置了 Auto Layout糜颠,根本不需要使用 textView.sizeThatFit()裹匙!
優(yōu)雅的解決方案
第一步
從新的 Single View 項目開始(使用 Swift)然后打開 Main.storyboard 并移除 Xcode 好心添加的視圖控制器》柙埽現(xiàn)在,打開 ViewController.swift 文件然后將 ViewController 改為繼承自 UITableViewController抑淫,而不是 UIViewController绷落。
import UIKit
class ViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
}
第二步
回到 Main.storyboard姥闪,添加一個 UITableViewController始苇,選中 Table View Cell,拖一個 UITextView 進(jìn)去筐喳。正確地為新添加的 Text View 設(shè)置 Auto Layout constraints 是相當(dāng)關(guān)鍵的催式!
熱心提示:當(dāng)你想快速把視圖固定在父視圖上時,使用這個小竅門:
選擇視圖避归,然后點擊右下角的 Pin 按鈕荣月。在彈出窗口里,輸入 left梳毙,top哺窄,right 和 bottom 到父視圖的距離,然后點擊 Add 4 Constraints账锹,就會立刻獲得固定好的視圖了萌业!
PS:確定你點擊了輸入框旁邊的紅色 constraint 標(biāo)記。這樣會添加你想要的 constraints(如果你改了框里的值奸柬,Xcode 會自動選中它們)生年。
我把行高更改為 70,這是我最后的結(jié)果:
第三步
現(xiàn)在我們需要添加一個自定義 cell廓奕,帶有一個連接到 UITextView 的 IBOutlet抱婉。
(為了簡化之目的,我在 ViewController.swift 文件中添加了這 3 行桌粉,這樣就不用添加新的文件了)
// 使用一個簡單的自定義 cell 這樣我們就可以設(shè)置 text view 的 delegate 了 ??
class MyCell: UITableViewCell {
@IBOutlet weak var textView: UITextView!
}
回到 Main.storyboard 然后把自定義 cell 的類設(shè)置為 MyCell:
第四步
把 text view 連接到 IBOutlet:
第五步
如果你禁用了 text view 的 Scrolling Enabled 屬性蒸绩,它將計算自己的固有內(nèi)容大小,并阻止?jié)L動(doh)铃肯。這意味著它會了解自己所持有的內(nèi)容患亿。相當(dāng)神奇。
第六步
現(xiàn)在要為 View Controller 施加一點魔法了缘薛!
要讓 Auto Layout 和 UITableView 良好合作窍育,最重要的是把 table view 的 estimatedRowHeight
設(shè)置為大于零的值,并把 rowHeight
設(shè)置為 UITableViewAutomaticDimension
在 viewDidLoad 里這樣做:
class ViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 70
tableView.rowHeight = UITableViewAutomaticDimension
}
...
}
這將告訴 table view 由它來負(fù)責(zé)計算 cell 的尺寸(在沒有我們干預(yù)的情況下)宴胧。
為了演示之目的漱抓,我這樣設(shè)置了 delegate 代碼,以便在一個 section 里返回 5 個 items:
class ViewController: UITableViewController {
...
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
...
}
對于第三個函數(shù)恕齐,需要做的就是 dequeue cell 然后將其 text view 的 delegate 設(shè)為 self乞娄。我們還要實現(xiàn) textViewDidChange 回調(diào)以確保修改文字時正確調(diào)整表格大小。
class ViewController: UITableViewController {
...
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! MyCell
cell.textView.delegate = self
return cell
}
}
由于我們的 View Controller 不符合 UITextViewDelegate,所以要在這個文件里添加一個 extension仪或。
**注意:奇跡要發(fā)生了确镊。如果我們調(diào)用 tableView.reloadData()
會導(dǎo)致 text view 失去第一響應(yīng)者并隱藏鍵盤。
蹩腳的用戶體驗和難以使用的 App范删!
作為替代蕾域,我們給 table view 做了兩次調(diào)用:
...
extension ViewController: UITextViewDelegate {
func textViewDidChange(textView: UITextView) {
tableView.beginUpdates()
tableView.endUpdates()
}
}
...
修正:你們可能會發(fā)現(xiàn),上面的代碼 UI 有一個 bug —— 如果編輯最后幾行到旦,table view 會跳來跳去旨巷。我還沒有找到最終的解決方案,但禁用動畫和重置 table view 的 contentOffset 會修復(fù)這個跳躍的問題添忘。更新后的 textViewDidChange 就像這樣:
...
extension ViewController: UITextViewDelegate {
func textViewDidChange(textView: UITextView) {
let currentOffset = tableView.contentOffset
UIView.setAnimationsEnabled(false)
tableView.beginUpdates()
tableView.endUpdates()
UIView.setAnimationsEnabled(true)
tableView.setContentOffset(currentOffset, animated: false)
}
}
...
這不會 reload 表格但會調(diào)整 cell 的高度(如果需要的話)采呐。很神奇!??終于可以啟動最終的 App 了搁骑,看看它是否可以正常工作(當(dāng)然可以)斧吐。
代碼
如果要看完整的實例項目,可以看一下我 GitHub 上的 repo仲器。