Get UITableViewCell from another UIViewController's UITableView - ios

I have a UITableView in a HomeViewController. This UITableView has one customized UITableViewCell in it.
I want to use the same UITableViewCell in ListViewController by dequeueing it. Below code does not work.
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let homeVC: HomeViewController = storyboard.instantiateViewController(withIdentifier: "HomeViewController") as! HomeViewController
if let homeTableView = homeVC.tableView {
// Above homeVC.tableView is becoming nil.
// Would like to dequeue here.
}

Since I cannot see your HomeViewController code, I am assuming the tableView property is linked from the Interface Builder as #IBOutlet weak var tableView: UITableView!. This means that that tableView is not instantiated until the view actually appears on the screen. To prove this, try placing a breakpoint inside your HomeViewConntrollers viewDidLoad() method, and notice that it is never reached.
To solve your problem, I would add a table view into your ListViewController, and copy the custom table cell from HomeViewController into your table view. Then create a custom UITableViewCell, and in IB assign both cells in HomeViewController and ListViewController as your custom table view cell.

You can create a xib file and a custom UITableViewCell class for the cell you want to reuse in multiple view controllers. Then, in the xib's identity inspector, define the custom type of the cell as your custom class. Then, register the cell class in each view controller by calling register function on the tableView instance in viewDidLoad. Then dequeue your cell by implementing UITableViewDataSource's tableView(_:cellForRowAt:).

Related

Navigate to view controller from Tableview cell inside collectionview

So I have tableview embedded in collectionview.
I have xib for tableview.
When user select a cell of tableview I want to navigate to another view controller.
I tried this method but its not working
let storyboardId = "Login"
let vc = storyboard?.instantiateViewController(withIdentifier: storyboardId)
navigationController?.pushViewController(vc!, animated: true)
But its not working because this viewcontroller in not added to navigation stack.
class DetailCollectionViewCell: UICollectionViewCell, UITableViewDelegate,UITableViewDataSource {
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// let navigationController = UINavigationController()
// let storyboardId = "Login"
// let vc = storyboard?.instantiateViewController(withIdentifier: storyboardId)
// navigationController?.pushViewController(vc!, animated: true)
}
}
How do i solve this problem.
Any help is appreciated.
You have following options
1) Implement tableview datasource and delgate in viewController instead of collection view cell
2) Use Delegate (explained below )
3) Use Closures
4) Use NotificationCenter
You need to create delegate or protocol as collection view cell can't push or present view controller.
Here is simple example (This is not exact code you may need modification)
Create protocol
protocol TableViewInsideCollectionViewDelegate:class {
func cellTaped(data:IndexPath)
}
Inside your collectionview cell add weak property
weak var delegate:TableViewInsideCollectionViewDelegate?
Now in your ViewController class you in cellForItem method of collectionview
you need to set delegate to self
like
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "YourCell", for: indexPath) as! CustomCollectionCell
cell.delegate = self
return cell
and implement delegate method in viewController class and write code to push your view controller from there like self.navigationController.push
Now In Goto Collectionview Cell method
and whenever your tableviewDidSelect called
call delegate method like self.delegate?.cellTaped(data: dataYouWantToPass)
Hope it is helpful
You have to check some info:
First:
Check your navigationController is nil or not
Second:
Check your initial view controller method is correct or not,
this is my way:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "StoryboardIdentifier") as? ViewController
You can solve this problem by using a delegation pattern use the following steps :
Confirm table view delegate to the collection view and collection view delegate to the respective view controller.
delegation chaining can be used to solve this problem. In this example, I have shown how you can pass data from table view cell to collection view cell.
Implement collection view delegate and data source methods.
Implement table view delegate and data source methods.
whenever did select row will get called the call delegate method to tell view controller that some row is selected and according to the row index change handle your navigation.
code example:
Step 1: Create a protocol.
protocol RowSelected : class {
func rowSelected(_ index : Int)
}
Step 2: Declare delegate variable in TableViewCell.
weak var delegate: RowSelected?
Step 3: In collection view confirm delegate and Implement delegated method.
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellIdentifier", for: indexPath) as! CollectionViewCell
cell.delegate = self
return cell
extension CollectionViewCell : RowSelected {
func rowSelected() {
// Pass the information to view controller via a delegate/closure/notification. just like we passed information from table view cell to collection view cell and handle navigation accordingly.
}
}
Step 4: In ViewController you can confirm a delegate for collection view and implement it's delegate method and can handle navigation.
You can use a closure and notification center as well to inform view controller to navigate to next screen.

How to create outlet from collection cell xib file to collection view controller

I have custom Collection View Cell and .swift and .xib file for it. In the .xib file I have textField which I have to take the data from in Collection ViewController and act accordingly but I am not sure how because the .xib file has the custom class of the Collection View Cell and I cannot create outlet to the Collection ViewController file so I can refer to it.
And if I create an outlet in Collection View Cell I can't refer to it in Collection ViewController.
You can't do that.
You can't create an outlet that references a UITextField placed inside a custom CollectionViewCell in your CollectionViewController.
What you can do is create an outlet that references the UITextField in your .xib in the respective CollectionViewCell .swift class (1), create an outlet that references your collectionView in the CollectionViewController (2), and use these CollectionViewCells in your collectionView (3).
You can then access the UITextField of a specific CollectionViewCell in your CollectionViewController through the cell itself (3), like for instance with:
let indexPath = IndexPath(row: 2, section: 0)
let cell = self.collectionView.cellForItemAtIndexPath(indexPath) as! CollectionViewCell // so the compiler knows that the cell you are referring to is of type CollectionViewCell and thus has your custom textField property
let textInTextField = cell.textField.text
(1)
(2)
(3)
As far as I could understand, you need a way to access the CollectionViewController data from your CollectionViewCell, if this is the case you can try the following:
extension UIView {
var parentViewController: UIViewController? {
var parentResponder: UIResponder? = self
while parentResponder != nil {
parentResponder = parentResponder!.next
if let viewController = parentResponder as? UIViewController {
return viewController
}
}
return nil
}
}
//In the CollectionViewCell class
if let controller = self.parentViewController as? YourViewController
{
controller.action()
}
Through this extension you can access the parent controller of your cell and execute functions or obtain data as needed
An example:
https://github.com/AngelH2/CollectionViewCell-Comunication/tree/master/CollectionCellAction

Having more than one storyboard in Xcode

The problem: Created a view controller in a xib with a table view. But, the table view acts very weirdly and doesn't look like the table view we use in the storyboard.
My plan was to populate this table view with a custom table view cell which I have created in another xib file. But, sadly, it doesn't work as I have expected because everything was off and those cells are instantiated, I know my custom cells work because it has worked in my other view controller that was created in the storyboard:
I wanted a way to design my view controllers so I can just instantiate them when I need, my reasoning behind this is I don't want to have a very populated storyboard. Now I know that I can't use a table view in a xib file like how we use it in a storyboard. Is there a work around to this? do I need another storyboard to achieve this?
Task 1. Load UIViewcontroller form Xib
We can load a UIViewcontroller form Xib instead of Storyboard. We can use following procedure:
1. Create a UIViewcontroller.
XCode File -> New -> File -> Cocoa Touch Class -> Fill Class with your class name , subclass of with UIViewController , check Also create Xib file, language Swift -> Next - Create.
Example: ViewControllerFromXib
2. Override init().
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
{
super.init(nibName: "ViewControllerFromXib", bundle: Bundle.main)
}
required init?(coder aDecoder: NSCoder)
{
super.init(coder:aDecoder)
}
3. Open newly created Controller
let controller = ViewControllerFromXib.init()
self.present(controller, animated: true, completion: nil)
In this above way, We can load a UIViewcontroller from XIB.
Task 2. Create a tableview & populate it's cell using custom xib
1. Create a Custom UItableViewCell
XCode File -> New -> File -> Cocoa Touch Class -> Fill Class with your cell name , subclass of with TableViewCell , check Also create Xib file, language Swift -> Next - Create.
Example: CustomTableViewCell
1.Register UItableViewCell for Your TableView.
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Item"
self.tableView.register(UINib(nibName: "CustomTableViewCell", bundle:Bundle.main), forCellReuseIdentifier: "CustomTableViewCell");
}
2. Implement UITableViewDataSource into Your Viewcontroller
extension ViewControllerFromXib:UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3;
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomTableViewCell", for: indexPath) as! CustomTableViewCell
return cell
}
}
2. Implement UITableViewDelegate into Your Viewcontroller.
extension ViewControllerFromXib:UITableViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat{
return UITableViewAutomaticDimension;
}
}
Yes, you can use as many storyboards as you would like in an application.
I try to setup 1 storyboard per workflow. However some less storyboard enthusiastic developers use 1 storyboard per view controller.
To get the initial view controller from a storyboard named "MyStoryboard":
let storyboard = UIStoryboard(name: "MyStoryboard", bundle: nil)
let viewController = storyboard.instantiateInitialViewController()!
or to get a view controller with the identifier "MyViewController".
let storyboard = UIStoryboard(name: "MyStoryboard", bundle: nil)
let viewController = storyboard.instantiateViewController(withIdentifier: "MyViewController")!
There are two basic ways for a view controller to access a second view controller in another storyboard.
In the storyboard, use a Storyboard Reference to the second view controller, then use a show segue to push the second view controller.
In the view controller's source, create the second view controller using instantiateInitialViewController() or instantiateViewController(withIdentifier:) and push it onto the navigation controller.
You can have many storyboards in the same project.
I usually like to have 1 storyboard per "main" screen, which is easy to instantiate programmatically, and can be linked in the IB as well.
As for your problem, i'd suggest your custom uitableviewcell be created in a .xib file. As an independent view. This way on your code you can just register it as the "reusable cell" for any view controller you want regardless of the storyboard that contains it.

Step by step--how to add tableview to nib

I'm working with nib files for the first time and trying to add a tableview with a tableview cell. I created a nib file of type UIView controller, then I dragged the tableview onto the view, and in the viewcontroller.swift I added all of the necessary delegate, datasource, cellForRowatIndexPath, numberOfRowsinSection, etc, just like normal. But the app crashes on loading. I have looked at several other questions, notably these:
Custom UITableViewCell from nib in Swift
Can't make UiTableView work
But those solutions did not work for me completely and it still crashes on loading. Another error message I've gotten has been "this class is not key value compliant."
So, what are the exact steps to make a uitableview in a nib file? From what I understand:
File--> New-->File-->Cocoa Touch Class-->Subclass UITableViewController. this sets up the table view. we will call this View1.swift
File-->New-->File-->Cocoa Touch Class-->Subclass UITableViewCell. this sets up the cell in the tableview. we'll call this View1TableCell.swift
in View1.swift, register the nib:
tableView.registerNib(UINib(nibName: "View1", bundle: nil), forCellReuseIdentifier: "View1CellID")
Give the cell a reuse identifier. We will say this is "View1CellID"
in View1.swift, in cellforRowAtIndexPath, dequeue the cell with the correct cell identifier.
So, all these steps should work so that I can add any label or button to my View1TableCell nib, and those changes will be seen on the tableview when I build and run, correct? What am I doing wrong?
The nib you register should be the one containing the cell, not the view controller. From Apple docs:
Parameters
A nib object that specifies the nib file to use to
create the cell. This parameter cannot be nil.
So the view that holds your table is View1, from your explanation.
If you want to implement a custom cell, you need to create a new class, to extend UITableViewCell. That class should also have a nib
So create a new class
class MyCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
setupCell()
}
func setupCell() {
//setup your ui here
}
}
Now in View1.swift, in viewDidLoad, register the nib
func viewDidLoad() {
super.viewDidLoad()
//register the nib of the cell for your cell
tableView.registerNib(UINib(nibName: "MyCell", bundle: nil), forCellReuseIdentifier: "MyCellIdentifier")
}
now use the cell in your tableview
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("MyCellIdentifier") as! MyCell
//set the stuff you need in your cell
return cell
}

Adding Child View Controller view to dynamic UITableViewCell

How can I add a view of a child view controller to a custom UITableViewCell? I can add the view like this inside cellForRowAtIndexPath:
self.addChildViewController(controlsViewController)
cell!.cellView.addSubview(controlsViewController.view)
controlsViewController.didMoveToParentViewController(self)
But when the cell disappears, I need to remove this child view controller. I'm not really sure how to do that. Is there a better way to go about this?
Do it via delegation. I have done on collection view ,you can do it on tableview too. follow the below steps
1 .In your custom cell class create a delegateHandler and override your awakeFromNib() method. eg
protocol BibleReadingSliderProtocol: class {
func addThisViewControllerAsChild(audioViewController :AudioViewController)
}
class BibleReadingSliderCollectionCell: UICollectionViewCell {
#IBOutlet weak var containerView: UIView!
var audioVC = AudioViewController()
weak var bibleReadingSliderDelegate:BibleReadingSliderProtocol?
override func awakeFromNib() {
super.awakeFromNib()
print("Awake call from cell")
// Initialization code
let storyboard = UIStoryboard(name: "Main", bundle: nil)
audioVC = storyboard.instantiateViewController(withIdentifier: "AudioViewController") as! AudioViewController
audioVC.view.frame = self.containerView.bounds
self.containerView.addSubview(audioVC.view)
if self.bibleReadingSliderDelegate != nil {
self.bibleReadingSliderDelegate?.addThisViewControllerAsChild(audioViewController: audioVC)
}
}
}
In your ViewController where you are using this custome cell (either tableview or collection view) define the delegate hander
func addThisViewControllerAsChild(audioViewController: AudioViewController) {
self.addChildViewController(audioViewController);
}
And Dont forget to set your delegate to this viewcontroller in cellForItemAt/cellForRowAt
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let imageSliderCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! BibleReadingSliderCollectionCell
imageSliderCollectionViewCell.bibleReadingSliderDelegate = self
return imageSliderCollectionViewCell
}
Don't misunderstand MVC. Not every view in the world needs to have its own personal view controller! A main view has a view controller, but a button in that main view does not have its own personal view controller; it simply talks to the main view's view controller.
The same is true of this view. Views can come and go very easily; do not add the heavyweight burden of an additional view controller when you don't need to! Just grab the view (somehow) and stick it into the cell's contentView or remove it from the cell's contentView in cellForRowAtIndexPath:, just like any other view - but manage it using your table view controller or table view data source / delegate or whatever is in charge here. Don't add an extra view controller to the story just for the sake of this one little view. That's likely to be a bad use of view controllers.

Resources