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
}}
Related
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 :)
I have TabBarController project, with navigation bar. On navigation bar I have popover on right BarButtonItem. On that popover ViewController I have one button. On that button click I want to move to any TabBarController.
I tried with
tababarController.selectedIndex = 1
But view is not switching
Is there any solution?
You will dismiss popover and call delegate method
From popover viewcontroller in main view controller
And then set the selected index of a toolbar in your delegate method.
setSelectedIndex should work right.
Maybe you are not getting correct reference of your UITabBarController and while checking, you might get it nil.
class PopupviewController {
weak var delegate: NavigateViewDelegate?
func buttonPress () {
delegate?.navigate()
}
}
protocol NavigateViewDelegate : class {
func navigate()
}
class TabBarViewController :NavigateViewDelegate {
func navigate() {
tababarController.selectedIndex = 1
}
}
I have two ViewControllers, first and second. They are connected with show (push) segue. I click button on firstViewController to go to secondViewController. Then using automatically added navigation controller <First , I go back to firstViewController. However, here I would like to get alert message when navigation controller to firstViewContoller is pressed. How do I do it?
What you are looking for is UINavigationControllerDelegate.
I believe the method that gives you the message you need is
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
And
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated;
In your CustomViewController, you are going to want to conform to the UINavigationControllerDelegate protocol like this:
#interface CustomViewController : UIViewController <UINavigationControllerDelegate>
And then override the delegate methods above to get the messages you are looking for.
Here is an complete implementation in Swift:
import UIKit
class ViewController: UIViewController, UINavigationControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
navigationController?.delegate = self
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
println(viewController)
}
}
class FirstViewController: ViewController {
}
class SecondViewController: ViewController {
}
You can work on the viewWillDisappear method on your second view controller like this:
- (void)viewWillDisappear:(BOOL)animated
{
if(self.isMovingFromParentViewController){
NSLog(#"Controller being popped");
}
}
In this case, self.isMovingFromParentViewController will be true if the controller is being popped.
You can also check for self.isMovingToParentViewController on viewWillAppear for example, to check that the controller is being pushed.
Also self.isBeingDismissed and self.isBeingPresented are available and refer to when a controller is being presented/dismissed (modally).
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.
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