How to add a subview to a UITableViewCell - swift

I'm making a to-do planner using a table view. When the user presses enter after typing in a task, a button should appear to the left of the task.
The textFieldShouldReturn method in my ViewController class adds the button but not always in the top left corner of the screen. I have it set up like this:
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")! as! TextInputTableViewCell
self.view.addSubview(cell.cellButton) // puts a button in a wrong location, button doesn't respond to any action
cell.addSubview(cell.cellButton)
tableViewData.append(textField.text!)
lastItem = tableViewData.count
print(tableViewData)
print(lastItem)
self.tableView.reloadData() //update row count and array count
textField.resignFirstResponder()
return true
}
self.view.addSubview(cell.cellButton)
adds a button to the view but the button doesn't appear in the right place, (i tried moving the location using storyboard, but it doesn't do anything). Plus the button doesn't respond to its appropriate click action, which is declared in the subclass.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = self.tableView.dequeueReusableCell(withIdentifier: "cell")! as! TextInputTableViewCell
cell.textField.delegate = self
...
return cell
}
Any help is appreciated. Let me know if I am not too clear

You're not grabbing the right cell in this line:
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")! as! TextInputTableViewCell
You're basically telling the system to just give you a cell. You need to figure out what text field actually returned, and grabbing the cell that contains that text field and then adding your subview to the right cell. The way todo this would be to tag your textFields with the row number.
It works in the first example because you are in cellForRow, so you do have the right cell.

Would it be easier if you aligned the button in your .xib or storyboard file, and then kept it hidden until you needed it to appear?

Related

textFieldDidEndEditing is not called in swift

I have a textfield in my tableview cell. When I am entering something and click on the submit button, I want to save the text in a struct to use it in other view controller. But the problem is , when I am clicking the submit button for the first time, my textFieldDidEndEditing function is not calling so I am unable to assign the value to the struct.
My code is-
func textFieldDidEndEditing(_ textField: UITextField)
{
let position: CGPoint = textField.convert(CGPoint.zero, to: self.tableView)
if let indexPath = self.tableView.indexPathForRow(at: position)
{
if let cell: InsuranceInformationTableViewCell = self.tableView.cellForRow(at: indexPath) as? InsuranceInformationTableViewCell{
if textField == cell.policyHolderFullNameTextField{
insuranceModel.policuHolderFullName = textField.text
}
}
}
}
SubmitButtonAction-
#IBAction func SubmitButtonAction(_ sender: UIButton) {
if (insuranceModel.policuHolderFullName == nil) {
showAlert(withTitle: "Data Missing", withMessage: "Policy holder fullName field cannot be empty")
return
}
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(insuranceModel) {
let defaults = UserDefaults.standard
defaults.set(encoded, forKey: "SavedInsuranceInformation")
self.navigationController?.pushViewController(StockAndBillPreviewViewController.instantiateStockAndBillPreviewViewController(), animated: true)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
case TableSection.insuranceType.rawValue:
guard let cell = tableView.dequeueReusableCell(withIdentifier: "InsuranceInformationTableViewCell", for: indexPath) as? InsuranceInformationTableViewCell else {
return UITableViewCell()
}
cell.policyHolderFullNameTextField.delegate = self
}
When I am clicking on the submit button it is showing me the error message.
Here i don't want to use "UITextField, shouldChangeCharactersIn range". Becausei have lots of textfields in my tableview cell as it is a medical form.
How to resolve this issue ? Can anyone help me?
1.one of most common reason this might happen is that you might not be assigning the text view delegate method.
So just add "UITextViewDelegate" as conformance to your table View Cell and then set the delegate in storyboard.
2. if you have done the first method and it is still not working then you can follow the most basic way to get whatever there is in text field when you click your submit button.
at anypoint in your code you can access the text in textfield through "textfield.text",
just create an outlet of your text field and use youroutletname.text in submit button to save in struct.
I am not sure that textFieldDidEndEditing is calling in this case. I use the following approach:
(1) define custom cell with UITextView and make cell delegate for UITextViewDelegate textViewDidChange method
(2) create custom cell delegate protocol to send text changes down to TableViewController
(3) make TableViewController delegate for custom cell and record all changes for UITextView text inside the controller

Swift - Clicked button inside tableview cell does not call the did select row at function of that cell

I creating a tableview programatically and each tableview cell have buttons associated to them.
If I click the row I can work out the tag associated with the buttons on that row and then able to edit the title when applying some other functions.
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
TmpActionConnectorTag = indexPath.row + 700 //Tag associated to button
}
When the button is clicked , I have this code which changes the other button on that row
let tag = TmpActionConnectorTag
let tmpButton = self.view.viewWithTag(tag) as? UIButton
The issue is if I directly click on the button in the tableview cell, the did select row does not get call and no tag value is given. To do it I have to click within the cell first and then the button to be able to know the tag associated with the row.
Is there a way to workout the index row when the button is clicked so I don't have to click the actual cell?
above is how the cell looks normally and below shows how the cell has to be selected to be able to get the index value.
Button action won't call the didSelectRowAt. You should go for delegate method. If not aware about delegate means refer this
The cell's button will not trigger func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) instead you need to add a target to your button which is typically done within cellForItemAt.
Add target to button within cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = MyTableViewCell()
cell.button.tag = indexPath.row
cell.button.addTarget(self, action: #selector(didTapCellButton(sender:)), for: .touchUpInside)
}
Handle button action
#objc func didTapCellButton(sender: UIButton) {
guard viewModels.indices.contains(sender.tag) else { return } // check element exist in tableview datasource
//Configure selected button or update model
}
Disabling the button's control worked for me.
Programmatically:
editButton.isEnabled = false
~OR~
IB:
Uncheck the Enabled box in the Control section.

I have a UItableView cell with a Image but can't get a image click to allow parameters

I have a EDIT2 UItableView, in Swift, with the normal labels and relevant Information. I have added a Image to the cell and all is working except when I click on the image I would like to open another ViewController with that particular cells relevant information. I have used:
let popup: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.popupalrmcontrnt))
cell.alrmwarning.addGestureRecognizer(popup);
as the means for calling the pushViewController function, But the Viewcontoller requires a parameter AlarmID to be set Beforehand. Said Viewcontroller needs AlarmID to fetch its relevant information.
Edit1 The parameter is dependant on the selected cell.
Create a closure variable in cell:
class SomeCell: UITableViewCell {
var imageClicked: (() -> Void)?
}
then in action of your tap gesture self.popupalrmcontrnt
add this line
func popupalrmcontrnt() {
self.imageClicked?()
}
then in your cellForRow, you can access this as:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
(cell as? SomeCell)?.imageClicked = {
//Navigate to your view controller here
}
return cell
}
Hope it helps!!
Better use UIButton. you can set image to the button as background image. Also you can pass relevant info as an object to the new ViewController.

adding Button in last tableView Cell shown twice when scrolling

Using Swift4, iOS11.2.1, Xcode9.2,
I successfully added a custom button to the last cell of a tableView. (the button is used to add cells in the tableView - this also works fine...) - see Screenshot-1.
But now the issue: When adding more cells (i.e. more than fit in the tableView-height), and if you scroll to the top, then there is a second cell that shows this button - see Screenshot-2.
(see code below....)
Here are the two screenshots:
How can I get rid of the extra button ????
(it seems that a cell is reused and the button is partly drawn. The cut-off can be explained since the height of the last cell is made bigger than the other cells. But still, there shouldn't be parts of buttons in any other cell than the very last one...even when scrolling up).
Any help appreciated !
Here is my code (or excerts of it...):
override func viewDidLoad() {
super.viewDidLoad()
// define delegates
self.fromToTableView.dataSource = self
self.fromToTableView.delegate = self
// define cell look and feel
self.addButton.setImage(UIImage(named: "addButton"), for: .normal)
self.addButton.addTarget(self, action: #selector(plusButtonAction), for: .touchUpInside)
//...
self.fromToTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let resItems = resItems else {
self.rowCount = 0
return 0
}
self.rowCount = resItems.count - 1
return resItems.count
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if(indexPath.row == (self.rowCount)) {
return 160
} else {
return 120
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = fromToTableView.dequeueReusableCell(withIdentifier: "SettingsCell") as? SettingsCustomTableViewCell
// configure the cell
cell?.configureCell(tag: indexPath.row)
// fill cell-data
if let resItem = self.resItems?[indexPath.row] {
cell?.connectionItem = resItem
}
// add addButton to last cell
if(indexPath.row == (self.rowCount)) {
// add addButton
cell?.contentView.addSubview(self.addButton)
// scroll to last cell (that was just added)
// First figure out how many sections there are
let lastSectionIndex = self.fromToTableView.numberOfSections - 1
// Then grab the number of rows in the last section
let lastRowIndex = self.fromToTableView.numberOfRows(inSection: lastSectionIndex) - 1
// Now just construct the index path
let pathToLastRow = IndexPath(row: lastRowIndex, section: lastSectionIndex)
// Scroll to last cell
self.fromToTableView.scrollToRow(at: pathToLastRow as IndexPath, at: UITableViewScrollPosition.none, animated: true)
}
return cell!
}
You already figured out what the problem is :)
As you say here:
it seems that a cell is reused and the button is partly drawn
And that is exactly what happens here. You have a pool of UITableViewCell elements, or in your case SettingsCustomTableViewCell. So whenever you say:
let cell = fromToTableView.dequeueReusableCell(withIdentifier: "SettingsCell") as? SettingsCustomTableViewCell
Then, as the name implies, you dequeue a reusable cell from your UITableView.
That also means that whatever you may have set on the cell previously (like your button for instance) stays there whenever you reuse that specific instance of a cell.
You can fix this in (at least) three ways.
The Hack
In your tableView(_:cellForRowAt:indexPath:) you have this:
if(indexPath.row == (self.rowCount)) {
//last row, add plus button
}
You can add an else part and remove the plus button again:
if(indexPath.row == (self.rowCount)) {
//last row, add plus button
} else {
//not last row, remove button
}
The Right Way
UITableViewCell has the function prepareForReuse and as the documentation says:
If a UITableViewCell object is reusable—that is, it has a reuse identifier—this method is invoked just before the object is returned from the UITableView method dequeueReusableCell(withIdentifier:)
So, this is where you "reset" your custom table view cell. In your case you remove the plus button.
You already have a custom UITableViewCell it seems, so this should be a small change.
The Footer Way
As #paragon suggests in his comment, you can create a UIView and add that as a tableFooterView to your UITableView.
let footerView = YourFooterViewHere()
tableView.tableFooterView = footerView
Hope that helps

didSelectCellAtIndexPath not called

I know there are a lot of relevant post on this and I have tried all of the ones I can find and unfortunately the problem still persists.
So I have a tableView inside of an UIViewController which is populated by 2 custom cells, one has got an UITextField and the other one got an UISwitch, and both have a UILabel. I then proceeded to implement the didSelectCell(at: IndexPath) method
I made sure the delegate is hooked up to the view controller.
Inside of the method, I simply wrote a print statement to ensure the taps are registering. However, the print message did to get printed to the console when I tapped on the UITextField and on the UILabel.
I have a feeling it is something to do with the firstResponder thing with the UITextField, and it is eating away my tap registration, but not really sure.
Any feedbacks are welcome!
UPDATE
Here is the setup on my custom cell:
Here is the didSelect method:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("tapped on cell")
if let cell = tableView.cellForRow(at: indexPath) as? NumericInputTableViewCell{
print("Tapped on numericcell")
} else if let cell = tableView.cellForRow(at: indexPath) as? BooleanInputTableViewCell {
print("Tapped on booleancell")
}
}
The delegate method is triggered when you click on the cell, not other elements on the cell. So create some empty cell in your table view and have another try. If it get printed out then that means you have elements covered over the cell and the cell itself is not clicked.
You can then disable user interaction for your UITextField stuffs from storyboard and then have a try. Ideally, when you click on the textfield on a table view cell, the correct respond I am thinking is that keyboard will show up and didSelectCell should not be triggered.
Here is the demo