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 }()
Related
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.
I'm trying to embed a Table View in a regular View Controller because I want some other Views in the View Controller besides just the Table View. I had a Table View Controller with some methods that I was overriding. I copied the code for the Table View Controller, pasted it into a Table View .swift file, and removed the "override" before each method as the methods weren't inherent to the Table View class. Unfortunately I'm still getting errors in a few lines of the code:
super.viewDidLoad()
"Value of type 'UITableView' has no member 'viewDidLoad'"
tableView.rowHeight = UITableViewAutomaticDimension
"Ambiguous reference to member 'tableView(_:numberOfRowsInSection:)'"
tableView.estimatedRowHeight = 60.0
"Ambiguous reference to member 'tableView(_:numberOfRowsInSection:)'"
super.didReceiveMemoryWarning()
"Value of type 'UITableView' has no member 'didReceiveMemoryWarning'"
tableView.insertRows(at: [indexPath], with: .automatic)
"Ambiguous reference to member 'tableView(_:numberOfRowsInSection:)'"
What do I need to do to those lines of code to make my program run? It fails to build if I leave the code as it is, and if I put two slashes in front of those lines of code, it runs but just creates a black screen. Any help would be greatly appreciated! Thanks!
import UIKit
class TaskListTableView: UITableView {
var tasks:[Task] = taskData
func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 60.0
}
func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tasks.count
}
#IBAction func cancelToLoLFirstTableViewController(_ segue:UIStoryboardSegue) {
}
#IBAction func saveAddTask(_ segue:UIStoryboardSegue) {
if let AddTaskTableViewController = segue.source as? AddTaskTableViewController {
if let task = AddTaskTableViewController.task {
tasks.append(task)
let indexPath = IndexPath(row: tasks.count-1, section: 0)
tableView.insertRows(at: [indexPath], with: .automatic)
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
-> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "TaskCell", for: indexPath)
as! TaskCell
let task = tasks[indexPath.row] as Task
cell.task = task
return cell
}
}
I think you are confused about what the UITableView and UITableViewController classes do.
A UITableView class is the class that draws the table view component you see on your screen, updates it when necessary and interprets your taps and scrolls into something that makes sense in terms of a table. UITableView extends UIView.
The UITableViewController class is designed to manage a UITableView instance, providing data to display and responding to the actions that it generates. It is an extension of the UIViewController class.
UIView and UIViewController are completely different things and perform completely different functions. You cannot cut and paste code from one to the other because they won't understand it.
So my first recommendation is to hit the books. Read up on what UIView and UIViewController's do to get an understanding of their place in the iOS universe and how they relate to each other. Then look at UITableView and UITableViewController.
Secondly, as to your problem of wanting to have a screen with a table view as well as other components. There are multiple ways to do this and the best solution will vary depending on the complexity of the UI you are trying to build and the data and code behind it.
Once you've got your head around how views and controllers work. I would start by building a simple screen with several simple components on it and a single controller behind it. ie. A class you have written that extends UIViewController.
When your happy with this, you have two choices that I typically see:
You can add a UITableView UI component and set your UITableViewController as it's dataSource <UITableViewDataSource> and delegate <UITableViewDelegate>. Then add in the various methods from these protocols to define the data to display and how your controller will respond to your actions on the table view.
A more complex choice you should look at if #1 gets too messy. This choice can result in cleaner code. Instead of adding the UITableView UI component, you add a UIContainerView UI component. UIContainerViews are designed to link to a seperate controller and view. Effectively this means you have two controllers. One for the general UI components and one for the table view.
This is all going to depend on exactly what you are trying to do. But first you need to do some reading.
I am coding an app which has a UITableView. I currently have a segue set up set up for the cells in the table as such:
var selectedRow = Int() //global variable so it can be used in both VCs
import UIKit
class TableViewController: UITableViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "rowSelected", sender: Any?.self)
selectedRow = indexPath.row
}
}
The segue works fine. However, in the swift file that controls the viewController (only being used to change the text of a label) does not work appropriately. Here is the code from that VC:
override func viewDidLoad() {
super.viewDidLoad()
firstLabel.text = infoArray[selectedRow]
print(selectedRow)
}
The infoArray is set up correctly, but the label is not always change to the correct text... printing selectedRow returns inconsistent numbers... if I hit the first cell it will return 0 sometimes, but it also returns 1, 3, 2, etc... It seems random and isn't correctly returning the current int (and therefore the label text isn't set correctly). Why is this?
What you're doing is not the way to pass information from one view controller to another. To pass information, pass the information. Instead of dropping the information in a global, implement prepare(for:sender:), where you can get the segue's destination view controller as it prepares, and set a property of the destination view controller.
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.
Hi I am beginner in Swift programming language and I have problem to implementing two collection view inside of view controller, this is the link of my picture that I want to achieve:
The picture I want to achieve
is it possible? so far I have the codes that doesn't seems work:
class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, UICollectionViewDataSource, UICollectionViewDelegate{
#IBOutlet weak var collectionView: UICollectionView!
#IBOutlet weak var collectionViewTwo: UICollectionView!
var optionOne = ["Gulsah", "Hurrem", "Mihrimah", "Nilufer"]
override func viewDidLoad() {
super.viewDidLoad()
self.resetFilterThumbnails()
self.collectionView.delegate = self
}
//For the collectionView, number of filters in the section
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if (filters == true){
//do all the stuff here for FILTER_CELL
return self.filters.count}
else{
return self.optionOne.count}
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("FILTER_CELL", forIndexPath: indexPath) as FilterThumbnailCell
let cellTwo = collectionView.dequeueReusableCellWithReuseIdentifier("FILTER_CELL_TWO", forIndexPath: indexPath) as FilterThumbnailCell
var filterThumbnail = self.filterThumbnails[indexPath.row]
println("filter cell two")
if (indexPath.item == 0){
//do all the stuff here for FILTER_CELL
if filterThumbnail.filteredThumbnail != nil {
cell.imageView.image = filterThumbnail.filteredThumbnail
} else {
cell.imageView.image = filterThumbnail.originalThumbnail
//filterThumbnail is a class instance
filterThumbnail.generateThumbnail({ (image) -> Void in
if let cell = collectionView.cellForItemAtIndexPath(indexPath) as? FilterThumbnailCell {
cell.imageView.image = image
}
})
}
return cell
}else{
//FILTER_CELL_TWO
var button = cellTwo.viewWithTag(1) as UILabel
button.text = optionOne[indexPath.row]
cellTwo.backgroundColor = UIColor.redColor()
return cellTwo
}
}
}
This is very Possible. You could do this in several ways.
1:
You could implement two contained view controllers. This might be the cleanest since you could handle interaction in separate controllers. In this case you may need to also implement some delegate/protocols to pass information back to the parentViewController.
2:
You could create a separate datasource/delegate class to handle the associated functions for each view controller and assign them in viewDidLoad. This case may also need to have delegates/protocols implemented to pass information from the collectionView dataSource/Delegate class.
Here is an example, taken from the Master-Detail starting class Create your class like this (add conform to the UITableViewDelegate if needed):
import UIKit
class MyDataSource: NSObject, UITableViewDataSource {
var objects = [AnyObject]()
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objects.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as UITableViewCell
let object = objects[indexPath.row] as NSDate
cell.textLabel!.text = object.description
return cell
}
}
Then in your UIViewController you create a variable of type MyDataSource:
var dataSource1 = MyDataSource()
var dataSource2 = MyDataSource()
Inside of viewDidLoad you can assign the dataSource its objects and assign the datasource to the tableviews:
dataSource1.objects = objects1;
dataSource2.objects = objects2;
tableView1.dataSource = dataSource1
tableView2.dataSource = dataSource2
Now each of the tableviews will use a different instance of this DataSource, with its own set of model objects. If you need to further customize you datasource (as it looks like you do) Simply create another data source class and assign it to the appropriate object. If you conform to the delegate of the tableview inside these data source objects, you should implement a standard protocol for the data source, and conform to that protocol inside of your view controller:
Above the datasource object:
protocol DataSourceDelegate{
func didSelectCellAtIndexPath(indexPath:NSIndexPath)
}
Inside of the data source object:
var dataSourceDelegate:DataSourceDelegate?
Inside of the view controller:
dataSource.dataSourceDelegate = self
Make sure to conform to the delegate in the class definition. Then instantiate the method in the view controller:
func didSelectCellAtIndexPath(indexPath:NSIndexPath){
// Your code here
}
Make sure to call the delegate inside the datasource
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
dataSourceDelegate?.didSelectCellAtIndexPath(indexPath)
}
To register cells, if you are using storyboard, just make sure that the cell identifiers are the correct ones, and you will not need to register anything else. If not, you could register the cell inside the dataSource (probably a good idea, since it would keep all the info contained)
3:
You could use one viewController and differentiate between the cases by comparing the collectionView passed into the dataSource/Delegate functions to an outlet connected to the corresponding collectionViews in the view. While this case will not need any delegates implemented, it will fill the viewController up with a lot of if/else statements, perhaps making the code more difficult to maintain/read.