Adding Child View Controller view to dynamic UITableViewCell - ios

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.

Related

Table View in Xib file doesn't load custom table view cell properly

I have a Xib with a table view in it (I'm using a Xib and not a regular storyboard because I'm inserting the Xib into a UIPageViewController). In the Xib's class, I register a custom table view cell. Though the cell inserts into the table view, it doesn't resize properly based on it's constraints nor does it change the display of the table view.
Here's my xib:
Here's my custom cell:
And here's the setup code I have in the view controller that has the table view Xib:class
YearbookPageViewController: UIViewController {
var signatures: [Signature] = []
var pageNumber: Int = 0
convenience init(signatures: [Signature], pageNumber: Int) {
self.init(nibName: "YearbookPageViewController", bundle: nil)
self.signatures = signatures
self.pageNumber = pageNumber
}
// MARK: Properties
#IBOutlet var pageTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
self.pageTableView.delegate = self
self.pageTableView.dataSource = self
let nib = UINib(nibName: "SignatureTableViewCell", bundle: nil)
self.pageTableView.register(nib, forCellReuseIdentifier: SignatureTableViewCell.identifier)
}
}
When I run the app, this is what it looks like:
So basically the custom cell is added to the table view from the xib file, but they aren't sized properly even though the cell has constraints on everything? And, the rest of the tableview is still white with table view lines? I'm not sure if the table view isn't responding correctly because it's in a Xib or if I'm missing something that I'm supposed to add.
Would appreciate any pointers or help on how to get things displaying properly!
By default, tableView displays default height of UITableViewCell. If we want custom height, there can be two ways.
1. Using heightForRow method
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 200
}
2. Using AutoLayout
In this case, your tableView cell must apply all required constraints that completes a top to bottom constraint ladder, so that autoLayout can calculate the height of cell at RunTime.
For the second problem, you can remove extra separator. by executing following code in viewDidLoad
tableView.tableFooterView = UIView()
You should use this;
tableView.separatorStyle = .none
for the empty tableView view.

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.

Properly segue from collectionview inside tableviewcell to another viewcontroller

I am trying to segue (pass data) from a collectionview inside a tableviewcell to a another viewcontroller. I tried using the didselect delegate but stuck on how to pass the data properly. It seems i kind of somehow hack my way around it but i would like to learn the proper way. Below is my code:
My Main view controller:
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "segue" {
let vc = segue.destination as! DetailViewController
vc.text = "Hello World"
}
}
func segue() {
self.performSegue(withIdentifier: "segue", sender: self)
}
}
My Table View:
import UIKit
class MainTableView: UITableView, UITableViewDelegate, UITableViewDataSource {
override func awakeFromNib() {
self.delegate = self
self.dataSource = self
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MainTableViewCell", for: indexPath) as! MainTableViewCell
return cell
}
}
My Collection View:
import UIKit
class MoviesCollectionView: UICollectionView, UICollectionViewDelegate, UICollectionViewDataSource {
override func awakeFromNib() {
self.delegate = self
self.dataSource = self
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 10
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MoviesCollectionViewCell", for: indexPath) as! MoviesCollectionViewCell
return cell
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
let vc = ViewController()
vc.segue()
}
}
The View controller I am trying to segue to:
class DetailViewController: UIViewController {
#IBOutlet weak var label: UILabel!
var text: String?
override func viewDidLoad() {
super.viewDidLoad()
self.label.text = text
// Do any additional setup after loading the view.
}
}
My tableviewcell and collectionviewcell are empty at the moment.
Your question is a little hard to answer as currently stated because it's not entirely clear what you are doing. For example, it's not clear if you are using Storyboards or not.
If you are, then you probably want to define your segues in the storyboard and let UIKit invoke them for you. This documentation provides the overview that will hopefully help you. In particular, you don't have to do the segue manually because UIKit will do it for you once you've set it up in the Storyboard (emphasis mine):
You do not need to trigger segues programmatically. At runtime, UIKit
loads the segues associated with a view controller and connects them
to the corresponding elements. When the user interacts with the
element, UIKit loads the appropriate view controller, notifies your
app that the segue is about to occur, and executes the transition. You
can use the notifications sent by UIKit to pass data to the new view
controller or prevent the segue from happening altogether.
In figure 9-4 you will see the event flow of a segue process. In particular, note that if you override prepareForSegue:sender: in your source view controller for the segue then that is your opportunity to prepare data and send it to the destination view controller (either view setting the representedObject for the destination view controller, or via a custom setter method defined for your destination view controller class). The description text below that figure spells it out:
The prepareForSegue:sender: method of the source view controller lets
you pass data from the source view controller to the destination view
controller. The UIStoryboardSegue object passed to the method contains
a reference to the destination view controller along with other
segue-related information.
If you aren't using storyboards, then you're essentially doing the same thing by hand. You allocate the UIViewController subclass you want to show in response to the touch in the cell item, and then set its representedObject to the data it should display (or call a custom method defined by that class and pass in the data the view controller needs to display), then you show the view controller. To present it manually you'll want to read the Presenting a View Controller document from Apple. In particular:
Presenting a View Controller
There are several ways to initiate the presentation of a view controller:
Use a segue to present the view controller automatically. The segue
instantiates and presents the view controller using the information
you specified in Interface Builder. For more information on how to
configure segues, see Using Segues. Use the showViewController:sender:
or showDetailViewController:sender: method to display the view
controller. In custom view controllers, you can change the behavior of
these methods to something more suitable for your view controller.
Call the presentViewController:animated:completion: method to present
the view controller modally.
Hopefully that's enough to get you going. If not, maybe clarify your question with a bit more context and we'll try again.

Perform Segue from Collection View Cell

import UIKit
class ActionCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var myLabel: UILabel!
#IBOutlet weak var actionGIF: UIImageView!
#IBAction func actionPressed(sender: AnyObject) {
print(myLabel.text)
Global.actionButtonIndex = myLabel.text!.toInt()! - 1
print(actionGIF.image)
ActionViewController.performSegueWithIdentifier("showActionPreview", sender: nil)
}
}
I am trying to perform a Segue after User Clicking on One of the Cell in my Collection View. Can't seem to do that using performSegueWithIdentifier. App Screenshot
Here's an elegant solution that only requires a few lines of code:
Create a custom UICollectionViewCell subclass
Using storyboards, define an IBAction for the "Touch Up Inside" event of your button
Define a closure
Call the closure from the IBAction
Swift 4+ code
class MyCustomCell: UICollectionViewCell {
static let reuseIdentifier = "MyCustomCell"
#IBAction func onAddToCartPressed(_ sender: Any) {
addButtonTapAction?()
}
var addButtonTapAction : (()->())?
}
Next, implement the logic you want to execute inside the closure in your
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MyCustomCell.reuseIdentifier, for: indexPath) as? MyCustomCell else {
fatalError("Unexpected Index Path")
}
// Configure the cell
// ...
cell.addButtonTapAction = {
// implement your logic here, e.g. call preformSegue()
self.performSegue(withIdentifier: "your segue", sender: self)
}
return cell
}
You can use this approach also with table view controllers.
Instance method performSegue is not available from a UICollectionViewCell:
Since an UICollectionViewCell is not an UIViewController, you can not use performSegue(withIdentifier:sender:) from it. You may prefer use delegates to notify your parent view controller and then, performSegue from there.
Take a look at the details of this answer. The question is slightly different but the solution lies in the same pattern.
Have you set the segue identifier by exactly named "showActionPreview". Moreover, ensure that your segue linked from your parent view controller to your destination view controller in storyboard. Hope this would be help.

How do I instantiate a UIView / UITableView from a xib (nib) in a ViewController

I want to display a user interface that has a segmented control, and a different table view for each section of the segmented control; so, 2 table views (buddies and bunches) that can be switched between.
To implement this, I have done the following
Create a ViewController in Storyboard
Delete the View from the ViewController
Create a new UIViewController swift class with an associated xib file
Put the segmented control in the the main UIView in the xib
Put a inner UIView element inside of the main UIView to take the space where the table views where replace it
Created two subclasses of UITableView and corresponding xib files
Some options I have thought of:
I can set the class of the inner UIView in Interface Builder to be that of one of the table views, but I wouldn't know how to instantiate the other one in place of the initial one. If I created overlapping inner UIViews that each was associated with a table view and hiding one of them when I switch the segmented control, that actually kind of works, but the overlapping nature of views makes layout difficult and unintuitive.
What I want to know how to do: Instantiate the table views in place of the single main UIView element
Alternative: Have one UITableView subclass that has a condition based on the state of the segmented control for what data it displays. I don't like this as much because it will mix the code together for the table views. In this case, I wouldn't even need to use xibs anymore, I could do this in the storyboard with just one table view.
** ViewController Code **
#objc(BuddiesBunchesViewController) class BuddiesBunchesViewController: UIViewController {
#IBOutlet weak var segmentedControl: UISegmentedControl!
#IBOutlet weak var tableView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Instantiate tableView here to BuddiesTableView
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func segmentedControlIndexChanged(sender: AnyObject) {
switch segmentedControl.selectedSegmentIndex {
case 0: // Buddies
// Set tableview to the buddies table view
case 1: // Bunches
// Set tableview to the buddies table view
default:
break;
}
}
}
** Table View **
#IBDesignable class BuddiesTableView: UITableView, UITableViewDataSource, UITableViewDelegate {
var view: UIView!
var nibName: String = "BuddiesTableView"
//init
override init(frame: CGRect) {
// set properties
super.init(frame: frame)
// Set anything that uses the view or visible bounds
setup()
}
required init(coder aDecoder: NSCoder) {
//set properties
super.init(coder: aDecoder)
// Setup
setup()
}
func setup() {
view = loadViewFromNib()
view.frame = self.bounds
view.autoresizingMask = UIViewAutoresizing.FlexibleWidth | UIViewAutoresizing.FlexibleHeight
addSubview(view)
}
func loadViewFromNib() -> UIView {
let bundle = NSBundle(forClass: self.dynamicType)
let nib = UINib(nibName: nibName, bundle: bundle)
let view = nib.instantiateWithOwner(self, options: nil)[0] as! UIView
return view
}
// MARK: - Table View
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "MyTestCell")
cell.textLabel?.text = "\(indexPath.row)"
return cell
}
}
I personally wouldn't create two table views and swap them out. The only significant difference between the two would be that they're different instances, and you could test for that in the data source code. Would they point to different data sources? Why not just change the data source on-the-fly, rather than the table view?
Create one table view. Create two (or more) classes to act as data sources (and possibly delegates). They could be based on a common superclass if there are any behaviors they have in common.
When the segmented control value changes, just change the table view's dataSource property to point to the class with the code for that state. You may also need to follow that with a -reloadData call, if changing the data source doesn't reload it automatically.
#IBOutlet private weak var tableView: UITableView!
let moviesDataSource = MyMoviesDataSource()
let tvShowsDataSource = MyTVShowsDataSource()
#IBAction func segmentedControlTouched(sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
self.tableView.dataSource = self.moviesDataSource
self.tableView.delegate = self.moviesDataSource
case 1:
self.tableView.dataSource = self.tvShowsDataSource
self.tableView.delegate = self.tvShowsDataSource
}
self.tableView.reloadData()
}
My solution will be as below.
You have a segment control having two segments, so Instead of having two table views you can have only one UITableView object and change the datasource based on segment control selectedSegmentIndex number and after that reload the table view. In storyboard put the table view on top of the UIVIewController and create an #IBOutlet for that.

Resources