swift multiple cell subclasses in UITableViewCOntroller - ios

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.

Related

UITableView scrolls up on reloadData

I have a UITableView that gets reloaded if a button in some of its cells gets tapped. The issue appears if the following steps are made:
Tap a button on a cell so that another cells appear below the tapped cell on reloadData.
Scroll up the table view so that it hides some of the upper content.
Tap the button again to hide the cells that were just shown making another call to reloadData.
Then the table view goes up and hides the upper content (the whole first cell and part of the second one). Here is some of the code:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if shouldShowImageResolutionOptions && (indexPath.row == 2 || indexPath.row == 3 || indexPath.row == 4) {
return isLastKnownDeviceOrientationLandscape ? 60 : 80
}
if shouldShowImageDisplayOptions && (indexPath.row == 3 || indexPath.row == 4) {
return isLastKnownDeviceOrientationLandscape ? 60 : 80
}
return isLastKnownDeviceOrientationLandscape ? tableView.frame.size.height / 2.5 + 40 : tableView.frame.size.height / 4.5 + 40
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if shouldShowImageResolutionOptions {
return 6
}
if shouldShowImageDisplayOptions {
return 5
}
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifiers.IntervalCell) as! IntervalCell
cell.selectionStyle = .none
cell.dottedSliderView.contentMode = .redraw
cell.adjustThumbPosition()
return cell
case 1:
let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifiers.SettingsCell) as! SettingsCell
cell.selectionStyle = .none
cell.titleLabel.text = LabelTitles.ImageResolution
cell.choiseLabel.text = LabelTitles.HDResolutioin
cell.onButtonTap = {
self.shouldShowImageResolutionOptions = !self.shouldShowImageResolutionOptions
self.shouldShowImageDisplayOptions = false
self.menuTableView.reloadData()
}
return cell
case 2:
if(shouldShowImageResolutionOptions) {
let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifiers.SingleSettingCell, for: indexPath) as! SingleSettingCell
cell.selectionStyle = .none
cell.mainSettingLabel.text = Settings.HDResolution
return cell
}
let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifiers.SettingsCell) as! SettingsCell
cell.selectionStyle = .none
cell.titleLabel.text = LabelTitles.ImageDisplay
cell.choiseLabel.text = LabelTitles.EnlargeImage
cell.onButtonTap = {
self.shouldShowImageDisplayOptions = !self.shouldShowImageDisplayOptions
self.shouldShowImageResolutionOptions = false
self.menuTableView.reloadData()
}
return cell
case 3, 4:
if(shouldShowImageResolutionOptions) {
let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifiers.SingleSettingCell, for: indexPath) as! SingleSettingCell
cell.selectionStyle = .none
cell.mainSettingLabel.text = indexPath.row == 3 ? Settings.HighResolution : Settings.MediumResolution
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifiers.SingleSettingCell, for: indexPath) as! SingleSettingCell
cell.selectionStyle = .none
cell.mainSettingLabel.text = indexPath.row == 3 ? Settings.ShowFullImage : Settings.EnlargeImage
return cell
}
case 5:
let cell = tableView.dequeueReusableCell(withIdentifier: CellIdentifiers.SettingsCell) as! SettingsCell
cell.selectionStyle = .none
cell.titleLabel.text = LabelTitles.ImageDisplay
cell.choiseLabel.text = LabelTitles.EnlargeImage
cell.onButtonTap = {
self.shouldShowImageDisplayOptions = !self.shouldShowImageDisplayOptions
self.shouldShowImageResolutionOptions = false
self.menuTableView.reloadData()
}
return cell
default:
return UITableViewCell()
}
}
From the UITableView's reloadData method documentation (https://developer.apple.com/documentation/uikit/uitableview/1614862-reloaddata):
The table view’s delegate or data source calls this method when it wants the table view to completely reload its data. It should not be called in the methods that insert or delete rows, especially within an animation block implemented with calls to beginUpdates and endUpdates.
There are dedicated insert/delete rows methods for inserting and deleting:
insertRowsAtIndexPaths:withRowAnimation:(https://developer.apple.com/documentation/uikit/uitableview/1614879-insertrowsatindexpaths)
deleteRowsAtIndexPaths:withRowAnimation: (https://developer.apple.com/documentation/uikit/uitableview/1614960-deleterowsatindexpaths)
So when you refactor your code to use those it should work smoothly and as expected.

Multiple selection UITableView without dequeueReusableCellWithIdentifier

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.

Missing return in a function expected to return 'UITableViewCell', but actually returning twice

I am trying to set up a table view that will change cells based on the segmented controller at the top. However, in attempting to change the cells when reloading the tableview, I am receiving a return function error when in fact I have a return function. What can I do to fix this?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if friendSelector.selectedSegmentIndex == 0 {
print("0")
cell = self.friendsTable.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! FriendsTableViewCell
cell.nameLabel.text = friends[indexPath.row]
cell.bacLabel.text = String(friendsBac[indexPath.row])
cell.statusImageView.image = friendsImage[indexPath.row]
return cell
}
if friendSelector.selectedSegmentIndex == 1 {
print("1")
celladd = self.friendsTable.dequeueReusableCell(withIdentifier: "celladd", for: indexPath) as! FriendsAddTableViewCell
celladd.nameLabel.text = requested[indexPath.row]
celladd.statusImageView.image = UIImage(named: "greenlight")
return celladd
}
}
You should return a cell. In the above code, if both conditions failed, then nothing will be returned. So a warning raised. Just remove second "if" condition and use else case as follows:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if friendSelector.selectedSegmentIndex == 0 {
print("0")
cell = self.friendsTable.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! FriendsTableViewCell
cell.nameLabel.text = friends[indexPath.row]
cell.bacLabel.text = String(friendsBac[indexPath.row])
cell.statusImageView.image = friendsImage[indexPath.row]
return cell
}
else {
print("1")
celladd = self.friendsTable.dequeueReusableCell(withIdentifier: "celladd", for: indexPath) as! FriendsAddTableViewCell
celladd.nameLabel.text = requested[indexPath.row]
celladd.statusImageView.image = UIImage(named: "greenlight")
return celladd
}
}
You have two if statements which both could potentially not be true, so you must return a cell in the case that the selected index is neither 0 or 1.
if friendSelector.selectedSegmentIndex == 0 {
...
return cell
}
else if friendSelector.selectedSegmentIndex == 1 {
...
return celladd
}
return UITableViewCell()
I prefer to use switch statements for this kind of thing.
switch friendSelector.selectedSegmentIndex {
case 0:
...
return cell
case 1:
...
return celladd
default:
return UITableViewCell()
}
It's very obvious. You have two return statements in two if conditions. What if both your 'if' conditions do not get executed? There is no return statement for that case. That's why the compiler is complaining

Why is the second return statement being called and why am I getting the return error?

I have two uitableviews not related to each other at all, but they are on the same view. The animal tableview is showing the information, but animallocation is not displaying anything. I put a breakpoint on the second return statement in the second if statement, but the program does not stop. I am also getting an error (Look below). How do I fix this? the first if statement works. animallocationarray is populated it prints ("Africa", "India", "South America"). I get an error http://puu.sh/mNK2J/549dde1225.png
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if(tableView == animal){
var cell: animalCell = self.reviews.dequeueReusableCellWithIdentifier("Cell") as! animalCell
cell.box.text = animalarray[indexPath.row]
if(indexPath.row % 2 == 0){
cell.backgroundColor = RGB("ffffff")
}else{
cell.backgroundColor = RGB("F98233")
}
return cell
}
if(tableView == animallocation){
var cell: location = self.reviews.dequeueReusableCellWithIdentifier("location") as! location
cell.label.text = animallocatioarray[indexPath.row]
if(indexPath.row == 0){
cell.label.textColor = RGB("fffff")
}
if(indexPath.row != 0){
cellvs.vslabel.textColor = RGB("f52654")
}
return cell //placed a breakpoint here but nothing happens
}
}
You miss case Default. You can edit
if(tableView == animal){
var cell: animalCell = self.reviews.dequeueReusableCellWithIdentifier("Cell") as! animalCell
cell.box.text = animalarray[indexPath.row]
if(indexPath.row % 2 == 0){
cell.backgroundColor = RGB("ffffff")
}else{
cell.backgroundColor = RGB("F98233")
}
return cell
}
if(tableView == animallocation){
var cell: location = self.reviews.dequeueReusableCellWithIdentifier("location") as! location
cell.label.text = animallocatioarray[indexPath.row]
if(indexPath.row == 0){
cell.label.textColor = RGB("fffff")
}
if(indexPath.row != 0){
cellvs.vslabel.textColor = RGB("f52654")
}
return cell //placed a breakpoint here but nothing happens
}
To:
if(tableView == animal){
var cell: animalCell = self.reviews.dequeueReusableCellWithIdentifier("Cell") as! animalCell
cell.box.text = animalarray[indexPath.row]
if(indexPath.row % 2 == 0){
cell.backgroundColor = RGB("ffffff")
}else{
cell.backgroundColor = RGB("F98233")
}
return cell
}
var cell: location = self.reviews.dequeueReusableCellWithIdentifier("location") as! location
cell.label.text = animallocatioarray[indexPath.row]
if(indexPath.row == 0){
cell.label.textColor = RGB("fffff")
}
if(indexPath.row != 0){
cellvs.vslabel.textColor = RGB("f52654")
}
return cell //placed a breakpoint here but nothing happens
The function must return a UITableViewCell. Within your code you have two if statements. In the event that both of them fail, no return statement would be executed. That's why your seeing the error - it is a compilation error if there are code paths that do not return the required type.
In your case, one of the two if statements will always be true, but the compiler can't know that. If you recode the second to be an else, instead of another if, there will be no code paths that don't return a value. i.e.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if (tableView == animal) {
// animal code
return cell
} else { // i.e. if (tableview == animallocation)
// animallocation code
return cell
}
}
Replace your tableview:cellForRowAtIndexPath: with below code.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if(tableView == animal){
var cell: animalCell = self.reviews.dequeueReusableCellWithIdentifier("Cell") as! animalCell
cell.box.text = animalarray[indexPath.row]
if(indexPath.row % 2 == 0){
cell.backgroundColor = RGB("ffffff")
}else{
cell.backgroundColor = RGB("F98233")
}
return cell
}
else{
var cell: location = self.reviews.dequeueReusableCellWithIdentifier("location") as! location
cell.label.text = animallocatioarray[indexPath.row]
if(indexPath.row == 0){
cell.label.textColor = RGB("fffff")
}
else if(indexPath.row != 0){
cellvs.vslabel.textColor = RGB("f52654")
}
return cell //placed a breakpoint here but nothing happens
}
}
In you case, you are returning the cell in if condition,
but also we have to consider the case where if condition will not true,
i.e. else part.

CollectionView objects (Swift)

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.
}

Resources