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.
Related
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.
Here is my problem:
I have a MainTableViewController with an outlet for a table that uses custom UITableViewCells. I also have an outlet for a UIView, called BlackView, in MainTableViewController.
What I want to do: Inside myCustomCell I would like to set "BlackView.hidden = false". I am trying to use "class func" in my MainTableViewController file, and call it from myCustomCell, but it is not working, because Xcode stops recognizing BlackView when I put the word "class" before "func".
So, I would like to call a function of a MainTableViewController or access its outlet from the .swift of my .xib file.
Does anybody knows how to do that?
Here is my .xib file:
My .xib file
Here is the .swift for my .xib file:
class myCustomCell: UITableViewCell {
#IBOutlet weak var commentTextView: UITextView!
override func awakeFromNib() {
commentTextView.delegate = self
super.awakeFromNib()
}
func textViewDidBeginEditing(textView: UITextView) {
MainTableViewController.hideBlackView(true)
}
func textViewDidEndEditing(textView: UITextView) {
var comment = commentTextView.text
}
}
Here is my MainTableViewController:
class MainTableViewController: UIViewController
#IBOutlet weak var MyTable: UITableView!
#IBOutlet weak var BlackView: UIView!
override func viewDidLoad() {
BlackView.hidden = true;
MyTable.registerNib(UINib(nibName: "myCustomCell", bundle: nil), forCellReuseIdentifier: "myCustomCellID")
}
class func hideBlackView(setToHidden: Bool) {
if setToHidden == true {
BlackView.hidden = true
} else {
BlackView.hidden = false
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("myCustomCellID") as! PublishHeaderTableViewCell
cell.selectionStyle = UITableViewCellSelectionStyle.None
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
}
Here is my Main.storyboard:
My Main.storyboard
The Answer is Delegation
BlackView is an instance which will be created by the OS. The outlet is a special property (called an outlet) referencing that instance. When MainTableViewController is shown, an instance of it is created by the OS.
You probably want to use an instance method, not a class method to change the hidden property on instance of BlackView. To do that you will need to pass a reference of the MainTableViewController instance to myCustomCell. This is called delegation, which is how ios programming and most MVC models work.
To do this add define a delegate protocol (just above the definition for the custom cell would be normal) and add a weak var to the cell of this type:
// use a class protocol for delegates so weak properties can be used
protocol MyCustomCellDelegate: class {
func hideBlackView(setToHidden: Bool)
}
class MyCustomCell: UITableViewCell {
#IBOutlet weak var commentTextView: UITextView!
weak var delegate: MyCustomCellDelegate?
override func awakeFromNib() {
commentTextView.delegate = self
super.awakeFromNib()
}
func textViewDidBeginEditing(textView: UITextView) {
delegate?.hideBlackView(true)
}
func textViewDidEndEditing(textView: UITextView) {
var comment = commentTextView.text
}
}
Then when you are setting up the cells in cellForRowAtIndexPath, cast as the the proper cell type which should be MyCustomCell in the example you've given not PublishHeaderTableViewCell (also note that I've switched your custom cell class name to starting with a capital letter as is industry standard in ios development). Finally, set the delegate to the instance of the MainTableViewController (which is called "self" from within instance functions).
BTW, in your case, you are only using one cell, so you probably don't need to dequeue and reuse cells. You could just take all that out and return a simple instance you created of the cell in the cellForRowAtIndexPath method. Anyway, I will leave all that in place in case you have just simplified your code for Stack Overflow.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// you need to cast the cell to your custom class to use it
let cell = tableView.dequeueReusableCellWithIdentifier("myCustomCellID") as! MyCustomCell
cell.selectionStyle = UITableViewCellSelectionStyle.None
// set the delegate
cell.delegate = self
return cell
}
Finally, and VERY IMPORTANTLY, you need to declare that MainTableViewController conforms to the the protocols that will use it so the functions (methods) that other objects want to delegate to it will succeed. In your case it needs to conform to both MyCustomCellDelegate which we wrote above, but also since you are using it for the for the tableView's data source (for cellForRowAtIndexPath and numberOfRowsInSection) you need to declare that it conforms to UITableViewDataSource (You may have done this already through Interface Builder (story board).. if not you can do it in the class definition).
// Declare objects conform to protocols by including protocol names separated by commas after the colon (or the class inherited from)
class MainTableViewController: UIViewController, MyCustomCellDelegate, UITableViewDataSource {
#IBOutlet weak var MyTable: UITableView!
#IBOutlet weak var BlackView: UIView!
override func viewDidLoad() {
BlackView.hidden = true
MyTable.registerNib(UINib(nibName: "myCustomCell", bundle: nil), forCellReuseIdentifier: "myCustomCellID")
}
func hideBlackView(setToHidden: Bool) {
// since they are both bools just set BlackView.hidden to the setToHidden parameter directly
BlackView.hidden = setToHidden
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("myCustomCellID") as! MyCustomCell
cell.selectionStyle = UITableViewCellSelectionStyle.None
// set the delegate
cell.delegate = self
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
}
As a final note, I'm not sure is setting the delegate for the UITextView is a appropriate in the awakeFromNib method in your custom cell. I know that this method doesn't always fire.. In your case since it is on an outlet, I think it is OK, but I don't use XIB files very much my self, so you may want to print to the console to make sure it is being called each time or research the issue more.
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 }()
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.
I created a TableView directly in the storyboard; this tableView contains 8 static cells (style basic) in 4 section; now, how could I edit via code these cells? for example for to change textLabel, backgroundColor, separator ecc
I tried to set an identifier to each cel but didn't work...
For static cells created in the storyboard, you can simply set the IBOutlet for the elements you want to edit by ctrl-dragging from the storyboard to the corresponding view controller, to end up with something like this:
class MyViewController: UIViewController {
#IBOutlet weak var cell1: UITableViewCell!
#IBOutlet weak var cell2: UITableViewCell!
}
Then you can access the built-in elements in the Basic view with cell1.textLabel etc.
Check out the documentation about setting IBOutlets.
To change the background color, you can do it in the storyboard UI directly or access the backgroundColor property. You might want to read the UITableViewCell Class Reference.
Thanks p4sh4 for #IBOutlet suggestion.
But instead of multiple Outlets, you should try an "Outlet Collection".
#IBOutlet var tableLabelCollection: [UILabel]!
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
print("accessory Button Tapped: \(indexPath.row) => \(tableLabelCollection[indexPath.row].text)")
}