How to initialize a programmatic UITableViewCell with a property via dependency injection? - ios

I've looked around and I can't find any examples of this. I simply want to create a custom initializer on a programmatic UITableViewCell subclass that includes one property.
class CustomCell: UITableViewCell {
var foo: Foo
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// custom init with a foo
}
What I want is:
let cell = CustomCell(foo: myFoo) // other default init params if needed
Not:
let cell = CustomCell()
cell.foo = myFoo
Pointing me to a resource (if I missed one) that shows an implementation like this would be great. I assume this is a common need, but a couple hours being yelled at by init compiler errors and a few Medium articles later and I can't find anything.

Instances of UITableViewCell are created by the system, using the classic init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) initializer.
Don't forget to add this magic line after initializing your UITableView:
myTableView.register(CustomCell.self, forCellReuseIdentifier: Constant.myCellReuseIdentifier)
Let's say you write this:
class CustomCell: UITableViewCell {
convenience init(reuseIdentifier: String, customObject: Any?) {
self.init(style: UITableViewCell.CellStyle.default, reuseIdentifier: reuseIdentifier)
}
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
Then you can create a cell with this line of code:
let cell = CustomCell(reuseIdentifier: Constant.myReuseIdentifier, customObject: nil)
... but what will you do with this cell? How to plug it into your "tableview's cells management" logic.
To inject something to your cell, you may plug in:
tableView(_:cellForRowAt:) for a UITableView (in the object implementing UITableViewDataSource)
collectionView(_:cellForItemAt:) or collectionView:willDisplayCell:forItemAtIndexPath: for a UICollectionView.
Those 3 methods may be call very frequently by the system, avoid memory allocations or any memory / CPU intensive task when possible.

Related

How to render #IBDesignable UITableViewCell in Storyboard?

I create a UITableViewCell subclass CustomTableViewCell and mark it as #IBDesignable. In CustomTableViewCell.xib file I drag a label to its center, then setup an #IBOutlet and name this label as contentLabel. Next, I drag a TableViewCell and put it on a ViewController's table view in the storyboard file, and change its class to CustomTableViewCell. My question is: how to render this contentLabel in the storyboard?
The following snippet doesn't work and I believe it's because contentLabel is not initialized yet in init(), but I want to give it an initial text and render it in the storyboard. How to achieve this?
#IBDesignable class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var contentLabel: UILabel!
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
contentLabel.text = "Hello"
}
required init?(coder: NSCoder) {
super.init(coder: coder)
contentLabel.text = "Hello"
}
}
EDIT: This cell needs to reused in several different view controllers.

Dequeue UITableViewCell with a generic parameter

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

iOS UITableViewCell with custom accessoryView in InterfaceBuilder

I'm creating a settings page for my Swift app and (following examples here on SO) I've subclassed a UITableViewCell and in the init() handlers I've set the accessoryView to be a UISwitch().
I also have a custom #IBInspectable property on the subclass which is also marked #IBDesignable:
#IBDesignable class TableViewSwitchCell: UITableViewCell {
#IBInspectable var settingsKey: String?
func build()
{
accessoryView = UISwitch()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
build()
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
build()
}
// code omitted that uses the settingsKey with NSUserDefaults
}
How can I arrange for the UISwitch to also be visible in InterfaceBuilder so that I can attach additional targets to it whilst still just subclassing from UITableViewCell
Ideally I don't want to create a whole new nib with its own layout, since I still want to retain most of the other features of a standard UITableViewCell.
Drop UISwitch in Interface Builder inside of cell. Then wire it with up accessoryView of your cell. On standard basic cells the control tends to be locked in the middle of cell in IB. However in runtime it will be properly positioned.

IBDesignable never finishes updating UITableViewCell in storyboard

This is a similar question to this but not quite the same.
I have created a subclass of UITableViewCell which references a custom nib and marked it as #IBDesignable. Changes that are made both in code and from the .xib file display correctly in the simulator and on a device but not in the storyboard.
import UIKit
#IBDesignable class TextFieldTableViewCell: UITableViewCell {
var view: UIView!
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
setup()
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setup()
}
func setup() {
view = loadViewFromNib()
view.frame = bounds
view.autoresizingMask = [UIViewAutoresizing.FlexibleWidth, UIViewAutoresizing.FlexibleHeight]
contentView.addSubview(view)
}
func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: "TextFieldTableView", bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
}
The storyboard displays a permanent "Designables Updating".
Breaking the problem down into a less complex test of only subclassing a UITableViewCell and marking it as #IBDesignable results in the same permanent "Designables Updating".
import UIKit
#IBDesignable class TextFieldTableViewCell: UITableViewCell {
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
}
Has anyone had success with creating a #IBDesignable UITableViewCell subclass? This is happening in Xcode 7 beta 6 and beta 5.
This issue has been resolved as of Xcode 9.
I had reported this issue to Apple and at least have the piece of mind knowing that this is a known bug. It is currently an open issue under the problem ID 17973876.
Edit: As of 12/7/2016 this bug is still marked as open.
I was having similar problems. This thread helped me understand the issue: Is there a way for Interface Builder to render IBDesignable views which don't override drawRect:
I got it to work by adding
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
layoutSubviews()
}
as far as i understand after reading the above stack link, IBDesignable loads the nib in storyboard not from nib or from coder but from the init with frame. Not too sure but I think init with style is doing a similar thing.
It seems that the "Updating" bug is happening only when the cell is inside a storyboard. Everything works OK when you create special .xib file just for the cell.
Hopefully, they will fix it soon.

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