I'm working on a basic list workout app right now that keeps track of workouts and then the exercises for each workout. I want to extend the current 'editing' mode of a TableViewController to allow for more advanced editing options. Here is what I have so far:
As you can see, I am inserting a section at the top of the table view so that the title of the workout can be edited. The problem I am facing is twofold:
There is no animation when the edit button is tapped anymore.
When you try to swipe right on one of the exercises (Squat or Bench press) the entire section containing exercises disappears.
I start by triggering one of two different functions on the setEditing function, to either switch to read mode or edit mode based on whether the boolean editing returns true or false.
override func setEditing(_ editing: Bool, animated: Bool) {
super.setEditing(editing, animated: true)
tableView.setEditing(editing, animated: true)
if editing {
switchToEditMode()
} else {
switchToReadMode()
}
}
Then I either insert the "addTitle" section (the text field seen in the second image) to an array called tableSectionsKey which I use to determine how to display the table (seen further below), and then reload the table data.
func switchToEditMode(){
tableSectionsKey.insert("addTitle", at:0)
self.tableView.reloadData()
}
func switchToReadMode(){
tableSectionsKey.remove(at: 0)
self.tableView.reloadData()
}
Here is my tableView data method. Basically the gist of it is that I have the array called tableSectionsKey I mentioned above, and I add strings that relate to sections based on what mode I'm in and what information should be displayed. Initially it just has "addExercise", which related to the "Add exercise to routine" cell
class WorkoutRoutineTableViewController: UITableViewController, UITextFieldDelegate, UINavigationControllerDelegate {
var tableSectionsKey = ["addExercise"]
}
Then in viewDidLoad I add the "exercise" section (for list of exercises) if the current workout routine has any, and I add the addTitle section if it's in new mode, which is used to determine if the view controller is being accessed from an add new workout button or a from a list of preexisting workouts (so to determine if the page is being used to create a workout or update an existing one)
override func viewDidLoad() {
super.viewDidLoad()
if workoutRoutine.exercises.count > 0 {
tableSectionsKey.insert("exercise", at:0)
}
if mode == "new" {
tableSectionsKey.insert("addTitle", at: 0)
}
}
Then in the cellForRowAt function I determine how to style the cell based on how the section of the table relates with an index in the tableSectionsKey array
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let section = indexPath.section
let sectionKey = tableSectionsKey[section]
let cellIdentifier = sectionKey + "TableViewCell"
switch sectionKey {
case "addTitle":
guard let addTitleCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? AddTitleTableViewCell else {
fatalError("Was expecting cell of type AddTitleTableViewCell.")
}
setUpAddTitleTableViewCell(for: addTitleCell)
return addTitleCell
case "exercise":
guard let exerciseCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath) as? ExerciseTableViewCell else {
fatalError("Was expecting cell of type ExerciseTableViewCell.")
}
let exercise = workoutRoutine.exercises[indexPath.row]
setUpExerciseTableViewCell(for: exerciseCell, with: exercise)
return exerciseCell
case "addExercise":
let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier, for: indexPath)
return cell
default:
fatalError("Couldn't find section: \(section), in WorkoutRoutineTableView" )
}
}
private func setUpExerciseTableViewCell(for cell: ExerciseTableViewCell, with exercise: Exercise) {
let titleText = exercise.name
let detailsText = "\(exercise.sets)x\(exercise.reps) - \(exercise.weight)lbs"
cell.titleLabel.text = titleText
cell.detailsLabel.text = detailsText
}
private func setUpAddTitleTableViewCell(for cell: AddTitleTableViewCell) {
cell.titleTextField.delegate = self
if (workoutRoutine.title != nil) {
cell.titleTextField.text = workoutRoutine.title
}
// Set the WorkoutRoutineTableViewController property 'titleTextField' to the 'titleTextField' found in the addTitleTableViewCell
self.titleTextField = cell.titleTextField
}
This isn't all of my code but I believe it is all of the code that could be relevant to this problem.
Your animation issue is due to the use of reloadData. You need to replace the uses of reloadData with calls to insert or delete just the one section.
func switchToEditMode(){
tableSectionsKey.insert("addTitle", at:0)
let section = IndexSet(integer: 0)
self.tableView.insertSections(section, with: .left) // use whatever animation you want
}
func switchToReadMode(){
tableSectionsKey.remove(at: 0)
let section = IndexSet(integer: 0)
self.tableView.deleteSections(section, with: .left) // use whatever animation you want
}
Related
in my View:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TransactionTableCell", for: indexPath) as! TransactionTableCell
let newItem = getTransactionsInSection(section: sectionHeader[indexPath.section])[indexPath.row]
cell.configure(item: newItem)
}
in my TransactionTableCell
func configure(item: TransactionModel) {
guard let withdrawalBonuses = item.withdrawalBonuses,
withdrawalBonuses < 0,
let accruedBonuses = item.accruedBonuses,
accruedBonuses > 0 else {
configureWithOneOperation(item)//shows one line of operation
return
}
//show 2 lines of operations
firstOperationAmountLabel.text = "+\(Int(accruedBonuses))"
secondOperationAmountLabel.text = "\(Int(withdrawalBonuses))"
}
When I scroll the cell , second operation line is appears in wrong cells where its shouldn't be, even If I reload my table , that also has this problem.
You should use prepareForReuse() method
Simply just clear data of your labels:
override func prepareForReuse() {
super.prepareForReuse()
firstOperationAmountLabel.text = nil
secondOperationAmountLabel.text = nil
}
There are few things to check here.
Make sure you reset all fields before configure a new cell.
If you have created a cell using xib or storyboard, make sure you haven't filled labels with static text.
Is your guard statements passing for every item?
Else block for guard configures cell with a single operation, Is it handling all ui elements in cell?
I have a table view where I am getting data from Firebase. The table is in a view controller that is part of a navigation controller. When coming to the view from it's parent view, the table data always displays correctly. When I go to the detail view when I click a row and come back, the changes I have made to my data are not showing even though the data values are the new values.
I am loading the table in viewWillAppear so that I can reload the data when coming back from the detail view controller. I am using DispatchQueue.main.async since the UI code is in a completion handler from the Firebase Call. I can set breakpoints and see that the data is being updated from FB and the table is reloading all the rows.
The specific issue I am having is an image, which is hidden by default, should no longer be hidden based on a data value from FB. I can step in the debugger and see that the data says not to hide the image, the image.isHidden = false is called, yet the image doesn't appear. All of my UI calls are wrapped in DispatchQueue.main.async if they are called from a closure. The image will show if I come from the parent view controller to the VC with the tableview, but when going back from the detail VC it doesn't show.
According to the code it should be working when stepping through in the debugger, the data values are correct and the image is being set to hidden/not hidden correctly.
Here is the ViewController Class that has the TableView
override func viewDidLoad() {
super.viewDidLoad()
}
// We want the table to reload every time the page appears
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
RouteService.instance.getRouteByIdFromFirebase(id: route.id) { [weak self] (route) in
guard let self = self else {return}
self.route = route
// Initialize the ViewModel as the table view delegate
self.routeDetailsVM.initialize(route : route,routeDetailsVC : self)
self.segmentsTableView.dataSource = self.routeDetailsVM
self.segmentsTableView.delegate = self.routeDetailsVM
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
self.segmentsTableView.reloadData()
}
}
}
The UITableViewCell Class
class TravelSegmentTableViewCell: UITableViewCell {
static let cellHeight : CGFloat = 100
static let identifier = "TravelSegmentTableViewCell"
#IBOutlet weak var completedImage: UIImageView!
func initToModel(segment : Segment, routeDetailsVM : RouteDetailsVM) {
setCompletedImageVisible(segment: segment,completedImage : completedImage)
}
func setCompletedImageVisible(segment : Segment,completedImage : UIImageView) {
if (segment.status == .finished) {
// This is getting called correctly but when coming
// back from the details VC it's not showing the image
// When coming from parent VC it works
completedImage.isHidden = false
}
}
UITableViewDelegate Class
// Cell for Row at Index Path Method
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let segment = route.segments[indexPath.row]
let type = segment.type
switch (type) {
case .pickup :
if let cell = tableView.dequeueReusableCell(withIdentifier: PickupSegmentTableViewCell.identifier, for: indexPath) as? PickupSegmentTableViewCell {
cell.initToModel(segment: segment,routeDetailsVM: self)
return cell
} else {
let cell = PickupSegmentTableViewCell()
cell.initToModel(segment: segment, routeDetailsVM: self)
return cell
}
case .delivery :
if let cell = tableView.dequeueReusableCell(withIdentifier: DeliverySegmentTableViewCell.identifier, for: indexPath) as? DeliverySegmentTableViewCell {
cell.initToModel(segment: segment,routeDetailsVM: self)
return cell
} else {
let cell = DeliverySegmentTableViewCell()
cell.initToModel(segment: segment, routeDetailsVM: self)
return cell
}
// This is the cell type in question
case .travel :
if let cell = tableView.dequeueReusableCell(withIdentifier: TravelSegmentTableViewCell.identifier, for: indexPath) as? TravelSegmentTableViewCell {
cell.initToModel(segment: segment, routeDetailsVM: self)
} else {
let cell = TravelSegmentTableViewCell()
cell.initToModel(segment: segment, routeDetailsVM: self)
return cell
}
default :
return UITableViewCell()
}
print("RouteDetailsVM:cellForRowAt indexPath ERROR - Returning blank UITableViewCell for type = \(type)")
return UITableViewCell()
}
The reason for the behavior is if you look inside cell for row at index path and at the .travel switch, I never return the tableViewCell that I dequeued. The code would fall through to the bottom and return a default UITableViewCell instead of the TravelSegmentTableViewCell
Here is what the code should look like
....
case .travel :
if let cell = tableView.dequeueReusableCell(withIdentifier: TravelSegmentTableViewCell.identifier, for: indexPath) as? TravelSegmentTableViewCell {
cell.initToModel(segment: segment, routeDetailsVM: self)
// THIS IS WHAT WAS MISSING
return cell
} else {
let cell = TravelSegmentTableViewCell()
cell.initToModel(segment: segment, routeDetailsVM: self)
return cell
}
default :
return UITableViewCell()
}
return UITableViewCell()
}
I have a UITableView that has sections (Category0, Category1,..), and every row of a specific section is a UITableView that has one section which is the question (Question1,..) and rows which are the options to be answered (option1, option2,..).
The problem is when I click on a button in a specific category and a specific question (Category0, question1, option0) see screenshot1,
immediately another buttons in another categories are clicked (Category1, question2, option0) see screenshot2,
and (Category4, question1, option0) see screenshot3.
the code below:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell") as? insideTableViewCell
cell?.answerlabel.text = "option \(indexPath.row)"
cell?.initCellItem(id: (myObject?.id)! , answer: (myObject?.answerArray![indexPath.row] as? String)!)
return cell!
}
In a custom UITableViewCell which is insideTableViewCell:
func initCellItem(id: Int , answer: String) {
radioButton.setImage( imageLiteral(resourceName: "unchecked"), for: .normal)
radioButton.setImage( imageLiteral(resourceName: "checked"), for: .selected)
radioButton.tag = id
radioButton.setTitle(answer, for: UIControlState.disabled)
radioButton.addTarget(self, action: #selector(self.radioButtonTapped), for: .touchUpInside)
}
#objc func radioButtonTapped(_ radioButton: UIButton) {
print(radioButton.tag)
print(radioButton.title(for: UIControlState.disabled) as Any)
let answer = radioButton.title(for: UIControlState.disabled) as Any
let StrId = String(radioButton.tag)
defaults.set(answer, forKey: StrId)
let isSelected = !self.radioButton.isSelected
self.radioButton.isSelected = isSelected
if isSelected {
deselectOtherButton()
}
}
func deselectOtherButton() {
let tableView = self.superview as! UITableView
let tappedCellIndexPath = tableView.indexPath(for: self)!
let section = tappedCellIndexPath.section
let rowCounts = tableView.numberOfRows(inSection: section)
for row in 0..<rowCounts {
if row != tappedCellIndexPath.row {
let cell = tableView.cellForRow(at: IndexPath(row: row, section: section)) as! insideTableViewCell
cell.radioButton.isSelected = false
}
}
}
You haven't posted code still guessing.
You can create model object like
class QuestionData {
var strQuestion:String? // This may contains Question
var strOptions:[String]? // It may contains options titles of your buttons
var selectedAnswerIndex:Int? // When any button tapped
}
And you should create category models like
class Categories {
var categoryTitle:String?
var questions:[QuestionData] = []
}
you can use this Categories class as main source of your dataSource array
var arrayDataSource = [Categories]()
And fill this with your original data.
now whenever any button tapped you can use selectedAnswerIndex:Int to store current selected option for question. and if it is null then user has not selected any option yet.
I have created class so it is reference type you can directly set the value without worry
Hope it is helpful to you
There has some and simple code I think it will help you :- if it is not sutable for you pls don't mind :-
if (!btnGreen3.isSelected)
{
btnGreen3.isSelected = !btnGreen3.isSelected
}
btnBlue3.isSelected = false
btnBlack3.isSelected = false
You need to save the states of every cell.
The reason is you are using dequereuseable cell with identifier when you scroll it switch to another cell.
So make Array or Dictionary where save the state of every selected and unselected Rows.
Still very much a Swift noob, I have been looking around for a proper way/best practice to manage row deletions in my UITableView (which uses custom UserCells) based on tapping a UIButton inside the UserCell using delegation which seems to be the cleanest way to do it.
I followed this example: UITableViewCell Buttons with action
What I have
UserCell class
protocol UserCellDelegate {
func didPressButton(_ tag: Int)
}
class UserCell: UITableViewCell {
var delegate: UserCellDelegate?
let addButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("Add +", for: .normal)
button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
addSubview(addButton)
addButton.rightAnchor.constraint(equalTo: self.rightAnchor, constant: -6).isActive = true
addButton.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
addButton.heightAnchor.constraint(equalToConstant: self.frame.height / 2).isActive = true
addButton.widthAnchor.constraint(equalToConstant: self.frame.width / 6).isActive = true
}
func buttonPressed(_ sender: UIButton) {
delegate?.didPressButton(sender.tag)
}
}
TableViewController class:
class AddFriendsScreenController: UITableViewController, UserCellDelegate {
let cellId = "cellId"
var users = [User]()
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! UserCell
cell.delegate = self
cell.tag = indexPath.row
return cell
}
func didPressButton(_ tag: Int) {
let indexPath = IndexPath(row: tag, section: 0)
users.remove(at: tag)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
where the Users in users are appended with a call to the database in the view controller.
My issues
The button in each row of the Table View is clickable but does not do anything
The button seems to be clickable only when doing a "long press", i.e. finger stays on it for a ~0.5s time
Will this method guarantee that the indexPath is updated and will not fall out of scope ? I.e. if a row is deleted at index 0, will deleting the "new" row at index 0 work correctly or will this delete the row at index 1 ?
What I want
Being able to click the button in each row of the table, which would remove it from the tableview.
I must be getting something rather basic wrong and would really appreciate if a Swift knight could enlighten me.
Many thanks in advance.
There are at least 3 issues in your code:
In UserCell you should call:
button.addTarget(self, action: #selector(buttonPressed), for: .touchUpInside)
once your cell has been instantiated (say, from your implementation of init(style:reuseIdentifier:)) so that self refers to an actual instance of UserCell.
In AddFriendsScreenController's tableView(_:cellForRowAt:) you are setting the tag of the cell itself (cell.tag = indexPath.row) but in your UserCell's buttonPressed(_:) you are using the tag of the button. You should modify that function to be:
func buttonPressed(_ sender: UIButton) {
//delegate?.didPressButton(sender.tag)
delegate?.didPressButton(self.tag)
}
As you guessed and as per Prema Janoti's answer you ought to reload you table view once you deleted a row as your cells' tags will be out of sync with their referring indexPaths. Ideally you should avoid relying on index paths to identify cells but that's another subject.
EDIT:
A simple solution to avoid tags being out of sync with index paths is to associate each cell with the User object they are supposed to represent:
First add a user property to your UserCell class:
class UserCell: UITableViewCell {
var user = User() // default with a dummy user
/* (...) */
}
Set this property to the correct User object from within tableView(_:cellForRowAt:):
//cell.tag = indexPath.row
cell.user = self.users[indexPath.row]
Modify the signature of your UserCellDelegate protocol method to pass the user property stored against the cell instead of its tag:
protocol UserCellDelegate {
//func didPressButton(_ tag: Int)
func didPressButtonFor(_ user: User)
}
Amend UserCell's buttonPressed(_:) action accordingly:
func buttonPressed(_ sender: UIButton) {
//delegate?.didPressButton(sender.tag)
//delegate?.didPressButton(self.tag)
delegate?.didPressButtonFor(self.user)
}
Finally, in your AddFriendsScreenController, identify the right row to delete based on the User position in the data source:
//func didPressButton(_ tag: Int) { /* (...) */ } // Scrap this.
func didPressButtonFor(_ user: User) {
if let index = users.index(where: { $0 === user }) {
let indexPath = IndexPath(row: index, section: 0)
users.remove(at: index)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
Note the if let index = ... construct (optional binding) and the triple === (identity operator).
This downside of this approach is that it will create tight coupling between your User and UserCell classes. Best practice would dictate using a more complex MVVM pattern for example, but that really is another subject...
There is a lot of bad/old code on the web, even on SO. What you posted has "bad practice" written all over it. So first a few pointers:
Avoid an UITableViewController at all cost. Have a normal view controller with a table view on it
Delegates should always be weak unless you are 100% sure what you are doing
Be more specific when naming protocols and protocol methods
Keep everything private if possible, if not then use fileprivate. Only use the rest if you are 100% sure it is a value you want to expose.
Avoid using tags at all cost
The following is an example of responsible table view with a single cell type which has a button that removes the current cell when pressed. The whole code can be pasted into your initial ViewController file when creating a new project. In storyboard a table view is added constraint left, right, top, bottom and an outlet to the view controller. Also a cell is added in the table view with a button in it that has an outlet to the cell MyTableViewCell and its identifier is set to "MyTableViewCell".
The rest should be explained in the comments.
class ViewController: UIViewController {
#IBOutlet private weak var tableView: UITableView? // By default use private and optional. Always. For all outlets. Only expose it if you really need it outside
fileprivate var myItems: [String]? // Use any objects you need.
override func viewDidLoad() {
super.viewDidLoad()
// Attach table viw to self
tableView?.delegate = self
tableView?.dataSource = self
// First refresh and reload the data
refreshFromData() // This is to ensure no defaults are visible in the beginning
reloadData()
}
private func reloadData() {
myItems = nil
// Simulate a data fetch
let queue = DispatchQueue(label: "test") // Just for the async example
queue.async {
let items: [String] = (1...100).flatMap { "Item: \($0)" } // Just generate some string
Thread.sleep(forTimeInterval: 3.0) // Wait 3 seconds
DispatchQueue.main.async { // Go back to main thread
self.myItems = items // Assign data source to self
self.refreshFromData() // Now refresh the table view
}
}
}
private func refreshFromData() {
tableView?.reloadData()
tableView?.isHidden = myItems == nil
// Add other stuff that need updating here if needed
}
/// Will remove an item from the data source and update the array
///
/// - Parameter item: The item to remove
fileprivate func removeItem(item: String) {
if let index = myItems?.index(of: item) { // Get the index of the object
tableView?.beginUpdates() // Begin updates so the table view saves the current state
myItems = myItems?.filter { $0 != item } // Update our data source first
tableView?.deleteRows(at: [IndexPath(row: index, section: 0)], with: .fade) // Do the table view cell modifications
tableView?.endUpdates() // Commit the modifications
}
}
}
// MARK: - UITableViewDelegate, UITableViewDataSource
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myItems?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCell(withIdentifier: "MyTableViewCell", for: indexPath) as? MyTableViewCell {
cell.item = myItems?[indexPath.row]
cell.delegate = self
return cell
} else {
return UITableViewCell()
}
}
}
// MARK: - MyTableViewCellDelegate
extension ViewController: MyTableViewCellDelegate {
func myTableViewCell(pressedMainButton sender: MyTableViewCell) {
guard let item = sender.item else {
return
}
// Delete the item if main button is pressed
removeItem(item: item)
}
}
protocol MyTableViewCellDelegate: class { // We need ": class" so the delegate can be marked as weak
/// Called on main button pressed
///
/// - Parameter sender: The sender cell
func myTableViewCell(pressedMainButton sender: MyTableViewCell)
}
class MyTableViewCell: UITableViewCell {
#IBOutlet private weak var button: UIButton?
weak var delegate: MyTableViewCellDelegate? // Must be weak or we can have a retain cycle and create a memory leak
var item: String? {
didSet {
button?.setTitle(item, for: .normal)
}
}
#IBAction private func buttonPressed(_ sender: Any) {
delegate?.myTableViewCell(pressedMainButton: self)
}
}
In your case the String should be replaced by the User. Next to that you will have a few changes such as the didSet in the cell (button?.setTitle(item.name, for: .normal) for instance) and the filter method should use === or compare some id or something.
try this -
update didPressButton method like below -
func didPressButton(_ tag: Int) {
let indexPath = IndexPath(row: tag, section: 0)
users.remove(at: tag)
tableView.reloadData()
}
I have created a custom tableViewCell class for a prototype cells which is holding a text field. Inside ThirteenthViewController, I would like to reference the tableViewCell class so that I can access its doorTextField property in order to assign to it data retrieved from UserDefaults. How can I do this?
class ThirteenthViewController: UIViewController,UITableViewDelegate,UITableViewDataSource,UITextFieldDelegate {
var options = [
Item(name:"Doorman",selected: false),
Item(name:"Lockbox",selected: false),
Item(name:"Hidden-Key",selected: false),
Item(name:"Other",selected: false)
]
let noteCell:NotesFieldUITableViewCell! = nil
#IBAction func nextButton(_ sender: Any) {
//save the value of textfield when button is touched
UserDefaults.standard.set(noteCell.doorTextField.text, forKey: textKey)
//if doorTextField is not empty assign value to FullData
guard let text = noteCell.doorTextField.text, text.isEmpty else {
FullData.finalEntryInstructions = noteCell.doorTextField.text!
return
}
FullData.finalEntryInstructions = "No"
}
override func viewDidLoad() {
let index:IndexPath = IndexPath(row:4,section:0)
let cell = tableView.cellForRow(at: index) as! NotesFieldUITableViewCell
self.tableView.delegate = self
self.tableView.dataSource = self
cell.doorTextField.delegate = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return options.count
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
// configure the cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
-> UITableViewCell {
if indexPath.row < 3 {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
cell.textLabel?.text = options[indexPath.row].name
return cell
} else {
let othercell = tableView.dequeueReusableCell(withIdentifier: "textField") as! NotesFieldUITableViewCell
othercell.doorTextField.placeholder = "some text"
return othercell
}
}
}//end of class
class NotesFieldUITableViewCell: UITableViewCell {
#IBOutlet weak var doorTextField: UITextField!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
}
In order to access the UITextField in your cell, you need to know which row of the UITableView contains your cell. In your case, I believe the row is always the 4th one. So, you can create an IndexPath for the row and then, you can simply do something like this:
let ndx = IndexPath(row:3, section: 0)
let cell = table.cellForRow(at:ndx) as! NotesFieldUITableViewCell
let txt = cell.doorTextField.text
The above might not be totally syntactically correct since I didn't check for syntax, but I'm sure you can take it from there, right?
However, do note that in order for the above to work, the last row (row 4) has to be always visible. If you try to fetch rows which are not visible, you do run into issues with accessing them since UITableView reuses cells and instantiates cells for the visible rows of data.
Also, if you simply want to get the text that the user types and text input ends when they tap "Enter", you can always simply bypass accessing the table row at all and add a UITextFieldDelegate to your custom cell to send a notification out with the entered text so that you can listen for the notification and take some action.
But, as I mentioned above, this all depends on how you have things set up and what you are trying to achieve :)
Update:
Based on further information, it appears as if you want to do something with the text value when the nextButton method is called. If so, the following should (theoretically) do what you want:
#IBAction func nextButton(_ sender: Any) {
// Get the cell
let ndx = IndexPath(row:4, section: 0)
let cell = table.cellForRow(at:ndx) as! NotesFieldUITableViewCell
//save the value of textfield when button is touched
UserDefaults.standard.set(cell.doorTextField.text, forKey: textKey)
//if doorTextField is not empty assign value to FullData
guard let text = cell.doorTextField.text, text.isEmpty else {
FullData.finalEntryInstructions = cell.doorTextField.text!
return
}
FullData.finalEntryInstructions = "No"
}
You can create a tag for the doorTextField (for instance 111)
Now you can retrieve the value.
#IBAction func nextButton(_ sender: Any) {
//save the value of textfield when button is touched
guard let textField = self.tableViewview.viewWithTag(111) as! UITextField? else { return }
prit(textField.text)
.....
}