I have three viewControllers Main, A, B. Main ViewController holds ContainerView and other content as well does all transactions in containerView. ViewControllerA has ButtonA when pressing it content of container has to change to ViewControllerB
how can I do that? I cannot find any similar examples.
You will need to create create delegate for that.
First create a protocol
protocol ViewControllerADelagate {
func didPressOnButtonA()
}
In ViewControllerA add following delegate variable
class ViewControllerA {
....
var delegate : ViewControllerADelagate?
....
}
In ViewControllerA add following on button press
#IBAction buttonAPressed(sender : UIButton) {
self.delegate?.didPressOnButtonA()
}
In MainViewController assign the delegate of ViewControllerA to self
like
vcA.delegate = self
Implement the delegate method in MainViewController like
func didPressOnButtonA {
let storyboard : UIStoryboard = UIStoryboard(name: storyboard, bundle: nil)
let vcB : UIViewController = storyboard.instantiateViewController(withIdentifier: viewControllerIdentifier) as! ViewControllerB
self.addChildViewController(vcB)
self.containerView.addSubview(vcB.view)
vcB.didMove(toParentViewController: self)
vcB.view.frame = CGRect.init(x: 0, y: 0, width: self.containerView.frame.size.width, height: self.containerView.frame.size.height)
}
While click on ButtonA. Post a notification to mainView. There remove viewController A from Container and add View Controller B .
I have created a storybaord with sample. you can download it from here.
You need to change the embed view to a navigation controller and then you can use segue to show second view on button press. also hide/show navigation bar depends on requirement.
I am not fond of using these but you can get the child view controller from parent controller by accessing an array childViewControllers. On view did load you would need to go through all of these and find the one that can be typecast into your view controller type like
childViewControllers.flatMap { $0 as? AViewController }.first.
Now that you found the correct view controller I suggest you to either assign yourself as a delegate
childViewControllers.flatMap { $0 as? AViewController }.first.delegate = self
or simply add a button target
childViewControllers.flatMap { $0 as? AViewController }.first.button.addTarget...
Now this can easily be done if you simply embed the 2 view controllers at the same time (have 2 content views) and hide one or the other depending on which you show. At least this way you can assign connection straight away. When this is not the case then you will need to iterate again when setting a new controller or assign connections where you initialize a new view controller.
In this case it then seems better to turn the system around: When child view controller is loaded rather call
self.delegate = parentViewController as? AViewControllerDelegate
Although this will work it seems wrong that a child view controller will control who its listener is so I advise you to avoid such coding.
I in general use a custom implementation for container view which you could do the same or maybe at least subclass the native one and target the methods so that your code will look something like:
private onBPressed() {
containerView.setViewController(viewController: BViewController(delegate: self), animation: .fade)
}
When you pressing Button A from View A:
#IBAction func BtnAPress(_ sender: Any)
{
//Moving Storyboard
let Storyboard = UIStoryboard(name: "Main", bundle: nil)
let MainVC : UIViewController = Storyboard.instantiateViewController(withIdentifier: "ViewControllerB")
self.present(MainVC, animated: true, completion: nil)
}
Related
I want to create a navigation hierarchy where I want to go to a SecondViewController from FirstViewController using a NavigationController. The FirstViewController contains a button B that I intend to use to go to a SecondViewController. I am employing the navigation controller concept since I want to return to the FirstViewController later. I have done the following steps to achieve it:
I have embedded a NavigationController into a storyboard X containing both the FirstViewController and SecondViewController.
I have created a segue from the button B present in the FirstView (has FirstViewController associated with it) to the SecondView (has SecondViewController).
I set the navigationViewController nvc as following after I have presented the firstViewController:
nvc = UINavigationController.init(rootViewController: firstViewController)
Later on, when the button B gets pressed, I execute the following code to push the secondViewController onto the navigation hierarchy:
self.nvc?.pushViewController(secondViewController, animated: true)
However, the secondView doesn't present itself even after pushing.
I have named the segue between the button B and secondViewController as kSegue, so I tried to perform the segue as an addition to see if the secondViewController presents itself or not:
self.performSegue(withIdentifier: "kSegue", sender: self)
An exception occurs when both the 4th and 5th steps are performed together. The exception states that I'm pushing a viewController that already exists on the navigation hierarchy, but the second view still doesn't open even if I comment the performSegue code.
May I ask what mistake I am making here?
In the storyboard, make sure there is a rootViewController segue from the navigation controller to the first view controller. Also, make sure the navigation controller is marked as the initial view controller.
In the code, change
self.nvc?.pushViewController...
To
self.navigationController?.pushViewController...
1) Take a navigation controller on your storyboard
2) Set navigation controller as initial view controller
3) set view controller A as a root view controller of your navigation controller
4) in "GoToB" method access View controller B's instance and push it in navigation controller
5) On View controller B's "Go Back" method write code to pop it.
6) Dont forget to set storyboard Id on both A & B view controller
class FirstViewController: UIViewController
{
lazy var secondViewController : SecondViewController? =
{
let secondViewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "SecondViewController") as? SecondViewController
return secondViewController
}()
override func viewDidLoad()
{
super.viewDidLoad()
}
#IBAction func goToB(sender : UIButton)
{
guard let secondViewController = self.secondViewController else
{
return
}
self.navigationController?.pushViewController(secondViewController, animated: true)
}
}
class SecondViewController: UIViewController
{
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func goBack(sender : UIButton)
{
self.navigationController?.popViewController(animated: true)
}
}
There's a view controller B. So, have embedded B as child view controller in viewController A in a UIView. Now, need to call a api in child view controller B when it is added as a child. So, need to set a check when added B as child only then this api need to be hit. Kindly help in this.
Below is the code, how i embedded B as child View in A viewController:
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vcController: UIViewController = storyboard.instantiateViewController(withIdentifier: "BNameViewController") as! BNameViewController
//add as a childviewcontroller
addChild(vcController)
// Add the child's View as a subview
viewBList.addSubview(vcController.view)
vcController.view.frame = viewBList.bounds
vcController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
// tell the childviewcontroller it's contained in it's parent
vcController.didMove(toParent: self)
viewBList is the UIView in View controller A.
So, now need in this case an api to be called in VC B not when we navigate to B from any other VC. I tried passing a bool or string by declaring same in VC B and the pass value from A, that doesn't work. Please guide. Hope i made my question clear, please feel free to ask if any doubt.
There are so many ways to do this, When you are navigating to view controller B from some other view controller then you have navigationController property not nil.
In that case you can check like that
override func viewDidLoad(animated: Bool) {
super.viewDidLoad(animated: animated)
//If navigating from some view controller then
if self.navigationController != nil {
//Code here for case, when you are navigating from some one
}
//View controller is child of someone
if (self.navigationController == nil || self.presentingViewController == nil || self.parent != nil ) {
//This is your case,
// hit api here
}
}
I'll give an example of what I want so it's not so confusing:
Example:
Let's say that I have a map that adds every time that my user scrolls 3 annotations dynamically. Now I have a button under the map and when I press it I go to another viewController do what I want and get back to the viewController with the map, now I want to find all the annotations that my map had and not reload the view at all.
I used to use this function that I made to move between viewControllers:
func move(identifier: String , viewController : UIViewController) -> Void {
let mstoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
let vc: UIViewController = mstoryboard.instantiateViewControllerWithIdentifier(identifier)
viewController.presentViewController(vc, animated: true, completion: nil)
}
I also tried this:
let vc = self.storyboard?.instantiateViewControllerWithIdentifier("view") as? MyViewcontroller
self.presentViewController(vc!, animated: true, completion: nil)
These two when I use them the viewcontroller that appears is calling viewDidload so its like it appeared for the first time.
Another example is the tabBarViewController if you notice when you navigate through tabs nothing reloads (only function that is called is viewDidAppear )
EDIT
test file
The problem is caused by the fact that the map controller gets deallocated when navigating back to the other controller, and another one is created when you want to move again to the map screen.
What you need is to hold on onto the same controller instance, and present that one. Keeping a strong reference in the presenting controller would suffice.
class PresentingController {
// making the property lazy will result in the getter code
// being executed only when asked the first time
lazy var mapController = { () -> UIViewController in
let mstoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
return mstoryboard.instantiateViewControllerWithIdentifier("mapControllerIdentifier")
}()
func moveToMap() {
// simply use the mapController property
// the property reference will make sure the controller won't
// get deallocated, so every time you navigate to that screen
// you'll get the same controller
presentViewController(mapController, animated: true, completion: nil)
}
}
According to the same project you posted, you instantiate a new UIViewController when going from view 2 back to view 1 and that is why your viewDidLoad gets called again and your entire map view is reloaded.
In your sample project, instead of
lazy var mapController2 = { () -> UIViewController in
let mstoryboard: UIStoryboard = UIStoryboard(name: "Main", bundle: nil)
return mstoryboard.instantiateViewController(withIdentifier: "first")
}
You should just dismiss your view 2 on the button press.
#IBAction func butto(_ sender: AnyObject) {
//Your initial code
//PresentingController().moveToMap(self, flag: 1)
self.dismiss(animated: true, completion: nil)
}
When you present a new UIViewController, the older UIViewController is not removed from memory, it is just hidden behind the new UIViewController. So whenever you wish to go back to a UIViewController with the previous state maintained, all you need to do is close the new UIViewController
However, if you are doing some tasks that you performed on your second UIViewController that you wish to be reflected in your initial UIViewController, you will have to setup closures to update your initial UIViewController.
I have a view controller which has a UISegmentControl with two buttons. I load this View and when the SegmentValue has changed i'll add a view controller into the view.
This is MainViewController
In the class I define the controllers
var tabNavigation: UISegmentedControl!
let viewController1:ViewController1 = ViewController1()
let viewController2:ViewController2 = ViewController2()
// Create the segment control
self.tabNavigation = UISegmentedControl(items: ["v1", "v2"])
self.tabNavigation.selectedSegmentIndex = 0
self.tabNavigation.frame = CGRectMake(60, 250, 130, 25)
self.tabNavigation.addTarget(self, action: "segmentedValueChanged", forControlEvents: .ValueChanged)
self.navigationItem.titleView = self.tabNavigation
// set first view
self.addChildViewController(viewController1)
self.view.addSubview(viewController1.view)
// Segement changed
func segmentedValueChanged() {
if (self.tabNavigation.selectedSegmentIndex == 0) {
self.addChildViewController(viewController1)
self.view.addSubview(viewController1.view)
} else {
self.addChildViewController(viewController2)
self.view.addSubview(viewController2.view)
In the storyboard i have a segue from MainViewController to ViewController3
Now in ViewController1 i'm trying to create a segue (should be part of a previous navigation controller so swipe across with a back button) from ViewController1 to ViewController3. But fails as the segue does not exist as ViewController1 is made completely from Code.
My code for the segue is below but obviously fails as that is the segue identifier for MainViewController to ViewController3
performSegueWithIdentifier("loadVC3", sender: nil)
What you're looking for is to initialize a viewController programmatically and then show it, for that you need first to set a unique Storyboard ID and then initialize it. Like this:
let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("ViewController3") as! UIViewController
// and then present it modally
presentViewController(viewController, animated: true, completion: nil)
// or push it onto the navigation stack
pushViewController(viewController, animated: true)
You cannot create segues programmatically. Look at this answer:
Creating a segue programmatically
I would probably use a XIB to design all the view controllers that you create programmatically.
So, I created a login page with basic logic. If username and/or password are empty, it displays an error message. If they're full, then it transitions to the next page (home). I created a second ViewController on the storyboard, and created a SecondViewController". I then defined the respective ViewController class to "SecondViewController".
On the first ViewController.swift file, i used this code:
func transition(Sender:UIButton!)
{
let secondViewController: SecondViewController = SecondViewController()
self.presentViewController(secondViewController, animated:true, completion:nil)
}
When I tested it out however, pressing the login button (when both the username and password are filled) transfers me to a black screen instead of the second View Controller. Any ideas on what to do?
If you want the view to contain things added to it on the storyboard, you could give it a Storyboard ID (a couple of boxes below where you set the class of the view controller in the storyboard).
If you give it for example the ID "secondVC" you can then create it using the following:
let secondViewController = storyboard?.instantiateViewControllerWithIdentifier("secondVC") as? SecondViewController
You can't initialize a view controller with just the default init method.
You should probably create a view controller scene in your storyboard, add a unique identifier on it, instantiate it using instantiateViewControllerWithIdentifier:, and then display it like you are doing.
You'll want to make a segue between the view controllers in your Storyboard, and call self.performSegueWithIdentifier().
Or you can give your `UIViewController an identifier and present it programmatically too:
let storyboard = UIStoryboard(name: "MyStoryboardName", bundle: nil)
let secondViewController storyboard.instantiateViewControllerWithIdentifier("") as? SecondViewController
if let secondViewController = secondViewController {
self.presentViewController(secondViewController, animated:true, completion:nil)
}
There are basically three ways to do this
1)Creating segue in storyboard,though it will only work if you have single segue from that item
2)using prepareforSegue and performSegue Methods in your presenting view controller
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "YourIdentifier" {
let controller = segue.destinationViewController as!secondViewController
}
}
Then use this in your IBACtion
performSegueWithIdentifier("YourIdentifier", sender:sender)
3)(Recommended One)
let controller = self.storyboard?.instantiateViewControllerWithIdentifier("ContactsVC")! as! ContactsVC
self.presentViewController(controller, animated:true, completion:nil