I'm working in Swift with one TableViewController with one prototype cell. The cell has a reuse identifier specified in the storyboard, but it never dequeues properly. I always get the "unexpectedly found nil while unwrapping an Optional value" error.
I've properly registered the class as follows:
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "myNewCell")
The offending code is:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("myNewCell") as UITableViewCell
let textField = cell.viewWithTag(123) as UITextField
textField.text = "test"
return cell
}
I feel like I've tried everything here but it never properly gives a cell with that identifier. Even using the fallback (if nil, create a cell with that identifier) still gives the error. It's definitely having trouble getting a cell with that identifier, but it's registered and specified in the storyboard... Any assistance is greatly appreciated!
When using cell prototypes, you do not call registerClass. The storyboard does that for you. If the cell prototype has its identifier specified, then just all dequeueReusableCellWithIdentifier and it should find your cell prototype without incident.
I'd suggest checking the spelling/capitalization of the identifier in storyboard and make sure it is identical to what is used in cellForRowAtIndexPath code.
I notice that you are trying to access a cell's label using a tag number. Nowadays, when dealing with custom cell layouts, we'd generally create our own table view subclass, e.g.:
// CustomTableViewCell.swift
import UIKit
class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var customTextField: UITextField! // note, I would use something other than textLabel to avoid confusion with base class
}
We'd then go to our cell prototype and specify its base class:
We'd also set the cell prototype's identifier:
We'd then hook up the outlet between the cell prototype and our custom class #IBOutlet.
Having done all of that, the cellForRowAtIndexPath would be:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("myNewCell", forIndexPath: indexPath) as CustomTableViewCell
cell.customTextField.text = "Row \(indexPath.row)"
return cell
}
If dequeueReusableCellWithIdentifier is giving you problems then just don't use it.
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = UITableViewCell()
let textField = cell.viewWithTag(123) as UITextField
textField.text = "test"
return cell
}
Related
I'm trying to make a UITableView that can support having different objects/elements inside it. Specifically, these elements are a UITextField and UISwitch.
The first problem:
The elements do not show up. They are placed in the prototype cell, which is then constructed inside the class I have set up. I have verified that the cell setup is working because I can change the words on each cell, but there are no elements within the cell.
Here is the code that constructs my cells right now:
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
return 1
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = UITableViewCell(style: UITableViewCellStyle.default, reuseIdentifier: "EmailCell")
return cell
}
Second problem (which might be solved along with the first):
I have no way of accessing the information in each UITextField or each UISwitch. How can I get this information from all cells that exist?
Thanks in advance for the help!
There are multiple things wrong with your code.
For custom cells you need to implement a custom UITableViewCell subclass. Here is an example:
import UIKit
class EmailCell: UITableViewCell {
#IBOutlet var customTextField: UITextField!
#IBOutlet var customSwitch: UISwitch!
}
After that, open your Storyboard and select the prototype cell. Change it's class to EmailCell in the Identity Inspector.
Also make sure to connect your ui elements to the #IBOutlets created earlier. See this StackOverflow post if you need help with #IBOutlet.
In the next step, change your tableView(_:, cellForRowAt:) implementation like this:
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "EmailCell", for: indexPath) as! EmailCell
// Configure your custom ui elements however you want
cell.customTextField.text = "somestring"
cell.customSwitch.isOn = true
return cell
}
Make sure your cells have reuse identifiers and you're using
tableView.dequeueReusableCell(withIdentifier: -Your cell Id- , for: indexPath) as? -Your Cell Class-
in your cell for row at index datasource method
next you can add targets to your cell text field / switch by doing this in your cell for row at index datasource method
cell.-your switch / text field-.addTarget(self, action: #selector(valueChanged(_:)), for: .valueChanged)
and you should subclass a uitableview cell to add the property / iboutlets
class YourTableViewCell: UITableViewCell {
#IBOutlet weak var yourSwitch: UISwitch!
}
I created two ViewControllers and two TableViews. Then i added prototype cell to one TableView, set it up according to my needs, copied it to the other TableView, changed its class and identifier and linked it up in ViewController that is datasource and delegate for each one.
The problem is, FEEDING one is behaving good, having constraints as expected, and the WALKING one is not, but i have no idea why since they have all same properties in each one's:
ViewControllers:
FEEDING
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = myFeedingTableView.dequeueReusableCellWithIdentifier("feedingcell", forIndexPath: indexPath) as! FeedingCell
cell.time.text = self.vremena[indexPath.row]
return cell
}
WALKING
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = myWalkingTableView.dequeueReusableCellWithIdentifier("walkingcell", forIndexPath: indexPath) as! WalkingCell
cell.time.text = self.vremena[indexPath.row]
return cell
}
CustomCell files
each one is connected to its class
FeedingCell is class of feeding prototype cell
WalkingCell is class of feeding prototype cell
Constraints
and the constraints are same, as you can see on the picture.
Here is the image providing different results and constraints:
image
Solved by changing rowHeight settings in TableView. Thanks #SilentLupin
I have created a component in XIB file. This file includes 2 components
Label
TableView
I have then linked and set it's File's Owner class to SampleView. I have attached the XIB file view with SampleView.swift file and this file have only following code in it's class:
#IBOutlet var view: UIView!
I have now created a controller file SampleController with protocols UIViewController, UITableViewDelegate and UITableViewDataSource. I have placed the following code in it's init() func to display the custom component:
init() {
super.init(nibName: nil, bundle: nil)
modalPresentationStyle = UIModalPresentationStyle.Custom
view.addSubview(SampleView())
}
I am using this SampleController to programmatically display as a Modal.
These codes does display as the Modal showing Label and TableView. It also populates the data in TableView. The problem is:
When I tap the cell in table, it doesn't trigger the event on first attempt. When I tap another cell then it trigger the previous cell event.
Any idea why is this happening?
Here are 2 functions used for populating and handling cell tap:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("CELL")
if (cell == nil) {
cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "CELL")
}
cell!.textLabel?.text = sampleData[indexPath.row]["title"]
return cell
}
func tableView(tableView: UITableView, didDeselectRowAtIndexPath indexPath: NSIndexPath) {
print("tapped")
}
Darn it! I was using didDeselectRowAtIndexPath instead of didSelectRowAtIndexPath. That's what will happen when you are programming after midnight.
You need to use a different method to dequeue the cell:
var cell = tableView.dequeueReusableCellWithIdentifier("CELL", forIndexPath: indexPath)
The check for a nil cell is unnecessary, by the way.
First, I wanted to point out the reason I want to make a common call, when a reusable cell is dequeued in a base class, it's because of this line of code you will see again soon further in my question:
cell.backgroundColor = cell.contentView.backgroundColor;
This is a bug fix for iPad not respecting the UITableView.backgroundColor = myCustomColor and UITableViewCell.backgroundColor = clearColor I have set. iPad displays white instead, everything is fine in iPhone versions, you can see this bug here, I have to set the background color again each time the cell is dequeued that is the only solution that works for me. I am trying to do this once in my base class, and come up with a solution where I do not have to remember to call a func for every child class (might not be possible).
I have a couple custom UITableViewControllers classes, let's call them ATableViewController and BTableViewController they inherit from a base class called UIBaseDashboardTableViewController which inherits from UITableViewController.
I am generating dynamic Prototype Table cells and making use of the function below in ATableViewController and BTableViewController:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("ATableViewCellId", forIndexPath: indexPath)
//common setting to fix always white background in iPad bug
cell.backgroundColor = cell.contentView.backgroundColor;
return cell
}
The TableViewCell Id ACustomTableCellId is unique or different for ATableViewController and BTableViewController. I have a common setting for all my UITableViewControllers that inherit from my base class, UIBaseDashboardTableViewController. You can see the backgroundColor line of code above is my common setting that will be the same in all child classes of UIBaseDashboardTableViewController. In each child class I first tried to do the following:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
super.tableView(tableView: tableView, cellForRowAtIndexPath: indexPath)
...
}
But that is not going to work, I need the ReusableCellIndentifer.
My current solution, which really is just fine probably, is the following, in my child classes I have the following:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let data = dataArray[indexPath.row]
let dequeuedCell = tableView.dequeueReusableCellWithIdentifier("BTableViewCellID", forIndexPath: indexPath)
let cell = dequeuedCell as! MyCustomTableViewCell
// Configure the cell...
cell.model = data
//call common settings for cells function in base class
super.setDequeueReusableCellCommon(cell)
return cell
}
And then in my base class UIBaseDashboardTableViewController I implemented:
func setDequeueReusableCellCommon(cell: UITableViewCell) {
cell.backgroundColor = cell.contentView.backgroundColor
}
The only downside to this is that I have to remember to call super.v setDequeueReusableCellCommon in all my child classes.
Any better suggestions on how solve this?
You are changing background color for cell, but made inheritance for tableViews. Use inheritance for tableViewCell, not the whole tableView. And in root class for tableViewCell setup self.backgroundColor = self.contentView.backgroundColor in awakeFromNib method.
I'm building a table that has two cells of the same class but of different identifiers.
I am using a segmented control to display either or.
I believe everything is hooked up properly on Storyboard, however,
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let friendRequest = self.friendRequestsToDisplay[indexPath.row]
requestDirection = isAnIncomingRequest ? "IncomingRequestCell" : "OutgoingRequestCell"
if let cell = tableView.dequeueReusableCellWithIdentifier(requestDirection) as? RequestCell {
cell.configureCell(friendRequest, isAnIncomingRequest: isAnIncomingRequest)
return cell
} else {
return UITableViewCell()
}
}
FAILS AT dequeueReusableCellWithIdentifier, according to breakpoints, with:
fatal error: unexpectedly found nil while unwrapping an Optional value
After hard coding the Identifiers ("IncomingRequestCell" and "OutgoingRequestCell" in the dequeueReusable... method as suggested in the comments, it appears that these values are the source of the problem. However, they properly identify their respective UITableViewCells in IB.
Any ideas?
Why you use global variable for reuseIdentifier and update it every time? You can use local var for it.
Also avoid using dequeueReusableCellWithIdentifier, please use dequeueReusableCellWithIdentifier:forIndexPath: instead. I had some strange issues in one of my previous project because of it.
Here is quick example, which works like a charm:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let identifier = (indexPath.row == 0) ? "Cell1" : "Cell2"
let cell = tableView.dequeueReusableCellWithIdentifier(identifier, forIndexPath: indexPath)
self.configureCell(cell, atIndexPath: indexPath)
return cell
}
UPDATE: After investigation, we figured, that problem was in custom cell class initialization code.
If everything is hooked up properly - which means that you correctly set your reuseIdentifiersin IB and the UITableViewCells are prototyp cells within your UITableView (and not loaded from a nib - which requires manually registering the UITableViewCells), than just use
dequeueReusableCellWithIdentifier:forIndexPath: