I have a UITableView with dynamically sized prototype cells using auto layout to fit their contents. In one of my prototype cells I have a UITextView which I have configured to dynamically change its height and become scrollable once the TextViews height reaches a threshold(6 lines of text). This process also works in reverse, shrinking and becoming un-scrollable when below the threshold.
As of right now, when I select the UITextView to enter text the TableView scrolls up to make room for the keyboard as it should, however the last line of text is partially hidden by the keyboard.
My initial thought is to make the TableView scroll up with each new line of text entered so that the bottom edge of the TextView is always right above the top of the keyboard.
What is the preferred/correct method of doing this? I've been reading a lot of contradictions about resizing the TextView along with these words (contentInset, scrollRectToVisible)
I have found a similar question which illustrates the problem but the answer that came out of that was it was a bug in iOS 7. I'm working with iOS 10.3
TableView.swift
class NewRecipe: UITableViewController, UIPickerViewDataSource, UIPickerViewDelegate, UITextFieldDelegate, ExpandingCellDelegate {
@IBOutlet weak var uiTable: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.separatorStyle = .none
self.tableView.allowsSelection = false
}
func updateCellHeight(_ indexPath: IndexPath, comment: String) {
UIView.setAnimationsEnabled(false)
self.uiTable.beginUpdates()
self.uiTable.endUpdates()
UIView.setAnimationsEnabled(true)
}
// To enable self-sizing table view cells
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
//print("setting automatic dim 1")
//return UITableViewAutomaticDimension
if (indexPath.row == 4) {
//setting cell size for the image
let screenSize = UIScreen.main.bounds
let screenHeight = screenSize.height
return screenHeight/2
} else {
// set each row to be self sizing!
return UITableViewAutomaticDimension
}
}
// To enable self-sizing table view cells
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
//print("setting automatic dim 2")
return UITableViewAutomaticDimension
}
// determine number of rows
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//print("returning number of rows = 7")
return 9
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch (indexPath.row) {
case 4:
let cell = tableView.dequeueReusableCell(withIdentifier: "Image Cell", for: indexPath) as! ImageTableViewCell
cell.foodImage.image = UIImage(named: "defaultPhoto")
return cell
case 0, 1, 2, 3:
let cell = tableView.dequeueReusableCell(withIdentifier: "Label-Text Field Split Cell", for: indexPath) as! LabelTextFieldSplitCell
if (indexPath.row == 1) {
cell.label.text = "Recipe Title "
} else if (indexPath.row == 2) {
cell.label.text = "Quantity / Servings "
} else if (indexPath.row == 3) {
cell.label.text = "Prep Time "
} else {
cell.label.text = "Cook Time "
}
return cell
case 5:
let cell = tableView.dequeueReusableCell(withIdentifier: "Stack-Label Split Cell", for: indexPath) as! StackLabelSplitCell
return cell
case 6:
let cell = tableView.dequeueReusableCell(withIdentifier: "Buttons Cell", for: indexPath) as! ButtonsCell
return cell
case 7:
let cell = tableView.dequeueReusableCell(withIdentifier: "Picker-Text View Split Cell", for: indexPath) as! PickerTextViewCell
cell.picker.delegate = self
cell.cellIndexPath = indexPath
cell.delegate = self as ExpandingCellDelegate
return cell
case 8:
let cell = tableView.dequeueReusableCell(withIdentifier: "Text View Cell", for: indexPath) as! TextViewCell
cell.cellIndexPath = indexPath
cell.delegate = self as ExpandingCellDelegate
return cell
default:
let cell = tableView.dequeueReusableCell(withIdentifier: "Single Text Field Cell", for: indexPath) as! SingleTextFieldCell
return cell
}
}
//MARK: UIPickerViewDelegate
// set number of columns
func numberOfComponents(in pickerView: UIPickerView) -> Int {
//print("measurement count is " , measurements.count)
return measurements.count
}
func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
return 35.0
}
// set number of rows for each column
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return (measurements[component] as [AnyObject]).count + 1
}
func pickerView(_ pickerView: UIPickerView, viewForRow row: Int, forComponent component: Int, reusing view: UIView?) -> UIView {
// set the first item in picker to -- then set all other items to the strings in the array @ measurements[component][row-1]
let string = row == 0 ? "--" : "\((measurements[component] as [AnyObject])[row - 1])"
// styling the picker labels
let pickerLabel = UILabel()
pickerLabel.text = string
pickerLabel.textColor = UIColor.black
pickerLabel.font = UIFont(name: "Helvetica Neue", size: 17)
pickerLabel.textAlignment = .left
return pickerLabel
}
// retreiving the values out of the picker
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
/*
switch (component) {
case (0):
let wholeNum = row == 0 ? 0 : measurements[component][row-1] as? Int
case (1):
let fraction = row == 0 ? "" : measurements[component][row-1] as? String
case (2):
let unit = row == 0 ? "" : measurements[component][row-1] as? String
default: ()
}
*/
}
//MARK: UITextFieldDelegate
func textFieldDidBeginEditing(_ textField: UITextField) {
//This code disables the Save button while the user is editing the text field.
//saveButton.isEnabled = false
//self.view.bounds.origin.y = 60
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// Hide the keyboard when the user presses Done.
textField.resignFirstResponder()
return true
}
func textFieldDidEndEditing(_ textField: UITextField) {
// The first line calls updateSaveButtonState() to check if the text field has text in it, which enables the Save button if it does. The second line sets the title of the scene in the nav bar to that text.
//updateSaveButtonState()
//navigationItem.title = textField.text
}
// func textViewDidBeginEditing(_ textView: UITextView)
// {
// if UIScreen.main.bounds.height < 568 {
// UIView.animate(withDuration: 0.75, animations: {
// self.view.bounds.origin.y = 60
// })
// }
// }
//
// func textViewDidEndEditing(_ textView: UITextView)
// {
// if UIScreen.main.bounds.height < 568 {
// UIView.animate(withDuration: 0.75, animations: {
// self.view.bounds.origin.y = 0
// })
// }
// }
}
TextViewCell.swift
class TextViewCell: UITableViewCell, UITextViewDelegate {
var delegate: ExpandingCellDelegate!
var cellIndexPath: IndexPath!
@IBOutlet weak var textHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var textViewBottomConstraint: NSLayoutConstraint!
override func awakeFromNib() {
super.awakeFromNib()
self.textView.delegate = self
}
@IBOutlet weak var label: UILabel! {
didSet {
label.font = UIFont.systemFont(ofSize: 15)
label.textColor = UIColor.lightGray
}
}
@IBOutlet weak var textView: UITextView! {
didSet {
textView.layer.borderWidth = 2
textView.layer.cornerRadius = 5
textView.layer.borderColor = UIColor(red: 0.9, green: 0.9, blue: 0.9, alpha: 1.0).cgColor
}
}
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
//print("Content Height \(self.textView.contentSize.height) ")
if(self.textView.contentSize.height < self.textHeightConstraint.constant) {
self.textView.isScrollEnabled = false
} else {
self.textView.isScrollEnabled = true
}
self.delegate.updateCellHeight(self.cellIndexPath, comment: textView.text)
return true
}
}