I am very new to swift development and I am working on this section of an apple provided swift tutorial. I created an outlet for a label, image, and custom view that are nested in a Table Cell. When I run I get this error
2016-07-20 23:16:11.110 FoodTracker[8446:3016336] Unknown class MealTableViewCell in Interface Builder file.
2016-07-20 23:16:11.124 FoodTracker[8446:3016336] * Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key nameLabel.'
* First throw call stack:
There are no duplicate outlets and I believe the cellIdentifier used in my Table View Controller is correct.
My table cell
import UIKit
class MealTableViewCell: UITableViewCell {
// MARK: Properties
#IBOutlet weak var nameLabel: UILabel!
#IBOutlet weak var photoImageView: UIImageView!
#IBOutlet weak var ratingControl: RatingControl!
override func awakeFromNib() {
super.awakeFromNib()
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
}}
My Table View Controller
import UIKit
class MealTableViewController: UITableViewController {
// MARK: Properties
var meals = [Meal]()
override func viewDidLoad() {
super.viewDidLoad()
loadSampleMeals()
}
func loadSampleMeals() {
let photo1 = UIImage(named: "meal1")!
let meal1 = Meal(name: "Caprese Salad", photo: photo1, rating: 4)!
let photo2 = UIImage(named: "meal2")!
let meal2 = Meal(name: "Chicken and Potatoes", photo: photo2, rating: 5)!
let photo3 = UIImage(named: "meal3")!
let meal3 = Meal(name: "Pasta with Meatballs", photo: photo3, rating: 3)!
meals += [meal1, meal2, meal3]
}
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return meals.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// Table view cells are reused and should be dequeued using a cell identifier.
let cellIdentifier = "MealTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! MealTableViewCell
// Fetches the appropriate meal for the data source layout.
let meal = meals[indexPath.row]
cell.nameLabel.text = meal.name
cell.photoImageView.image = meal.photo
cell.ratingControl.rating = meal.rating
return cell
}}
Thanks!
Check for a broken outlet link in your storyboard file. Find your controller and control-click on the yellow circle above its nib to open the actions and outlets popover. You should see an outlet for nameLabel with a yellow warning sign. Either reconnect it to your controller's file or remove it if its a duplicate.
Its very simple , the story board can not find the outlet with the name "nameLabel". So either create a label by that name or delete the reference of the created label from the storyboard.
Here are the steps
Just click the storyboard and select the last Tab.
Now search for the nameLabel and click on cross to disconnect.
All the best ;)
First, you must link your UITableView from your interface builder to your MealTableViewController class ( Ctrl + drag )
Then, in the viewDidLoad method of your MealTableViewController class, you must register your custom cell class to your table view :
self.tableView.registerClass(MealTableViewCell.self, forCellReuseIdentifier: "MealTableViewCell")
Related
My very first attempt at nib based project , here are my steps and a nib area snapshot
Current Error - Thread 1: Exception: "invalid nib registered for identifier (Cell) - nib must contain exactly one top level object which must be a UITableViewCell instance"
Step 1 - I create a nib file called HotelNib and add a viewCotroller , create a class called HotelViewController: UIViewController, attach it to the viewController
Step 2 - in the nib add a tableView to viewController, give it constraints
Step 3 - go to SceneDelegate and add below code as i do not want the storyboard to load , also remove the main from drop-down
guard let winScene = (scene as? UIWindowScene) else { return }
window = UIWindow(windowScene: winScene)
guard let homeController = Bundle.main.loadNibNamed("HotelNib", owner: self, options: nil)?.first as? HotelViewController else { return }
window?.rootViewController = homeController
window?.makeKeyAndVisible()
Step 4 - i run the project, it runs well
Step 5 - add a tableViewCell in nib area, create a class called ItemViewCell: UITableViewCell
Step 6 - add #IBOutlet weak var tableView: UITableView!, in HotelView Controller, add delegates and datasource by control + drag from tableView to viewcontroller
Step 7 - Create #IBOutlet weak var nameLabel: UILabel! in ItemViewCell
Step 8 - My code in HotelViewController, i just cannot get over, error after error about nib not being not able to register , some one please guide here
class HotelViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var restaurantNames = ["Cafe Deadend", "Homei", "Teakha", "Cafe Loisl", "Petite Oyster", "For Kee Restaurant", "Po's Atelier", "Bourke Street Bakery", "Haigh's Chocolate", "Palomino Espresso", "Upstate", "Traif", "Graham Avenue Meats And Deli", "Waffle & Wolf", "Five Leaves", "Cafe Lore", "Confessional", "Barrafina", "Donostia", "Royal Oak", "CASK Pub and Kitchen"]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
restaurantNames.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ItemViewCell
cell.nameLabel.text = restaurantNames[indexPath.row]
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
print("right load")
tableView.delegate = self
tableView.dataSource = self
let nib = UINib(nibName: "HotelNib", bundle: nil)
tableView.register(nib, forCellReuseIdentifier: "Cell") //always fails here
// Do any additional setup after loading the view.
}
}
Having multiple views in a single .xib file also causes this error. Creating the table view cell in their own separate .xib file will resolve this error.
I want
to create a custom table cell and add all the components and register it
I did
I created these files below :
PlaceDetailCell.xib
with the id of customPlaceDetailCell
CustomPlaceDetailCell.swift
import UIKit
class CustomPlaceDetailCell: UITableViewCell {
#IBOutlet weak var country: UILabel!
#IBOutlet weak var address: UILabel!
#IBOutlet weak var distance: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
I register
//register xib file
placesTable.register(UINib(nibName: "PlaceDetailCell", bundle: nil), forCellReuseIdentifier: "customPlaceDetailCell")
This is my cellForRowAt
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customPlaceDetailCell", for: indexPath)
as! CustomPlaceDetailCell
if places[indexPath.row]["name"] != nil {
cell.textLabel?.text = places[indexPath.row]["name"]! + " (" + distances[indexPath.row] + " miles)"
}
return cell
}
I got
a crash when I click on that cell even if the app is successfully Build.
Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key address.'
*** First throw call stack:
I hope
someone can provide some guidance for me to do the right things.
You need to set the xib file custom class to the class you cast it to
let cell = tableView.dequeueReusableCell(withIdentifier: "customPlaceDetailCell", for: indexPath)
as! CustomPlaceDetailCell
as it seems that xib name is PlaceDetailCell with class PlaceDetailCell and you cast it to CustomPlaceDetailCell
if you register this
placesTable.register(UINib(nibName: "PlaceDetailCell", bundle: nil), forCellReuseIdentifier: "customPlaceDetailCell")
then the xib custom class should be set to CustomPlaceDetailCell not to PlaceDetailCell which is expected as always the xib name = it's class name that you create and then wanted to change the class name
I'm creating a quiz app with custom cells that include a label of questions and then an answer coming from a UISegmentedControl.
The values of the segmentedcontrols get changed when scrolling and this leads to an inaccurate score. I understand that this is due to UITableView reusing cells.
My tableview's datasource in my main vc is simply the labels for all my questions coming from a plist file.
The code for my custom tableviewcell class is
class QuestionsTableViewCell: UITableViewCell {
#IBOutlet weak var questionLabel: UILabel!
#IBOutlet weak var selection: UISegmentedControl!
var question: String = "" {
didSet {
if (question != oldValue) {
questionLabel.text = question
}
}
}
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
//Just for testing
#IBAction func segmentChanged(_ sender: UISegmentedControl) {
print("value is ", sender.selectedSegmentIndex);
}
}
where the View is stored in an .XIB file.
And the code for my main vc is
class ViewController: UIViewController, UITableViewDataSource {
let questionsTableIdentifier = "QuestionsTableIdentifier"
#IBOutlet var tableView:UITableView!
var questionsArray = [String]();
var questionsCellArray = [QuestionsTableViewCell]();
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let path = Bundle.main.path(forResource:
"Questions", ofType: "plist")
questionsArray = NSArray(contentsOfFile: path!) as! [String]
tableView.register(QuestionsTableViewCell.self,
forCellReuseIdentifier: questionsTableIdentifier)
let xib = UINib(nibName: "QuestionsTableViewCell", bundle: nil)
tableView.register(xib,
forCellReuseIdentifier: questionsTableIdentifier)
tableView.rowHeight = 108;
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return questionsArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(
withIdentifier: questionsTableIdentifier, for: indexPath)
as! QuestionsTableViewCell
let rowData = questionsArray[indexPath.row]
cell.question = rowData
return cell
}
#IBAction func calculate(_ sender: UIButton) {
var score = 0
for cell in tableView.visibleCells as! [QuestionsTableViewCell] {
score += cell.selection.selectedSegmentIndex
}
let msg = "Score is, \(score)"
print(msg)
}
#IBAction func reset(_ sender: UIButton) {
for cell in tableView.visibleCells as! [QuestionsTableViewCell] {
cell.selection.selectedSegmentIndex = 0;
}
}
}
What I'd like to do is just keep track of all 'selection' changes of the Questions cells in an array, and then use that array in cellForRowAt. I'm just confused as to how i can dynamically keep track of changes from a view in another class. I'm new to Swift and would like to solve this is a proper MVC fashion. Thanks
Instead of a simple string array as data source create a class holding the text and the selected index
class Question {
let text : String
var answerIndex : Int
init(text : String, answerIndex : Int = 0) {
self.text = text
self.answerIndex = answerIndex
}
}
Declare questionArray as
var questions = [Question]()
Populate the array in viewDidLoad with
let url = Bundle.main.url(forResource: "Questions", withExtension: "plist")!
let data = try! Data(contentsOf: url)
let questionsArray = try! PropertyListSerialization.propertyList(from: data, format: nil) as! [String]
questions = questionsArray.map {Question(text: $0)}
In the custom cell add a callback and call it in the segmentChanged method passing the selected index, the property question is not needed, the label is updated in cellForRow of the controller
class QuestionsTableViewCell: UITableViewCell {
#IBOutlet weak var questionLabel: UILabel!
#IBOutlet weak var selection: UISegmentedControl!
var callback : ((Int) -> ())?
#IBAction func segmentChanged(_ sender: UISegmentedControl) {
print("value is ", sender.selectedSegmentIndex)
callback?(sender.selectedSegmentIndex)
}
}
In cellForRow add the callback and update the model in the closure
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: questionsTableIdentifier, for: indexPath) as! QuestionsTableViewCell
let question = questions[indexPath.row]
cell.questionLabel.text = question.text
cell.selection.selectedSegmentIndex = question.answerIndex
cell.callback = { index in
question.answerIndex = index
}
return cell
}
To reset the segmented controls in the cells set the property in the model to 0 and reload the table view
#IBAction func reset(_ sender: UIButton) {
questions.forEach { $0.answerIndex = 0 }
self.tableView.reloadData()
}
Now you could calculate the score directly from the model instead of the view.
Don't try to use cells to hold information. As the user scrolls through your table view, cells that scroll out of view will get recycled and their field settings will be lost. Also, newly dequeued cells will have the settings from the last time they were used.
You need to refactor your code to read/write information into a data model. Using an array of Structs as a data model is a reasonable way to go. (Or, as vadian suggests in his answer, and array of Class objects, so you get reference semantics.)
You have an IBAction segmentChanged() in your custom cell class. The next trick is to notify the view controller when the user changes the selection, and to update cells when you set them up in cellForRowAt.
I suggest defining a protocol QuestionsTableViewCellProtocol, and have the view controller conform to that protocol:
protocol QuestionsTableViewCellProtocol {
func userSelected(segmentIndex: Int, inCell cell: UITableViewCell)
}
}
Add a delegate property to your QuestionsTableViewCell class:
class QuestionsTableViewCell: UITableViewCell {
weak var delegate: QuestionsTableViewCellProtocol?
//The rest of your class goes here...
}
Update your cell's segmentChanged() method to invoke the delegate's userSelected(segmentIndex:inCell:) method.
In your view controller's cellForRowAt, set the cell's delegate to self.
func userSelected(segmentIndex: Int, inCellCell cell: UITableViewCell) {
let indexPath = tableView.indexPath(for: cell)
let row = indexPath.row
//The code below assumes that you have an array of structs, `dataModel`, that
//has a property selectedIndex that remembers which cell is selected.
//Adjust the code below to match your actual array that keeps track of your data.
dataModel[row].selectedIndex = segmentIndex
}
Then update cellforRowAt() to use the data model to set the segment index on the newly dequeued cell to the correct index.
Also update your calculate() function to look at the values in your dataModel to calculate the score, NOT the tableView.
That's a rough idea. I left some details out as "an exercise for the reader." See if you can figure out how to make that work.
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
}
}
I have created two custom labels in a table cell in order to be able to dynamically resize the cell(s) to it's content. I first tried using the "Subtitle" style and this worked out great except that the cell(s) didn't resize the way i wanted to and it looked really messy.
My question is: how do I access these labels in order to append my value's from my API to them?
View controller code:
import UIKit
class nyheterViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, APIControllerProtocol {
#IBOutlet weak var nyheterTableView: UITableView!
#IBOutlet weak var titleLabel: UILabel!
var searchResultsData: NSArray = []
var api: APIController = APIController()
func JSONAPIResults(results: NSArray) {
dispatch_async(dispatch_get_main_queue(), {
self.searchResultsData = results
print(self.searchResultsData)
self.nyheterTableView.reloadData()
})
}
override func viewDidLoad() {
super.viewDidLoad()
var APIBaseUrl: String = "http://*.se/*/*.php"
var urlString:String = "\(APIBaseUrl)"
//Call the API by using the delegate and passing the API url
self.api.delegate = self
api.GetAPIResultsAsync(urlString, elementName:"news")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//print(self.searchResultsData.count)
return self.searchResultsData.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier: String = "nyheterResultsCell"
let cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as UITableViewCell
//nyheterTableViewCell.cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier);
//Create a variable that will contain the result data array item for each row
var cellData: NSDictionary = self.searchResultsData[indexPath.row] as NSDictionary
//Assign and display the Title field
var releaseDate: String = cellData["date"] as String
var titleVar: String = cellData["title"] as String
var titleMix: String = "\(titleVar)" + " - " + "\(releaseDate)"
cell.textLabel?.text = titleMix //textLabel worked out fine using "Subtitle" style.
// Get the release date string for display in the subtitle
cell.detailTextLabel?.text = cellData["content"] as String? //Same
return cell
}
}
I understand that I can't access these labels without somehow connecting them to the ViewController. Creating outlets to the ViewController generates an error about that I can't use connections from the prototype cell to the ViewController.
So, i created a new class, called nyheterTableViewCell which i connect to the table cell and connected outlets to my labels.
nyhterTableViewCell code:
import UIKit
class nyheterTableViewCell: UITableViewCell {
#IBOutlet weak var nyhetLabel: UILabel!
#IBOutlet weak var titleLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
I'm an beginner at Swift-programming and Xcode.
Cheers!
You don't need the labels connected to the view controller. Your example of the custom table view cell looks correct.
To access the label properties, you're going to want to change the line
let cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as UITableViewCell
to
let cell: nyheterTableViewCell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as nyheterTableViewCell