Swift crash when unwrapping UILabel in UITableView with custom cell - ios

I have a Table Bar where i have 3 separate MVC. The first MVC is a UITableView where i can search for a location and it will display 10 day forecast. The second MVC also a UITableView stores my favourite locations. I can tap on the location and it will bring me another UITableView that displays the 10 day forecast just like the first tab bar. However i get an "Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value" I'm using a custom prototype cell just like the first tab bar view and it works great there.
The code for my first tab bar mvc looks like:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "WeatherDataCell", for: indexPath)
let weatherCell = timeSeries[indexPath.row]
if let wc = cell as? WeatherTableViewCell {
wc.timeSeries = weatherCell
}
return cell
}
The custom prototype cell looks something like:
class WeatherTableViewCell: UITableViewCell {
#IBOutlet weak var WeatherImageView: UIImageView!
#IBOutlet weak var WeatherTimeLabel: UILabel!
#IBOutlet weak var WeatherTemperatureLabel: UILabel!
#IBOutlet weak var WeatherWindLabel: UILabel!
#IBOutlet weak var WeatherPrecipitationLabel: UILabel!
var timeSeries : TimeSeries? {
didSet {
updateUI()
}
}
func updateUI() {
for parameters in timeSeries!.parameters {
if parameters.name == "t" {
let temperature = parameters.values[0]
WeatherTemperatureLabel.text? = "\(temperature) °C" // <- works for the first MVC but crashes on the second MVC even though i can print out the temperature
}
}
}
The func in the seconds MVC basically looks the same as the first one so i don't understand why its crashing, it should have data.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "DetailedWeatherCell", for: indexPath) as! WeatherTableViewCell
let weatherCell = timeSeries[indexPath.row]
let wc = cell
wc.timeSeries = weatherCell
return cell
}
added additional code for clarity (my FavoriteDetailedTableViewController class):
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(WeatherTableViewCell.self, forCellReuseIdentifier: "DetailedWeatherCell")
}

I think you forgot to change identifier of prototype cell in FavoritedDetailedTableViewController. In this TableViewController change it to
and it should work.
Second you maybe forgot to set your cell class here
Also delete this line from viewDidLoad
self.tableView.register(WeatherTableViewCell.self, forCellReuseIdentifier: "DetailedWeatherCell")

2 Viewcontrollers can not reuse the same prototype cell which had been created by one of two viewcontrollers
WeatherTableViewCell can't have 2 layouts in mvc1, mvc2
=> You have to create your customer cell in xib file and register a cell for UITableViewCell reuse.

Related

implicitly unwrapping an Optional value on custom cell of table view

first of all i'm saying straight forward I know this is "duplicate" and and this is my 2nd time asking the same question - the problem is that my first one has been closed without me understanding the problem so please if someone wants to close this question again first let me understand what am I doing wrong. The solution I got last time was not relevant so if I could get addressed specifically that would be great!
I am trying to create a custom cell on tableview from an array. when I append any filed on my custom cell I get unexpected nil on all of the fileds and I have no idea why
this is my custom cell:
class CustomMovieCell: UITableViewCell {
#IBOutlet weak var title: UILabel!
#IBOutlet weak var rating: UILabel!
#IBOutlet weak var releaseYear: UILabel!
#IBOutlet weak var genre: UILabel!
var imageBackground: String!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
and this is my UITableView cellForRowAtIndexPath method:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MovieCell", for: indexPath) as! CustomMovieCell
let movieFetched = Movie(title: moviesArray[indexPath.row].title, image: moviesArray[indexPath.row].image, rating: moviesArray[indexPath.row].rating, releaseYear: moviesArray[indexPath.row].releaseYear, genre: moviesArray[indexPath.row].genre)
print(movieFetched)
cell.title.text? = movieFetched.title
cell.rating.text? = String(movieFetched.rating)
cell.releaseYear.text? = String(movieFetched.releaseYear)
cell.genre.text? = String(movieFetched.genre[0])
return cell
}
what am I missing? when appending ANY of the files I get unexpectedly found nil while unwrapping optional value - I did not know UIlabel as IBOutlet are optional? even-though they are not optional in my custom cell class.
when debugging I can see that all values of the cell - title, image, rating, releaseYear and genre are nil when trying to assign them a value - so I really have no idea what to do at this point. I have deleted and re-created the cell from scratch and it did not make any differents.
As I already stated - I know this is "duplicate". please though - do not close it before you help me because I did not get any answer the last time, I got directed to a wall-of-text page that did not help me understand my issue. The other "duplicate" pages are like a general "what are optional values" kind of question and do not help me with this specific issue.
edit:
I have uploaded this project to github if it helps anyone help me to figure out this issue
https://github.com/alonsd/MoviesApi
You've connected the custom cell class with 2 cells. One is in xib and another one is in this UIViewController. This UIViewController's prototype cell doesn't have these labels. So it will be nil and it will crash
Delete the prototype cell from MoviesViewController in storyboard. And add this in MoviesViewController viewDidLoad
tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "MovieCell")
Change tableView cellForRowAt method as follows
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MovieCell") as! TableViewCell
cell.title.text = moviesArray[indexPath.row].title
cell.rating.text = String(moviesArray[indexPath.row].rating)
cell.releaseYear.text = String(moviesArray[indexPath.row].releaseYear)
cell.genre.text = String(moviesArray[indexPath.row].genre[0])
return cell
}

How to change image of imageview in tableview custom cell with pagination.

I am using a tableview in an app in which I have used pagination. The request is sent to the server and it returns items in batches of size 10. everything is working fine till now. Now I have an imageview in my tableview cells (custom). I want that when the image of that imageview toggles when user taps on it. I tried this thing in the following way:
TableviewController:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell : AdventureTableViewCell = tableView.dequeueReusableCell(withIdentifier: "adventureCell" , for: indexPath) as? AdventureTableViewCell else {
fatalError("The dequeued cell is not an instance of AdventureViewCell.")
}
cell.adventureName.text = adventureList[indexPath.row]
cell.amountLabel.text = "\(adventurePriceList[indexPath.row])$"
cell.favouriteButtonHandler = {()-> Void in
if(cell.favouriteButton.image(for: .normal) == #imageLiteral(resourceName: "UnselectedFavIcon"))
{
cell.favouriteButton.setImage(#imageLiteral(resourceName: "FavSelectedBtnTabBar"), for: .normal)
}
else
{
cell.favouriteButton.setImage(#imageLiteral(resourceName: "UnselectedFavIcon"), for: .normal)
}
}
}
CustomCell:
class AdventureTableViewCell: UITableViewCell {
#IBOutlet weak var adventureName: UILabel!
#IBOutlet weak var adventureImage: UIImageView!
#IBOutlet weak var amountLabel: UILabel!
#IBOutlet weak var favouriteButton: UIButton!
#IBOutlet weak var shareButton: UIButton!
var favouriteButtonHandler:(()-> Void)!
var shareButtonHandler:(()-> Void)!
override func awakeFromNib() {
super.awakeFromNib()
adventureName.lineBreakMode = .byWordWrapping
adventureName.numberOfLines = 0
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}
override func prepareForReuse() {
adventureImage.af_cancelImageRequest()
adventureImage.layer.removeAllAnimations()
adventureImage.image = nil
}
#IBAction func favouriteButtonTapped(_ sender: Any) {
self.favouriteButtonHandler()
}
Now the problem which I am facing is that if user taps the first the imageview on any cell it changes its image, but along with that every 4th cell changes it image.
For example, if I have tapped imageview of first cell its image is changed but image of cell 5, 9, 13... also get changed.
What is wrong with my code? Did I miss anything? It is some problem with indexPath.row due to pagination, but i don't know what is it exactly and how to solve it. I found a similar question but its accepted solution didn't work for me, so any help would be appreciated.
if you need to toggle image and after scrolling also that should be in last toggle state means you need to use an array to store index position and toggle state by comparing index position and scroll state inside cellfoeRowAtIndex you can get the last toggle state that is one of the possible way to retain the last toggle index even when you scroll tableview otherwise you will lost your last toggle position
if self.toggleStatusArray[indexPath.row]["toggle"] as! String == "on"{
cell.favouriteButton.setImage(#imageLiteral(resourceName: "FavSelectedBtnTabBar"), for: .normal)
} else {
cell.favouriteButton.setImage(#imageLiteral(resourceName: "UnselectedFavIcon"), for: .normal)
}
cell.favouriteButtonHandler = {()-> Void in
if self.toggleStatusArray[indexPath.row]["toggle"] as! String == "on"{
//Assign Off status to particular index position in toggleStatusArray
} else {
//Assign on status to particular index position in toggleStatusArray
}
}
Hope this will help you
Your code looks OK, I see just one big error.
When u are setting dynamic data (names, images, stuff that changes all the time) use func tableView(UITableView, willDisplay: UITableViewCell, forRowAt: IndexPath) not cellForRowAt indexPath.
cellForRowAt indexPath should be used for static resources, and cell registration.
If u are on iOS 10 + take a look at prefetchDataSource gonna speed things up a loot, I love it.
https://developer.apple.com/documentation/uikit/uitableview/1771763-prefetchdatasource
Small example:
here u register the cell, and set up all the stuff that is common for all the cells in the table view
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "adventureCell" , for: indexPath)
cell.backgroundColor = .red
return cell
}
here adjust all the stuff that is object specific
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.nameLabel.text = model[indexPath.row].name
// also all the specific UI stuff goes here
if model[indexPath.row].age > 3 {
cell.nameLabel.textColor = .green
} else {
cell.nameLabel.textColor = .blue
}
}
You need this because cells get reused, and they have their own lifecycle, so you want to set specific data as late as possible, but you want to set the generic data as less as possible ( most of the stuff you can do once in cell init ).
Cell init is also a great place for generic data, but u can not put everything there
Also, great thing about cell willDisplay is the that u know actual size of the frame at that point

IBoutlet is nil in my custom table cell?

//custom cell in one swift file
import UIKit
class CardTableViewCell: UITableViewCell {
#IBOutlet weak var elapsedTime: UILabel!
#IBOutlet weak var todo: UILabel!
#IBOutlet weak var startAndStop: UIView!
#IBOutlet weak var progress: UIView!
#IBOutlet weak var cardView: UIView!
}
//custom Table view controller in another swift file
import UIKit
class CardFeedTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(CardTableViewCell.self, forCellReuseIdentifier: "cardCell")
tableView.delegate = self
tableView.dataSource = self
}
// MARK: - Table view data source
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 3
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: CardTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "cardCell", for: indexPath) as! CardTableViewCell
cell.todo.text = "study"
return cell
}
running the app triggers a error
storyboard
I don't know why all the properties in the my table cell is nil, when I run the app.
There are two variants to register, but both take a parameter called
forCellReuseIdentifier, which is a string that lets you register
different kinds of table view cells. For example, you might have a
reuse identifier "DefaultCell", another one called "Heading cell",
another one "CellWithTextField", and so on. Re-using different cells
this way helps save system resources.
If you want to use register() with a Swift class, you provide a table
view cell class as its first parameter. This is useful if your cell is
defined entirely in code. As an example, this uses the default
UITableViewCell class:
tableView.register(UITableViewCell.self, forCellReuseIdentifier: "DefaultCell")
You can then dequeue that cell like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "DefaultCell")!
return cell
}
The other option is to use register() with an Interface Builder nib
file. Nibs contain the class name to use along with their design, so
this method is more common. Here's an example
tableView.register(UINib(nibName: "yourNib", bundle: nil), forCellReuseIdentifier: "CellFromNib")
But if you're using storyboards you will find it easier to create
prototype cells and give them a reuse identifier directly inside
Interface Builder.So no need to register programmatically.
Remove this line form viewDidLoad
tableView.register(CardTableViewCell.self, forCellReuseIdentifier: "cardCell).

Looping through phone numbers, creating a custom view and passing it the phone number in swift

I'm having trouble creating a view programatically inside a for loop from another controller. The parent controller is a tableviewcell and I'm looping through a bunch of phone numbers inside a CNContact object. For each phone number the contact has I wish to create my custom view and add it to the tableviewcell and have it stack vertically.
So far I managed to create the view and add it to the tableviewcell but wasn't able to pass the data. It's the passing of the data from one controller to another that I'm struggling with.
Here is my code:
ContactListTableViewCell.swift
import UIKit
import Contacts
class ContactListTableViewCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var phonenumberView: UIView!
func configureCell(contact: CNContact) {
titleLabel.text = "\(contact.givenName) \(contact.familyName)"
for phoneNumber in contact.phoneNumbers {
let view = self.createContactListTableViewTelephoneRow(telephone: phoneNumber)
self.phonenumberView.addSubview(view)
}
}
func createContactListTableViewTelephoneRow(telephone: Any) -> UIView {
let controller = ContactListTableViewTelephoneRow()
let view = UINib(nibName: "ContactListTableViewTelephoneRow", bundle: nil).instantiate(withOwner: controller, options: nil)[0] as! UIView
return view
}
}
contactListTableViewCell prototype inside Main.storyboard
ContactListTableViewTelephoneRow.swift
class ContactListTableViewTelephoneRow: UIView {
#IBOutlet var view: UIView!
#IBOutlet weak var telephoneLabel: UILabel!
#IBOutlet weak var telephoneTypeLabel: UILabel!
func setData(telephoneLabelText: String, telephoneTypeLabelText: String) {
telephoneLabel?.text = telephoneLabelText
telephoneTypeLabel?.text = telephoneTypeLabelText
}
}
ContactListTableViewTelephoneRow.xib
Any help would be much appreciated. Thank you.
Simple way to pass data is you need to crate object in your second controller and pass data from first controller
let vc = self.storyboard!.instantiateViewController(withIdentifier: "Secondcontroller") as! Secondcontroller
vc.yourObject = object //To pass
self.present(tabvc, animated: true, completion: nil) // or push
You will need to cast the view you create using UNib.[...] and pass the data directly to it:
func createContactListTableViewTelephoneRow(telephone: CNLabeledValue<CNPhoneNumber>) -> UIView {
let nib = UINib(nibName: "ContactListTableViewTelephoneRow", bundle: nil)
let root = nib.instantiate(withOwner: nil, options: nil)[0]
let view = root as! ContactListTableViewTelephoneRow
view.setData(telephoneLabelText: telephone.value.stringValue,
telephoneTypeLabelText: telephone.label!) // make sure `telephone.label!` is correct – I never compiled it
return view
}
Note that I adjusted the signature of createContactListTableViewTelephoneRow(telephone:).
But as an advise overall: I would solve your UI problem in a very different way.
Background: UITableViews heavily reuses (queues/dequeues) cells so that scroll performance is acceptable. Although I assume you use the APIs of UITableViewDataSource correctly loading nibs inside the your cells can become a performance bottleneck very fast.
I would advise against having variable number of ContactListTableViewTelephoneRow in your cell. Instead make it a subclass of UITableViewCell as well. Your view controller of course must handle at least two different types of cells in this case. You can use different sections to still keep the logic fairly easy. Here is a full example: (you would of course need to adjust styling)
import Contacts
import UIKit
class ContactListTelephoneTableViewCell: UITableViewCell {
#IBOutlet weak var telephoneLabel: UILabel!
#IBOutlet weak var telephoneTypeLabel: UILabel!
func configureCell(telephone: CNLabeledValue<CNPhoneNumber>) {
telephoneLabel.text = telephone.value.stringValue
telephoneTypeLabel.text = telephone.label!
}
}
class ContactListTableViewCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
func configureCell(contact: CNContact) {
titleLabel.text = "\(contact.givenName) \(contact.familyName)"
}
}
class DataSource: NSObject, UITableViewDataSource {
var contacts: [CNContact]!
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return contacts[section].phoneNumbers.count + 1 // one extra for given and family name
}
func numberOfSections(in tableView: UITableView) -> Int {
return contacts.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 {
return self.tableView(tableView, nameCellForRowAt: indexPath)
} else {
return self.tableView(tableView, phoneCellForRowAt: indexPath)
}
}
func tableView(_ tableView: UITableView, nameCellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "name", for: indexPath) as! ContactListTableViewCell
cell.configureCell(contact: contacts[indexPath.section])
return cell
}
func tableView(_ tableView: UITableView, phoneCellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "phone", for: indexPath) as! ContactListTelephoneTableViewCell
let contact = contacts[indexPath.section]
let telephone = contact.phoneNumbers[indexPath.row - 1] // minus one for given and family name
cell.configureCell(telephone: telephone)
return cell
}
}

Swift Custom TableViewCell wont show labels

I have a custom cell class called CurrentFilesCell with the setting code below
class CurrentFileCell: UITableViewCell {
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var dateLabel: UILabel!
#IBOutlet weak var statusImage: UIImageView!
var currentContent: AircraftContent! {
didSet{
setStyles(Constants.appStyleSetting)
nameLabel.text = currentContent.contentName
dateLabel.text = currentContent.contentStatus
}
}
Within my CurrentFilesViewController I simply set it within cellForRowAtIndexPath
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CurrentFileCell", forIndexPath: indexPath) as? CurrentFileCell
cell?.currentContent = content
return cell!
}
I believe I also have everything linked correctly, as I have done something similar to this in other classes, both with cells and vc's.
My problem is that It does not load anything when run, there is no default text and no updated text after it should have been set. Here is an image showing the linkage
http://imgur.com/qlK4d5O
I'm really not sure what is going on and why this isn't working. I have tried deleting it and recreating but I must be missing something.
EDIT
Here is a picture of the debugger showing that the cell's currentContent is not empty. This is taken right before the return cell! is executed.
http://imgur.com/O250qXq
Did you register this cell in table view? If not than dqueRqusableCellWithIdentifier will return nil value...
You can register it using UITableView function "registerNib: forCellReuseIdentifier:"
In the storyboard, you must define subclass of the prototype table cell.
And then, you must define identifier of the prototype table cell as "CurrentFileCell".
Then you will show the content of the table when the app will be run.

Resources