I have an issue when working with a custom class for the UITableViewCell with Firebase-UI. Here is my code:
In my TableViewController:
self.dataSource = FirebaseTableViewDataSource(ref: firebaseRef, cellClass: MyEventTableViewCell.self, cellReuseIdentifier: "MyEventTableViewCell", view: self.tableView)
self.dataSource.populateCellWithBlock { (cell, obj) -> Void in
let snap = obj as! FDataSnapshot
print(cell)
let myEventCell = cell as! MyEventTableViewCell
myEventCell.eventNameLabel.text = "hello"
}
In my MyEventTableViewCell:
class MyEventTableViewCell: UITableViewCell {
#IBOutlet weak var eventImageView: UIImageView!
#IBOutlet weak var eventNameLabel: UILabel!
var error:String?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
I got:
'fatal error: unexpectedly found nil while unwrapping an Optional
value"
on this line:
myEventcell.eventNameLabel.text = "hello"
Weird is that the "print" gives the following output:
<test.MyEventTableViewCell: 0x7dab0400; baseClass = UITableViewCell; frame = (0 0; 320 44); autoresize = W; layer = <CALayer: 0x7be738c0">>
What do we have to do to manage subclass of UITableViewCell?
PS: I am working with the storyboard to define my Custom cell and I am working with Xcode 7.
FirebaseUI developer here:
If you're using storyboards/prototype cells, use the constructor that has the prototypeReuseIdentifier vs the cellReuseIdentifier (see here). This is an unfortunate wart caused by how iOS started doing UICollectionViews but left the UITableView implementation of Storyboards different. TL;DR: Storyboards automatically register the cell reuse identifier for you, and if you try to register it again it'll override it and consider it different, meaning you don't see anything. See the FirebaseUI docs on Using Storyboards and Prototype Cells for more info (though looks like I need to add in the prototype bit, so apologies for the confusion).
It seems you forgot to implement init in your custom cell
For FirebaseUI-ios github
Create a custom subclass of UITableViewCell or UICollectionViewCell, with or without the XIB file. Make sure to instantiate -initWithStyle: reuseIdentifier: to instantiate a UITableViewCell or -initWithFrame: to instantiate a UICollectionViewCell. You can then hook the custom class up to the implementation of FirebaseTableViewDataSource.
Related
The problem
When scrolling up and down in my (programmatically) created collectionView the cells doesn't seem to dequeued properly. This is resulting in duplication of it contents.
Video
Bug replication
Wished behaviour
I wish that the cells correctly getting dequeued and that the content does not get duplicated.
Code snippet
Code snippets are provided via Pastebin below. I had to add some code to satisfy the markdown editor here on SO...
open class CollectionDataSource<Provider: CollectionDataProviderProtocol, Cell: UICollectionViewCell>: NSObject, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout where Cell: ConfigurableCell, Provider.T == Cell.T {
https://pastebin.com/CzHYxTDD
class ProductCell: UICollectionViewCell, ConfigurableCell {
}
https://pastebin.com/9Nkr3s4B
If anything else is need, please ask in the comments.
Each time you call
func configure(_ item: ProductViewModel, at indexPath: IndexPath) {
setupProductImage(with: item.productImage)
setupStackView()
setupProductLines(with: item.productLines)
}
You create new instance productLineLabel = UILabel() inside setupProductLines() and add it to the stackView
You should change this behavior or rather clear the stack view in prepareForReuse method.
Keep in mind, that addArrangedSubview increases suviews retain count for newly added elements. If you stop your applications execution using Debug View Hierarchy button (fig 1), most likely you will see more labels than you expect in the cell.
fig 1.
The problem
Each time I call:
func configure(_ item: ProductViewModel, at indexPath: IndexPath) {
setupProductImage(with: item.productImage)
setupStackView()
setupProductLines(with: item.productLines)
}
I create a new instance of productLineLabel = UILabel()
Therefore it will be duplicated each time the configure(_ item:) is being called from the cellForRowAtIndexPath.
The solution
I used prepareForReuse recommended by llb to remove the subviews that were kind of class UIStackview (containing UILabels). I wrote the following extension to make this less tedious:
func addSubviews(with subviews: [UIView], in parent: UIView) {
subviews.forEach { parent.addSubview($0) }
}
The implementation
The only thing what was left to do was calling the custom extension function from prepareForReuse like so:
override func prepareForReuse() {
let foundStackView = subviews.filter({$0.isKind(of: UIStackView.self)})[0] as? UIStackView
guard let labels = foundStackView?.arrangedSubviews.filter({$0.isKind(of: UILabel.self)}) else { return }
foundStackView?.removeArrangedSubviews(labels, shouldRemoveFromSuperview: true)
}
Credits go to llb, see comments below! <3 Thanks.
I'm sorry to be asking yet another "found nil unwrapping optional in TableViewCell" posting, but after hours of debugging and reading other such posts here, I'm still stuck. I think I've covered all of the usual mistakes, but still can't get things to work.
As a starting point, everything works fine as long as I use the default label in the cell, referenced with "cell.textLabel!.text = mystring." And I have another case where I customized the prototype cell with an image and a label, and that worked fine too.
In the current case, I have a normal UIViewController, with some other views and a UITableView embedded. The normal UIViewController is set as the delegate for the cells.
Here's the SettingsCell.swift code. Pretty simple. The little dot on the left of the Outlet in xcode is solid, showing that the outlet is properly connected to the CellTwo label on my prototype table cell.
class SettingsCell: UITableViewCell {
#IBOutlet weak var CellTwo: UILabel!
override init(style: UITableViewCellStyle, reuseIdentifier reuseID: String?) {
super.init(style: style, reuseIdentifier: reuseID)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
In the corresponding SettingsVC.swift view controller, here is the dequeueCell code that takes the nil fault on trying to set the cell.CellTwo value.
The cells display fine in the table view, but only the default textLabel.txt that is part of the UILabel shows up. My custom label does not show at all.
func tableView(
tv: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tv.dequeueReusableCellWithIdentifier("SettingsCell") as! SettingsCell
let row = indexPath.row
cell.textLabel!.text = SettingsNames[row]
// the commented out line below crashes on a nil unwrapped optional
// cell.CellTwo!.text = "Blah"
if let b = cell.CellTwo {
b.text = "Blah"
}
return cell
}
With the little "let b =" optional unwrap nil protection, the app does not crash, but my second label doesn't get set either. Finally, consider registration of the class.
Many examples exist on the net (without using prototypes) where you register the class, and I've done that before successfully. And I've done an example where I built a custom prototype cell, and that one worked fine too. But... I think all those examples were using a UITableViewController, not a normal UIViewController + embedded UITableView. Maybe that has something to do with it.
Anyhow, here is the init / viewDidLoad code in the UIViewController that contains the UITableView. As you can see from the code, the UIViewController claims the datasource and delegate responsibilities, and sure enough, the table rows show up and work fine.
I've tried registration both ways -- without the code registration, and with the code registration line. But it doesn't make any difference at all - my code still crashes on the nil reference to my custom label in the cell.
class SettingsVC: UIViewController, UITableViewDataSource, UITableViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
SettingsTV.dataSource = self
SettingsTV.delegate = self
// I tried with this line in and out, no difference, the crash still happens
//SettingsTV.registerClass(SettingsCell.self, forCellReuseIdentifier: "SettingsCell")
SettingsTV.estimatedRowHeight = 70
}
It's like my custom label doesn't get created or initialized somehow. But I can't explain it. Any ideas? Thanks
The answer to my issue was in the initialization of the SettingsCell shown in the first block of code above. That initialization sequence was copied from an example that worked fine, but that DID NOT USE THE STORYBOARD. Instead, that example just limited itself to using the default textLabel.text fields in the UITableViewCell definition.
To solve the problem, I just kept going back through my examples trying to recreate the problem in examples that already worked. Here's the key.
If you use the storyboard with a prototype cell, the table cell initialization cannot look like the first block in this posting!! That style only works for non-storyboard cells.
Instead, it should look like this one below. Notice that I commented out the default code that xcode inserts into the file when it is created. So you can basically have an empty class for the cell, as long as you have places for the Outlets.
class SettingsCell: UITableViewCell {
#IBOutlet weak var XXXLabel: UILabel!
#IBOutlet weak var CellTwo: UILabel!
// override func awakeFromNib() {
// super.awakeFromNib()
// // Initialization code
// }
//
// override func setSelected(selected: Bool, animated: Bool) {
// super.setSelected(selected, animated: animated)
//
// // Configure the view for the selected state
// }
}
A second thing that showed up was that the two labels on the storyboard were placed at either end of the any-any size storyboard. And my constraints put the right hand label off the edge of the simulated device. I happened to rotate the device in the simulator, and presto! there was the second label. I should use the previewer more regularly. :-)
Have you tried defining your custom cell in a seperate XIB instead of using prototype cells in the tableview? It is pretty much the same as what you did but it just has another XIB file out of the storyboard. To create the XIB:
File -> New File -> iOS -> User Interface ->Empty
Drag a UITableViewCell to the XIB and customize accordingly. Set its class to SettingsCell and wire up the labels, etc... to your SettingsCell class
Then in your VC you register it like this:
let nibName = UINib(nibName: "SettingsCell", bundle:nil)
self.SettingsTV.registerNib(nibName, forCellReuseIdentifier: "SettingsCell")
Another part of the answer is in the contents of the NSCoder required init block shown at the top of this post. That block was copied from a working example that did NOT use a storyboard prototype cell. So I often took a fatal error when I tried that with my prototype tableview cell.
Here is what that block SHOULD (must) look like if you are using a prototype cell. The required NSCoder must call the super.init, like so:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
//fatalError("init(coder:) has not been implemented")
}
It's odd that when xcode auto-fixes the NSCoder issue by injecting a template NSCoder required init? function, it injects one that will break with prototype cells. Why not add the super.init(..) line instead of the fatal error line? Probably there's a reason, but not one that I can see.
Anyhow, all this works for me now, even with a prototype storyboard table view. So I think that's all the questions/issues resolved for this one.
I'm working on iOS app, which needs to pull the data from Firebase and display in a table view. To give it a custom design, I've created a custom table view cell class. When I'm trying to display the data that firebase returns in "populateCellWithBlock" method, the app crushes, displaying this error:
Could not cast value of type 'UITableViewCell' (0x10a73f9f0) to 'EFRideSharing.RideTableViewCell' (0x108117f20).
dataSource = FirebaseTableViewDataSource(ref: ref, cellReuseIdentifier: "rideCell", view: self.ridesTableView)
dataSource.populateCellWithBlock
{ (cell,snapshot) -> Void in
let tvc: RideTableViewCell = cell as! RideTableViewCell
let snapshot: FDataSnapshot = snapshot as! FDataSnapshot
tvc.toLabel.text = snapshot.value["time"] as? String
}
Any ideas how to make it work?
Update: Additional pieces of code
class RideTableViewCell: UITableViewCell {
#IBOutlet weak var timeLabel: UILabel!
#IBOutlet weak var toLabel: UILabel!
#IBOutlet weak var fromLabel: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Author of FirebaseUI-iOS here.
Looks like the issue is that we're returning a UITableViewCell rather than a RideTableViewCell due to the fact that the default class of the cell returned by FirebaseTableViewDataSource is UITableViewCell when the cellClass property isn't set.
You can use the version of the constructor that contains cellClass to solve this problem:
dataSource = FirebaseTableViewDataSource(ref: ref, cellClass: RideTableViewCell.self, cellReuseIdentifier: "rideCell", view: self.ridesTableView)
This will allow us to return the appropriately cast versions of the object to you. Also, to address issues of knowing the type of the cell statically on population, w can do the following.
In Objective-C this is really easy, as we can just give the properties in the block a different type:
[self.dataSource populateCellWithBlock:^(YourCustomCellClass *cell, FDataSnapshot *snap) {
// Populate cell as you see fit
}];
Because we use __kindof UITableViewCell as the argument where available (id when not, so pre XCode 7), this behavior works as intended in any version of XCode, since XCode will either accept "Yes, this class is a subclass of UITableViewCell" (>= XCode 7) or "If it's id I can still send a message to it, so I'll allow it" (<= XCode 6.4).
Swift is a different story. While you'd think that you should be able to do:
dataSource.populateCellWithBlock{ (cell: YourCustomClass, snapshot: FDataSnapshot) -> Void in
// Populate cell as you see fit
}
And get reasonable, pre-cast versions of the cells. That said, I've consistently gotten errors thrown saying that the method signatures don't match comparing AnyObject to YourCustomClass and chalked it up as an Apple issue with __kindof, which is one of the least documented features I know of. If anyone has any thoughts on why this might be the case, or knows of better documentation, I'd love to hear where this is coming from (or if it's fixed in the GM release of XCode 7).
I'm using Firebase-UI-IOS for my iOS app. When i run the app i get the following error:
fatal error: ”unexpectedly found nil while unwrapping an Optional
value”.
Here is my code in ViewDidLoad:
let ref = Firebase(url: "https://XXXXXX.firebaseio.com")
var dataSource: FirebaseCollectionViewDataSource!
let messageRef = ref
.childByAppendingPath("brainstorms")
.childByAppendingPath(invite.brainstormId)
.childByAppendingPath("messages")
self.dataSource = FirebaseCollectionViewDataSource(ref: messageRef, cellClass: MessageCollectionViewCell.self, reuseIdentifier: "messageCell", view: self.collectionView)
self.dataSource.populateCellWithBlock { (cell: UICollectionViewCell, obj: NSObject) -> Void in
// Populate cell as you see fit
if let cell = cell as? MessageCollectionViewCell {
println(cell)
cell.messageText.text = "Hello world" <--- ERROR HERE
}
}
self.collectionView.dataSource = self.dataSource
I've tried to unwrap cell.messageText, but it always returns nil. My cell class MessageCollectionViewCell is registered in my Storyboard.
My println(cell) prints the following line: <xxxxx.MessageCollectionViewCell: 0x7fc26d8d9080; baseClass = UICollectionViewCell; frame = (17.5 155; 340 80); layer = <CALayer: 0x7fc26d8d92e0>>.
This looks to me like it should be working. Reference to populateCellWithBlock can be found here.
Anyone got any suggestions?
Creator of FirebaseUI here.
Looks like the FirebaseUI parts are working fine (our contract will return a populated cell that is a subclass of UICollectionViewCell and an object that is a subclass of NSObject, which appears to be working).
The issue seems to be that in your custom cell hasn't properly initialized the UILabel. Can you please post your MessageCollectionViewCell.swift file?
It should look something like this:
import Foundation
import UIKit
class MessageCollectionViewCell: UICollectionViewCell {
#IBOutlet var mainLabel: UILabel?
override init(frame: CGRect) {
super.init(frame: frame)
// Custom initialization code for label
let size = self.contentView.frame.size
let frame = CGRectMake(0.0, 0.0, size.width, size.height)
self.mainLabel = UILabel(frame: frame)
// Make sure you add the label as a subview
self.contentView.addSubview(self.mainLabel!)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
Once you've got an init(frame:) call, it should populate appropriately.
I tested this out with the following populateCellWithBlock:
dataSource.populateCellWithBlock { (cell: UICollectionViewCell, snap: NSObject) -> Void in
let cell: MessageCollectionViewCell = cell as! MessageCollectionViewCell
cell.mainLabel?.text = "Hello!"
}
I'm working on adding __kindof support to make the signature look more like:
dataSource.populateCellWithBlock { (cell: MessageCollectionViewCell, snap: Message) -> Void in
cell.mainLabel?.text = "Hello!"
}
But support for __kindof seems to be spotty and not really working (XCode 7 Beta 5 claims to have support, but I can't get Cocoapods + Swift to accept it, even though it builds in XCode and works in Objective-C).
Let me know if you've got any other feedback on FirebaseUI, and if you see any other bugs, feel free to add them to the issue tracker :)
I am Learning iOS Development with Big Nerd Ranch's latest iOS book. I have chosen to implement their apps in Swift. In one of their apps, they have the following code in Objective C:
- (UIView *)headerView
{
// If you have not loaded the header view yet...
if (!_headerView) {
// Load HeaderView.xib
[[NSBundle mainBundle] loadNibNamed:#"HeaderView" owner:self options:nil]
}
return _headerView;
}
Apple's Swift guide on "#IBOutlet":
When you declare an outlet in Swift, the compiler automatically converts the type to a weak implicitly unwrapped optional and assigns it an initial value of nil. In effect, the compiler replaces #IBOutlet var name: Type with #IBOutlet weak var name: Type! = nil.
As it was pointed out in Lazy loading Properties in swift, there are a couple of different options. None of them in that post explicitly mention lazy initialization with #IBOutlet, so I've done by best to implement their suggestions, and would like to know what would be considered best practices.
Attempt #1(failed): following a similar pattern, as the example from AppDelegate.swift. This brings the issue "'IBOutlet' attribute requires property to be mutable"
#IBOutlet var headerView : UIView {
// If the HeaderView has not been loaded yet...
if !_headerView {
// Load HeaderView.xib
NSBundle.mainBundle().loadNibNamed("HeaderView", owner: self, options: nil)
}
return _headerView!
}
var _headerView : UIView? = nil
Attempt #2(failed): using any variation of "#lazy" with "#IBOutlet" didn't worked because "#lazy" needs an initializer, but if a closure is used, then "#IBOutlet" has the same issue as from Attempt #1
Attempt #3(successful?): this is the only way I was able to get this to work. I got the idea from a somewhat different question, Lazy property initialization in Swift. My understanding of what is happening is headerView is actually declared as "#IBOutlet weak var headerView : UIView! = nil", will only be initialized once with the TableViewController subclass I have, and that initialization will be "lazy" in that it only occurs when the TableViewController needs to be loaded.
#IBOutlet var headerView : UIView
func loadHeaderView() {
// If the HeaderView has not been loaded yet...
if !headerView {
// Load HeaderView.xib
println("loaded HeaderView")
NSBundle.mainBundle().loadNibNamed("HeaderView", owner: self, options: nil)
}
}
override func viewDidLoad() {
super.viewDidLoad()
loadHeaderView()
tableView.tableHeaderView = headerView
}
So, how can this be improved?
Is viewDidLoad() the correct function to use?
Thanks
A lazily loaded outlet makes no sense- if it's an outlet, it's populated when loading the nib, not from code. If you're loading it from code, it doesn't need to be an outlet, so you can use #lazy.
You aren't actually providing a closure for headerView with that code, you're declaring it as a read-only computed property. #IBOutlet properties need to be mutable so the XIB/Storyboard can do its magic, so you'd need to implement it with both a getter and a setter, like this:
#IBOutlet var headerView : UIView {
get {
if !_headerView {
NSBundle.mainBundle().loadNibNamed("HeaderView", owner: self, options: nil)
}
return _headerView!
}
set {
_headerView = newValue
}
}
var _headerView : UIView? = nil
The following works...
#IBOutlet lazy var headerView : UIView? = {
return NSBundle.mainBundle().loadNibNamed("HeaderView", owner: self, options: nil)[0] as? UIView
}()
then set the headerView
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableHeaderView = headerView
}
You can also use a didSet:
#IBOutlet weak var profileImageView: UIImageView! {
didSet {
profileImageView.image = profileImage
profileImageView.layer.masksToBounds = true
profileImageView.layer.cornerRadius = 16
}
}