I try to set a string to my detailTextLabel in a tableView but it's returning nil. I have read other posts where I am not the first one but I cannot understand what is going wrong in my case. I am using Swift 4.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell = {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") else {
return UITableViewCell(style: UITableViewCellStyle.value1, reuseIdentifier: "Cell")
}
return cell
}()
let filtersRow: Bool = (currentSearchType == .all && indexPath.section == 0)
var titleText: String = ""
if filtersRow == true {
titleText = "Filters"
var detailText: String = ""
if currentFilters.count == 0 {
detailText = "None"
}
else if currentFilters.count == 1 {
detailText = currentFilters.first!
}
else {
detailText = "\(currentFilters.count)"
}
cell.textLabel?.text = titleText /// -> shows 'Filters' as expected
cell.detailTextLabel?.text = detailText /// -> shows nothing
print("Detail text: \(cell.detailTextLabel?.text)") --> returns nil
print("cell.textLabel? \(String(describing: cell.textLabel))") /// --> Optional(<UITAbleViewLabel...>)
print("cell.detailTextLabel? \(String(describing: cell.detailTextLabel))") /// ---> nil
cell.accessoryType = .disclosureIndicator
cell.accessoryType = .disclosureIndicator
return cell
}
...
There is definitely something wrong with the way I get my cell, but I do the same thing in an other viewController and it is going well...
Does anyone would have an idea?
This happens when the detailTextLabel isnt created. Mostly a bug in your code or storyboard. So check the creation of the problematic Cell.
Read also this Stackoverflow Q&A about this topic
Related
I have three different types of custom UITableCells. I have an if statement that sets them up:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if somePosts[indexPath.row].typeOfPost == .linkPost {
let cell: LinkTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "linkTableViewCell") as! LinkTableViewCell
} else if somePosts[indexPath.row].typeOfPost == .picturePost {
let cell: PictureTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "pictureTableViewCell") as! PictureTableViewCell
} else if somePosts[indexPath.row].typeOfPost == .textPost {
let cell: TextTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "textTableViewCell") as! TextTableViewCell
} else {
print("Type of post is not link, picture, or text")
}
}
Each of the custom cells has similar labels such as title and time. I would like to set these labels using the same line of code, such as:
cell.titleLabel.text = "Some title here"
However, in this example, I get an error saying I am using an unresolved identifier "cell," obviously because my variables are being declared non-globally. Is there a way around this since swift is strongly typed? Thanks!
Make a protocol that your TableViewCell classes extend, and store cell as a variable of that type.
protocol MyTableViewCell {
var titleLabel: UILabel { get }
// ...
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let identifier: String
switch somePosts[indexPath.row].typeOfPost {
case .linkPost: identifier = "linkTableViewCell"
case .picturePost: identifier = "pictureTableViewCell"
case .textPost: identifier = "textTableViewCell"
default: fatalError("Type of post is not link, picture, or text")
}
guard let cell = self.tableView.dequeueReusableCell(withIdentifier: identifier) as? MyTableViewCell else {
fatalError("Cell isn't castable to MyTableViewCell")
}
cell.titleLabel.text = "Some title here"
// ...
}
You have three basic solutions.
Repeat cell.text = ... inside each block. But this isn't what you really want as stated in your question.
Have your three custom cell classes all extend a common base class. Have this base class define any common properties.
Define a protocol with the common properties and have each of your custom cell classes conform to the protocol.
For options 2 and 3 you would declare a variable of the base/protocol type before the first if statement. Then after the whole if/else block, you can assign any of the common properties.
If you need to update any cell type specific properties, you can do that inside the appropriate block as well.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: BaseTableViewCell?
if somePosts[indexPath.row].typeOfPost == .linkPost {
cell = self.tableView.dequeueReusableCell(withIdentifier: "linkTableViewCell") as! LinkTableViewCell
} else if somePosts[indexPath.row].typeOfPost == .picturePost {
cell = self.tableView.dequeueReusableCell(withIdentifier: "pictureTableViewCell") as! PictureTableViewCell
} else if somePosts[indexPath.row].typeOfPost == .textPost {
cell = self.tableView.dequeueReusableCell(withIdentifier: "textTableViewCell") as! TextTableViewCell
} else {
print("Type of post is not link, picture, or text")
}
if let cell = cell {
cell.commonProperty = ...
return cell
} else {
return nil // this shouldn't happen but if it does, you have a bug to fix
}
}
If the subclasses each have their own titleLabel property, you will need to make them all conform to a protocol. Let's call it ConfigurableCell.
protocol ConfigurableCell {
var titleLabel: UILabel { get set }
}
Then, you can initialize your cells all the same way, but declare them as a ConfigurableCell:
var cell: ConfigurableCell? = nil // not set yet
if somePosts[indexPath.row].typeOfPost == .linkPost {
cell = self.tableView.dequeueReusableCell(withIdentifier: "linkTableViewCell") as! LinkTableViewCell
} else if somePosts[indexPath.row].typeOfPost == .picturePost {
cell = self.tableView.dequeueReusableCell(withIdentifier: "pictureTableViewCell") as! PictureTableViewCell
} else if somePosts[indexPath.row].typeOfPost == .textPost {
cell = self.tableView.dequeueReusableCell(withIdentifier: "textTableViewCell") as! TextTableViewCell
}
guard let cell = cell else {
// how to handle this error case is up to you
print("Type of post is not link, picture, or text")
return UITableViewCell()
}
// now, cell is a ConfigurableCell with a titleLabel property, regardless of class
cell.titleLabel.text = "Some title"
Of course, UITableViewCell does have a built-in textLabel property, which you could try to utilize in your cell classes, and then a protocol wouldn't be necessary, because the property is in UITableViewCell.
When selecting multiple cells in my tabeview the cells out of view are being selected too. I understand that this is because i am reusing the cell and its maintaining its selection as i scroll down. I have found a few people with similar issues but cant translate their solutions across to resolve my issue. I have tried not dequeing a cell and just use:
let cell = NewBillSplitterItemCell()
but get:
unexpectedly found nil while unwrapping an Optional value
on the line:
cell.currentSplitters.text = splitterList
in the following code:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
fetchBillItems()
let cell: NewBillSplitterItemCell = tableView.dequeueReusableCellWithIdentifier("NewBillSplitterItemCell") as! NewBillSplitterItemCell
let item = allItems[indexPath.row]
let numberOfSplitters = item.billSplitters?.count
if numberOfSplitters == 0 {
cell.currentSplitters.text = "No one is paying for this item yet."
} else {
var splitterList = "Split this item with "
let itemSplitters = item.billSplitters?.allObjects as! [BillSplitter]
for i in 0...Int((numberOfSplitters)!-1) {
if numberOfSplitters == 1 {
splitterList += "\(itemSplitters[i].name!)"
} else {
splitterList += ", \(itemSplitters[i].name!)"
}
}
cell.currentSplitters.text = splitterList
}
cell.name.text = item.name
cell.price.text = "£\(item.price!)"
return cell
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if let cell = tableView.cellForRowAtIndexPath(indexPath) {
if cell.accessoryType == .Checkmark
{
cell.accessoryType = .None
selectedItems.removeAtIndex(selectedItems.indexOf(allItems[indexPath.row])!)
} else {
cell.accessoryType = .Checkmark
selectedItems.append(allItems[indexPath.row])
}
}
}
I dont quite understand what to do and any help would be great. Thanks
In addition to what #Mike said, inside of cellForRowAtIndexPath you need an additional check because cells get reused.
Something along the line
let isSelected = selectedItems[indexPath.row].selected
if isSelected{
cell.accessoryType = .Checkmark
} else {
cell.accessoryType = .None
}
Same thing inside of didSelectRowAtIndexPath you should update the data source instead of relying on the UI of your cell for that condition.
Assuming your cell is nil, you should use
let cell = tableView.dequeueReusableCellWithIdentifier("..." forIndexPath:indexPath) as! NewBillSplitterItemCell
instead of
let cell= tableView.dequeueReusableCellWithIdentifier("...") as! NewBillSplitterItemCell
This ensures that cell will never be nil.
Also, I would check if the correct identifier is being used in all of your .xib .storyboard files.
am having this strange issue while am selecting any of the row from TableView another row is also get selected , say i have a tableView with multiple rows in my case its 11 and multiple selection with the accessory of tick mark is enabled (when i select a row a tick is marked on the selected row ) so when am selecting my first row , then row number 8 is also got selected (i can see the tick mark in the row number 8 but i selected only the row number 1 ) when i select another row number 2 my row number 9 is also get selected dont know why this is happening if anybody knows anything about this behaviour then please let me know it'll be so helpful for me , below is the code of didSelectRowAtIndexPath :
var selectedTextLabels = [NSIndexPath: String]()
var selectedTextLabelsName = [NSIndexPath: String]()
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath){
let cell = tableView.cellForRowAtIndexPath(indexPath) as! UsersTableViewCell
if (cell.accessoryType == UITableViewCellAccessoryType.Checkmark){
cell.accessoryType = UITableViewCellAccessoryType.None;
selectedTextLabels[indexPath] = nil
selectedTextLabelsName[indexPath] = nil
}else{
cell.accessoryType = UITableViewCellAccessoryType.Checkmark;
if (cell.accessoryType == UITableViewCellAccessoryType.Checkmark){
if let Id = cell.channelIDLbl?.text {
selectedTextLabels[indexPath] = Id
}
if let channelName = cell.lblChannelname?.text{
selectedTextLabelsName[indexPath] = channelName
}
}
}
cellForRowAtIndexpath:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! UsersTableViewCell
if isFavoritesTabActive == true {
let object : RChannels = self.favoritesArray.objectAtIndex(indexPath.row) as! RChannels
cell.lblChannelname.text = object.channelName
let favorite = object.isFavorite
if favorite == "true" {
cell.favIcon.image = UIImage(named: "Favourite")
return cell }
else {
let object : RChannels = self.noteObjects.objectAtIndex(indexPath.row) as! RChannels
cell.lblChannelname.text = object.channelName
let favorite = object.isFavorite
if favorite == "true" {
cell.favIcon.image = UIImage(named: "Favorite")
}else {
cell.favIcon.image = UIImage(named: "unFavorite") }
return cell
}
checking inside the channelIDLbl array for existence of the cell's id at cellforRowAtIndexPath did the job
if ((selectedTextLabels[indexPath]?.containsString("\(cell.channelIDLbl)")) != nil){
cell.accessoryType = .Checkmark
}else {
cell.accessoryType = .None
}
for more detail please check this same question
I want the highlight to change the size and appearance of an object inside the collection view.
How can I set object properties in a collection view cell, within the "didHighlight" method?
In "cellForItemAtIndexPath" you declare the reusable cells as the class
and just use "cell.MyOutlet.backgroundColor = UIColor.blueColor()"
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
if collectionView == self.CollectionViewController {
let (FriendFirstName,FriendLastName) = friends[indexPath.row]
let cell: CustomCellA = collectionView.dequeueReusableCellWithReuseIdentifier("demoCell", forIndexPath: indexPath) as! CustomCellA
if indexPath.section == 0 {
cell.cellTitle.text = Name
cell.imgCell.image = UIImage(named: Pics[indexPath.row])
cell.imgCell.layer.masksToBounds = true
cell.self.imgCell.layer.cornerRadius = 20
return cell
} else {
let cell2: AddCell = collectionView.dequeueReusableCellWithReuseIdentifier("demoCell2", forIndexPath: indexPath) as! AddCell
return cell2
}
} else if collectionView == self.EmojiCollectionViewController {
let cellB: CustomCellB = collectionView.dequeueReusableCellWithReuseIdentifier("demoCellB", forIndexPath: indexPath) as! CustomCellB
cellB.MyLabel.text = arrayOne[indexPath.row]
return cellB
} else {
let cellC: CustomCellC = collectionView.dequeueReusableCellWithReuseIdentifier("demoCellC", forIndexPath: indexPath) as! CustomCellC
// ...Set up cell
let height = self.CollectionViewController2.frame.height
cellC.frame = CGRectMake(cellB.frame.origin.x, 0, cellB.frame.size.width, height)
cellC.updateConstraintsIfNeeded()
cellC.layoutIfNeeded()
cellC.imgVw.image = UIImage(named: pictures[indexPath.row] as! String)
return cellC
}
}
func collectionView(collectionView: UICollectionView, didHighlightItemAtIndexPath indexPath: NSIndexPath) {
if collectionView == self.CollectionViewController {
if indexPath.section == 0 {
let cell: CustomCellA = CustomCellB()
cell.MyLabel.backgroundColor = UIColor.blueColor() //crashes due to nil value)
}
} else {
}
}
I tried using a similar definition in didHighlight and it keeps crashing.
Let didHighlightItemAtIndexPath only change the data, not the view. So, make friends[indexPath.row] an object or add another parameter to tuple. And in didHighlightItemAtIndexPath do something like the following:
if collectionView == self.CollectionViewController {
if indexPath.section == 0 {
let (fname, lname, color) = friends[indexPath.row];
friends[indexPath.row] = (fname, lname, UIColor.blueColor())
}
}
And in cellForItemAtIndexPath:
if collectionView == self.CollectionViewController {
let (FriendFirstName, FriendLastName, color) = friends[indexPath.row]
if indexPath.section != 0 {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("demoCell2", forIndexPath: indexPath) as! AddCell;
return cell;
} else if color == nil {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("demoCell", forIndexPath: indexPath) as! CustomCellA;
cell.cellTitle.text = Name
cell.imgCell.image = UIImage(named: Pics[indexPath.row])
cell.imgCell.layer.masksToBounds = true
cell.self.imgCell.layer.cornerRadius = 20
return cell
} else {
cell = collectionView.dequeueReusableCellWithReuseIdentifier("demoCellB", forIndexPath: indexPath) as! CustomCellB;
// your code for CustomCellB
return cell;
}
}
EDIT: Updated, so instead of objects it uses tuples. Also added the functionality that you need. Basically, you need to create two prototype cells in the interface builder with different Reuse Identifiers and Classes. And then dequeue the correct identifier in the index path. Also, I refactored some of your code and if I were you I would create a different function for each collectionView and do something like:
if collectionView == self.CollectionViewController {
return self.dequeueCollectionCell(indexPath);
} else if collectionView == self.EmojiCollectionViewController {
return self.dequeuEmojiCell(indexPath);
} else {
return self.dequeueSomeOtherCell(indexPath);
}
Also, the code that you provided... I hope it is not an actual production code and you changed the values for this forum. Otherwise, in couple of days even, you are going to get lost in what is happening here. Too many inconsistent variable names and identifiers.
One more also. Use naming conventions in your class names. Read this forum post for more information. Apple uses camelCase everywhere. In majority of instances, the first letter is capitalized for class names, not object names.
first you have to define the collectionView Cell then do what ever you want on that cell. to define your sell add the below lines into didHighlightItemAtIndexPath
if let cellToUpdate = self.dataCollection.cellForItemAtIndexPath(indexPath) {
//your code here.
}
i'm trying to add multiple subclasses into a UITableView. The problem is that it keep giving me following error:
Type UITableVieCell does not conform to protocol NilLiteralConvertible
CellForRowAtIndexPath
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
cell.textLabel?.text = self.section1[indexPath.row]
cell.accessoryType = .DisclosureIndicator
return cell
} else if indexPath.section == 1 {
let cell = tableView.dequeueReusableCellWithIdentifier("SwitchViewCell", forIndexPath: indexPath) as SwitchViewCell
cell.cellLabel?.text = self.section2[indexPath.row]
return cell
}
return nil
}
You can't return nil. Instead by default return empty cell.
var cell :UITableViewCell!
switch (indexPath.row) {
case 0:
cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
cell.textLabel?.text = self.section1[indexPath.row]
cell.accessoryType = .DisclosureIndicator
break;
case 1:
cell = tableView.dequeueReusableCellWithIdentifier("SwitchViewCell", forIndexPath: indexPath) as SwitchViewCell
(cell as SwitchViewCell).cellLabel?.text = self.section2[indexPath.row]
break;
default:
break;
}
return cell
Make sure that you have registered the nib
self.tableView.registerNib(UINib(nibName: "SwitchViewCell", bundle: nil), forCellReuseIdentifier: "SwitchViewCell")
tableView:cellForRowAtIndexPath: must return a UITableViewCell and can't return nil. So you will have to remove return nil. But it won't be enough. Your if else statement also has to be complete. What it means is that every possible section value has to be provided or, at least, send to a fallthrough.
Your if else statement should look like this:
if indexPath.section == 0 {
/* ... */
} else if indexPath.section == 1 {
/* ... */
} else {
/* ... */
}
Or like this:
if indexPath.section == 0 {
/* ... */
} else {
/* ... */
}
However, the following if else statement is not complete:
if indexPath.section == 0 {
/* ... */
} else if indexPath.section == 1 {
/* ... */
}
In this case, tableView:cellForRowAtIndexPath: will not know what to return if any of these conditions is verified. Thus, if you try it, Xcode (that is smart) will also display an error message:
Missing return in a function expected to return 'UITableViewCell'
Therefore, the following code should work:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
cell.textLabel?.text = self.section1[indexPath.row]
cell.accessoryType = .DisclosureIndicator
return cell
} else {
let cell = tableView.dequeueReusableCellWithIdentifier("SwitchViewCell", forIndexPath: indexPath) as SwitchViewCell
cell.cellLabel?.text = self.section2[indexPath.row]
return cell
}
}
PS:
If you also use switch statement inside your tableView:cellForRowAtIndexPath: method in order to set your cells, this answer to a similar question may help you.
In my situation, I did the following:
let cellOne = mTableView.dequeueReusableCell(withIdentifier: "CellOne") as! customTableViewCellOne
let cellTwo = mTableView.dequeueReusableCell(withIdentifier: "CellTwo") as! customTableViewCellTwo
if (..something...) {
cellOne.mLabel = "hey"
return cellOne
}
else if (..another condition..) {
cellTwo.mButton.layer.cornerRadius = 5
return cellTwo
}
return UITableViewCell()
I hope it helps
You are not allowed to return nil for cellForRowAtIndexPath
You could use at the end:
let cell:UITableViewCell!
return cell
instead of
return nil
This should be (if you just have 2 sections) never get executed.
Edit:
You could also use instead of "} else if indexPath.section == 1 {" only } else { - to return a cell. I just showed up what is the problem. Or use a Switch/Case and returning an empty Cell on default.