How to add UITableviewController inside UIView - ios

I have one view controller which is a subclass of UITableViewcontroller, that UITableViewcontroller i need show under one UIView which I need to assign Corner Radius so it will match with my design.
UITableViewcontroller is the Generic tableview class which I have used in the whole project so I couldn't make changes in the structure.
All my ViewController are created programmatically, i have not used anywhere Storyboard.
Here is my Viewconroller Code where I am implementing
headerViewController is part which i mark as white
deal and team controller is my UITableviewController which i have added in SJSegment
private func setupSegmentController() {
segmentViewController.headerViewHeight = 350
segmentViewController.headerViewController = headerViewController
segmentViewController.segmentControllers = [
dealViewController,
teamViewController]
segmentViewController.delegate = self
addChild(segmentViewController)
segmentViewController.view.frame = view.bounds
view.addSubview(segmentViewController.view)
segmentViewController.didMove(toParent: self)
}
Below in red highlighted is the area which i need to design

What I understand you want to add a view controller as child in some other view controller. You have to use ContainerView in the View Controller where you want to show that table Table View Controller. Create an outlet of that Container View then add the table view as child.
let yourTableViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "yourTableViewController") as! UITableViewcontroller
yourTableViewController.view.autoresizingMask = [.flexibleHeight, .flexibleWidth]
yourTableViewController.view.frame = self.containerView.bounds
self.addChild(yourTableViewController)
containerView.addSubview(yourTableViewController.view)
yourTableViewController.didMove(toParent: self)

You need to subclass UITableView and view.addSubview(yourCustomClass) . It is not necessary to add UITableViewController inside your UIView

Controllers it's also a basic class that can control your view. You can add as subview tableView you your generic view and set their delegate and dataSource to the main view controller. You don't need to create tablewviewcontroller for this.
all you need is - (inside main view controller)
tableView.delegate = self
tableView.dataSource = self.
and after this you must implement protocol conformance
MainViewController: UITableViewCOntrollerDelegate {
// implementation here
}
MainViewController: UITableViewControllerDataSource {
// implementation here
}
After this you can customise your table view like view and do what you want

Related

viewController.view not getting added to custom view inside Parent View Controller

I would like to add a childViewController into a custom UIView part of a parentViewController. However, if I am doing self.customView.addSubview(childViewController.view) I cannot see the childViewController.view as it doesn't get added. In contrast, if I do self.view.addSubview(childViewController.view) it all works well. Can someone explain why this is happening? I really need to add childViewController.view as subview of the customView and not as part of the self.view.
if let childViewController = self.storyboard?.instantiateViewController(withIdentifier: "ChildVC") as UIViewController? {
self.addChildViewController(childViewController)
childViewController.view.frame = customView.bounds
self.customView.addSubview(childViewController.view)
childViewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
childViewController.didMove(toParentViewController: self)
childViewController.view.frame = CGRect(x: customView.frame.origin.x,
y: customView.frame.origin.y,
width: customView.frame.width,
height: customView.frame.height)
}
How about using a Container view from Object Library (place from where we drag table view, textView and all UI components onto our storyboard). which is
Container view define a region of view controller that include
a child view controller
When you take Container view from object library in your desired
ViewController on storyBoard.
it automatically gives you a another view controller attached to your view controller with a segue.
you just need to override this segue code and that dragged container view will work as a child view controller for you it did load will call automatically.
just Override this is your parent view Controller
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if let identifier = segue.identifier {
switch identifier {
case "SegueIndetifierName":
(segue.destination as? YourChildViewController)?.parentViewControllerObject = self
default:
break
}
}
}
NOTE: declare parentView controller object in your child view controller like this
weak var parentViewControllerObject: ParentViewController!
to uniquely identify the relation ship between child and parent. and rest your work will be done automatically.
You can check the Sample Working project to add Subviews
Link https://github.com/RockinGarg/Container_Views.git
Required Code:
Class Object to be added as Subview
private lazy var FirstObject: firstVC =
{
// Instantiate View Controller
let viewController = self.storyboard?.instantiateViewController(withIdentifier: "firstVC") as! firstVC
// Add View Controller as Child View Controller
self.addChildViewController(viewController)
return viewController
}()
Add in ParentView as Subview
private func add(asChildViewController viewController: UIViewController)
{
// Configure Child View
viewController.view.frame = CGRect(x: 0, y: 0, width: self.firstContainer.frame.size.width, height: self.firstContainer.frame.size.height)
// Add Child View Controller
addChildViewController(viewController)
viewController.view.translatesAutoresizingMaskIntoConstraints = true
// Add Child View as Subview
firstContainer.addSubview(viewController.view)
// Notify Child View Controller
viewController.didMove(toParentViewController: self)
}
Where : -
firstContainer is the parent view In which subview is to be added
viewController class object whose view is to be added as Subview
Note- This can be used in containerViews as well as Normal UIView too for adding a Controller as Subview

Found nil when adding view controller view to subview [duplicate]

This question already has an answer here:
iOS UIButton in programmatically instantiated subview not firing
(1 answer)
Closed 5 years ago.
I have a view controller with a table view, and need to add it as a sub view to another view controller, but when I add the view controller's view I get a nil pointing to the table. All the connections are there, but unable to crack the reason why it crashes.
Child Controller
class childController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
table.delegate = self
table.dataSource = self
}
}
Parent Controller
func presentChildController() {
let childVC = childController()
self.view.addSubview(childVC.view)
}
This code is completely illegal:
func presentChildController() {
let childVC = childController()
self.view.addSubview(childVC.view)
}
You cannot simply create a view controller, grab its view, and stuff its view into your own view. If you want your view controller (self) to function as a parent view controller, and childController to function as a child view controller, there is a careful dance you have to do — and you are not doing that dance. Therefore, childVC (your childController) goes out of existence immediately, and that is the cause of the nil crash you are experiencing.
Another possible cause, by the way, is that if your childController is designed in the storyboard, childController() is the wrong way to instantiate it — the table view in the storyboard will never come into existence. You have to make sure the view controller's view is the view you have designed.

Swift: Call Function in PageViewController from other Viewcontroller

I got an PageViewController which loads two "child "ViewControllers in order to let the user "swipe" through them. I don't want this swipe gesture , but instead I want to have a function inside my ViewController which allows me to use setViewControllers in the PageViewController.
I tried using protocols but even that didn't work out.
I would realy appreciate any help or suggestions on how I could accomplish that. Thanks!
To access setViewControllers from your child view controllers, you will need your child view controllers to be aware of their parent PageViewController. To do so, start by making a Protocol (I know you've said you've tried Protocols, but please please see my method through). This Protocol will ensure that every child view controller has a reference to the parent PageViewController.
protocol PageObservation: class {
func getParentPageViewController(parentRef: PageViewController)
}
Ensure that your child view controllers adhere to the PageObservation Protocol.
class Child1ViewController: UIViewController, PageObservation {
var parentPageViewController: PageViewController!
func getParentPageViewController(parentRef: PageViewController) {
parentPageViewController = parentRef
}
}
class Child2ViewController: UIViewController, PageObservation {
var parentPageViewController: PageViewController!
func getParentPageViewController(parentRef: PageViewController) {
parentPageViewController = parentRef
}
}
In your PageViewController, as you create each child view controller, cast them to the PageObservation type and pass a reference of the parent PageViewController. I use an array called orderViewControllers to create my pages. My UIPageViewControllerDataSource delegate methods uses it to know which pages to load but that is irrelevant to this example, I just thought I'd let you know in case you have a different way of creating your pages.
class PageViewController: UIPageViewController {
var orderedViewControllers: [UIViewController] = []
//creating child 1
//i am using storyboard to create the child view controllers, I have given them the identifiers Child1ViewController and Child2ViewController respectively
let child1ViewController = UIStoryboard(name: "Main", bundle: nil) .
instantiateViewController(withIdentifier: "Child1ViewController")
let child1WithParent = child1ViewController as! PageObservation
child1WithParent.getParentPageViewController(parentRef: self)
orderedViewControllers.append(child1ViewController)
//creating child 2
let child2ViewController = UIStoryboard(name: "Main", bundle: nil) .
instantiateViewController(withIdentifier: "Child2ViewController")
let child2WithParent = child2ViewController as! PageObservation
child2WithParent.getParentPageViewController(parentRef: self)
orderedViewControllers.append(child2ViewController)
}
Now inside your child view controllers, you have access to setViewControllers. For example, if I want to call setViewControllers in the child1ViewController, I have created a func called accessSetViewControllers() where I access the setViewControllers:
class Child1ViewController: UIViewController, PageObservation {
var parentPageViewController: PageViewController!
func getParentPageViewController(parentRef: PageViewController) {
parentPageViewController = parentRef
}
func accessSetViewControllers() {
parentPageViewController.setViewControllers( //do what you need )
}
}
On a side note, despite what other answers above have said, you can set dataSource to whatever you like. I sometimes set dataSource to nil to prevent the user from swiping away from a screen before doing something and then add the dataSource back to allow them to continue swiping.
Don't set dataSource. When it's nil, then gestures won't work.
https://developer.apple.com/reference/uikit/uipageviewcontroller
When defining a page view controller interface, you can provide the content view controllers one at a time (or two at a time, depending upon the spine position and double-sided state) or as-needed using a data source. When providing content view controllers one at a time, you use the setViewControllers(_:direction:animated:completion:) method to set the current content view controllers. To support gesture-based navigation, you must provide your view controllers using a data source object.
Simplistic approach... remove the inbuilt gesture recogniser in viewDidLoad of pageViewController:
for view in self.pageViewController!.view.subviews {
if let subView = view as? UIScrollView {
subView.scrollEnabled = false
}
}
Then add your own gesture below it. i just happened to be working with double tap at the moment but you could make it swipe left, swipe right easy enough:
let doubleTap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didDoubleTap))
doubleTap.numberOfTapsRequired = 2
doubleTap.delaysTouchesBegan = true
self.addGestureRecognizer(doubleTap)
and the gesture function with your code:
func didDoubleTap(gesture: UITapGestureRecognizer) {
//... stuff
}

Pass data to View Controller embedded inside a Container View Controller

My view controller hierarchy is the following:
The entry point is a UINavigationController, whose root view controller is a usual UITableViewController. The Table View presents a list of letters.
When the user taps on a cell, a push segue is triggered, and the view transitions to ContainerViewController. It contains an embedded ContentViewController, whose role is to present the selected letter on screen.
The Content View Controller stores the letter to be shown as a property letter: String, which should be set before its view is pushed on screen.
class ContentViewController: UIViewController {
var letter = "-"
#IBOutlet private weak var label: UILabel!
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
label.text = letter
}
}
On the contrary, the Container View Controller should not know anything about the letter (content-unaware), since I'm trying to build it as reusable as possible.
class ContainerViewController: UIViewController {
var contentViewController: ContentViewController? {
return childViewControllers.first as? ContentViewController
}
}
I tried to write prepareForSegue() in my Table View Controller accordingly :
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let containerViewController = segue.destinationViewController as? ContainerViewController {
let indexPath = tableView.indexPathForCell(sender as! UITableViewCell)!
let letter = letterForIndexPath(indexPath)
containerViewController.navigationItem.title = "Introducing \(letter)"
// Not executed:
containerViewController.contentViewController?.letter = letter
}
}
but contentViewController is not yet created by the time this method is called, and the letter property is never set.
It is worth mentioning that this does work when the segue's destination view controller is set directly on the Content View Controller -- after updating prepareForSegue() accordingly.
Do you have any idea how to achieve this?
Actually I feel like the correct solution is to rely on programmatic instantiation of the content view, and this is what I chose after careful and thorough thoughts.
Here are the steps that I followed:
The Table View Controller has a push segue set to ContainerViewController in the storyboard. It still gets performed when the user taps on a cell.
I removed the embed segue from the Container View to the ContentViewController in the storyboard, and I added an IB Outlet to that Container View in my class.
I set a storyboard ID to the Content View Controller, say… ContentViewController, so that we can instantiate it programmatically in due time.
I implemented a custom Container View Controller, as described in Apple's View Controller Programming Guide. Now my ContainerViewController.swift looks like (most of the code install and removes the layout constraints):
class ContainerViewController: UIViewController {
var contentViewController: UIViewController? {
willSet {
setContentViewController(newValue)
}
}
#IBOutlet private weak var containerView: UIView!
private var constraints = [NSLayoutConstraint]()
override func viewDidLoad() {
super.viewDidLoad()
setContentViewController(contentViewController)
}
private func setContentViewController(newContentViewController: UIViewController?) {
guard isViewLoaded() else { return }
if let previousContentViewController = contentViewController {
previousContentViewController.willMoveToParentViewController(nil)
containerView.removeConstraints(constraints)
previousContentViewController.view.removeFromSuperview()
previousContentViewController.removeFromParentViewController()
}
if let newContentViewController = newContentViewController {
let newView = newContentViewController.view
addChildViewController(newContentViewController)
containerView.addSubview(newView)
newView.frame = containerView.bounds
constraints.append(newView.leadingAnchor.constraintEqualToAnchor(containerView.leadingAnchor))
constraints.append(newView.topAnchor.constraintEqualToAnchor(containerView.topAnchor))
constraints.append(newView.trailingAnchor.constraintEqualToAnchor(containerView.trailingAnchor))
constraints.append(newView.bottomAnchor.constraintEqualToAnchor(containerView.bottomAnchor))
constraints.forEach { $0.active = true }
newContentViewController.didMoveToParentViewController(self)
}
} }
In my LetterTableViewController class, I instantiate and setup my Content View Controller, which is added to the Container's child view controllers. Here is the code:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if let containerViewController = segue.destinationViewController as? ContainerViewController {
let indexPath = tableView.indexPathForCell(sender as! UITableViewCell)!
let letter = letterForIndexPath(indexPath)
containerViewController.navigationItem.title = "Introducing \(letter)"
if let viewController = storyboard?.instantiateViewControllerWithIdentifier("ContentViewController"),
let contentViewController = viewController as? ContentViewController {
contentViewController.letter = letter
containerViewController.contentViewController = contentViewController
}
}
}
This works perfectly, with an entirely content-agnostic container view controller. By the way, it used to be the way one instantiated a UITabBarController or a UINavigationController along with its children, in the appDidFinishLaunching:withOptions: delegate method.
The only downside of this I can see: the UI flow ne longer appears explicitly on the storyboard.
The only way I can think of is to add delegation so that your tableViewController implements a protocol with one method to return the letter; then you have containerViewController setting its childViewController (the contentViewController) delegate to its parent. And the contentViewController can finally ask its delegate for the letter.
At your current solution the presenting object itself is responsible for working both with the "container" and the "content", it doesn't have to be changed, but such solution not only has the issues like the one you described, but also makes the purpose of the "container" not very clear.
Look at the UIAlertController: you are not configuring its child view controller directly, you are not even supposed to know it exists when using the alert controller. Instead of configuring the "content", you are configuring the "container" which is aware of the content interfaces, lifecycle and behavior and doesn't expose it. Following this approach you achieve a properly divided responsibility of the container and content, minimal exposure of the "content" allows you to update the "container" without a need to update the way it is used.
In short, instead of trying to configure everything from a single place, make it so you configure only the "container" and let it configure the "content" when and where it is needed. E.g. in the scenario you described the "container" would set data for the "content" whenever it initializes the child controllers. I'm using "container" and "content" instead of ContainerViewController and ContentViewController because the solution is not strictly based on the controllers because you might as well replace it wth NSObject + UIView or UIWindow.

How to access the master's tableview from detail view in iOS

A portion of my app has an embedded master-detail section. Each detail view is using a custom UIViewController. When I change the value of something inside one of these UIViewControllers I need to be able to grey out one of the table rows in the master UITableViewController.
The closest I have seen to a solution is to use NSNotificationCenter to bubble up any changes, though this feels a little untidy..
Another solution is to use delegates? But I haven't come across any example solutions or tutorials in how to use this in Swift?
I've also experimented just trying to access the table view by navigating back up the hierarchy:
let navController = self.splitViewController!.viewControllers[0];
navController.tableView.reloadData()
I know the example above is wrong, but I don't know how to access the master view that way, or even if it is the right approach.
Oh, I am trying to call reloadData() because in the master view there is some logic which checks the condition as to wether to grey out a table row is applicable (i'm using Core Data)
I've seen that you figured this one out already. However a cleaner and more future proof way would be to use a delegate protocol:
protocol DetailViewControllerDelegate: class {
func reloadTableView()
}
Then add a delegate property to your DetailViewController class and implement the call to the delegate:
class DetailViewController: UIViewController {
weak var delegate: DetailViewControllerDelegate?
....
func reloadMasterTableView() {
delegate?.reloadTableView()
}
}
And then in your MainViewController implement the delegate method:
extension MainViewController: DetailViewControllerDelegate {
func reloadTableView() {
tableView.reloadData()
}
}
Don't forget to set the delegate on your DetailViewController instances when you create them:
let detailViewController = DetailViewController()
detailViewController.delegate = self
I would suggest you use NSNotificationCenter .
If you want to to do it via Navigation controller here is to code should work for you in swift.
let navController: UINavigationController = self.splitViewController!.viewControllers[0] as! UINavigationController
let controller: MasterViewController = navController.topViewController as! MasterViewController
controller.tableView.reloadData()
Since I was able to access my viewController, I was able to access the parent viewcontroller like so:
func reloadMasterTableView(){
let navVC: UINavigationController = self.splitViewController!.viewControllers[0] as! UINavigationController
let sectionsVC : UIMasterViewController = navVC.topViewController as! UIMasterViewController
sectionsVC.tableView.reloadData()
}

Resources