My answer is based on what we exactly use in production of our social app Impether, since you asked me on Twitter that you used the app and you saw expanding UITextView there.
First of all, we have a custom UITableViewCell based class containing the UITextView
, which will be expanded (this class has a corresponding xib file also, which you can design on your own):
class MultiLineTextInputTableViewCell: UITableViewCell {
//our cell has also a title, but you
//can get rid of it
@IBOutlet weak var titleLabel: UILabel!
//UITextView we want to expand
@IBOutlet weak var textView: UITextView!
override init(style: UITableViewCellStyle, reuseIdentifier: String!) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
/// Custom setter so we can initialize the height of the text view
var textString: String {
get {
return textView?.text ?? ""
}
set {
if let textView = textView {
textView.text = newValue
textView.delegate?.textViewDidChange?(textView)
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Disable scrolling inside the text view so we enlarge to fitted size
textView?.scrollEnabled = false
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
if selected {
textView?.becomeFirstResponder()
} else {
textView?.resignFirstResponder()
}
}
}
Having a custom cell defined, you can use it in a UITableViewController
based class like that:
class YourTableViewController: UITableViewController {
//in case where you want to have
//multiple expanding text views
var activeTextView: UITextView?
override func viewDidLoad() {
super.viewDidLoad()
//registering nib for a cell to reuse
tableView.registerNib(
UINib(nibName: "MultiLineTextInputTableViewCell", bundle: nil),
forCellReuseIdentifier: "MultiLineTextInputTableViewCell")
}
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
//hide keyboard when view controller disappeared
if let textView = activeTextView {
textView.resignFirstResponder()
}
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
//put your value here
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//put your value here
return 2
}
override func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let row = indexPath.row
let cell = tableView.dequeueReusableCellWithIdentifier(
"MultiLineTextInputTableViewCell",
forIndexPath: indexPath) as! MultiLineTextInputTableViewCell
let titleText = "Title label for your cell"
let textValue = "Text value you want for your text view"
cell.titleLabel.text = titleText
cell.textView.text = textValue
//store row of a cell as a tag, so you can know
//which row to reload when the text view is expanded
cell.textView.tag = row
cell.textView.delegate = self
return cell
}
override func tableView(tableView: UITableView,
estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
//standard row height
return 44.0
}
override func tableView(tableView: UITableView,
heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
// Override to support conditional editing of the table view.
override func tableView(tableView: UITableView,
canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
}
//extension containing method responsible for expanding text view
extension YourTableViewController: UITextViewDelegate {
func textViewDidEndEditing(textView: UITextView) {
let value = textView.text
//you can do something here when editing is ended
}
func textView(textView: UITextView, shouldChangeTextInRange range: NSRange,
replacementText text: String) -> Bool {
//if you hit "Enter" you resign first responder
//and don't put this character into text view text
if text == "\n" {
textView.resignFirstResponder()
return false
}
return true
}
func textViewDidBeginEditing(textView: UITextView) {
activeTextView = textView
}
//this actually resize a text view
func textViewDidChange(textView: UITextView) {
let size = textView.bounds.size
let newSize = textView.sizeThatFits(CGSize(width: size.width,
height: CGFloat.max))
// Resize the cell only when cell's size is changed
if size.height != newSize.height {
UIView.setAnimationsEnabled(false)
tableView?.beginUpdates()
tableView?.endUpdates()
UIView.setAnimationsEnabled(true)
let thisIndexPath = NSIndexPath(forRow: textView.tag, inSection: 0)
tableView?.scrollToRowAtIndexPath(thisIndexPath,
atScrollPosition: .Bottom,
animated: false)
}
}
}