Adding UILabel to a custom tableViewCell programmatically - ios

I'm trying to add a label programmatically to a custom table view cell. The app will still crash with an 'unexpectedly found nil'. I've looked into other posts here but I can't see what I'm doing wrong. Please see the code for details.
import UIKit
class MyTableViewCell: UITableViewCell {
var titleLabel: UILabel!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
titleLabel = UILabel()
titleLabel.textColor = UIColor.blackColor()
titleLabel.font = UIFont.systemFontOfSize(17)
titleLabel.frame = CGRect(x: 40.0, y: 2, width: bounds.width, height: bounds.height)
addSubview(titleLabel)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Inside the view controller:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("CustomCell", forIndexPath: indexPath) as! MyTableViewCell
// Crashes the app with: 'fatal error: unexpectedly found nil while unwrapping an Optional value'
cell.titleLabel.text = "Some text"
}
The custom table view class is properly assigned to the prototype cell in the storyboard.
If I change the declaration inside MyTableViewCell to var titleLabel = UILabel() and delete that line from inside the init() the table view will display but my label will not show up.
The code looks good to me though, any advice as to what I may be missing here?

You made custom cell in storyboard,so you don't need to register class for table view.And views from storyboard will call initWithCoder and awakeFromNib,please add label in these two functions.

Related

Why isn't the slider in my custom UITableViewCell reacting to the drag attempts?

I'm creating an app in UIKit, programmatically.
One screen has a UITableView with custom cells, some have sliders inside. All cells show up and can be selected. Other custom cell elements like buttons and text field react to interaction, but not the sliders.
Here's my code:
class CellWithSliderValues: UITableViewCell{
#IBOutlet var slider: UISlider! = {
let ctrl = UISlider()
// color setup here
return ctrl
}()
// some labels
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.addSubview(slider);
// layout code, skipped for StackOverflow
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
How the cell is created for the table, with tag calculation skipped:
let cell = mainScreen.table.dequeueReusableCell(withIdentifier: "CellWithSliderValues") as! CellWithSliderValues
cell.slider.maximumValue = Constants.BEST
cell.slider.minimumValue = Constants.WORST
cell.slider.value = currvalue
// tag is set here
cell.slider.addTarget(self, action: #selector(sliderChange), for: .valueChanged)
return cell
Part of the delegate that is called when clicking of the slider cell:
guard let cell = (mainScreen.table.cellForRow(at: indexPath) as? CellWithSliderValues) else {return}
cell.slider.becomeFirstResponder()
return
Finally, the function that should be called when the slider is dragged but is never actually called (seen with breakpoints):
#objc func sliderChange(sender: UISlider){
// get the values here
sender.resignFirstResponder()
}
The same approach did work with the text entry fields, so what am i doing wrong here? Is it because of the Slider not having delegates?
Commenter Sulthan's solution of adding a slider as a subview to the cell.contentView instead of the the cell directly worked like a charm.
self.contentView.addSubview(slider);
I wonder why didn't other cell elements require that.

Custom TableViewCells do not show up

So I am pretty new to iOS development. I try to create everything programmatically so my Storyboard is empty. I'm currently trying to get a TableView with custom cells. The TableView is running and looking fine when I use the standard UITableViewCell. I created a very simple class called "GameCell". Basically, I want to create a cell here with multiple labels and maybe some extra UIObjects in the future (imageView etc.). For some reason, the custom cells do not show up.
Game cell class:
class GameCell: UITableViewCell {
var mainTextLabel = UILabel()
var sideTextLabel = UILabel()
func setLabel() {
self.mainTextLabel.text = "FirstLabel"
self.sideTextLabel.text = "SecondLabel"
}
}
Here the additional necessary code to get the number of rows and return the cells to the TableView which I have in my ViewController. self.lastGamesCount is just an Int here and definitely not zero when I print it.
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.lastGamesCount
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellID) as! GameCell
In my viewDidLoad() I register the cells like this:
tableView.register(GameCell.self, forCellReuseIdentifier: cellID)
When I run everything the Build is successful I can see the navigation bar of my App and all but the TableView is empty. I go back to the normal UITableViewCell and the cells are showing up again. What am I missing here? Any help is appreciated.
Thanks!
The problem is you need to set constraints for these labels
var mainTextLabel = UILabel()
var sideTextLabel = UILabel()
after you add them to the cell
class GameCell: UITableViewCell {
let mainTextLabel = UILabel()
let sideTextLabel = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setLabel()
}
func setLabel() {
self.mainTextLabel.translatesAutoresizingMaskIntoConstraints = false
self.sideTextLabel.translatesAutoresizingMaskIntoConstraints = false
self.contentView.addSubview(mainTextLabel)
self.contentView.addSubview(sideTextLabel)
NSLayoutConstraint.activate([
mainTextLabel.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor),
mainTextLabel.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor),
mainTextLabel.topAnchor.constraint(equalTo: self.contentView.topAnchor,constant:20),
sideTextLabel.leadingAnchor.constraint(equalTo: self.contentView.leadingAnchor),
sideTextLabel.trailingAnchor.constraint(equalTo: self.contentView.trailingAnchor),
sideTextLabel.topAnchor.constraint(equalTo: self.mainTextLabel.bottomAnchor,constant:20),
sideTextLabel.bottomAnchor.constraint(equalTo: self.contentView.bottomAnchor,constant:-20)
])
self.mainTextLabel.text = "FirstLabel"
self.sideTextLabel.text = "SecondLabel"
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Why don't we follow the same process creating customTableViewCell with xib as creating customView with xib?

When we are creating customView, we set the view File's owner to custom class and we instantiate it with initWithFrame or initWithCode.
When we are creating customUITableViewCell, we set the view's class to custom class, instead File's owner's. And then register all the nibs so on.
İn this way, we always need to register the xibs to UIViewController and
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath)so on.
What I find is that I don't want to register nibs all the time where I want to use customUITableViewCell. So I want to initialize xib inside my customUITableCell like the same process of creating customUIView. And I succeed. Here are the steps.
My question is what is the preferred way of creating customUITableCell?
With this method there is no need to register nibs and we can call customCell where we want to without loading/registering nib.
Set the view's File's Owner of xib to customUITableCell class. Not the view's class set to customClass, just File's Owner.
Image 1
My custom class called myView: UITableViewCell
import UIKit
class myView: UITableViewCell {
var subView: UIView!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
initSubviews()
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
initSubviews()
}
func initSubviews(){
subView = Bundle.main.loadNibNamed("TableViewCell", owner: self, options: nil)?.first as! UIView
subView.autoresizingMask = UIViewAutoresizing(rawValue: UIViewAutoresizing.RawValue(UInt8(UIViewAutoresizing.flexibleWidth.rawValue) | UInt8(UIViewAutoresizing.flexibleHeight.rawValue)))
self.addSubview(subView)
}
}
İnside UIVivController, I did't register nibs and use
let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifier", forIndexPath: indexPath)
Instead, I did this.
let cell = myView(style: .default , reuseIdentifier: "TableViewCell")
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var tableStyle: UITableView = UITableView()
override func viewDidLoad() {
super.viewDidLoad()
tableStyle.frame = CGRect(x: 0, y: 0, width: self.view.frame.size.width, height: self.view.frame.size.height)
tableStyle.delegate = self
tableStyle.dataSource = self
view.addSubview(tableStyle)
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100.00
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1 }
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = myView(style: .default , reuseIdentifier: "TableViewCell")
return cell
}
}
Here is the result.
Image 4
THANKS FOR YOUR TIME!!!
Your approach means every single time the UITableView requests a new cell, you're creating a brand new cell from scratch. That it means it has to:
find the nib
load the nib
parse it to find the views
make the views
update the cell
This is no better than having a long scroll view with custom views for it's entire length.
The beauty of UITableView is it optimizes much of this process and re-uses cells, massively cutting down the performance cost of having more cells than fit on your screen. With the traditional (correct) approach, steps 1-4 only have to happen once.
To expand on the differences in the xib:
When creating a cell with UITableView, you only give it the nib, and the system looks in the nib to find a UITableViewCell. A simple UIView will not work.
You actually can subclass the UIView in your xib with your custom class. It just happens that the norm is to use fileOwner, largely because that's the norm when using nibs with UIViewControllers as was required in the pre-storyboard era
An addition to the accepted answer:
If your only problem with the "classic" approach is that you need to register the nib and call dequeueReusableCell, you can simplify the calls with a nice protocol extension as discussed in this article:
protocol ReuseIdentifying {
static var reuseIdentifier: String { get }
}
extension ReuseIdentifying {
static var reuseIdentifier: String {
return String(describing: Self.self)
}
}
extension UITableViewCell: ReuseIdentifying {}
To register you just call
self.tableView.register(UINib(nibName: MyTableViewCell.reuseIdentifier, bundle: nil), forCellReuseIdentifier: MyTableViewCell. reuseIdentifier)
And to create it you call
let cell = self.tableView.dequeueReusableCell(withIdentifier: MyTableViewCell. reuseIdentifier, for: indexPath) as! MyTableViewCell
(Of course this only works if class, xib and reuse identifier all have the same name)

Custom UITableviewCell IBOutlet always nil

I have a custom UITableViewCell subclass with a simple IBOutlet setup for a UILabel.
class SegmentCell: UITableViewCell {
#IBOutlet weak var test: UILabel!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
test.text = "Some Text"
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Convinced I have everything set up correct have followed other answers, but the UILabel is always nil.
ViewController:
viewDidLoad:
self.tableView.registerClass(SegmentCell.self, forCellReuseIdentifier: "Cell")
cellForForAtIndexPath
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! SegmentCell
return cell
}
Cell is set to Custom
Reuse identifier is correct
Cells class is SegmentCell
Tableview content is Dynamic Prototypes
What am I missing?
Since You are uisg Xib file you have to register with ,
tableView.register(UINib(nibName: "yourNib", bundle: nil), forCellReuseIdentifier: "CellFromNib")
Think, if only register with class ,system will not know the its Xib.This work for me.
Based on the way you register your cell, it is not going to be loading it from a storyboard or xib file. It will be invoking that init method only. Your init method does not create the label, so it will always be nil.
You should also use dequeueReusableCellWithIdentifier(_:forIndexPath:) instead of dequeueReusableCellWithIdentifier(_:). The latter predates storyboards and will return nil unless you have previously created a cell with that identifier and returned it.
Lastly, the init method that the tableView is calling is not the one you've implemented, or the app would crash on test.text = ... while trying to unwrap a nil optional.
UITableViewCell class and ContentView class can't be same.
You should register it first in the viewDidLoad function.
self.tableView.registerClass(CustomCell.self, forCellReuseIdentifier: "Cell")
I have same issue, after add custom cell class to cell in tableview placed in Storyboard.
I also register cell, like in documentation. But now I solve my issue.
"I remove registering and inspect cell again, all IBOutlets initialised"
I think that basically the outlets are not setup yet at init time. If you want to get to the outlets and manipulate them, then override didMoveToSuperview or similar and do it in there. For instance, this is what I had to do to get to the button outlet in my cell:
open class MyTableViewCell: UITableViewCell {
// Top stack view, not the inner one.
#IBOutlet weak var stackView: UIStackView!
#IBOutlet weak var buttonView: UIButton?
open override func didMoveToSuperview() {
buttonView?.titleLabel?.adjustsFontForContentSizeCategory = true
}
func adjustForAccessibilityCategory(accessible: Bool) {
if accessible {
stackView.axis = .vertical
stackView.alignment = .leading
stackView.spacing = 2
} else {
stackView.axis = .horizontal
stackView.alignment = .center
stackView.spacing = 20
}
}
open override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
if #available(iOS 11.0, *) {
adjustForAccessibilityCategory(accessible: traitCollection.preferredContentSizeCategory.isAccessibilityCategory)
}
}
}

Custom UITableViewCell on Swift

I need to create a custom UITableViewCell,
I have created a class inside of a ViewController class.
I have written the code below;
class FavoritesCell: UITableViewCell
{
var timeLabel:UILabel?
override init(style: UITableViewCellStyle, reuseIdentifier: String!) {
super.init(style: UITableViewCellStyle.Value1, reuseIdentifier: reuseIdentifier)
timeLabel = UILabel(frame: CGRectMake(250, 10, 70, 20))
//how to add this time label as a subview of FavoritesCell
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
As i mentioned in commented line, how to add timeLabel to UITableViewCell?
Thanks
After you've created your timeLabel, this should do it:
self.contentView.addSubview(timeLabel!)
This adds the timeLabel to the cell's contentView rather than the cell itself, but that's generally what you do in table view cells.
Other things to note:
It's fine to use self here - after your super.init call, the object is fully initialised.
You've just created the timeLabel, so it's safe to force-unwrap it in addSubview.
As UITableViewCell class is derived from the UIView, so you can use the same method - self.addSubview(timeLabel!).

Resources