I am working with a library from github which requires a table view to be within a view controller.
Like this: HidingNavigationBarManager(viewController: self, scrollView: tableView)
But my table view is not inside my VC, instead I have a container view with an embedded segue to the table view.
So how can I pass my embedded table view thats inside my container view in the function:
HidingNavigationBarManager(viewController: self, scrollView: tableView)
If a ViewController has containerViews in it, it triggers prepareForSegue Method in a ViewController that contains containerViews after viewDidLoad. There you can get reference of viewController which is embedded in a containerView.
So for example you have a containerView which is linked with viewcontroller of class TestViewController:
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var vc: AnyObject = segue.destinationViewController
if vc .isKindOfClass(TestViewController) {
NSLog("GOTCHA!")
}
}
So you can refer to the tableView in a TestViewController like forexample: vc.tableView inside the if block of prepareForSegue method.
Related
I have UIPageViewController with 3 UIViewControllers ("FirstVC", "SecondVC", "ThirdVC").
ViewControllers changes by scroll, but I need to change its by click on UIButtons.
How I can do this?
Maybe some func, in which VC will setup by StoryboardID?
Thanks for all answers!
You can easily programmatically navigate through the pages of a UIPageViewController using:
setViewControllers([targetPage], direction: .forward, animated: true, completion: nil)
In the case where you have a UIPageViewController embedded in a ContainerView, and you want buttons in the "root" view to control the page view controller, the basic process is:
add navigation methods (funcs) to your page view controller class
save a reference to the page view controller when it is loaded have
your buttons call the navigation funcs using that reference
When your "root" view controllers loads and instantiates the view controller that is embedded in your ContainerView, it calls prepare(for segue:...) - which is where you get your reference.
In Storyboard, where you embed your view controller in the ContainerView, you will see a standard "segue" connection. In the Attributes Inspector, give that segue an Identifier, such as "PageControllerEmbedSegue".
In your root controller class, add a class-level var:
var myPageVC: BasicPageViewController?
and in prepare():
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// get a reference to the embedded PageViewController on load
if let vc = segue.destination as? BasicPageViewController,
segue.identifier == "PageControllerEmbedSegue" {
self.myPageVC = vc
}
}
Now, you can call functions / get properties of your embedded view controller.
I have a full example on GitHub: https://github.com/DonMag/EmbeddedPageView
There are a couple ways. You could add a segue in the Storyboard file and call
performSegue(withIdentifier: "toResponseTime", sender: self)
otherwise you could do something like.
let controller = self.storyboard!.instantiateViewController(withIdentifier: "AngelDetailViewController") as! AngelDetailViewController
self.navigationController!.pushViewController(controller, animated: true)
I do have 4 Views with a Headerpart which I outsourced into a containerview to have the same fields and layout on all 4 views. Inside my container im having a lot of labels which i know wanna fill with data. My problem now is, that i have to fill the labels accordingly to game the user selected. game is a enum inside my player class. I have no idea how i can gain that information from inside my containerview and set the game variable accordingly to perform my code. Is there a solution to get the storyboardid from the view my containerview is on out of the containerview?
switch game
case .Coinflip:
Player1PointsLabel.Text = (player1.points.coinflip)
case .RollingDices
Player1PointsLabel.Text = (player1.points.rollingdices)
Maybe i did something wrong, design wise, i'm not that experienced yet, so i'm also open for advises.
Best regards
As far as I know, the only way to get the ViewController of a view that was inserted into a ContainerView, is to save a reference to it in the parent ViewController when the ContainerView is instantiated.
Swift 4 Examples:
If you used a ContainerView in a storyboard and added an embed segue:
var containerVC: UIViewController?
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "YourEmbedSegueName" {
if let vc = segue.destination as? YourViewController {
self.containerVC = vc
}
}
}
Or, if you inserted a View inside a ContainerView programmatically:
var containerVC: UIViewController?
func customContainerEmbed(vc: UIViewController) {
self.addChildViewController(vc)
yourContainer.addSubview(vc.view)
vc.view.frame = yourContainer.bounds
vc.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
vc.didMove(toParentViewController: self)
self.containerVC = vc
}
The goal of your question is not very clear.
If you want to access the superview of your view (the view containig your subview), then use 'myView.superview'.
If you want to access the UIViewController that host your UIViewController, then use 'myViewController.presentingViewController'.
Finally, if you want to access the UIViewController hosting your view, you must walk the responder chain until you reach the first UIViewController or the end of the chain (UIView is a subclass of UIResponder):
func viewController(forView: UIView) -> UIViewController? {
var nr = forView.next
while nr != nil && !(nr! is UIViewController) {
nr = nr!.next
}
return nr as? UIViewController
}
Implement the prepareForSegue method of your main controller.
Based on the segue name, you can create a reference to the destination controller, managing you container view
I have built an app that centers around a pretty tableview that I've built.
I have setup a secondary view controller as a menu that is presented modally, and I would like to filter the tableview by selecting one of the buttons on the secondary view controller.
For example, each cell has a City assigned to it. In the menu, I'd like to be able to click a city and filter the tableview to only show cells with that city.
I have too much code to paste, and I'm confident I can solve this problem with a smidge of direction.
Thanks for your help!
You can do this with an unwind segue from your second view controller's buttons back to your table view controller.
In your table view controller, say,
func unwindToTableView(_ segue: UIStoryboardSegue) {
switch segue.identifier {
case "FilterNames":
filterByName()
etc…
}
}
or you could have different unwind funcs for each filter…
func unwindAndFilterName(_ segue: UIStoryboardSegue) {
filterByName()
}
etc
To hook up an unwind segue, just add the method to your table view controller, then in your storyboard, drag from the button on the second view controller to it's Exit icon. The segue func should appear in the list
To do this you would like to have separate DataSource layer in the application to have move clear code.I will write small example for you how it is possible to implement.
For example you have class DataSource. With information that you are showing. In my case it is cities. Now when I would like to do sorting I will call sortAlphabetically() and reload tableview. It is quite simple way and you're solution really depends on how you are working with UITableView.
class DataSource {
var cities = ["Lviv", "Lutsk", "Kiev", "Rivne"]
func sortAlphabetically() {
cities = cities.sorted { $0 < $1 }
//reload tableview hear
}
}
Best way to do so is to use delegates, add a protocol to your filter view controller and a delegate function in tableView that filters datasource for tableView. Don't forget to assign your table view controller as the delegate before you segue to the filter viewcontroller
Best way to do so is to use delegates, add a protocol to your filter view controller and a delegate function in tableView that filters datasource for tableView. Don't forget to assign your table view controller as the delegate before you segue to the filter viewcontroller
Before your filterViewController
protocol FilterViewControllerDelegate {
func tableViewCriteria(criteria: AnyObject)
}
In your filterViewController:
var delegate: FilterViewControllerDelegate?
In the class declaration of tableViewController, add FilterViewControllerDelegate
class MyTableViewController: UITableViewController, FilterViewControllerDelegate{
Don't forget to set the FilterViewControllerDelegate to self before you segue to the filterView:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "showFilter" {
if let fvc = segue.destination as? FilterViewController{
fvc.delegate = self
}
}
}
Implement the func in tableView that will update tableView data source based on the chosen criteria:
//In myTableViewController
func tableViewCriteria(criteria: AnyObject) {
//update tableView data source base on criteria here
return
}
Finally, call the delegate function from filterView whenever you need to return to tableview:
self.delegate?.tableViewCriteria(criteria: foo)
Voila! :)
I have a storyboard with two main view controllers (ViewController / SecondViewController) and a container with an embedSegue that displays the EmbeddedViewController view.
Desired Result
I would like to pass the 5 from ViewController to EmbeddedViewController using prepareForSegue (I don't see a need for delegation here, since the data is only going 1 way, and that's TO the EmbeddedViewController.) Once "Segue" button is pressed, the second view controller appears with the yellow VC embedded with 5 in it.
Note - SecondViewController has no user interaction in displaying EmbeddedViewController. It just shows up automatically.
Problem
I can't get a reference to EmbeddedViewController from the mainSegue prepareForSegue. Nor can I access any child View Controllers from SecondViewController. Thought I could access properties of EmbeddedViewController if I access the children VC's of SecondViewController. See code below.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "mainSegue" {
let secondVCSegue = segue.destinationViewController as! SecondViewController
print("array -- \(secondVCSegue.childViewControllers)")
// Array prints nothing. Just a blank [] :(
}
}
Any tips are appreciated thanks.
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.