I've created a custom class that inherits from PFTableViewCell and for some reason the imageView is nil. Anybody know what the issue could be? I'm dequeuing the cell from the storyboard. When I completely programmatically generated the class it was fine. Here's my code.
PFTableViewCell has a property called imageView that should've been auto initialized from a child class (I thought).
class PhotoVC: PFQueryTableViewController
override func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!, object: PFObject!) -> PFTableViewCell! {
let CellIdentifier = "Cell"
var cell : PhotoCell = tableView.dequeueReusableCellWithIdentifier(CellIdentifier) as PhotoCell!
if cell == nil {
cell = PhotoCell(style: .Default, reuseIdentifier: CellIdentifier)
}
println(cell.imageView) //Always prints nil
}
class PhotoCell: PFTableViewCell
init(style: UITableViewCellStyle, reuseIdentifier: String!) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
When I try to add an IBOutlet for the imageView I get an error.
As far as I know, the PFTableViewCell does not work well with standard styles. I took a glance at their code (it's open-source), and the comments indicate that they indeed have issues with imageView in the standard styles.
I suggest that you create a custom cell with an imageView that you need to set as a PFImageView in the storyboard.
Then the whole PFTableViewCell loading works fine (that's the only way it worked for me).
Related
Let's say we have a generic UITableViewCell:
class ControlTableViewCell<ControlType: UIControl>: UITableViewCell
Because when we need to create the cell we need to know the control type, we should disable this initializer:
#available(*, unavailable)
override convenience init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
fatalError("Use init().")
}
and only use the following initializer:
init(with control: ControlType, reuseIdentifier: String? = nil) {
self.control = control
super.init(style: .default, reuseIdentifier: reuseIdentifier)
configureForControl()
}
If I create a class that I want to use:
class DogTableViewCell: ControlTableViewCell<UIButton>
how would I go about dequeuing the reusable cells?
I would register first:
tableView.register(DogTableViewCell.cellClass,
forCellReuseIdentifier: DogTableViewCell.cellReuseIdentifier)
And then in the cellForRowAt:
let cell = tableView.dequeueReusableCell(withIdentifier: DogTableViewCell.cellReuseIdentifier,
for: indexPath) as? DogTableViewCell ?? DogTableViewCell()
But because dequeueReusableCell uses init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) we would crash at fatalError("Use init().")
Is there a good way to deal with this situation? I found a number of posts that ask the same question, but they all seem to want to set some properties that can be set later on, so no point in trying to use a custom initializer when dequeueing.. but in my case the class being generic, it needs to be initialized with a type.
Thanks for your help.
Similar posts: UITableViewCell dequeueReusableCellWithIdentifier with custom initializer
You should remove all your initializers and add call configure method after the cell is created. For example,
func setControl(control: ControlType) {
self.control = control
configureForControl()
}
Call this methods after dequeueReusableCell
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 a variable that I need to pass from the main UITableView to the cell subview to use in the initialisation method. I tried doing this:
class CollectionCell : UITableViewCell {
var collectionElement : PFObject = PFObject(className:"CollectionElement")
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.collectionElementArray = (collectionElement["elementFileArray"] as? [PFObject])!
...
}
I then create the cell in this way:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
...
let cell : CollectionCell = tableView.dequeueReusableCellWithIdentifier("CollectionCell", forIndexPath: indexPath) as! CollectionCell
cell.collectionElement = collectionElements[indexPath.row]
return cell
}
The variable is not set in the init method.
As ncerezo says the cells are not getting created, they are getting recycled so no init functions get called. the better approach is to use didSet on your collectionElement variable and do whatever view updating based on the new value there after ensuring it isn't nil. Then when you set the property of the cell in tableView(tableView: , cellForRowAtIndexPath:) the cell will actually update itself.
var collectionElement : PFObject = PFObject(className:"CollectionElement") {
didSet {
// Update the cell's view elements here, rather than an init function
}
}
UITableView reuses cells, and it's responsible for creating them.
You are NOT creating the cell: as the method explains by its name, dequeueReusableCellWithIdentifier just gets an already created (and probably used) cell from the table view.
Imagine that, given your cell size and device screen, the tableView can show 10 rows at a time. It will create a few more, lets say 12, and then will call cellForRowAtIndexPath on the UITableViewDataSource every time it needs to fill a cell with new information to make it visible.
When you call dequeueReusableCellWithIdentifier, you get one of those already created cells, and you're responsible for updating the necessary elements on it to reflect the underlying data. You can thus not assume that any part of it will be "empty" or uninitialized.
I'm trying to display an icon for each PFTableViewCell based on the imageView property which doesn't work. The code I'm using is below which throws the follow error, "fatal error: unexpectedly found nil while unwrapping an Optional value".
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject) -> PFTableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! i!
if cell == nil {
cell = PFTableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "Cell")
}
cell.textLabel?.text = "Some Label"
cell.detailTextLabel?.text = "Another label"
cell.imageView.image = UIImage(named: "icon.png")
return cell
}
I had the same issue. Your crash is linked to the imageView being nil.
I guess this is a bug with PFTableViewCell: it does not "see" the standard imageView from the "Basic" style, and cannot create the corresponding PFImageView (I opened an issue with ParseUI).
Generally speaking, it's better to set your line like so:
cell.imageview?.image
then you won't have the crash.
You won't have the image either, but that's because of Parse's PFTableViewCell ...
If you want your code to work, you need to create a custom cell, with an imageView that you need to set as a PFImageView. This way, there will indeed be an imageView, and the whole PFTableViewCell file loading works perfectly.
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:.