UITextView inside UITableViewCell Keyboard Issue - ios

As new to IOS development, I’ve been struggling with the problem of keyboard movement for several days. I embedded UISrollView which has UITableView with fixed height constraint inside UIViewController. Also I've created custom cells with unscrollable UITextView inside. The problem is that the keyboard doesn't move down when I type inside TextView.
I followed guidance from here: Embedding UITextView inside UITableViewCell , but there is an example only with UITableView, not UIScrollView -> UITableView
Hope you understand my problem. Thanks in advance
Some additional Images/GIFs are attached below:
My UIView hierarchy structure
Expected Behaviour of keyboard
Current behaviour of UIKeyboard
View Controller code:
import UIKit
class ViewController: UIViewController {
//MARK: - Constraints
#IBOutlet weak var tableViewHeightConstraint: NSLayoutConstraint!
//MARK: - Outlets
#IBOutlet weak var scrollView: UIScrollView!
#IBOutlet weak var tableView: UITableView!
override func viewWillLayoutSubviews() {
super.updateViewConstraints()
self.tableViewHeightConstraint.constant = self.tableView.contentSize.height
}
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
setupToHideKeyboardOnTapOnView()
registerCell()
}
}
//MARK: - Extensions
extension ViewController {
func setupUI() {
tableView.isScrollEnabled = false
}
func registerCell() {
tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: TableViewCell.reuseId)
}
func setupToHideKeyboardOnTapOnView()
{
let tap: UITapGestureRecognizer = UITapGestureRecognizer(
target: self,
action: #selector(dismissKeyboard))
tap.cancelsTouchesInView = false
view.addGestureRecognizer(tap)
}
#objc func dismissKeyboard()
{
view.endEditing(true)
}
}
extension ViewController: UITableViewDataSource, UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
self.viewWillLayoutSubviews()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: TableViewCell.reuseId, for: indexPath) as! TableViewCell
cell.textChanged {[weak tableView] (_) in
DispatchQueue.main.async {
tableView?.beginUpdates()
tableView?.endUpdates()
self.viewWillLayoutSubviews()
}
}
return cell
}
}
Custom cell code:
import UIKit
class TableViewCell: UITableViewCell, UITextViewDelegate {
//MARK: - Outlets
#IBOutlet weak var textView: UITextView!
var textChanged: ((String) -> Void)?
static let reuseId = "TableViewCell"
override func awakeFromNib() {
super.awakeFromNib()
textView.isScrollEnabled = false
textView.delegate = self
}
func textChanged(action: #escaping (String) -> Void) {
self.textChanged = action
}
func textViewDidChange(_ textView: UITextView) {
textChanged?(textView.text)
}
}

Chris is right, the current function you're experiencing is because you are adding new text in the middle of the scroll view rather than at the bottom of the scroll view.
If you want to make sure that the content you're editing is constantly above the keyboard then you'll have to calculate where the content ends and keyboard begins and move your views accordingly.
Here's some additional info that may help with keyboard issues.

Related

iOS - UITableView inside a UIView

I want to display a UITableView inside a UIViewController. This View Controller contains a UISegmentedControl with two screens (FirstViewControllerand SecondViewController).
The first View Controller is the one that contains the UIViewTable (please don't mind the second).
When I execute the app in the simulator, everything works fine, but when I try to scroll the table view in the first ViewController, the cells disappear. The only way to make them reappear is to kill the app and reopen it again.
I'm new to iOS development (I come from Android), and I'm obviously missing something here.
I already tried adding a UIViewTable outside a container UIView and it works fine. So I'm guessing the problem has to do with the container or the segmented control...
Here's my implementation:
Storyboard
UIViewController with UISegmentedControl and UIView (which will contain the two screens of the segmented control).
View Controller
#IBOutlet weak var container: UIView!
var sectionViews:[UIView]!
override func viewDidLoad() {
super.viewDidLoad()
sectionViews = [UIView]()
sectionViews.append(FirstViewController().view)
sectionViews.append(SecondViewController().view)
for v in sectionViews {
container.addSubview(v)
}
container.bringSubviewToFront(sectionViews[0])
}
#IBAction func switchViewsAction(_ sender: UISegmentedControl) {
self.container.bringSubviewToFront(self.sectionViews[sender.selectedSegmentIndex])
}
First View Controller
The FirstViewController has a swift and a xib files, and has two files Cell.swift and Cell.xib for the table cell.
FirstViewController.swift
#IBOutlet weak var tableView: UITableView!
let cellID = "CellId"
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(UINib(nibName: "Cell", bundle: nil), forCellReuseIdentifier: cellID)
self.tableView.dataSource = self
self.tableView.delegate = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCell(withIdentifier: cellID, for: indexPath) as! Cell
cell.label.text = "\(indexPath.row)"
return cell
}
FirstViewController.xib
Cell.xib
Any help will be appreciated!
Thanks
One obvious problem is that you are saying container.addSubview(v) without giving v any frame or constraints. Since you use autolayout to position container, you ought to use autolayout to position v as well. You should set its top, bottom, leading, and trailing anchors to equal those of container with a constant of zero. (And set its translates... to false.) Do that for both cases of v in the loop.
However, there is much more serious problem, which is that the view controllers that you create by saying FirstViewController() and SecondViewController() are not retained. Therefore they vanish in a puff of smoke. They thus lose their functionality; for example, the table view no longer has any data source or delegate so it has no cells.
What you are doing is totally illegal. You cannot simply use a view controller to "dumpster-dive" as a way of grabbing its view and shove its view, willy-nilly, into the interface. You must make the view controller a child view controller of your parent view controller (Item in this case). There is an elaborate dance you must do in order to ensure that the child view controller has its proper place in the view controller hierarchy and receives in good order all the messages that a view controller must receive, and you are not doing the dance.
For examples of how to do the dance, see for instance my answers
https://stackoverflow.com/a/41898819/341994
and
https://stackoverflow.com/a/52666843/341994
import UIKit
class TestViewController: UIViewController , UITableViewDataSource, UITableViewDelegate{
#IBOutlet weak var segmentControlOutlet: UISegmentedControl!
#IBOutlet weak var tableView: UITableView!
var arrayName = ["Label1", "Label2", "Label3","Label4","Label5","Label6","Label7","Label8","Label9","Label10"]
var arrayName2 = ["Label1", "Label2", "Label3","Label4","Label5"]
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func segmentControlAction(_ sender: UISegmentedControl) {
self.tableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if segmentControlOutlet.selectedSegmentIndex == 0 {
return arrayName.count
}else{
return arrayName2.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TestTableViewCell", for: indexPath) as! TestTableViewCell
if segmentControlOutlet.selectedSegmentIndex == 0 {
cell.textLabel?.text = arrayName[indexPath.row]
}else{
cell.textLabel?.text = arrayName2[indexPath.row]
}
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 50
}
}
And this code is for UITableViewCell Class:-
import UIKit
class TestTableViewCell: UITableViewCell {
#IBOutlet weak var labelName: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}

how to add label text to tableViewCell

I'm practicing creating an app where I have a label that gets its text from an UITextField when the user presses a button. Now, I added another button and a tableview and I want to be able to "save" the label's text to the table cells with the same mechanism of stopwatch's laps.
So, to be clear, I want the button to transfer the label's text to the table view cells each time I press it.
After your save button, you need to store the texts somewhere and reload the table. (Or insert it with animation)
class ViewController: UIViewController {
#IBOutlet private var textField: UITextField!
#IBOutlet private var tableView: UITableView!
var texts: [String] = [] {
didSet { tableView.reloadData() }
}
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "SimpleCell")
tableView.dataSource = self
}
#IBAction func saveButtonTapped(_ sender: UIButton) {
guard let newText = textField.text else { return }
self.texts.append(newText)
}
}
And in tableView dataSource methods:
extension ViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return texts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SimpleCell", for: indexPath)!
cell.textLabel?.text = texts[indexPath.row]
return cell
}
}

TableViewCell is not clickable with one finger tap, but it is with two fingers

I created a table view and the tableViewCell is not clickable with one finger, but when I try to click the tableViewCell with two fingers the click event takes place. I don't know why this occurres. I created a custom cell in tableView.
InviteVC
import UIKit
class InvitePeopleVC: UIViewController, UITextFieldDelegate, UITableViewDelegate, UITableViewDataSource {
var nameArray = ["Alwin Lazar", "Ajith Ramesh CR", "Ebrahim KK", "Vishnu Prakash"]
var emailArray = ["alwin#xeoscript.com", "ajith#xeoscript.com", "ebrahim#xeoscript.com", "vishnu#xeoscript.com"]
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var doneImg: UIImageView!
#IBOutlet weak var nameTextFld: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
delegates()
uiModifications()
gestureRecognizers()
}
func delegates() {
tableView.dataSource = self
tableView.delegate = self
nameTextFld.delegate = self
}
func uiModifications() {
nameTextFld.attributedPlaceholder = NSAttributedString(string: "Name or email address", attributes: [NSForegroundColorAttributeName: UIColor.white])
}
func gestureRecognizers() {
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(InvitePeopleVC.dismissKeyboard)))
self.doneImg.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(InvitePeopleVC.doneImgPressed)))
}
func dismissKeyboard() {
nameTextFld.resignFirstResponder()
}
func doneImgPressed() {
print("done Image tapped")
}
func inviteBtnPressed() {
print("invite button pressed")
}
// UITextFieldDelegate method
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
if textField == self.nameTextFld {
self.nameTextFld.resignFirstResponder()
}
return true
}
// TableView DataSource methods
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return nameArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = "InviteCell"
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) as! InviteCell
cell.nameLbl.text = nameArray[indexPath.row]
cell.emailLbl.text = emailArray[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100
}
// TableView Delegate methods
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("selected row is \(indexPath.row)")
}
#IBAction func backBtnPressed(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
}
#InviteCell
import UIKit
class InviteCell: UITableViewCell {
#IBOutlet weak var nameLbl: UILabel!
#IBOutlet weak var emailLbl: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
}
}
UIViewController Images
TableView Attributes Inspector
InviteCell Atribute Inspector
In the code above, I'm trying to select a cell with one finger, but the selection does not happen.
Thanks in advance...
A more elegant way of dealing with the tap issue is:
let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(AppController.dismissKeyboard))
view.addGestureRecognizer(tap)
//this is the KEY of the fix
tap.cancelsTouchesInView = false
This way you can keep your gesture recognizer and still get the table view action in one single tap/selection.
You have the following line in your set up code:
self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(InvitePeopleVC.dismissKeyboard)))
That sets up a gesture recognizer for your whole view and that would swallow any touches on the main view. If you remove that, you should get the table cell selection working correctly :)
The Tap gesture you have added in the code is causing the issue. Tapgesture recogniser is listening to the user tap actions in the view. The cell select listner is being blocked by the added Tap gesture.
As #Fahim said, if you remove the tap gesture from your code, then cell selection will work smoothly.

How to know which cell in tableview has been selected

I have got a view controller which contains a tableview, this tableview contains dynamic cells done in storyboard. Most cells contain a textfield and in the view controller i need to detect when the textfields have been selected and which cell it was. (I load a popover pointing to the cell, and this must be called from the view controller).
Cell code
import UIKit
class AddNew_Date_Cell: UITableViewCell {
#IBOutlet var Label: UILabel!
#IBOutlet var textField: UITextField!
func loadItem(var data:NSArray) {
Label.text = data[0] as? String
}
}
ViewControllerCode
import UIKit
class AddNewDetailView: UIViewController, UITableViewDelegate, UITableViewDataSource, UIPopoverPresentationControllerDelegate, UITextFieldDelegate {
#IBOutlet var tableView: UITableView!
var items : NSMutableArray!
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return items.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
switch(items[indexPath.row][1] as! Int)
{
...
case 2:
var cell:AddNew_Date_Cell = tableView.dequeueReusableCellWithIdentifier("AN_Date_Cell") as! AddNew_Date_Cell
cell.loadItem(items[indexPath.row] as! NSArray, view: self)
cell.textField.delegate = self
return cell
...
}
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
}
func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
//Need to know here which cell the textfield is attached to
turn true
}
}
Each time i select a textfield view I'm hitting a breakpoint in "textFieldShouldBeginEditing" however i have no idea which cell its in.
if i select the textfield in a cell "didSelectRowAtIndexPath" is never hit.
How do i find out which cell has been selected.
Thanks
You need to set the interactions from user off while the text field is not been edit, in cellForRowAtIndexPath add this line
cell.textField.userInteractionEnabled = false
In the didSelectRowAtIndexPath you will need to enable again for edition otherwise the user won't be to touche it if they want to and call first responder:
cell.textField.userInteractionEnabled = true
cell.textField.becomeFirstResponder()
once the user finish editing in textFieldShouldEndEditing you disable the interaction again:
cell.textField.userInteractionEnabled = false
This way the cellForRowAtIndexPath will always be called but will be up to you to set the user to use the correct UITextField.
I hope that helps you
You could use a custom cell class with a delegate protocol letting you know when a text field has started editing.
The custom cell would be the delegate of the text field and the view controller would be the delegate of the custom cell.
For a basic example, your cell class could look like the following:
import UIKit
protocol TextFieldCellDelegate {
func textFieldCellDidBeginEditing(cell: TextFieldCell)
}
class TextFieldCell: UITableViewCell {
#IBOutlet weak var textField: UITextField!
var delegate: TextFieldCellDelegate?
}
extension TextFieldCell: UITextFieldDelegate {
func textFieldDidBeginEditing(textField: UITextField) {
delegate?.textFieldCellDidBeginEditing(self)
}
}
And your view controller would implement the method like this:
extension ViewController: TextFieldCellDelegate {
func textFieldCellDidBeginEditing(cell: TextFieldCell) {
println(cell)
// Do something with cell
}
}
If you want to find out the index of your cell write the below code in func textFieldShouldBeginEditing(textField: UITextField)
var index = 0
var position: CGPoint = textField.convertPoint(CGPointZero, toView: self.<your_table_outlet_name>)
if let indexPath = self.<your_table_outlet_name>.indexPathForRowAtPoint(position)
{
index = indexPath.row
}

Custom UITableViewCell using xib and autolayout doesn`t show correctly in several cells on the top

I used custom UITableViewCell named "SCTableViewCell" and linked with "CustomCell.xib" and loading in "CenterViewController"
and when it runs, several cells on the top looks bad(like follow picture) and, after scroll down screen and up again(bad cells once disappear and appear again) it looks fine
it is custom cell class
SCTableViewCell.swift
class SCTableViewCell: UITableViewCell {
#IBOutlet weak var thumbnailImage : UIImageView!
#IBOutlet weak var titleLabel : UILabel!
#IBOutlet weak var descLabel : UILabel!
func loadItem(#title: String, desc: String, image: UIImage){
titleLabel.text = title
descLabel.text = desc
thumbnailImage.image = image
self.contentView.setTranslatesAutoresizingMaskIntoConstraints(true)
}
override func layoutSubviews() {
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
and CenterViewController.swift
override func viewDidLoad() {
super.viewDidLoad()
//Setting Custom Tableview Cell
self.tableView.registerNib(UINib(nibName: "CustomCell", bundle: nil), forCellReuseIdentifier: "CustomCell")
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("CustomCell", forIndexPath: indexPath) as SCTableViewCell
cell.loadItem(title: title!, desc: desc!, image: pic)
cell.updateConstraints()
return cell
}
console shows nothing....
please help me...
Oh!
I didn`t set up my cell height... ha...ha....
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 160
}
added in CenterViewController.swift and it works

Resources