Reusing header sections in tableview - swift

I've been searching the whole WWW for a decent solution, but most seem outdated or insufficient.
I'm struggling in simply reusing a custom UITableViewHeaderFooterView in my UITableViewController.
This is my approach:
Create a nib.
Create a custom UITableViewHeaderFooterView, named HeaderView
Add a label and connect it to HeaderView
Register the nib in UITableViewController's viewDidLoad()
Dequeue and return the nib in tableView(_ tableView: UITableView, viewForHeaderInSection section: Int)
Custom HeaderView class:
class HeaderView: UITableViewHeaderFooterView {
#IBOutlet weak var customLabel: UILabel!
}
In UITableViewController class:
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(UINib(nibName: "HeaderView", bundle: nil), forHeaderFooterViewReuseIdentifier: "Header")
}
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = tableView.dequeueReusableHeaderFooterView(withIdentifier: "Header") as! HeaderView
headerView.customLabel.text = "This is a header section"
return headerView
}
This seems to be the approach advised by many, but I believe I'm unable to properly set the nib's owner custom class to HeaderView (UITableViewHeaderFooterView). When the cell is dequeued I get a fatal crash:
*** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[<NSObject 0x60000020ebe0> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key customLabel.'
I'm definitely sure I've connected the UILabel correctly to File's Owner in nib IB.
I tried changing the HeaderView class to UIView, but then dequeueReusableHeaderFooterView(withIdentifier: "Header") as! HeaderView would result in a crash when forcing UITableViewHeaderFooterView to UIView.
Why is this, and what is the current way in solving this?

I found the issue was that I set the nib - File's owner to HeaderView and connected my customLabel to that.
The solution was to set the nib's view class to HeaderView and connect the label to that instead. It was also oddly necessary to put it inside a UIView.

Related

Swift - Unable to add data to UITableView

Learning Swift and Storyboards, I'm attempting to create a View of repeating cells (UITableView).
So far I have created a view with a UIView, linked to a UITableView with a UITableViewCell inside. The issue I'm having is my cells are not displaying "woof" as per below.
My View Controller looks like this:
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var myTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellId") as! MyListViewCell
cell.myLabel.text = "woof"
return cell
}
}
After some research, I believe I don't need to register:
myTableView.register(UITableViewCell.self, forCellReuseIdentifier: "cellId")
as it's already linked as an outlet successfully
but I thought I may need to do delegate and datasource like so:
myTableView.datasource = self
myTableView.delegate = self
but I received: Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
I am attempting to use MVVM which I think is meant to only use the one ViewController. Am I meant to be using a UITableViewController instead?
I have verified that my cell identifier is set correctly in the storyboard.
This error in your case can be related to the missing connection between UITableView in a storyboard and your IBOutlet myTableView.
Concerning the UITableView.register(_:forCellIdentifier:) method, when you are creating a cell in UITableView in a storyboard then this method is called by UIKit when loading the storyboard. You should call this method when you have created a custom UITableViewCell subclass in code or Xib file.
Setting delegate and dataSourceDelegate can be done in a storyboard file without creating an IBOutlet in a view controller. Just select Table View and go to the connections inspector and drag a delegate and a datasource delegate to the view controller.
Firstly, check if you have connected properly on storyboard(Outlets,identifier everything).
Secondly, if nothing works try disconnecting and connecting the Outlets again.

UItableView Section Header View with Xib Error - Swift

I try to implement Section Header with Xib File. I create a Xib View and attend UITableViewHeaderFooterView to this view . When I run the App , it gives me error without any description. Where is the problem ?
Thanks in advance.
tableView.register(UINib(nibName: "EventViewCell", bundle: nil), forCellReuseIdentifier: "EventViewCell")
tableView.register(UINib(nibName: "EventCellSectionHeader", bundle: nil), forHeaderFooterViewReuseIdentifier: "EventCellSectionHeader")
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableHeaderFooterView(withIdentifier: "EventCellSectionHeader") as! EventCellSectionHeader
cell.headerName.text = "aa"
return cell
}
class EventCellSectionHeader: UITableViewHeaderFooterView {
#IBOutlet var headerName: UILabel!
}
libc++abi.dylib: terminating with uncaught exception of type NSException
Xcode 10 SWIFT 4
implement your viewForHeaderInSection as below:
//EventCellHeader is the name of your xib file. (EventCellHeader.xib)
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let header = Bundle.main.loadNibNamed("EventCellHeader", owner: self, options: nil)?.last as! EventCellSectionHeader
header.headerName.text = "aa"
return header
}
I tested it: (if you need more help let me know to send you the whole project)

Reusing XIB views for collectionviews as a partial for other views?

I'm wanting to reuse one XIB file for collection views, and other views.
For example, I have a card that needs to be shown as a collection view cell, but I also would like to use the same xib/view for a standalone page.
ie;
I would like to use the same card image for collection views and common views, almost like as if it were a partial; and can therefore use it wherever I need it.
I register my cell like this;
self.myCollectionView.register(UINib(nibName: "MyCollectionViewCell", bundle: nil), forCellWithReuseIdentifier: "myCellIdentifier")
Then use it;
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell:MyCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: "myCellIdentifier", for: indexPath) as! MyCollectionViewCell
...
}
I've tried to cast the dequeueReusableCell code with a UIView I created, but it doesn't like the view to be used in this way.
Both the cell and my XIB view file is a standalone swift file so its not in storyboard.
How can I use one uiview for collection views and then reuse the same view on other pages?
Many thanks
Summary
I want to use one UIView and XIB file in many places in the app
Currently, I have a collection view that uses a UICollectionViewCell and associated XIB file
I want to use the same XIB file in many places in the app.
How do I reuse the same UIView in both my collection view and the places in the app where I need it?
Thanks
(a) I have a swift file which I created; it a custom subclassed UICollectionViewCell. It has a XIB file.
Register the nib with the collection view. In cellForItemAt, when you need a cell, dequeue it. You will receive a copy of the collection view cell from the nib. Configure it as desired. Return it.
(b) I want to reuse the same XIB elsewhere in the app, including all outlets and connections
Load the nib and pull out the first (and only) top-level view. You will receive a copy of the collection view cell from the nib. Add it as a subview to a view in the interface.
(The easiest way to do that is through UINib and its instantiate method. This returns an array of the top-level objects.)
True, one does not usually use a collection view cell as a subview of an ordinary view. But I cannot think of any reason against it; after all, a view is a view is a view.
(If you would prefer not to do that, I can think of workarounds; for example, make the view in the nib a custom UIView and configure each cell by loading the nib and putting that view inside the cell. This will introduce some inconveniences, but it will make the nib more universally usable.)
I made a quick example. Here's the nib, very simple because it's only an example:
Here's my app, showing a collection view (showing four cells on a blue background) and also the view from the nib extracted and stuck into the interface:
Here's the entire code:
class MyView : UIView {
#IBOutlet var iv : UIImageView?
}
class MyCell : UICollectionViewCell {
var v : MyView?
}
class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate {
#IBOutlet weak var cv: UICollectionView!
override func viewDidLoad() {
super.viewDidLoad()
self.cv.register(MyCell.self, forCellWithReuseIdentifier: "cell")
let arr = UINib(nibName: "View", bundle: nil).instantiate(withOwner: nil, options: nil)
let v = arr[0] as! UIView
self.view.addSubview(v)
v.frame.origin = CGPoint(x:20, y:300)
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCell
if cell.v == nil {
let arr = UINib(nibName: "View", bundle: nil).instantiate(withOwner: nil, options: nil)
let v = arr[0] as! MyView
cell.contentView.addSubview(v)
cell.v = v
}
print(cell.v?.iv) // image view
return cell
}
}
Note the use of subclasses and outlets. cell.v takes us to the view, so cell.v?.iv takes us to the image view — and now we could perform further configuration, as desired. Thus we have one level of indirection added in order to reach a subview from the nib, but this seems a very small price to pay.

Separate UIViewController in same Storyboard

Issue
I have a single page app with a single view controller. On the screen there is a button that slides out/in a (smaller) UIView with a TableView (functions correctly). My goal is to simplify my view controller, hence my idea was to split off the UIView with the TableView into its own view controller. Therefore I've created a second view controller in the Storyboard and created a class HintsViewTableViewController, that contains the TableView datasource and delegate methods.
Main View controller
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var HintsViewTableVC = HintsViewTableViewController()
HintsViewTableViewController
class HintsViewTableViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var HintsViewTableView: UITableView!
#IBOutlet var hintsLabel: UILabel!
override func viewDidLoad() {
hintsLabel.text = "HINT" <---fatal error: unexpectedly found nil while unwrapping an Optional value
}
// MARK: - Table view data source
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 4
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return CGFloat (40)
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
// Configure the cell...
cell.textLabel!.text = "\(indexPath.row)"
return cell
}
}
Problem
If the storyboard entry point is the Main View Controller, the compiler gives an error stating that my property hintsLabel! is nil and crashes.
If I move the storyboard entry point directly to the HintsViewTableViewController, then the app runs and shows the correct view on screen.
Question
Apparently, the procedure to initialize a view controller directly (using the storyboard entry point) is different from assigning the view controller to a variable (as I do in the first case). I've searched high and low for init methods, but have come up blank.
Another solution I've tried: making a separate XIB file and linking this to my HintsViewTableViewController, however TableViews in XIB files can't have prototype cells.
What am I missing here, or stated differently: what's the correct procedure to separate a UIView into a separate view controller (in the same Storyboard)?
The problem is that when you instantiate your HintsViewTableViewController like this:
var HintsViewTableVC = HintsViewTableViewController()
you are creating an instance of the class, BUT that class knows nothing about your Storyboard, so all of the #IBOutlets will be nil because they aren't wired to anything.
Instead, you need to ask the Storyboard to create the ViewController:
self.storyboard?.instantiateViewControllerWithIdentifier("hintsController") as! HintsViewTableViewController
where hintsController is the Storyboard ID you have set for that ViewController in the Identity Inspector.
Note: You will need to make this call to the Storyboard in a method (such as viewDidLoad where self will refer to an instance of your ViewController class.
If you want to declare it as a property like you were doing before, making it a lazy property will allow it to be created when first accessed (and self will be available then):
lazy var hintsViewTableVC: HintsViewTableViewController = { self.storyboard?.instantiateViewControllerWithIdentifier("hintsController") as! HintsViewTableViewController }()

UICollectionViewCell register class fails, but register nib works

Creating a custom UICollectionViewCell for my UICollectionViewController, it works by registering the nib; however, fails to register by class.
Using registerClass() - fails
Registering by class seems correct - it builds, but throws an exception at runtime unwrapping optional elements from the UIView. Without referencing outlets, it runs; however, no cells appear within the collection view.
collectionView?.registerClass(MyCollectionViewCell.self,
forCellWithReuseIdentifier: "myCell")
Clearly the view is not loaded; however, I'd think setting the custom class of the cell would provide necessary linkage.
Using registerNib() - works
Registering by nib works, which makes sense as it is explicitly loading the view.
let nib = UINib(nibName: "MyCollectionViewCell", bundle: nil)
collectionView?.registerNib(nib, forCellWithReuseIdentifier: "myCell")
Here the view is explicitly referenced, and custom class is set for the view; however, something about this seems wrong.
Sample project
Isolating the issue in a sample project, below are views and code to replicate; or, this project is available at GitHub.
Main.storyboard
My main storyboard initial view controller is a UICollectionViewController subclassed as MyCollectionViewController:
MyCollectionViewController.swift
Showing both registering by nib, and by class:
import UIKit
class MyCollectionViewController: UICollectionViewController {
var data = [String]()
override func viewDidLoad() {
super.viewDidLoad()
data = ["a", "b", "c"]
// This works, by nib
let nib = UINib(nibName: "MyCollectionViewCell", bundle: nil)
collectionView?.registerNib(nib, forCellWithReuseIdentifier: "myCell")
// This fails, by class
//collectionView?.registerClass(MyCollectionViewCell.self, forCellWithReuseIdentifier: "myCell")
}
override func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return data.count
}
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("myCell", forIndexPath: indexPath) as! MyCollectionViewCell
cell.title.text = data[indexPath.row]
return cell
}
}
MyCollectionViewCell.xib
View is a UICollectionViewCell subclassed as MyCollectionViewCell with a UILabel:
In my nib the Collection Reusable View Identifier has been set to: myCell:
MyCollectionViewCell.swift
Defines the class, and has an IBOutlet to the label:
import UIKit
class MyCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var title: UILabel!
}
Using the nib, execution appears as:
Why can't I register UICollectionViewCell by class?
As well, I'm a little fuzzy as to whether the prototype cell needs to remain on the main storyboard collection view controller. There, I have not defined any reusable view identifier.
I see this link Overview Collection View
If the cell class was written in code, the registration is performed using the registerClass: method of UICollectionView. For example:
[self.myCollectionView registerClass:[MyCollectionViewCell class] forCellWithReuseIdentifier:#"MYCELL"];
In the event that the cell is contained within an Interface Builder NIB file, the registerNib: method is used instead.
So your collection cell create with nib, you should register with nib. If your cell written totally in code you will need register with class.
Hope this help.
Actually if you register the class in the storyboard and give it a reuse identifier there, then you shouldn't be registering it's class or it's nib in code.
It is not collectionview that is failing here. You custom class contains label which is implicitly unwrapped optional defined as this,
#IBOutlet weak var title: UILabel!
And that is the reason for failure. Where do you instantiate it ? And your datasource methods gets called which is like this,
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("myCell", forIndexPath: indexPath) as! MyCollectionViewCell
cell.title.text = data[indexPath.row]
return cell
}
There you are trying to set text to this property title which is nil, which crashes your app.
Initialize your label inside collectionView initWithFrame: method if you use it in code that should should be fixed.
Add this code to your cell subclass when using in code,
class MyCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var title: UILabel!
override init(frame: CGRect) {
super.init(frame: frame)
let title = UILabel(frame: CGRectZero)
title.translatesAutoresizingMaskIntoConstraints = false;
contentView.addSubview(title)
self.title = title
title.topAnchor.constraintEqualToAnchor(contentView.topAnchor).active = true
title.leftAnchor.constraintEqualToAnchor(contentView.leftAnchor).active = true
self.backgroundColor = UIColor.redColor()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
if you define cell from a nib file,system will choose nib file to instantiate the cell. If not define any nib file(totally maintain by code), the cell should be init by the
- initWithStyle:reuseIdentifier:
method.
When you use
- dequeueReusableCellWithIdentifier:
the method - awakeFromNib will be called if you use nib to maintain the view;
else
- initWithStyle:reuseIdentifier:
will be called.