Is it possible in iOS 6 to know when a UIStoryboardSegue has finished its transition? Like when i add a UIStoryboardSegue from UIButton to push another UIViewController on the navigationcontroler, i want to to something right after the push-transition is finished.
You can use the UINavigationControllerDelegate protocol and then define:
– navigationController:didShowViewController:animated:
In case you don't want to use the viewDidAppear: method, you could create a custom segue. In the perform method you would use an animation for the transition, and that can have a completion block. You can add the code there after the animation is complete.
In Swift, from a UIViewController subclass you can get the UINavigationController instance and set the delegate, in order to be informed about the completion of segues, as shown. Another logical place to track segues might be the AppDelegate.
Example of doing it from a view controller (VC for short):
class MyViewControllerSubclass : UIViewController, UINavigationControllerDelegate {
func viewDidLoad() {
self.navigationController.delegate = self
}
func navigationController(navigationController: UINavigationController, didShowViewController viewController: UIViewController, animated: Bool) {
println("Did show VC: \(viewController)")
}
}
But that only shows you when the segue to the VC is complete,
as would viewWillAppear() or viewDidAppear() delegate methods in the VC being presented; however, they don't inform about when the target VC is un-presented. It will also only work if your View Controller is part of a Navigation Controller stack.
In the VC you're tracking, you could add the following to detect when the VC (and its memory) are deallocated, or override the viewWillDisappear() method.
deinit {
println(__FUNCTION__, "\(self)")
}
You can use - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
This method will be called right before a segue is performed in the source UIViewController. If you want to do some code in the destination UIViewController you can get the destination viewcontroller of segue.
You can also add this code in the viewdidAppear in the desintation viewController.
you can call a method of destination UIViewController in prepareForSegue method.
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSLog(#"prepareForSegue: %#", segue.identifier);
if ([segue.identifier isEqualToString:#"Happy"]) {
[segue.destinationViewController setHappiness:100];
} else if ([segue.identifier isEqualToString:#"Sad"]) {
[segue.destinationViewController setHappiness:0];
}
}
here setHappiness method is of destination Controller and here 100 is passing there. so you can write a method in destination controller and call it here
Related
How can I invoke a function/method in another firstViewController swift file while the control/state is in secondViewController.
In Second ViewController when a button is pressed the secondViewController should invoke a function in firstViewController and transfer the control/state to thirdViewController to which it was pushed from secondViewController.
secondViewController Button Action
#IBAction func EnterGallery(_ sender: Any){
// Want to invoke EnterGallery function in firstViewController and dismiss from secondViewController
self.dismiss(animated: true, completion: nil)}
firstViewController pushViewController function
func EnterGallery() {
let dest = self.storyboard?.instantiateViewController(withIdentifier:
"GalleryViewController") as! GalleryViewController // thirdViewController
self.navigationController?.pushViewController(dest, animated: true)
}
Please Note: I am not passing any Data from secondViewController to firstViewController. I just want my firstViewController to push to thirdViewController while I just dismiss from secondViewController which was presented from firstViewController with the present function.
Once I dismiss from secondViewController I want my screen to go directly to thirdViewController.
Basically I just want to invoke a function in another ViewController without any data passing from initial ViewController. So I cannot use Protocols and Delegates or Notifications and Observers. How should I approach this?
There are many other cases where I need to use this similar functionality. So I am not sure how to exactly perform this.
As I am new to Swift, any help will be appreciated.
Thanks in advance.
Your viewController should know about other viewControllers and should be able to interact with them.
Here is a good article about passing data between viewControllers (or just interacting between viewControllers - as you like)
The most common practice is delegation pattern. In two words about delegation:
Create a delegate protocol:
protocol MyDelegate {
func doSmth()
}
Add delegate property to viewController that will trigger something in anotherViewController:
var delegate: MyDelegate?
anotherViewController should conform MyDelegate protocol:
class anotherViewController: MyDelegate {
func doSmth() {
print("I am doing something")
}
}
And then assign your class that is conformed to MyDelegate protocol into this property
viewController.delegate = anotherViewController
Thats it! Now you can trigger delegate method inside viewController
delegate.doSmth()
Google delegate pattern for this. YT: https://youtu.be/DBWu6TnhLeY
Hopefully this helps you out. By the way delegate pattern works even if you don’t want to pass data in between.
In my app I have a “main” ViewController. On app launch in the body of its viewDidAppear, I retrieve the value of a UserDefaults boolean: if the user is not logged in, I present modally another viewcontroller this way:
self.performSegue(withIdentifier:"loginView", sender:self)
This launches modally AuthViewController, a viewcontroller that contains a “Container View”, a region of AuthViewController that includes a UIPageViewController (AuthPageViewController).
This last one has two “pages”: LoginViewController and RegisterViewController.
LoginViewController is used to login users: once the user is logged in I want to call a function (no matter what it does) on the main ViewController.
So, to summarize, I’m in LoginViewController and I want to call a method of ViewController.
While trying a solution I discovered the following:
I can’t get a reference to presentingViewController because LoginViewController has not been opened directly from ViewController. Moreover, AuthViewController was not launched with present(_:animated:completion:) but with self.performSegue as I said before;
instantiating a ViewController from LoginViewController and calling a method works but it’s not a good idea;
NSNotification works like a charm;
apparently delegates aren’t working or I’m missing something about their implementation in the contest of this app;
Can you help me understand how to call a ViewController method from my LoginViewController? Code examples are very well received, but I appreciate any advice.
Let me explain it with code of how to use delegate in this case,
First you need create a delegate in LoginViewController like this
protocol LoginViewControllerDelegate: class {
func goToContent(animated:Bool)
}
then create a variable for delegate like this
weak var delegate: LoginViewControllerDelegate?
Now in you ViewController you have to assign the delegate like this in your prepare prepareforsegue method, as you are using segue like this
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "yourSegueIdentifier" {
let nextScene = segue.destination as! LoginViewController
nextScene.delegate = self
}
}
and implement that method like this
func goToContent(animated: Bool) {
print("test")
}
in this way you are able to call goToContent from LoginViewController
I've got a custom UIStoryboardSegue that 'zooms' a UIImageView. Now I also need a custom UIStoryboardSegue that 'zooms out' when a user presses the back button in the UINavigationController. I've been trying to do this for some days now, but without success.
I've subclassed UINavigationController and added the code below to it:
- (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController fromViewController:(UIViewController *)fromViewController identifier:(NSString *)identifier {
UIStoryboardSegue *theSegue;
NSLog(#"Unwind called");
if ([fromViewController isKindOfClass:[SetDetailViewController class]]) {
theSegue = [ZoomOutSegue segueWithIdentifier:identifier source:fromViewController destination:toViewController performHandler:^(void){}];
} else {
theSegue = [super segueForUnwindingToViewController:toViewController fromViewController:fromViewController identifier:identifier];
}
return theSegue;
}
However, this isn't called. What am I doing wrong here?
I'm assuming you are still switching views with your zooming
I'm a Swiftie so my help will be in Swift
You should be able to capture these events by making your class a delegate of your navigation controller and then using the willShowViewController method of your navigation controller
Add UINavigationControllerDelegate to your class when you define it
Under your viewDidLoad() function add self.navigationController?.delegate = self
Now you should be able to use a function that will run just before the navigation controller switches to a new view controller (so yes it will also run when the back button is pressed). In Swift it looks like this:
func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
//if the view controller we are going to is one we want to trigger this action
if let _ = viewController as? ViewController {
//perform the thing you want here
}}
So, I have Navigation Controller. there are segue from Root View Controller to other View Controller.
When I want to get access to other View Controller I override prepareForSegue method and use destinationViewController property.
But that's not ok for me. All my stuff in prepareForSegue will be execute every time when segue is called, but I don't want it. Secondly, it destroys logic of my code: after performSegueWithIdentifier(actually before) execution jumps to other place in code.
It would be great if I can get access to other View Controller like I did it with Root ViewController - by keyword self, for example.
That's code example to make my question more clearer:
func startWorking() {
/*here we made some stuff for current VC
...
...
*/
//next we go to new View Controller
performSegueWithIdentifier("newVC", sender: nil)
//then all actions that I want to do begin at another method - prepareForSegue
//But I want get access to View Controller that user sees now!
//For example present some view:
let someView = UIView(frame: someFrame)
/*question subject*/.view.addSubview(somView)
}
/question subject/ - is the current ViewController that I have presented by segue and point of my question.
Sergey Gamayunov,
You can always access the top mostViewController in navigation stack using,
let viewCOntroller = self.navigationController?.topViewController
EDIT
I believe if you cant get your logic around the prepareForSegue or self.navigationController?.topViewController you must take a look into your design pattern :)
That being said I understand all you want to do is to access the ViewController after performSegue without using prepareForSegue, you can use this code
func navigationController(navigationController: UINavigationController, didShowViewController viewController: UIViewController, animated: Bool) {
if viewController is YourDestinationViewControllerClass {
print("You have access to viewController loaded do whatever you want")
}
}
The function stated above is a navigation controller delegate :) So you will have to declare your viewController to confirm UINavigationControllerDelegate. like
class ViewController: UIViewController,UINavigationControllerDelegate
and in
override func viewDidLoad() {
super.viewDidLoad()
self.navigationController?.delegate = self
}
Thats it you are good to go :) Happy coding buddy :)
This is tricky to word but I have a view controller (vc1) that contains a container view (I'm using storyboards). Within that container view is a navigation controller and a root view controller (vc2).
From within the vc2 how can I get access to vc1?
Or, how do I pass vc1 to vc2? (baring in mind that I'm using storyboards).
You can use the prepareForSeguemethod in Vc1 as an embed segue occurs when the ContainerViewController is made a child. you can pass self as an obj or store a reference to the child for later use.
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSString * segueName = segue.identifier;
if ([segueName isEqualToString: #"embedseg"]) {
UINavigationController * navViewController = (UINavigationController *) [segue destinationViewController];
Vc2 *detail=[navViewController viewControllers][0];
Vc2.parentController=self;
}
}
Edit: minor code fix
To access parent view controller from within your child view controller you must override didMoveToParentViewController:
- (void)didMoveToParentViewController:(UIViewController *)parent {
[super didMoveToParentViewController:parent];
//Use parent
}
On Xcode Command+Click over this method for more info:
These two methods are public for container subclasses to call when transitioning between child
controllers. If they are overridden, the overrides should ensure to call the super. The parent argument in
both of these methods is nil when a child is being removed from its parent; otherwise it is equal to the new
parent view controller.
addChildViewController: will call [child willMoveToParentViewController:self] before adding the
child. However, it will not call didMoveToParentViewController:. It is expected that a container view
controller subclass will make this call after a transition to the new child has completed or, in the
case of no transition, immediately after the call to addChildViewController:. Similarly
removeFromParentViewController: does not call [self willMoveToParentViewController:nil] before removing the
child. This is also the responsibilty of the container subclass. Container subclasses will typically define
a method that transitions to a new child by first calling addChildViewController:, then executing a
transition which will add the new child's view into the view hierarchy of its parent, and finally will call
didMoveToParentViewController:. Similarly, subclasses will typically define a method that removes a child in
the reverse manner by first calling [child willMoveToParentViewController:nil].
You can use delegation using the same method Bonnie used. Here is how you do it:
In your containerViews ViewController:
class ContainerViewViewController: UIViewController {
//viewDidLoad and other methods
var delegate: ContainerViewControllerProtocol?
#IBAction func someButtonTouched(sender: AnyObject) {
self.delegate?.someDelegateMethod() //call this anywhere
}
}
protocol ContainerViewControllerProtocol {
func someDelegateMethod()
}
In your parent ViewController:
class ParentViewController: UIViewController, ContainerViewControllerProtocol {
//viewDidLoad and other methods
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "filterEmbedSegue" {
let containerViewViewController = segue.destinationViewController as ContainerViewViewController
containerViewViewController.delegate = self
}
}
func someDelegateMethod() {
//do your thing
}
}
Use property parentViewController as self.parentViewController
Thank you Bonnie for telling me what to do. Indeed the prepare for segue method is the way to go.
I'm just clarifying the code and steps here.
So first off, name the segue(link) in the storyboard that connects the container view to its first view controller. I named mine "toContainer".
Then in the view controller containing the container view add this method
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString: #"toContainer"]) {
UINavigationController *navViewController = (UINavigationController *) [segue destinationViewController];
UIViewController *vc2 = [navViewController viewControllers][0];
}
}
So vc2 was the controller I wanted to get reference to.
This worked for me, your method would be slightly different inside the prepareForSegue if your first viewconroller wasn't a navigation controller.
1) on VC2 expose a property for passing in a reference to VC1
//VC2.h
#import "VC1.h"
#interface VC2 : NSObject
#property (strong, nonatomic) VC1 *parent;
#end
2) on VC1, pass self into the property exposed in VC2 in your prepareForSegue method after you setup your segue's identifier to "ToVC2". Then pass the reference like so:
//VC1.m
#implementation VC1
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if([segue.identifier isEqualToString:#"ToVC2"]) {
VC2 *vc2 = segue.destinationViewController;
vc2.parent = self;
}
}
Swift - An alternative is to create a reference in parent UIViewController (vc1) to child/subview UIViewController (vc2) and in vc2 to vc1. Assign the references in parent(vc1) viewDidLoad() example below.
Parent UIViewController vc1:
class vc1: UIViewController {
#IBOutlet weak var parentLabel: UILabel!
var childVc2: vc2?;
overide func viewDidLoad() {
super.viewDidLoad();
// Use childViewControllers[0] without type/class verification only
// when adding a single child UIViewController
childVc2 = self.childViewControllers[0] as? vc2;
childVc2?.parentVc1 = self
}
}
Child UIViewController vc2:
class vc2: UIViewCortoller {
var parentVc1: vc1?;
// At this point child and parent UIViewControllers are loaded and
// child views can be accessed
override func viewWillAppear(_ animated: Bool) {
parentVc1?.parentLabel.text = "Parent label can be edited from child";
}
}
In the Storyboard remember to set in Identity Inspector the parent UIViewContoller class to vc1 and child UIViewContoller class to vc2. Ctrl+drag from Container View in vc1 UIViewController to vc2 UIViewController and select Embed.