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)
}
}
}
Related
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)
I have been trying to create a custom view loaded through xib that contains a button and tableview. The table view is shown or hidden when button is pressed.
This interaction works and the table is created / shown. The problem I have is that I can not click on the table rows.
I have been looking all over and haven't found a solution that works.
I made sure that delegate and dataSource are set. I also do not have a GestureRecognizer for the ViewController it is used in that could absorb the touch.
Does anybody have an idea what I am missing?
Here is the code of this custom view:
class SubUnitSpinner : UIView, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var spinnerTableView: UITableView!
let subUnitNames: [String] = ["World", "Add/Remove"]
override init( frame: CGRect ) {
super.init(frame: frame)
loadViewFromNib()
setupTableView()
}
required init?( coder aDecoder: NSCoder ) {
super.init(coder: aDecoder)
loadViewFromNib()
setupTableView()
}
func setupTableView() {
spinnerTableView.delegate = self
spinnerTableView.dataSource = self
spinnerTableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell")
spinnerTableView.rowHeight = 30
spinnerTableView.userInteractionEnabled = true
spinnerTableView.allowsSelection = true
spinnerTableView.hidden = true
}
func loadViewFromNib() {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "SubUnitSpinner", bundle: bundle)
let xibView = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
xibView.frame = bounds
xibView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight]
self.addSubview(xibView)
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 2
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = spinnerTableView.dequeueReusableCellWithIdentifier("Cell")! as UITableViewCell;
cell.userInteractionEnabled = true
cell.textLabel?.text = subUnitNames[indexPath.row]
cell.tag = indexPath.row
return cell;
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
//Interaction
print("cell with path: \(indexPath.row)")
}
#IBAction func spinnerTabbed(sender: AnyObject) {
spinnerTableView.hidden = !spinnerTableView.hidden
} }
Update:
View Layout creation:
The xib view layout has been defined in the "storyboard" and the File's Owner set to SubUnitSpinner. The IBOutlet and IBAction where created by ctrl drag and drop.
Usage in UIViewController:
I use it as part of a UIViewController which also has been defined in the storyboard. I added a UIView and declared the Custom Class to be SubUnitSpinner.
The SubUnitSpinner with layout as defined in xib shows up when running it and the button is clickable, the UITableView is shown / hidden when button is shown. The only thing not working is clicking on the tableView cells.
Is something wrong with the setup?
I just took your code and added to a dummy project to check. I did this for your method
required init?( coder aDecoder: NSCoder ) {
super.init(coder: aDecoder)
// removed the load from nib and setup tableview
}
In my case I did the following to add the SubUnitSpinner view to my parent view. Hope you did the same
let aTestView = SubUnitSpinner(frame: CGRectMake(50, 200, 200, 200))
view.addSubview(aTestView)
Also double check if you have connected any delegate and datasource from xib. All in all, your code looks fine and I was able to click on it properly. The below are my results that I achieved.
When do outlets get bound to a UITableViewCell instance? If I print myLabel in the debugger, it's nil and prints:
fatal error: unexpectedly found nil while unwrapping an Optional value
class MyTableViewCell: UITableViewCell {
#IBOutlet var myLabel: UILabel!
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
print("\(myLabel)")
}
}
Outlets are not yet set when the init method of your tableview cell is called!
I'm not sure what your intention is:
If you want to make sure you are not unwrapping a nil optional value just use if let like this:
if let label = myLabel {
print("\(label)")
}
If you want to setup the cell of your tableView, you should first register the cell by calling tableView.registerNib(UINib(nibName: "MyTableViewCell", bundle: nil), forCellReuseIdentifier: "reuse"). Then dequeue the cell in cellForRowAtIndexPath using tableView.dequeueReusableCellWithIdentifier("reuse") and make sure you set the values of your cell in awakeFromNib:
override func awakeFromNib() {
super.awakeFromNib()
//you can be sure that myLabel is initialzed here
print("\(myLabel)")
}
If you are connecting an outlet from the Main Storyboard, your problem could arise if the outlet was accidentally deleted. Recreate the outlet and the label will not be nil anymore.
The issue is that you are running into a case where myLabel is nil. It could be while the table view cell is being recycled.
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.
I'm trying to use a custom TableViewCell Class in my programmatically created UITableView and i can't figure out why this don't get to work. I've used custom TableViewCell Classes a few times without any problems (set the Classname in Storyboard) but here i have no Storyboard to set the Class which should be used to generate the Cells.
I'm doing this:
override func viewDidLoad() {
...
self.popupTableView.registerClass(CustomTableViewCellPopup.self, forCellReuseIdentifier: "popupTableViewCell")
...
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("popupTableViewCell") as CustomTableViewCellPopup
...
return cell
}
class CustomTableViewCellPopup: UITableViewCell {
var message: UILabel = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init(coder decoder: NSCoder) {
super.init(coder: decoder)
}
override func awakeFromNib() {
super.awakeFromNib()
println("I'm running!")
self.message.frame = CGRectMake(0, 0, 10, 10);
self.message.backgroundColor = UIColor.brownColor()
self.addSubview(self.message)
}
}
The println() Output never appears. The TableView gets rendered without the additional UILabel (message). Just like an out of the box UITableViewCell.
The same way (without registerClass()) i'm running a TableView with custom Cells from the Storyboard (the Classname directly defined in the Storyboard Inspector) without any problems.
Have you an idea why the custom Class don't get used?
awakeFromNib won't get called unless the cell is loaded from a xib or storyboard. Move your custom code from awakeFromNib into initWithStyle:reuseIdentifier: because that's the initializer used by the tableView when you call its dequeueReusableCellWithIdentifier: method
You're not seeing the println output because that's in awakeFromNib, which is only called if you cell comes from a storyboard or XIB. Here you're just registering the class with the table, so there's no Nib loading going on. Try making a call to configure the cell from tableView:cellForRowAtIndexPath:, rather than configuring in awakeFromNib.
initWithCoder: won't be called for the same reason - try overriding initWithStyle:reuseIdentifier:.