Swift collectionView non reusable cell - ios

I have a collectionView with 2 sections, each section should be based off the same cell (which only contains a UIImageView).
The only difference between the sections is the number of cells they should contain and types of images displayed.
If I set the cellforItemAtIndexPath method to use a dequeued cell (collectionView.dequeueReusableCellWithIdentifier) everything populates fine, if I set it to use an instance of my custom cell without dequeuing, it crashes.
cellForItemAtIndexPath method:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
//cannot use dequedReusableCell since some cells below scroll-line should remain highlighted
let cell = NumbersCollectionViewCell() // CAUSES CRASH
// let cell = collectionView.dequeueReusableCellWithReuseIdentifier(Constants.cellIdentifier, forIndexPath: indexPath) as! NumbersCollectionViewCell // WORKS FINE
switch indexPath.section {
case 0: cell.imageView.image = UIImage(named: numberImageFiles[indexPath.row])
case 1: cell.imageView.image = UIImage(named: specialNumberImageFiles[indexPath.row])
default: break
}
return cell
}
NumbersCollectionViewCell definition:
class NumbersCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var imageView: UIImageView!
}
The error that appears is: "Fatal error: unexpectedly found nil while unwrapping an Optional value" and it highlights the "case 0" row in my cellForItemAtIndexPath method.
The reason I don't want to use a dequeued cell is that I need some of the cells to be highlighted at run-time based on user selections, and if I use a dequeued cell it doesn't seem to keep the ones below the scroll-line highlighted.

Assuming that you have both a .swift and a .xib file for your cell, you need to instantiate your NumbersCollectionViewCell like this:
E.g.
let numbersCollectionViewCell = UINib(nibName: "NumbersCollectionViewCell", bundle: bundle).instantiateWithOwner(nil, options: nil)[0] as! NumbersCollectionViewCell
Otherwise, your IBOutlets will not be connected.

override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCellWithReuseIdentifier(identifier: "NumbersCollectionViewCell">, forIndexPath: NSIndexPath>) as? NumbersCollectionViewCell else {
print("failed to get cell")
return UICollectionViewCell()
}
switch indexPath.section {
case 0: cell.imageView.image = UIImage(named: numberImageFiles[indexPath.row])
case 1: cell.imageView.image = UIImage(named: specialNumberImageFiles[indexPath.row])
default: break
}
return cell
}

Related

Fill different tableViews inside the different collectionView cell

I have a complicated situation and I need your help to figure out what should I do.
I have one prototype UIcollectionView, this prototype should be created 4 times for each style type.
I defined these style type as an enum:
enum Colors {
case black, blue, red, green
}
var color = Colors.black
Inside of CollectionViewCell I have also a tableView that has one prototype that contain a label. And there are four arrays that TableViews should be filled by these arrays:
var black = ["black1","black2","black3"]
var blue = ["blue1","blue2","blue3"]
var red = ["red1","red2","red3"]
var green = ["green1","green2","green3"]
now, I tried to make a connection between these TableViews and collectionViews
first for UICollectionView
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
switch indexPath.row {
case 0,1,2,3:
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "colors", for: indexPath) as? colorsCell {
switch indexPath.row {
case 1:
self.color = .black
case 2:
self.color = .blue
case 3:
self.color = .red
case 4:
self.color = .green
default:
break
}
return cell
}
default:
break
}
return UICollectionViewCell()
}
Then, for TableView
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0,1,2,3:
if let cell = tableView.dequeueReusableCell(withIdentifier: "colCell", for: indexPath) as? colCellDashboard {
switch self.color {
case .black:
cell.title.text = black[indexPath.row]
case .blue:
cell.title.text = blue[indexPath.row]
case .red:
cell.title.text = red[indexPath.row]
case .green:
cell.title.text = green[indexPath.row]
}
return cell
}
return UITableViewCell()
}
The result isn't good enough, the first three tableview in the first three collectionview were filled by blue array, and the last one is correct that filled with green array.
I will be appreciated if you can help me on this.
When the tableView is nested inside the collection you should use
class CollectionCell:UICollectionViewCell,UITableViewDelegate,UITableViewDataSource {
var tableArr = [String]() // table dataSource array
func configure(_ res:[String]) {
tableArr = arr
self.tableView.reloadData()
}
///////
here implement the cellForRowAt and numberOfRows for the nested tableView
}
Inside the vc that contains the collectionView declare the array like this
let arr = [["black1","black2","black3"] , ["blue1","blue2","blue3"] ,
["red1","red2","red3"] , ["green1","green2","green3"]]
Then inside cellForItemAt
if let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "colors", for: indexPath) as? colorsCell {
cell.configure(arr[indexPath.row])
Ahh, The issue with you code is that in your cellForItemAt delegate method of collection view you are assigning color to a class variable self.color. This will be overwritten for each call to the delegate. So when cellForRowAt delegate for tableview is called it will have the overwritten value of self.color (whatever will be the most updated value) and as a result you will see unexpected entry in your table.
Having tableviews inside collectionViews is kinda common problem. The simplest approach to solve it is:
-Put your datasouce for tableview inside the collectionViewCell.(create a variable in UICollectionViewCell subclass.)
-Assign value to your datasource for each tableView inside cellForItemAt delegate of CollectionView.
-Write all tableViewDelegates inside collectionViewCell and use the datasource varaible of CollectionViewCell to fill tableView.
The key is to underStand that tableView is inside CollectionViewCell and therefore, your CollectionViewCell is responsible to create the tableView.
The CollectionViewCell Structure given be #Sh_Khan looks fine(So not putting similiar code).

UITableView not dequeuing some cells

I've never run into this issue before, but here is what I am currently experiencing:
I have a UITableView in a view controller. Delegate and dataSource are setup properly.
Now I'm running into performance issues after showing/inserting a few cells.
The issue is that the tableview seems to not dequeue some of the cells but rather creates a new one each time (always calls awakeFromNib in the custom class).
The ones with which the dequeuing seems to not work are just simple ones with a label and some with a label, an image and another label.
There is however one case in which dequeuing seems to work. I have a custom cell class that just contains a vertical stack view into which I dynamically add a variable amount of custom buttons.
I have a class that is responsible for setting up the cells. It has a method for each of the cell classes I use.
func initialModeratorCell(at indexPath: IndexPath, with message: Message, in tableView: UITableView) -> OnboardingInitialModeratorCell{
guard let cell = tableView.dequeueReusableCell(withIdentifier: initalModeratorCellIndentifier) as? OnboardingInitialModeratorCell else{
fatalError("No cell with \(initalModeratorCellIndentifier) identifier")
}
if indexPath.row != 0{
cell.moderatorImage.isHidden = true
}
let attributedMessage = attributedString(for: message, with: paragraphStyleFor(message: message))
cell.mesageText.attributedText = attributedMessage
cell.dateLabel.text = message.userType.name()
cell.mesageText.sizeToFit()
cell.dateLabel.sizeToFit()
return cell
}
All those methods look similar to this. This is one that doesn't seem to get reused.
This here is the one that does get reused (awakeFromNib only called once):
func componentCell(from item: ConversationItem, in tableView: UITableView, with owner: OnboardingComponentCellDelegate) -> OnboardingComponentCell{
guard let comp = item as? Component else{
fatalError("Type of item is not Component, but should be!")
}
guard let cell = tableView.dequeueReusableCell(withIdentifier: componentButtonsCellIdentifier) as? OnboardingComponentCell else{
fatalError("No cell with \(componentButtonsCellIdentifier) identifier")
}
cell.componentStack.translatesAutoresizingMaskIntoConstraints = false
setupComponentCell(cell, for: comp, owner: owner)
return cell;
}
This is setupCell():
setupComponentCell(_ cell: OnboardingComponentCell, for comp: Component, owner: OnboardingComponentCellDelegate){
cell.reset()
cell.component = comp
OnboardingComponentManager.createComponent(for: comp, in: cell, delegate: owner)
}
The cell.reset() method looks like this:
func reset(){
component = nil
delegate = nil
componentStack.removeAll() //removeAll is in an extension on UIStackView
}
The call OnboardingComponentManager.createComponent() just populates the stackview with the correct buttons for said component.
The methods above (componentCell(from:) and initialModeratorCell(at:) are called from the public method onboardingCellForItem(at indexPath: IndexPath, ..):
static func onboardingCellForItem(at indexPath: IndexPath, with displayedItems: Items, in tableView: Table, typingDelegate: IndicatorDelegate, buttonOwner: CellDelegate, onboardingType: ConvType) -> UITableViewCell{
let item = displayedItems[indexPath.row]
if item.type == .message{
return messageCell(from: item, at: indexPath, in: tableView, with: displayedItems)
}else if item.type == .component{
return componentCell(from: item, in: tableView, with: buttonOwner)
}else if item.type == .typingIndicator{
return typingIndicatorCell(with: typingDelegate, in: tableView)
}
return UITableViewCell()
}
This method then is called from the dataSource method cellForRowAtIndexPath like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
return OnboardingCellManager.onboardingCellForItem(at: indexPath, with: displayedItems, in: tableView, typingDelegate: self, buttonOwner: self, onboardingType: .onboarding)
}
The cells were prototyped in a storyboard in the tableView (as prototype cells).
I'm somewhat hitting a wall here. I've never run into this issue before and I can't seem to find the reason as to why it happens.
This performance degradation is most notable on older devices (iPhone 5, etc).

Thread1:EXC_BAD_instruction(code=exc_i386_invop,subcode=0*0) on tableView Project

Hey guys i just started Swift and i am having problems.I did give the label a tag and i implemented the UIViewController,UiTableViewDelegate and UITableViewDataSource. I did add in the storyboard the Delegate and the dataSource on the tableView but i keep getting this error
Thread1:EXC_BAD_instruction(code=exc_i386_invop,subcode=0*0) on tableView Project
on let lblName:UILabel = cell.viewWithTag(102) as! UILabel
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:UITableViewCell = tableView.dequeueReusableCellWithIdentifier("myCell")!
//remplissage cell
switch indexPath.section {
case 0:
let lblName:UILabel = cell.viewWithTag(102) as! UILabel
lblName.text = "30/09/2200"
let lblDate:UILabel = cell.viewWithTag(103) as! UILabel
lblDate.text = "30/09/2200"
//let imgProfile:UIImageView = cell.viewWithTag(103) as! UIImageView
// imgProfile.image = UIImage(named: "pexel")
cell.textLabel?.text = "Firas"
case 1:
cell.textLabel?.text = "sqdqsd"
default:
cell.textLabel?.text = "Nothing"
}
return cell
}
class FirasCell: UITableViewCell {
#IBOutlet weak var name: UILabel!
#IBOutlet weak var date: UILabel!
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// This will be an an option, as dequeue returns an optional
// This also assumes that all your cells are FirasCells
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell") as? FirasCell
// Sure you aren't thinking of rows here? An array would be rows, an array of arrays would be sections with rows in them
switch indexPath.section {
case 0:
cell?.name.text = "30/09/2200"
cell?.date.text = "30/09/2200"
cell?.textLabel?.text = "Firas"
case 1:
cell?.textLabel?.text = "Second Cell"
default:
cell?.textLabel?.text = "The rest of them"
}
// Quick and dirty check for this example as you are not allowed to return nil
return cell ?? UITableViewCell()
}
It might be possible to figure out the tag solution, but it's not very reliable. A quick and easy solution is to create your own cell (see FirasCell above) and make sure your cell in your table in your storyboard is of that type. Then add your two labels as outlets.
Now your cell can be a FirasCell and you can directly set the text on them after dequeuing them. I've avoided using ! in the example code as that outright crashes your app, so only use ! if you are very very sure.
The trickiest part is probably setting up the outlets, but once you know how it's easy. There's a good amount of tutorials out there that do explain it better than me, but the short version is that you want to open the Connections inspector (the circle with an arrow to the right tab in the utility bar on the right) when you have the cell selected and then click drag from the circle next to your outlet to your UILabel.

Swift - Type Casting

I am building a custom UITableView with custom cells.
Each of the custom cells are a subclass of FormItemTableViewCell
I am attempting to populate the cell data in cellForRowAtIndexPath
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = FormItemTableViewCell();
if(indexPath.row == 1){
cell = tableView.dequeueReusableCellWithIdentifier(twoOptionCellIdentifier, forIndexPath: indexPath) as! TwoOptionTableViewCell
} else {
cell = tableView.dequeueReusableCellWithIdentifier(oneTextFieldCellIdentifier, forIndexPath: indexPath) as! OneTextFieldTableViewCell
}
cell.questionLabel.text = "What is the meaning of life?";
return cell
}
How do I access the elements in the subclass?
For example: TwoOptionTableViewCell has a segControl
while the OneTextFieldTableViewCell has a answerTextField
There are some decent answers in this question but most of them have one bad thing in common, they force unwrapped optionals, which you should avoid as much as you can (pretty much the only acceptable place to use them is in IBOutlets)
This is what I think is the best way to handle this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCellWithIdentifier("Identifier", forIndexPath: indexPath) as? FormItemTableViewCell else {
fatalError("Cell is not of kind FormItemTableViewCell")
}
switch cell {
case let cell as TwoOptionTableViewCell where indexPath.row == 1:
// Configure cell, which is an object of class TwoOptionTableViewCell, but only when we are in row 1
break
case let cell as TwoOptionTableViewCell:
// Configure cell, which is an object of class TwoOptionTableViewCell, when the row is anything but 1
break
case let cell as OneTextFieldTableViewCell:
// Configure cell, which is an object of class OneTextFieldTableViewCell
break
case _: print("The cell \(cell) didn't match any patterns: \(indexPath)")
}
cell.questionLabel.text = "What is the meaning of life?";
return cell
}
Now let me walk you through the reasons I think it's the best way.
First of all, it doesn't force unwraps any optionals, everything is unwrapped nicely in the switch case.
It dequeues your cell from the table (something you should always do) and makes sure it's a subclass of FormItemTableViewCell, otherwise it throws a fatal error.
By using a switch case, it casts cell into the class you need, and at the same time it checks if it's the index path you want. So if you want to share some logic in different rows that share a class, you can compare indexPath.row to multiple values. If you don't use the where clause, it will use the same logic in all places where it finds a cell with that class.
Do note that you will need to add some logic to get the desired identifier depending on the row.
You can use one of the two approaches:
1) The best way:
if(indexPath.row == 1) {
let cell = tableView.dequeueReusableCellWithIdentifier(twoOptionCellIdentifier, forIndexPath: indexPath) as! TwoOptionTableViewCell
// the type of cell is TwoOptionTableViewCell. Configure it here.
return cell
} else {
let cell = tableView.dequeueReusableCellWithIdentifier(oneTextFieldCellIdentifier, forIndexPath: indexPath) as! OneTextFieldTableViewCell
// the type of cell is TwoOptionTableViewCell. Configure it here.
return cell
}
2) If you declare cell just once, as a superclass, then you have to downcast it like this.
var cell: FormItemTableViewCell
cell = ... // dequeue and assign the cell like you do in your code.
if let twoOptionCell = cell as? TwoOptionTableViewCell
{
// configure twoOptionCell
}
else if let oneTextFieldCell = cell as? OneTextFieldTableViewCell
{
// configure oneTextFieldCell
}
return cell
This is more verbose, once you add the code to dequeue the cell. So I personally prefer and recommend the first approach.
If I understand correctly, you want to keep main declaration of cell as FormItemTableViewCell to access common properties.
You can create a new variable and assign it the casted version.
Do your stuff with this instance as this is a class object it will point to same reference.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = FormItemTableViewCell();
// this can be replaced with below line as I don't see the purpose of creating an instance here while you use dequeue below.
// var cell: FormItemTableViewCell!
if(indexPath.row == 1){
cell = tableView.dequeueReusableCellWithIdentifier(twoOptionCellIdentifier, forIndexPath: indexPath);
let tempCell = cell as! TwoOptionTableViewCell;
// access members of TwoOptionTableViewCell on tempCell
tempCell.segControl.someProperty = 0;
} else {
cell = tableView.dequeueReusableCellWithIdentifier(oneTextFieldCellIdentifier, forIndexPath: indexPath);
let tempCell = cell as! OneTextFieldTableViewCell;
// access members of OneTextFieldTableViewCell on tempCell
tempCell.answerTextField.text = "42";
}
cell.questionLabel.text = "What is the meaning of life?";
return cell
}
You're going to have to conditionally cast them in that case. I like using Enums for Rows/Sections instead of == 1 (depending on how your TableView is setup), but basically you'll want to do the following:
if indexPath.row == 1 {
let cell = tableView.dequeueReusableCellWithIdentifier(twoOptionCellIdentifier, forIndexPath: indexPath) as! TwoOptionTableViewCell
// Note that we cast the cell to TwoOptionTableViewCell
// access `segControl` here
return cell
} else {
let cell = tableView.dequeueReusableCellWithIdentifier(oneTextFieldCellIdentifier, forIndexPath: indexPath) as! OneTextFieldTableViewCell
// This cell we cast to OneTextFieldTableViewCell.
// access `answerTextField` here
return cell
}
What you were doing was defining the cell as FormItemTableViewCell, so subsequent accesses would only know it in that form even though you explicitly cast it to a subclass during assignment.
As a side-note, you don't have to assign to the var as you did there, what you could do is let cell: FormItemTableViewCell. Then in the if-statements you could define new cells of the subclasses, operate on them, and then assign back to your original cell and then return that. This is useful if you're going to be performing the same operations on both cell types after the if statements (such as setting a background colour or something, regardless of which subclass you have).
Here is my favourite way of handling this situation:
enum CellTypes {
case TwoOption, OneTextField
init(row: Int) {
if row == 1 {
self = .TwoOption
} else {
self = .OneTextField
}
}
var reuseIdentifier: String {
switch self {
case .TwoOption: return "twoOptionReuseIdentifier"
case .OneTextField: return "oneTextFieldReuseIdentifier"
}
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: FormItemTableViewCell
let cellType = CellTypes(row: indexPath.row)
switch cellType {
case .TwoOption:
let twoOptionCell = tableView.dequeueReusableCellWithIdentifier(cellType.reuseIdentifier, forIndexPath: indexPath) as! TwoOptionTableViewCell
// do stuff with the `segControl`
cell = twoOptionCell
case .OneTextField:
let textFieldCell = tableView.dequeueReusableCellWithIdentifier(cellType.reuseIdentifier, forIndexPath: indexPath) as! OneTextFieldTableViewCell
// do stuff with the `answerTextField`
cell = textFieldCell
}
// Here do something regardless of which CellType it is:
cell.questionLabel.text = "What is the meaning of life?"
return cell
}

reloadRowsAtIndexPaths doesn't update my cell data

I have a UITableView in my ViewController.
One of the cell could be tap into another TableViewController to allow select a value.
I want to update my cell after back from the callee ViewController.
right now, i could pass back the selected value by delegate.
However, i tried following way, none of them works.
self.mainTable.reloadData()
dispatch_async(dispatch_get_main_queue()) {
self.mainTable.reloadData()
}
self.mainTable.beginUpdates()
self.mainTable.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None)
self.mainTable.endUpdates()
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
was called and executed without error.
but the UI just doesn't change
here is the way I update value in cellForRowAtIndexPath
if let currentCell = tableView.cellForRowAtIndexPath(indexPath) as UITableViewCell! {
currentCell.textLabel?.text = address
return currentCell
}
Here is my cellForRowAtIndexPath -
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let id = "Cell"
println(indexPath)
if indexPath.row == 1 {
var cell = tableView.dequeueReusableCellWithIdentifier(id) as? UITableViewCell
if cell == nil {
cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: id)
cell?.contentMode = UIViewContentMode.Center
cell?.selectionStyle = UITableViewCellSelectionStyle.None
cell?.contentView.addSubview(mapView!)
}
return cell!
}else{
let cell = UITableViewCell()
cell.textLabel?.text = self.address
return cell
}
}
Here is the delegate method -
func passBackSelectedAddress(address: String) {
self.address = address
var indexPath = NSIndexPath(forRow: 0, inSection: 0)
self.mainTable.beginUpdates()
self.mainTable.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
self.mainTable.endUpdates()
}
My fix:
After more debug, i find the cause,
the self.address value is updated in delegate, however it roll back to previous value in cellForRowAtIndexPath.
I change the property to a static property, then resolve the problem.
I'm not sure what's wrong with instance property, and why it reverses back.
static var _address:String = ""
It seems like you're trying to grab a cell from the UITableView and then update the textLabel value that way. However, UITableView and UITableViewCell are not meant to be updated in this way. Instead, store the value of address in your class and update this value when the delegate calls back into your class. If cellForRowAtIndexPath constructs the UITableViewCell with the value of self.address, calling mainTable.reloadData() after should update the cell to the new value.
For example:
var address: String
func delegateCompleted(address: String) {
self.address = address
self.mainTable.reloadData()
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(<your identifier>)
if (indexPath == <your address cell indexPath>) {
let textLabel = <get your textLabel from the cell>
textLabel?.text = self.address
}
return cell
}
Your cellForRowAtIndexPath has some problems -
You are using the same re-use identifier for different types of cell (one with a map, one without)
When you allocate the table view cell for the other row, you don't include the re-use identifier.
You have no way of referring to the map view that you are adding after the method exits because you don't keep a reference.
If you are using a storyboard then you should create the appropriate prototype cells and subclass(es) and assign the relevant cell reuse ids. If you aren't then I suggest you create a cell subclass and register the classes against the reuse identifiers. Your cellForRowAtIndexPath will then look something like -
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var returnCell:UITableViewCell
if indexPath.row == 1 {
var myMapCell = tableView.dequeueReusableCellWithIdentifier("mapCell", forIndexPath:indexPath) as MYMapCell
myMapCell.contentMode = UIViewContentMode.Center
myMapCell.selectionStyle = UITableViewCellSelectionStyle.None
// Set the properties for a map view in the cell rather than assigning adding an existing map view
returnCell=myMapCell
}else{
returnCell = tableView.dequeueReusableCellWithIdentifier("addressCell", forIndexPath:indexPath)
returnCell.textLabel?.text = self.address
}
return returnCell;
}

Resources