Swift 2.0, UITableView: cellForRowAtIndexPath returning nil for non-visible cells - ios

Please don't mark this as a duplicate question because I have found no suitable answer for my query.
I have a table view with cells that contain text fields. I have a button at the bottom of the screen. The number of rows is greater than the screen can display, so some cells are not in view. I want that at any point when the button is pressed, all textfields be read and the text input be processed. I am unable to do so because apparently because of the reusability of cells, cells out of view do not exist at all and cellForRowAtIndexPath for those indexPaths gives a runtime error. I have read answers that say that this simply can't be done. But I find it hard to believe that there is no workaround to this. Please help. Thanks in advance!

This definitely can't shouldn't be done (accessing cells that are off screen, or implementing workarounds to allow it), for reasons of (at least) performance and memory usage.
Having said that there is, as you put it, a workaround. What you will need to do it change how you are storing the values in those text fields. Rather than iterating through cells and retrieving the text directly from the text fields you should store the text for each field in an collection.
Define a dictionary property on your table view controller / data source to hold the text for each row.
Act as the delegate of UITextField and assign as such in tableView:cellForRowAtIndexPath:
Implement textField:didEndEditing: (or whatever is appropriate for your use case) and store the text in the dictionary keyed against the index path relating to the cell that contains that text field.
Update the button action method to use this dictionary instead of iterating through cells.

Create a UITableViewCell subclass, add your tableViewCells a index property and introduce a delegate like:
protocol CustomCellDelegate {
func didEditTextField(test : String, atIndex : Int)
}
Create a delegate variable on your UITableViewCell subclass.
var delegate : CustomCellDelegate?
Implement the UITextViewDelegate in your tableViewCell and set the cell to be the delegate of your textfield.
Implement these two methods:
func textFieldShouldReturn(textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
func textFieldDidEndEditing(textField : UITextField) {
// call back with the delegate here
// delegate?.didEditTextField(textfield.text, atIndex: self.index)
}
So when the user ends editing the textField, your cell will call out with a delegate, sending the text and the index.
On your viewController, when you init your tableViewCell, set the delegate of the cell to the viewController, and set the index to indexPath.row .
Set up on your viewController a String array with as many items as many tableViewCells you got. Init the array with empty strings.
So you got the delegate on your viewController, and whenever it is called, you just insert the returned text to right index in the string array on your viewcontroller.
What do think, will this work?

If we can assume that cells that have NEVER been created has no text inputs, then create an Array or Set ourselves and clone the content/input texts there whenever user inputs to a cell's textfield.
Whenever, that button is clicked, you can iterate the array instead of cells to get all the inputs.
This is a bit hacky though..

Related

Update array data used to create UITableView when IBAction is fired

I have created a UITableView that includes a UIImage, and two UILabels. This data is passed to a details view controller when the user selects the row. I have also successfully created a UIButton in the prototype cell that updates the text of one of the UILabels. I am unsuccessfully attempting to update this label within the array so this change is reflected on the details view controller. This is what I tried with no success:
#IBAction func updateTableData(_ sender: Any) {
grinds.removeAll()
grinds = createArray()
}
Here is a link to the repo: https://github.com/andrewTuzson/tableViewPractice
In the screenshot below, the left image displays the tableview after the first cell has been toggled to "Needs Work". The right image displays the details screen that is loaded after selecting the row.
How should I approach solving this?
If you want to update a tableViewCell upon the button click then, you should make the model array global to access it in click method and update what you want then reload the tableView either with delegate or NSNotificationCenter
--
in the custom class of the cell declare an integer property and in cellForRow set it like this
cell.index = indexpath.row

using a Struct to change tableViewCell from TableViewController

In my app's chat feature, I use a UITableView to present chat history.
Cells are subclasses of UITableViewCell. The class receives a Chat object that contains all the info it needs to build the cell.
I have a Firebase listener that fires when the "last message" is changed. It changes a UILabel text to "new Message!" and then a protocol/delegate reloads the table view. Now I need to change the label back to "".
When the user clicks the cell, I use didSelectRowAt to segue to JSQMessagesViewController after preparing for segue. My idea is to use prepare for segue or even didSelectRowAt itself to change a Boolean variable in the UITableViewCell that corresponds to that chat Object. Navigating back to the tableViewController will trigger reloadData.
Each ChatObject has a unique ID that I can access. After firing the listener, I will set this Boolean to one value. After clicking the cell, I will set it to another. the state of the variable will tell the IBOutlet what text to show. In the UITableViewCell class, I use:
var chat: Chat! {
didSet {
self.updateUI()
}
}
}
So in self.updateUI() I make necessary changes, but can I make properties in the UITableViewCell mutable/visible from the UITableViewController?
How can I make properties in the UITableViewCell that are mutable from the UITableViewController
You don't. You make changes in your model and tell the table view to reload its data.
So in self.updateUI() I make necessary changes, but can I make properties in the UITableViewCell mutable from the UITableViewController?
I don't think you're trying to do what you say you are. In the case that you are trying to access the properties of your custom UITableViewCell subclass, you might want to use cellForRowAtIndexPath(). Then cast the result to whatever class name you have for your UITableViewCell subclass.
Example:
If I want to change the 5th row outside of our table view dataSource, I could do something like this.
let fifthIndexPath = IndexPath(row: 5, section: 0)
let thisCell = myTableView.cellForRowAtIndexPath(at indexPath: fifthIndexPath) as! MyCustomTableViewCell
thisCell.property = value
See:
https://developer.apple.com/documentation/uikit/uitableviewdatasource/1614861-tableview

Simultaneously change display parameters on all table view cells

I am trying to implement a table view design where a user can click a button outside of a table view cell and the display mode of all the buttons should change. However this is not the 'selected' mode for a given cell (that will be yet a third state that becomes accessible via switching to this second state). What's the proper way to accomplish this?
I am using dequeueReusableCellWith so I don't want to simply cycle through every cell because some that are out of sight probably shouldn't be modified. I simply want any cell that is visible, or becomes visible, while the table view cell is in this second display mode to follow a second design rather than the first design.
The second design, for now, is being modified via a method I added to a subclass of UITableViewCell like so:
- (void) p_refreshDisplay {
if (self.editing) {
self.buttonToClearWidth.constant = 20;
self.buttonToClearLeadingWidth.constant = 20;
} else {
self.buttonToClearWidth.constant = 0;
self.buttonToClearLeadingWidth.constant = 0;
}
}
However, I'm not sure how to trigger this p_refreshDisplay for every visible (and to become visible) cell. It seems unwise to call this many times and refresh the table view. What would be the proper way to accomplish what I want to do?
You do what should be done for any table view change:
Update your data model or some flag as needed.
Either call reloadData on the table view or call reloadRowsAtIndexPaths:withRowAnimation: passing in indexPathsForVisibleRows as the list of rows to reload.
Implement cellForRowAtIndexPath to provide appropriate cells for the given data/flags.
It sounds like you should have a custom cell class that has one or more properties that can be set on the cell in cellForRowAtIndexPath so the cell can render itself properly based on the specified state.
You can achieve this by doing three things:
Establish some property that indicates the "mode" of the table, either a boolean or perhaps an enum if there are more than three states
Ensure that cellForRowAtIndexPath configures the cell appropriately based on the value of this property. This will ensure that newly displayed cells are configured correctly.
When the "mode" changes you can use the tableview's visibleCells property to update any currently visible cells:
for cell in tableview.visibleCells {
if let myCell = cell as? MyCustomCellClass {
myCell.setButtonStyle()
}
}

iOS Swift - Export Data from Custom Table View Cells on Exit/Save

I have a UITableView with 5 custom UITableViewCells. In the custom UITableViewCells I have IBOutlets for my UI-elements (UILabel, UITextField). I cannot create IBOutlets for the UI-elements in my view controller because they are already linked to my custom UITableViewCells.
I want to be able to access the data in my UITableViewCells when the UIBarButtonItem of type "Save" is pressed. In order to parse the data from my UITableViewCells to my own class.
I have already written the unwind method in the return controller, and have written the prepareForSegue method in the source controller, got that working. I only need to retrieve the data from the custom UITableViewCells at that specific time (on save, not on finished editing textfield or when a table row has been dis-selected).
What would be the "best practice" to achieve this kind of behaviour?
Since I don't know what your custom cells look like, I'll just use a cell that has a single text field in it (named myTextField) as an example.
In the custom cell class, add a property like this:
var value: String? {
return myTextField.text
}
And in the save button's #IBAction method, do this:
var data = [String]()
let sections = numberOfSectionsInTableView(self.tableView)
for s in 0..<sections {
let rows = tableView(self.tableView, numberOfRowsInSection: s)
for r in 0..<rows {
let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: r, inSection: s)) as! YourCustomCell
data.append(cell.value)
}
}
And now you have an array of strings!
If your cell's data is not as simple as a string, please consider using Eureka.

UITableViewCell overlapping issue

Hello
Now I've been trying to display html links in a UITableView.
I've been trying to do this via adding instances of UITableViewCell to the tableview's subview.
func updateViewController(){
for name : String in currentDirectoryNames {
var pusher = UITableViewCell();
pusher.textLabel?.text = name;
listView.addSubview(pusher);
}
}
Sadly the result ends up with the text overlapped in one row :(
It looks like this...
Any ideas?
That's not how UITableView works, at all. I highly suggest you read Apple's documentation on TableView Programming. At the very least, you'll need to set your view controller as the dataSource for the table view and implement the -tableView:numberOfRowsInSection: and -tableView:cellForRowAtIndexPath: methods.
The func you're calling is adding all of your subviews with origin.y equal to 0 (which makes them look like they're all on top of each other). You may have to do something like set pusher.frame.origin.y to dynamically calculate based on which cell is being added.

Resources