Changing Custom UITableViewCell Label from non-tableview class causes error - swift

I have a generic cell ItemCell that can display any kind of item. The descendant of my Item class is my Armor class. I have a function inArmor that overrides a function in Itemthat returns an ItemCell. However no matter what label, when I try to change the text value of one I get the error below. I can't even set a hard coded string. I also checked the ItemCell class and it's .xib file and everything is linked. Is there something I'm missing? Let me know what code you would need to see as I have no idea where this issue is coming from.
fatal error: unexpectedly found nil while unwrapping an Optional value
CharacterTableView.swift
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
tableView.register(UINib(nibName: "ItemCell", bundle: nil), forCellReuseIdentifier: "ItemCell")
switch sections[indexPath.section] {
case "Equipped":
return Character.inventory.equippedGear[indexPath.row].cell
case "Armor":
return Character.inventory.unequippedArmor[indexPath.row].cell
case "Weapons":
return Character.inventory.unequippedWeapons[indexPath.row].cell
case "Healing":
return Character.inventory.healingItems[indexPath.row].cell
default:
return ItemCell()
}
}
Armor.swift
override var cell: ItemCell {
let cell = ItemCell()
cell.name.text = "Name"
}
ItemCell.swift
class ItemCell: UITableViewCell {
//MARK: - IBOutlets
#IBOutlet weak var imageItem: UIImageView!
#IBOutlet weak var name: UILabel!
#IBOutlet weak var imageStat1: UIImageView!
#IBOutlet weak var labelStat1: UILabel!
#IBOutlet weak var imageStat2: UIImageView!
#IBOutlet weak var labelStat2: UILabel!
}

I see several potential problems in your code that can cause the crash.
You register your cell in tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell, but you don't dequeue a cell. This looks to me as that you are returning a cell that has no relation to the tableview that is requesting the cell.
You have a UITableViewCell with many outlets, but you don't set these outlets anywhere. This means that all the outlets except the name will be nil and be explicitly unwrapped (because of the !) when displayed in the table view.
You switch on indexPath. section and assumes there the sections are Strings that you have defined. I can't check whether this is the case and if the datasource call really comes with the stings as section. Have your checked this while debugging?
As a default you bluntly return an empty ItemCell. By empty I mean that none of the outlets are set and therefor empty. The labels are explicitly unwrapped (because of the !) and will make your app crash.
I hope this helps you solve the problem.

To create cells for a table, you should be calling something like…
tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath) as! ItemCell
Also, you might find it better to use an enum than an array of Strings for your sections:
enum Sections: Int {
equipped, armour, weapons, healing
}
And your cell should be registered just once. So…
enum Sections: Int {
equipped, armour, weapons, healing
}
override func viewDidload() {
super.viewDidLoad()
tableView.register(UINib(nibName: "ItemCell", bundle: nil), forCellReuseIdentifier: "ItemCell")
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath) as! ItemCell
guard let section = Section(rawValue: indexPath.section) else {
return cell
}
switch section {
case .equipped:
cell.item = Character.inventory.equippedGear[indexPath.row]
case .armour:
// etc
case .weapons:
// etc
case .healing:
// etc
}
return cell
}

Related

How to assign the specific endpoint to the specific selected cell

I have some data coming from the API and I am showing them in tableView cells, Each of those cell's have a "Download" button, I need to assign a fileID from API to the cell that the user has selected to Download.
I have already set up the button to a UITableViewCell class, I just need to have a idea how to make the button get the fileID depending on which cell is clicked since every cell has it's own fileID.
paymentsViewController class:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data?.count ?? 0
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "paymentsCell", for: indexPath) as? paymentsModel else {return UITableViewCell()}
let currentInvoice = payments.Result![changeCustomerKey.DefaultsKeys.keyTwo].properties?[0].payments
let currentInvoices = currentInvoice?[indexPath.row]
cell.priceLabelPayments?.text = "€\(currentInvoices?.amount ?? 0.00)"
cell.whenDateLabel?.text = "\(currentInvoices?.insertionDate?.convertToDisplayForm() ?? "")"
cell.fromSubLabel.text = "\(currentInvoices?.bank ?? "")"
cell.downloadButtonPayments.layer.cornerRadius = CGFloat(5)
cell.localizePaymentsModel()
return cell
}
}
paymentsModel class:
class paymentsModel: UITableViewCell {
#IBOutlet weak var cellContentViewPayments: UIView!
#IBOutlet weak var downloadButtonPayments: UIButton!
#IBOutlet weak var priceLabelPayments: UILabel!
#IBOutlet weak var fromSubLabel: UILabel!
#IBOutlet weak var whenDateLabel: UILabel!
func localizePaymentsModel() {
let defaults = UserDefaults.standard
let lang = defaults.string(forKey: changeLanguageKey.DefaultsKeys.keyOne)
downloadButtonPayments.setTitle(NSLocalizedString("Download", tableName: nil, bundle: changeLanguage.createBundlePath(lang: lang ?? "sq" ), value: "", comment: ""), for: UIControl.State.normal)
}
}
You are thinking about this wrong. It isn't the cell that has the fileID. When the user taps on a cell or a button, you should ask the table view (or collection view) for the indexPath it currently occupies, then use the IndexPath to look up the model data for that entry.
See my answer here:
Thant link shows code that lets you figure out the IndexPath for any view in a table view cell. You could use that code from your button's IBAction.

Downcast Conflict for Custom Cell in TableViewController

I have a TableViewController that uses a custom prototype cell. The outlets for the cell contents are defined in a swift file named BYMyCell.swift.
class BYMyCell: UITableViewCell {
#IBOutlet weak var yearLabel: UILabel!
#IBOutlet weak var descriptionLabel: UILabel!
#IBOutlet weak var startingDateLabel: UILabel!
#IBOutlet weak var commentsLabel: UILabel!
#IBOutlet weak var openStateImage: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
In my BusinessYearViewController which is a TableViewController view I define the cell for the CellForRowAt func as the following:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellID", for: indexPath) as! BYMyCell
This casting "as! BYMyCell" works fine. I am able to put data in the outlets that are labels and an image into my custom cell.
Now I would like to make the cell or row swipeable. I have loaded the SwipeCellKit cocoaPod which I have used before when the cellForRowAt did not use the downcasting for the custom cell. The documentation suggest that I need to downcast the "let cell ..." definition to downcast to "as! SwipeCellKit" such as:
let cell = tableView.dequeueReusableCell(withIdentifier: "cellID", for: indexPath) as! SwipeTableViewCell
I'm not sure if this will disable my outlets from the BYMyCell.swift file on behalf of my ViewController. Can someone suggest how I would handle both a custom cell and making it a cocoapod swipeable cell at the same time.
My reason is to put Edit, Flag, and Delete functions in the swipeable cell UI.

Configure in TableView is not being reached

So I am trying to have a TableView displayed, but I'm currently only getting an empty tableview. Upon further inspection, I see that the configure block is not being run. Why is this?
Code:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: teamCellIdentifier, for: indexPath)
print("reached")
func configure(cell: UITableViewCell,
for indexPath: IndexPath) {
print("not reached")
guard let cell = cell as? TeamCell else {
return
}
let team = fetchedResultsController.object(at: indexPath)
cell.teamLabel.text = team.teamName
cell.scoreLabel.text = "Wins: \(team.wins)"
if let imageName = team.imageName {
cell.flagImageView.image = UIImage(named: imageName)
} else {
cell.flagImageView.image = nil
}
}
return cell
}
}
TeamCell
class TeamCell: UITableViewCell {
// MARK: - IBOutlets
#IBOutlet weak var teamLabel: UILabel!
#IBOutlet weak var scoreLabel: UILabel!
#IBOutlet weak var flagImageView: UIImageView!
// MARK: - View Life Cycle
override func prepareForReuse() {
super.prepareForReuse()
teamLabel.text = nil
scoreLabel.text = nil
flagImageView.image = nil
}
}
The reason why your tableView is empty or it looks like empty and your tableView methods are not being called are two reasons.
1)- Table view heigh or width is equal to 0 and there is no need to load and display tableView, respectively tableView cell.
2)- You did not connect tableView data source and delegates to your view controller.
2.1) You can add TableView delegates and data sources through storyboard (see image down below)
2.2) You can add TableView delegates and data sources thrugh code in your view controller, like:
2.2.1) In viewDidLoad() method:
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// TableView Delegates & DataSource
tableView.delegate = self
tableView.dataSource = self
}
2.2.2) Or view tableView outlet didSet (I prefer this way, if I don't do it via storyboard):
#IBOutlet weak var tableView: UITableView! {
didSet {
tableView.delegate = self
tableView.dataSource = self
}
}
Your code has a structure like this:
// This next line defines a function called a
func a() {
print("a")
// This next line only defines a function called b:
// that is scoped inside the 'a' function
func b() {
print("b")
}
// Defining a function does not invoke the function
// That would need an actual call here like this:
// b()
}
// I can't call b here because it's nested inside the function 'a'
First you have a nested func inside function?
Take it out....
then after this line :
let cell = tableView.dequeueReusableCell(withIdentifier: teamCellIdentifier, for: indexPath)
do this:
configure(cell: cell,for indexPath: indexPath)

Connect Prototype Cells to View Controller [Swift 4]

I am new to programming and currently working on a newsfeed like app. I had a normal Table view up and running fine, but want to try it now with a costume cell type. So I created one and thought connecting the labels the usual way would be perfectly fine, but I was wrong. So I am wondering how I can let my Text label connect to my view controller, so I can use my custom cell.
class ViewController: BaseViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var newsfeedTableView: UITableView!
var ref: DatabaseReference!
var posts = [String]()
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return (posts.count)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: UITableViewCell.CellStyle.default, reuseIdentifier: "cell")
//here is where I need the custom label to get the posts
cell.textLabel?.text = posts[indexPath.row]
cell.textLabel?.font = UIFont.boldSystemFont(ofSize: 18.0)
return cell
}
}
Create subclass of UITableViewCell and connect IBOutlets to this class
class YourCell: UITableViewCell {
#IBOutlet weak var customLabel: UILabel!
...
}
don't forget to set class of your prototype cell in storyboard:
then in cellForRowAt data source method downcast your dequeued cell as YourCell
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! YourCell
then you have access to YourCell outlets
cell.customLabel.text = "SomeText"
...
I'm assuming that you are using Storyboard.
First of all, you should understand that there is little difference when you use own custom table cell. In that case, in the method "cellForRowAtIndexPath", after dequeue your cell, you just have to typecast table cell like 'as! YourCustomTableCellClass'. After this line, you can access each property of this class.
First, design your table cell on Storyboard whatever you want.
Now, make a subclass of UITableViewCell and assign this class to your prototype custom cell which you have designed on Storyboard. Also, don't forget to set "reuse identifier in Storyboard table cell.
Then connect your outlets with custom cell class from Storyboard.
Now you can use code like this:
class YourTableCellClass: UITableViewCell {
// I'm using these outlets as a sample, but you have to connect them from Storyboard.
var leftTextLabel: UILabel!
var rightTextLabel: UILabel!
}
class YourTableController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - TableView Delegate & DataSource
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1 // return your number of rows here...
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100 // return the height for the row here.....or you can set height from Storyboard if you have fix height of all rows.
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CellIdentifier", for: indexPath) as! YourTableCellClass
cell.leftTextLabel.text = "Text" // Set text here....
cell.rightTextLabel.text = "Text" // Set text here....
return cell
}
}

Swift - Increment Label with Stepper in TableView Cell

Another Swift beginner here. I simply want a Stepper in each of my TableView cells that increments a label in the same cell.
I have found a couple of questions on this topic, but they include other elements and I haven't been able to extract the basic concept.
Swift Stepper Action that changes UITextField and UILabel within same cell
Stepper on tableview cell (swift)
So far I have connected IBOutlets for my Label and Stepper, as well as an IBAction for my Stepper in my cell class.
class BuyStatsCell: UITableViewCell{
//these are working fine
#IBOutlet weak var category: UILabel!
#IBOutlet weak var average: UILabel!
#IBOutlet weak var price: UILabel!
//Outlet for Label and Stepper - How do I make these work?
#IBOutlet weak var purchaseAmount: UILabel!
#IBOutlet weak var addSubtract: UIStepper!
//Action for Stepper - And this?
#IBAction func stepperAction(_ sender: UIStepper) {
self.purchaseAmount.text = Int(sender.value).description
}
}
And I understand the concept of reusing the cell in the cellForRowAt indexPath
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "BuyStatsTabCell", for: indexPath) as! BuyStatsCell
cell.isUserInteractionEnabled = false
//these are working
cell.category.text = categories[indexPath.row]
cell.price.text = String(prices[indexPath.row])
cell.average.text = String(averages[indexPath.row])
//but is there something I need to add here to keep the correct Stepper and Label for each class?
return cell
}
One of the already asked questions includes a protocol and another function in the ViewController like this
protocol ReviewCellDelegate{
func stepperButton(sender: ReviewTableViewCell)
}
func stepperButton(sender: ReviewTableViewCell) {
if let indexPath = tableView.indexPathForCell(sender){
print(indexPath)
}
}
I don't know if this is the approach I should be trying to take. I am looking for the simplest solution, but I am having trouble putting the pieces together.
Any help is appreciated.
Thanks.
Easiest solution (simplyfied):
Create a model BuyStat with a property purchaseAmount (it's crucial to be a class).
You are strongly discouraged from using multiple arrays as data source
class BuyStat {
var purchaseAmount = 0.0
init(purchaseAmount : Double) {
self.purchaseAmount = purchaseAmount
}
}
In the view controller create a data source array
var stats = [BuyStat]()
In viewDidLoad create a few instances and reload the table view
stats = [BuyStat(purchaseAmount: 12.0), BuyStat(purchaseAmount: 20.0)]
tableView.reloadData()
In the custom cell create a property buyStat to hold the current data source item with an observer to update stepper and label when buyStat is set
class BuyStatsCell: UITableViewCell {
#IBOutlet weak var purchaseAmount: UILabel!
#IBOutlet weak var addSubtract: UIStepper!
var buyStat : BuyStat! {
didSet {
addSubtract.value = buyStat.purchaseAmount
purchaseAmount.text = String(buyStat.purchaseAmount)
}
}
#IBAction func stepperAction(_ sender: UIStepper) {
buyStat.purchaseAmount = sender.value
self.purchaseAmount.text = String(sender.value)
}
}
In cellForRowAtIndexPath get the data source item and pass it to the cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "BuyStatsTabCell", for: indexPath) as! BuyStatsCell
cell.buyStat = stats[indexPath.row]
return cell
}
The magic is: When you are tapping the stepper the label as well as the data source array will be updated. So even after scrolling the cell will get always the actual data.
With this way you don't need protocols or callback closures. It's only important that the model is a class to have reference type semantics.
NOTE: MY Cell class is just normal..All changes are in viewcontroller class
class cell: UITableViewCell {
#IBOutlet weak var ibAddButton: UIButton!
#IBOutlet weak var ibStepper: UIStepper!
#IBOutlet weak var ibCount: UILabel!
#IBOutlet weak var ibLbl: UILabel!
}
1.define empty int array [Int]()
var countArray = [Int]()
2.append countArray with all zeros with the number of data u want to populate in tableview
for arr in self.responseArray{
self.countArray.append(0)
}
3.in cell for row at
func tableView(_ tableView: UITableView, cellForRowAt indexPath:
IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! cell
let dict = responseArray[indexPath.row] as? NSDictionary ?? NSDictionary()
cell.ibLbl.text = dict["name"] as? String ?? String()
if countArray[indexPath.row] == 0{
cell.ibAddButton.tag = indexPath.row
cell.ibStepper.isHidden = true
cell.ibAddButton.isHidden = false
cell.ibCount.isHidden = true
cell.ibAddButton.addTarget(self, action: #selector(addPressed(sender:)), for: .touchUpInside)
}else{
cell.ibAddButton.isHidden = true
cell.ibStepper.isHidden = false
cell.ibStepper.tag = indexPath.row
cell.ibCount.isHidden = false
cell.ibCount.text = "\(countArray[indexPath.row])"
cell.ibStepper.addTarget(self, action: #selector(stepperValueChanged(sender:)), for: .valueChanged)}
return cell
}
4.objc functions
#objc func stepperValueChanged(sender : UIStepper){
if sender.stepValue != 0{
countArray[sender.tag] = Int(sender.value)
}
ibTableView.reloadData()
}
#objc func addPressed(sender : UIButton){
countArray[sender.tag] = 1//countArray[sender.tag] + 1
ibTableView.reloadData()
}