I'm trying to retrieve core data and display it in a custom cell class. I think it will be easier if I present my code first.
This is my "original code", with a regular cell:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let CellID:NSString = "cell"
var cell: UITableViewCell = self.tv.dequeueReusableCellWithIdentifier(CellID) as UITableViewCell
if let ip = indexPath as Optional {
var data:NSManagedObject = myList[ip.row] as NSManagedObject
cell.textLabel!.text = data.valueForKeyPath("username") as String!
}
return cell
}
This is what I change my code to when trying to use the custom cell:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let CellID:NSString = "cell"
var cell: CustomCell = tv.dequeueReusableCellWithIdentifier(CellID) as CustomCell
if let ip = indexPath as Optional {
var data:NSManagedObject = myList[ip.row] as NSManagedObject
cell.titleLabel.text = data.valueForKeyPath("username") as String!
cell.dateLabel.text = data.valueForKeyPath("date") as String!
}
return cell
}
The first code works perfectly, but when using the second one I get the (lldb) runtime error.
Both "username" and "date" are saved as strings.
Any suggestions would be appreciated.
EDIT:
Additional information:
var myList: Array<AnyObject> = []
The error that pops up is just "(lldb)" and "Thread 1: EXC_BREAKPOINT (code = EXC_l386_BPT, subcode = 0x0)".
My model-file:
#objc(Model)
class Model: NSManagedObject {
#NSManaged var username: String
#NSManaged var date: String
#NSManaged var isAnonymousMessage: Bool
}
My cellForRowAtIndexPath-function:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let CellID:NSString = "cell"
var cell: CustomCell = tv.dequeueReusableCellWithIdentifier(CellID) as CustomCell
if let ip = indexPath as Optional {
let data = myList[indexPath.row] as Model
cell.titleLabel.text = data.username
cell.dateLabel.text = data.date
}
return cell
}
My viewDidAppear-function:
override func viewDidAppear(animated: Bool) {
let appDel:AppDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let context:NSManagedObjectContext = appDel.managedObjectContext!
let freq = NSFetchRequest(entityName: "Message")
let en = NSEntityDescription.entityForName("Message", inManagedObjectContext: context)
let fetchRequest = NSFetchRequest(entityName: "Message")
myList = context.executeFetchRequest(fetchRequest, error: nil) as [Model]
tv.reloadData()
}
my CustomCell class looks like this:
import UIKit
class CustomCell: UITableViewCell {
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var dateLabel: 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 would suggest to change your code a little bit.
Load your NSManagedObjects into an Array of your Core Data Class, for example like this:
var myList = [ListObject]() // Where ListObject is your NSManagedClass
let fetchRequest = NSFetchRequest(entityName: "List")
myList = context.executeFetchRequest(fetchRequest, error: nil) as [ListObject]
// You should load this once (maybe in ViewDidLoad) so every Core Data object gets only fetched once (you could easy refresh this if needed).
Then use in your cellForRowAtIndexPath:
let data = myList[indexPath.row] as ListObject
cell.titleLabel.text = data.name
cell.dateLabel.text = data.date
// You dont need to use "valueForKeyPath" - just use the property as shown above.
var cell: CustomCell = tv.dequeueReusableCellWithIdentifier(CellID) as CustomCell
Crashing on this line means that you're not getting a CustomCell back when you dequeue from the tableview.
You need to register the class with that reuse identifier, either by setting it in the storyboard or xib, or calling registerClass(_ cellClass: AnyClass, forCellReuseIdentifier identifier: String) on the table view, normally in view did load.
If you've added a new cell to the storyboard and want to use it instead of the default one, make sure the reuse identifier is set correctly.
Related
I have my value from Firebase but Swift doesn't want to put it in my label.
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
To summarize, my database look like that:
Firebase database
I've created a standard model call ServiceModel:
import Foundation
class ServiceModel {
var name: String?
var category: String?
var pricing: String?
init(name: String?, category: String?, pricing: String?){
self.name = name
self.category = category
self.pricing = pricing
}
}
I want to display this values into a TableView, so I've created a custom cell like this (very standard too):
import UIKit
class SubscriptionTableViewCell: UITableViewCell {
#IBOutlet weak var imageService: UIImageView!
#IBOutlet weak var labelName: UILabel!
#IBOutlet weak var labelCategory: UILabel!
#IBOutlet weak var labelPricing: 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
}
}
And now, here is the controller of my view:
import UIKit
import FirebaseDatabase
class SecondViewController: UIViewController,UITableViewDelegate, UITableViewDataSource {
var refServices:DatabaseReference!
#IBOutlet weak var ListSub: UITableView!
var serviceList = [ServiceModel]()
var databaseHandle:DatabaseHandle?
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return serviceList.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SubCell", for: indexPath) as! SubscriptionTableViewCell
let service: ServiceModel
service = serviceList[indexPath.row]
//cell.imageService.image = UIImage(named: service.name! + ".png")
cell.labelName?.text = service.name //ERROR HERE
cell.labelCategory?.text = service.category
cell.labelPricing?.text = service.pricing
return cell
}
override func viewDidLoad() {
super.viewDidLoad()
ListSub.delegate = self
ListSub.dataSource = self
refServices = Database.database().reference().child("Categories");
refServices.observe(DataEventType.value, with: { (snapshot) in
if snapshot.childrenCount > 0 {
self.serviceList.removeAll()
for services in snapshot.children.allObjects as! [DataSnapshot] {
let serviceObject = services.value as? [String: AnyObject]
let serviceName = serviceObject?["Name"]
let serviceCategory = serviceObject?["Category"]
let servicePricing = serviceObject?["Pricing"]
let service = ServiceModel(name: serviceName as! String?, category: serviceCategory as! String?, pricing: servicePricing as! String?)
self.serviceList.append(service)
}
self.ListSub.reloadData()
}
})
}
When I launch this view, I have the error mentioned earlier.
When I debug, I see that I have the right values in service.name, service.category and service.pricing
It seems that I don't correctly handle Optional values, but I cannot see what is wrong.
Thanks for your help.
Potential lines to be crashed in case of optional unwrapping is this line
refServices = Database.database().reference().child("Categories");
refServices.observe(DataEventType.value, with: { (snapshot) in
Try to pur breakpoint and check if refServices is initialised properly or make ti to be optional not using !
Hope this help
Ps. please remove ; out of your Swift code :P
Use this code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SubCell", for: indexPath) as! SubscriptionTableViewCell
let service = serviceList[indexPath.row]
// If you sure that you have to display all the info use this code
if let name = service.name, let category = service.category, let price = service.pricing {
cell.labelName.text = name
// set other data also here....
}
// If you know any value may be empty or not exists then use this code.
if let name = service.name {
cell.labelName.text = name
}
if let category = service.category {
cell.labelCategory.text = service
}
if let pricing = service.pricing {
cell.labelPricing.text = pricing
}
return cell
}
Did you register your custom UITableViewCell with your tableView? Put this line into the init() function of your ViewController:
ListSub.register(SubscriptionTableViewCell.classForCoder(), forCellReuseIdentifier: "SubCell")
If you debug this function, what did you see for your service
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "SubCell", for: indexPath) as! SubscriptionTableViewCell
let service: ServiceModel
//Put a breakpoint here
service = serviceList[indexPath.row]
//Put a breakpoint here
//cell.imageService.image = UIImage(named: service.name! + ".png")
cell.labelName?.text = service.name
cell.labelCategory?.text = service.category
cell.labelPricing?.text = service.pricing
return cell
}
The issue is as follows: I have a tableview with a custom cell. That cell contains a label and a UISwitch. I have set the label.text value to an array, but the UISwitch is getting reused.
Example: If I toggle the switch in the first row, the 5th row gets enabled, and if I scroll it continues to reuse the cells and cause issue.
Video : https://vimeo.com/247906440
View Controller:
class ViewController: UIViewController {
let array = ["One","Two","Three","Four","Five","Six","Seven","Eight","Nine","Ten"]
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
}
extension ViewController: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
cell.label.text = array[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return array.count
}
}
Custom Cell:
class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var label: UILabel!
#IBOutlet weak var toggleSwitch: UISwitch!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
}
I realize there isn't code trying to store this data because I haven't been successful. Any ideas would be helpful. The project currently uses the MVC model and I believe that is the answer but just need some help.
I would recommend to you create cellViewModel class and keep array of it instead of just string. You cellViewModel may look like,
class CellViewModel {
let title: String
var isOn: Bool
init(withText text: String, isOn: Bool = false /* you can keep is at by default false*/) {
self.title = text
self.isOn = isOn
}
Now, build array of CellViewModel
let array =["One","Two","Three","Four","Five","Six","Seven","Eight","Nine","Ten"]
var cellViewModels = [CellViewModel]()
for text in array {
let cellViewModel = CellViewModel(withText: text)
cellViewModels.append(cellViewModel)
}
Change your tableVieDelegate function to :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
let cellViewModel = cellViewModels[indexPath.row]
cell.label.text = cellViewModel.title
cell.toggleSwitch.isOn = cellViewModel.isOn
cell.delegate = self
return cell
}
In you Custom Cell class, add this protocol :
protocol CellActionDelegate: class {
func didChangeSwitchStateOnCell(_ cell: CustomTableViewCell)
}
Add delegate as property in your custom cell,
weak var delegate: CellActionDelegate?
Also, on switch change, add this line,
delegate?.didChangeSwitchStateOnCell(self)
Now, your viewController should register and listen to this delegate :
I have added line cellForRowAtIndexPath to register for delegates. To listen this delegate, add this function in your VC.
func didChangeSwitchStateOnCell(_ cell: CustomTableViewCell) {
let indexPath = tableView.indexPath(for: cell)
cellViewModels[indexPath.row].isOn = cell.toggleSwitch.isOn
}
start creating a model for example :
struct item {
var id: String
var name: String
var isActivated: Bool
init(id: String, name: String, isActivated: Bool) {
self.id = id
self.name = name
self.isActivated = isActivated
}
}
let item1 = item(id: "1", name: "One", isActivated: false)
let item2 = ...........
let item3 = ...........
let items [item1, item2, item3]
With that you can trigger the boolean if it's activated or not.
You will also have to take a look to https://developer.apple.com/documentation/uikit/uitableviewcell/1623223-prepareforreuse I think.
I'm new to using CoreData - so I tried to set up an App where the User can save some text via button to the CoreData and load it back to display it in a tableView. I figured out how to save the data but I can't figure out how to load it back right. It has to be loaded when new information is stored and when the View load.
class ViewControllerExercises: UIViewController {
#IBOutlet weak var textField: UITextField!
#IBAction func tappedAddButton(sender: AnyObject) {
var appDel:AppDelegate = (UIApplication.sharedApplication().delegate as AppDelegate)
var context:NSManagedObjectContext = appDel.managedObjectContext!
var newExercises = NSEntityDescription.insertNewObjectForEntityForName("Exercises", inManagedObjectContext: context ) as NSManagedObject
newExercises.setValue(textField.text,forKey:"exercises")
context.save(nil)
}
func numberOfSectionsInTableView(tableView: UITableView?) -> Int {
return 1
}
func tableView(tableView: UITableView?, numberOfRowsInSection section: Int) -> Int {
return exercises.count
}
func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) -> UITableViewCell? {
let cell = tableView!.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath!) as UITableViewCell
var toDoItem:NSDictionary = exercises.objectAtIndex(indexPath!.row) as NSDictionary
cell.textLabel!.text = toDoItem.objectForKey("exercises") as? String
return cell
}
Try this way:
let toDoItem = exercises[indexPath.row]
cell.textLabel?.text = toDoItem["exercises"]!
For more reference check this answer How to make a table from a dictionary with multiple content types in Swift?
may be this will help you.
I am trying to figure out how Swift works. and right now I'm working on a table view cell, but there seems to be a problem.
import UIKit
class ProfileTableViewCell : UITableViewCell{
#IBOutlet var profilePicImageView: UIImageView!
#IBOutlet var profileNameText: UILabel!
}
ove
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cellResult :Dictionary<CellType, Dictionary<String, String>> = menuList[indexPath.row]
var cellType: CellType = Array(cellResult.keys)[0]
var cellData: Dictionary<String, String> = cellResult[cellType] as Dictionary<String, String>!
if(cellType == CellType.Profile){
var cell = tableView.dequeueReusableCellWithIdentifier("DisplayPicCellIdentifier") as? ProfileTableViewCell
if (cell == nil) {
cell = ProfileTableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "DisplayPicCellIdentifier")
}
cell.profileNameText!.text! = cellData["displayName"] // Error 'ProfileTableViewCell? does not have member named 'profileNameText'
// cell.setProfileNameText(cellData["displayName"])
return cell!
}
I keep getting this error does not have member named in profileNameText, may I ask how I am able to fix such issue?
Thanks
You should change the line to
cell!.profileNameText.text = cellData["displayName"]
I'm in the process of making a small app for myself to try out Swift and so forth, and I think I am missing something simple here.
So in my controller, i have the proper delegates and methods to support it:
class MatchListViewController: UIViewController, UITableViewDelegate, UITableViewDataSource
I then setup & populate the table
override func viewDidLoad() {
super.viewDidLoad()
tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
loadResults()
}
func loadResults() {
let delegate = UIApplication.sharedApplication().delegate as AppDelegate
let context:NSManagedObjectContext! = delegate.managedObjectContext
let fetchRequest = NSFetchRequest(entityName: "Series")
var error:NSError? = nil
results = context.executeFetchRequest(fetchRequest, error: &error) as NSArray
}
In Storyboard, the UITableView is connected to my IBOutlet, and refers to MatchListViewController as its delegate.
#IBOutlet var tableView: UITableView!
I setup the cellForRowAtIndexPath method as such:
func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as UITableViewCell
if let row = indexPath?.row {
let object = results.objectAtIndex(row) as NSObject
if (object != nil) {
let teamOneName = object.valueForKey("team_one") as NSString
let teamTwoName = object.valueForKey("team_two") as NSString
cell.textLabel!.text = "\(teamOneName) vs \(teamTwoName)"
}
}
return cell
}
Any ideas on what I may be missing here?