Switching ViewControllers with UISegmentedControl in iOS5 - ios

I am trying something very simple but somehow I can't get it to work. All I try to do is switching between 2 View Controllers using an UISegmentedControl as you can see it for example in the App Store application in the Highlights tab.
I am using iOS5 and Storyboards.
Here's my Storyboad line up:
So I have a root View Controller and two UITableViews - This 2 TableViews I want to switch.
Here's how the implementation file looks like
#import "SegmentedLocationViewController.h"
#import "PastEventsLocationViewController.h"
#import "FutureEventsLocationViewController.h"
#interface SegmentedLocationViewController()
#property (weak, nonatomic) IBOutlet UISegmentedControl *segmentedControl;
#property (strong, nonatomic) NSArray *viewControllers;
#end
#implementation SegmentedLocationViewController
#synthesize segmentedControl = _segmentedControl;
#synthesize viewControllers = _viewControllers;
- (IBAction)indexDidChangeForSegmentedControl:(UISegmentedControl*)segmentedControl
{
NSLog(#"index: %d", segmentedControl.selectedSegmentIndex);
}
- (void)setupViewControllers
{
PastEventsLocationViewController *pastEventsLocationViewController = [[PastEventsLocationViewController alloc] initWithStyle:UITableViewStylePlain];
FutureEventsLocationViewController *futureEventsLocationViewController = [[FutureEventsLocationViewController alloc] initWithStyle:UITableViewStylePlain];
self.viewControllers = [NSArray arrayWithObjects:pastEventsLocationViewController, futureEventsLocationViewController, nil];
}
- (void)setupUI
{
[self.segmentedControl addTarget:self action:#selector(indexDidChangeForSegmentedControl:) forControlEvents:UIControlEventValueChanged];
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
[super viewDidLoad];
[self setupViewControllers];
[self setupUI];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return YES;
}
#end
I can trigger the switch event and can log the currently selected index. But I don't have any idea where to go from here.
Maybe someone can turn my attention towards a certain direction...?

This code works pretty well for your purpose, I use it for one of my new apps.
It uses the new UIViewController containment APIs that allow UIViewControllers inside your own UIViewControllers without the hassles of manually forwarding stuff like viewDidAppear:
- (void)viewDidLoad {
[super viewDidLoad];
// add viewController so you can switch them later.
UIViewController *vc = [self viewControllerForSegmentIndex:self.typeSegmentedControl.selectedSegmentIndex];
[self addChildViewController:vc];
vc.view.frame = self.contentView.bounds;
[self.contentView addSubview:vc.view];
self.currentViewController = vc;
}
- (IBAction)segmentChanged:(UISegmentedControl *)sender {
UIViewController *vc = [self viewControllerForSegmentIndex:sender.selectedSegmentIndex];
[self addChildViewController:vc];
[self transitionFromViewController:self.currentViewController toViewController:vc duration:0.5 options:UIViewAnimationOptionTransitionFlipFromBottom animations:^{
[self.currentViewController.view removeFromSuperview];
vc.view.frame = self.contentView.bounds;
[self.contentView addSubview:vc.view];
} completion:^(BOOL finished) {
[vc didMoveToParentViewController:self];
[self.currentViewController removeFromParentViewController];
self.currentViewController = vc;
}];
self.navigationItem.title = vc.title;
}
- (UIViewController *)viewControllerForSegmentIndex:(NSInteger)index {
UIViewController *vc;
switch (index) {
case 0:
vc = [self.storyboard instantiateViewControllerWithIdentifier:#"FooViewController"];
break;
case 1:
vc = [self.storyboard instantiateViewControllerWithIdentifier:#"BarViewController"];
break;
}
return vc;
}
I got this stuff from chapter 22 of Ray Wenderlichs book iOS5 by tutorial.
Unfortunately I don't have a public link to a tutorial. But there is a WWDC 2011 video titled "Implementing UIViewController Containment"
EDIT
self.typeSegmentedControl is outlet for your UISegmentedControl
self.contentView is outlet for your container view
self.currentViewController is just a property that we're using to store our currently used UIViewController

It's Matthias Bauch solution, but thought of sharing it in Swift anyway!
Edit:
Adding a link to a Swift 2.0 ready made demo app.
https://github.com/ahmed-abdurrahman/taby-segmented-control
var currentViewController: UIViewController?
#IBOutlet weak var contentView: UIView!
#IBOutlet weak var segmentedControl: UISegmentedControl!
#IBAction func switchHappeningTabs(sender: UISegmentedControl) {
if let vc = viewControllerForSelectedSegmentIndex(sender.selectedSegmentIndex) {
self.addChildViewController(vc)
self.transitionFromViewController(self.currentViewController!, toViewController: vc, duration: 0.5, options: UIViewAnimationOptions.TransitionFlipFromRight, animations: {
self.currentViewController!.view.removeFromSuperview()
vc.view.frame = self.contentView.bounds
self.contentView.addSubview(vc.view)
}, completion: { finished in
vc.didMoveToParentViewController(self)
self.currentViewController!.removeFromParentViewController()
self.currentViewController = vc
}
)
}
}
override func viewDidLoad() {
super.viewDidLoad()
if let vc = self.viewControllerForSelectedSegmentIndex(self.segmentedControl.selectedSegmentIndex) {
self.addChildViewController(vc)
self.contentView.addSubview(vc.view)
self.currentViewController = vc
}
}
func viewControllerForSelectedSegmentIndex(index: Int) -> UIViewController? {
var vc: UIViewController?
switch index {
case 0:
vc = self.storyboard?.instantiateViewControllerWithIdentifier("FooViewController") as? UIViewController
case 1:
vc = self.storyboard?.instantiateViewControllerWithIdentifier("BarViewController") as? UIViewController
default:
return nil
}
return vc
}

For someone want to implement the same in swift
import UIKit
class HomeController: UIViewController {
var currentViewController:UIViewController?
#IBOutlet weak var homeController: UIView!
override func viewDidLoad() {
super.viewDidLoad()
let initialController:UIViewController = self.viewControllerForSegmentedIndex(0)
self.addChildViewController(initialController)
initialController.view.frame = self.homeController.bounds
self.homeController.addSubview(initialController.view)
self.currentViewController = initialController
}
#IBAction func segmentChanged(sender: UISegmentedControl) {
let viewCOntroller:UIViewController = viewControllerForSegmentedIndex(sender.selectedSegmentIndex)
self.addChildViewController(viewCOntroller)
self.transitionFromViewController(self.currentViewController!, toViewController: viewCOntroller, duration: 0.5, options: UIViewAnimationOptions.TransitionFlipFromBottom, animations: {
self.currentViewController?.view.removeFromSuperview()
viewCOntroller.view.frame = self.homeController.bounds
self.homeController.addSubview(viewCOntroller.view)
}, completion:{ finished in
viewCOntroller.didMoveToParentViewController(self)
self.currentViewController?.removeFromParentViewController()
self.currentViewController = viewCOntroller
})
}
func viewControllerForSegmentedIndex(index:Int) -> UIViewController {
var viewController:UIViewController?
switch index {
case 0:
viewController = self.storyboard?.instantiateViewControllerWithIdentifier("StoryboardIdForFirstController")
break
case 1:
viewController = self.storyboard?.instantiateViewControllerWithIdentifier("StoryboardIdForSecondController")
break
case 2:
viewController = self.storyboard?.instantiateViewControllerWithIdentifier("StoryboardIdForThirdController")
break
default:
break
}
return viewController!
}
}
Storyboard view

A is root view controller, and B, C is sub view controller.
When you click segment, add sub view controller.
result
design
code
import UIKit
class SegmentViewController: UIViewController {
#IBOutlet weak var containerView: UIView!
var leftViewController: LeftViewController!
var rightViewController: RightViewController!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
if let sb = storyboard {
leftViewController = sb.instantiateViewControllerWithIdentifier("leftViewController") as! LeftViewController
switchViewController(from: nil, to: leftViewController)
} else {
print("storyboard is nil")
}
}
func switchViewController(from fromVC: UIViewController?, to toVC: UIViewController?) {
if let from = fromVC {
from.willMoveToParentViewController(nil)
from.view.removeFromSuperview()
from.removeFromParentViewController()
} else {
print("fromVC is nil")
}
if let to = toVC {
self.addChildViewController(to)
to.view.frame = CGRectMake(0, 0, containerView.frame.width, containerView.frame.height)
self.containerView.insertSubview(to.view, atIndex: 0)
to.didMoveToParentViewController(self)
} else {
print("toVC is nil")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
removeViewController()
}
func removeViewController() {
if let leftVC = leftViewController {
if let _ = leftVC.parentViewController {
print("leftVC is using")
} else {
print("set leftVC = nil")
leftViewController = nil
}
}
if let rightVC = rightViewController {
if let _ = rightVC.parentViewController {
print("rightVC is using")
} else {
print("set rightVC = nil")
rightViewController = nil
}
}
}
#IBAction func onSegmentValueChanged(sender: UISegmentedControl) {
UIView.beginAnimations("xxx", context: nil)
UIView.setAnimationDuration(0.4)
UIView.setAnimationCurve(.EaseInOut)
switch sender.selectedSegmentIndex {
case 0:
UIView.setAnimationTransition(.FlipFromRight, forView: self.containerView, cache: true)
if let leftVC = leftViewController {
switchViewController(from: rightViewController, to: leftVC)
} else {
if let sb = storyboard {
leftViewController = sb.instantiateViewControllerWithIdentifier("leftViewController") as! LeftViewController
switchViewController(from: rightViewController, to: leftViewController)
} else {
print("storyboard is nil")
}
}
default:
UIView.setAnimationTransition(.FlipFromLeft, forView: self.containerView, cache: true)
if let rightVC = rightViewController {
switchViewController(from: leftViewController, to: rightVC)
} else {
if let sb = storyboard {
rightViewController = sb.instantiateViewControllerWithIdentifier("rightViewController") as! RightViewController
switchViewController(from: leftViewController, to: rightViewController)
} else {
print("storyboard is nil")
}
}
}
UIView.commitAnimations()
}
}

You could embed your initial view controller in a navigation controller. Do that by selecting the view controller in the storyboard, then Editor->Embed in->Navigation Controller.
In your indexDidChangeForSegmentedControl: method you simply push the corresponding view controller to the navigation stack:
- (IBAction)indexDidChangeForSegmentedControl:(UISegmentedControl*)segmentedControl
{
[self.navigationController pushViewController:[self.viewControllers objectAtIndex:[segmentedControl.selectedIndex]] animated:YES];
}
But your approach makes not too much sense at all when you are using storyboards.
I don't know whether you can wire up a single segmented control button to a view controller using segues. Just try it.

I ended up using a navigation controller to achieve a similar functionality.
import UIKit
class BaseViewController: UIViewController {
var currentSegmentIndex = 0
#IBOutlet weak var segmentedControl: UISegmentedControl!
override func viewDidLoad() {
super.viewDidLoad()
segmentedControl.selectedSegmentIndex = currentSegmentIndex
}
#IBAction func segmentedControlChanged(_ sender: Any) {
let idx = segmentedControl.selectedSegmentIndex
let storyboard = UIStoryboard(name: "Main", bundle: nil)
switch idx {
case 0:
let vc = storyboard.instantiateViewController(
withIdentifier: "Foo") as! FooViewController
vc.currentSegmentIndex = 0
navigationController?.viewControllers = [vc]
break
case 1:
let vc = storyboard.instantiateViewController(
withIdentifier: "Bar") as! BarViewController
vc.currentSegmentIndex = 1
navigationController?.viewControllers = [vc]
break
default:
break
}
}
}

Related

I am trying to using a custom delegate in swift but when I want to call its methods, it did not get called

I have been searching for how the delegate works and I tried to do it in my project. Unfortunately, the delegate method I implement does not get called ever. I am trying to do a slide-out navigation panel. so what I did is that I put two uicontainerviews, one is for slide-out navigation panel and the other for main view controller
enter image description here
The code is that
For main view controller
protocol MainViewControllerDelegate {
func toggleSideMenu()
}
class MainViewController: UIViewController {
var delegate: MainViewControllerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
}
// MARK: - Slide Action
#IBAction func slideMenuTapped(_ sender: UIBarButtonItem){
delegate?.toggleSideMenu()
print("Slide Menu has been tapped")
}
}
For container view controller
class ContainerVC: UIViewController {
#IBOutlet weak var SideMenuConstraint: NSLayoutConstraint!
#IBOutlet weak var slideMenuContainer: UIView!
#IBOutlet weak var mainViewContainer: UIView!
var mainViewController: MainViewController?
var isSideMenuOpened = false
override func viewDidLoad() {
super.viewDidLoad()
mainViewController = UIStoryboard.mainViewController()
mainViewController?.delegate = self
}
}
extension ContainerVC: MainViewControllerDelegate{
func toggleSideMenu() {
print("It works")
if isSideMenuOpened{
isSideMenuOpened = false
SideMenuConstraint.constant = -260
mainViewContainer.layer.shadowOpacity = 0
} else {
isSideMenuOpened = true
SideMenuConstraint.constant = 0
mainViewContainer.layer.shadowOpacity = 0.59
}
UIView.animate(withDuration: 0.3) {
self.view.layoutIfNeeded()
}
}
}
extension UIStoryboard{
static func mainStoryboard() -> UIStoryboard { return UIStoryboard(name: "Main", bundle: Bundle.main) }
static func mainViewController() -> MainViewController? {
return mainStoryboard().instantiateViewController(withIdentifier: "MainViewController") as? MainViewController
}
}
Please let know what's wrong
I think the reason is that you embed your main view controller in navigation controller :
let navigationController = self.childViewControllers.last as! UINavigationController
let mainViewController = navigationController.topViewController as! MainViewController
mainViewController?.delegate = self
Here is where you got wrong:
mainViewController = UIStoryboard.mainViewController()
mainViewController?.delegate = self
this mainViewController is not the same as the child of the container view controller, so setting its delegate doesn't really do anything.
You need to first get the VC that is the child of the container view controller:
mainViewController = self.childViewControllers.last as! MainViewController
mainViewController.delegate = self

Open UIViewController upon clicking on UITabbarItem

I have created a UIViewController with a UITabbar in it.
I did not use UITabbarController because I wanted UITabbar on the top of the screen.
Upon clicking tab1, I want to present controller1 and on clicking tab2 I want to present controller 2. I don't want the tabbar to hide. I want to display the controller beneath the tabbar.
#interface MTLeaderFactoViewController () <UITabBarDelegate>
#property (weak, nonatomic) IBOutlet UITabBar *tabBar;
#end
#implementation MTLeaderFactoViewController
- (void)viewDidLoad {
[super viewDidLoad];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
}
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item {
if (item.tag == 0) {
NSLog(#"item tag 0");
} else {
NSLog(#"item tag 1");
}
}
#end
My questions:
1) didSelectItem method is not triggered even after using UITabbarDelegate
2) What is the most elegant way of displaying the controller when clicked on a button? I don't want to use segue as all the controllers are in different storyboards.
For now, I plan to do
Controller1 *fp = [Controller1 controllerStoryboard:STORYBOARD_COURSE];
[self addChildViewController:fp];
[self.view addSubview:fp.view];
[fp didMoveToParentViewController:self];
EDIT 1:
Controller1 *fp = [Controller1 controllerStoryboard:STORYBOARD_COURSE];
[self addChildViewController:fp];
[self.view addSubview:fp.view];
[fp didMoveToParentViewController:self];
Tried this but it hides the tab bar. I want to utilize the space beneath the tab bar to display the controller
What you need to do is have a basecontroller class which will contain a tabbar(programatically created) then you can achive the desired output heres a sample baseController that i created,
import UIKit
class BaseViewController: UIViewController,UITabBarDelegate{
override func viewDidLoad() {
super.viewDidLoad()
let myTabBar = UITabBar()
myTabBar.frame = CGRect(x: 0, y: 60, width:self.view.frame.size.width, height: 50)
let one = UITabBarItem()
one.title = "one"
one.tag = 1
let two = UITabBarItem()
two.title = "two"
two.tag = 2
myTabBar.setItems([one,two], animated: false)
self.view.addSubview(myTabBar)
myTabBar.delegate = self
}
func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
switch item.tag {
case 1:
let controller = storyboard?.instantiateViewController(withIdentifier: "SecondViewController")
addChildViewController(controller!)
view.addSubview((controller?.view)!)
controller?.didMove(toParentViewController: self)
break
case 2:
let controller = storyboard?.instantiateViewController(withIdentifier: "ViewController")
addChildViewController(controller!)
view.addSubview((controller?.view)!)
controller?.didMove(toParentViewController: self)
break
default:
break
}
}
}
View Controller class :
import UIKit
class ViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
}
SecondView Controller :
import UIKit
class SecondViewController: BaseViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}
I would suggest using UITabBarController instead of UIViewController.
Add UITabViewController in your StoryBoard name it FirstTabBarController.
Add child view controller to FirstTabBarController. In all your child view of add UITabbar. Connect the TabBar delegate to each of your child ViewController.
Hide the default TabBar in your default FirstTabBarController.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.tabBar.isHidden = true;
[self setSelectedIndex:1];
}
Add action to TabBar in your child ViewController as
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item {
NSUInteger index = [[theTabBar items] indexOfObject:item];
NSLog(#"Tab index = %u", (int)indexO);
[self.navigationController.tabBarController setSelectedIndex:index];
}
I would prefer this method over manually adding adding or removing ViewController as subview, let the UITabBarController manage that for you. Do let me know if you have further queries.

Switch the sub viewController use segmented control

I want switch the sub view controller from the segment control.
There is vc1 and vc2 in the storyboard, and there is segment control on the main vc's navigation controller bar.
I want to add the vc1 and vc2 on the main vc, how to switch the sub vc use the segment controller?
How to do with that?
Follow below steps.
Add VC1 & VC2 as a childVC of mainVC.
On segment 1 selection VC1.view.hidden = false & vc2.view.hidden = true
On segment 2 selection VC2.view.hidden = false & vc1.view.hidden = true
take reference
How-to-add-childVC
How-tobind-segment-control-action
Code work
#IBAction func indexChanged(_ sender: AnyObject) {
switch segmentedControl.selectedSegmentIndex
{
case 0:
vc1.view.hidden = false
vc2.view.hidden = true
case 1:
vc2.view.hidden = false
vc1.view.hidden = true
default:
break
}
}
The accepted answer is obviously correct, but I honestly prefer using container view for each tab in UISegmentedControl. It that way the logic related with each view is separated in different view controller. You can achieve this in that way:
class TopViewController: UIViewController {
#IBOutlet weak var firstContainerView: UIView!
#IBOutlet weak var secondContainerView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
firstContainerView.alpha = 1.0
secondContainerView.alpha = 0.0
}
#IBAction func didChangeIndex(_ sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case 0:
firstContainerView.alpha = 1.0
secondContainerView.alpha = 0.0
case 1:
firstContainerView.alpha = 0.0
secondContainerView.alpha = 1.0
default:
break
}
}
}
If you would like to access properties of FirstViewController or SecondViewController, you can implement prepare(for segue: UIStoryboardSegue, sender: Any?) method.
You should add vc, and the vc.view to the Main ViewController:
When you select the Segmented Control, you can hide the sub viewcontroller's view like below:
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIStoryboard *sb = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
_vc1 = [sb instantiateViewControllerWithIdentifier:#"ViewController1"];
_vc2 = [sb instantiateViewControllerWithIdentifier:#"ViewController2"];
[self addChildViewController:_vc1];
[self addChildViewController:_vc2];
[self.view addSubview:_vc1.view];
[self.view addSubview:_vc2.view];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (IBAction)segAction:(UISegmentedControl *)sender {
if (1 == sender.selectedSegmentIndex) {
NSLog(#"1");
_vc1.view.hidden = YES;
_vc2.view.hidden = NO;
}else {
NSLog(#"%ld", sender.selectedSegmentIndex);
_vc2.view.hidden = YES;
_vc1.view.hidden = NO;
}
}

Can we handle the error "Cannot convert the value of type BOOL to expected argument UIiew"

I am tired of trying to PopOver the view controller and searched every where, tried myself also.This issue has again arrived and i am confused what to do next
func showPopover(base: UIView)
{
let storyboard : UIStoryboard = UIStoryboard(name: "Messaging", bundle: nil)
if let viewController = storyboard.instantiateViewControllerWithIdentifier("PreferencesViewController") as?PreferencesViewController
{
let navController = UINavigationController(rootViewController: viewController)
navController.modalPresentationStyle = .Popover
if let pctrl = navController.popoverPresentationController
{
pctrl.delegate = self
pctrl.sourceView = base
pctrl.sourceRect = base.bounds
self.presentViewController(navController, animated: true, completion: nil)
}
}
}
I am calling this method in any one of the actions clicked from UIBarButtons
func optionChoosed(hello:Bool)
{
if (hello)
{
self.showPopover(hello)
}
}
it says Cannot convert the value of type BOOL to expected argument UIiew.. can we fix this or am i going wrong direction.
class SHNewStylesViewController: UIViewController, UIPopoverPresentationControllerDelegate {
var popover: UIPopoverPresentationController? = nil
//MARK: - View life cycle
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
//MARK: - IBActions
#IBAction func showPopover(sender: AnyObject) {
let genderViewController = storyboard!.instantiateViewControllerWithIdentifier("ViewControllerTwoIdentifier") as! ViewControllerTwo
genderViewController.modalPresentationStyle = UIModalPresentationStyle.Popover
genderViewController.preferredContentSize = CGSize(width: 200.0, height: 400.0) // To change the popover size
popover = genderViewController.popoverPresentationController!
popover!.barButtonItem = sender as? UIBarButtonItem
popover!.delegate = self
presentViewController(genderViewController, animated: true, completion:nil)
}
//MARK: - Popover Presentation Controller Delegate methods
func adaptivePresentationStyleForPresentationController(controller: UIPresentationController) -> UIModalPresentationStyle {
return UIModalPresentationStyle.None
}
}
In my case showPopover is a IBAction of my bar button item. You can you that code inside the showPopover method wherever you want.
Thanks:)

After popToRootViewController View is empty, dont know why

I am using a UINavigationController with a different Controller. I have overridden the BackButton from the Navigation. In some cases I want to jump to RootController if BackButton is tapped.
But in this case the user jumps Back, but the whole View is empty.
The behaviour is strange and I don’t know why. Can someone help me please.
To go to RootController I do the following.
if([self.tempnavigationInfo.refkey isEqual:#"main"]){
[self.navigationController popToRootViewControllerAnimated:NO];
}
For the BackButton I have implemented this handler:
#implementation UINavigationController (ShouldPopOnBackButton)
- (BOOL)navigationBar:(UINavigationBar *)navigationBar shouldPopItem:(UINavigationItem *)item {
if([self.viewControllers count] < [navigationBar.items count]) {
return YES;
}
BOOL shouldPop = YES;
UIViewController* vc = [self topViewController];
if([vc respondsToSelector:#selector(navigationShouldPopOnBackButton)]) {
shouldPop = [vc navigationShouldPopOnBackButton];
}
if(shouldPop) {
dispatch_async(dispatch_get_main_queue(), ^{
[self popViewControllerAnimated:YES];
});
} else {
// Workaround for iOS7.1
for(UIView *subview in [navigationBar subviews]) {
if(subview.alpha < 1.) {
[UIView animateWithDuration:.25 animations:^{
subview.alpha = 1.;
}];
}
}
}
return NO;
}
In the ViewController I handle it this way:
-(BOOL) navigationShouldPopOnBackButton {
if (self.backConfirmation != nil
&& self.backConfirmation.navigation != nil
&& self.backConfirmation.confirmation == nil){
[[NSNotificationCenter defaultCenter] postNotificationName:#"gotoWithoutConfirmation" object:self.backConfirmation.navigation];
return NO;
}
return YES;
}
In my MainController I catch the notification and call the method with "popToRoot" logic above:
-(void) handleNotificationFromFormView:(NSNotification*) notification
{
if([notification.name isEqualToString:#"gotoWithoutConfirmation"]){
self.tempnavigationInfo = (NavigationInfo*)notification.object;
[self goBackTo];
}
in case anyone else ends up here - I had a similar issue that confused me for a while - it turned out to be because I was using a custom transition that moved the content of the root vc off screen. Popping to root without specifying a transition to reverse the translation means the root comes back with with its content off screen - it doesn't get reset to its default state automatically.
For example - if you set a custom transition using a UINavigationControllerDelegate like this:
class NavigationControllerDelegate: NSObject, UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if let _ = fromVC as? FirstVC, let _ = toVC as? SecondVC {
return SlideTransitionAnimator(direction: .left)
}
}
and your transition class does some transformation to the rootViewController - eg, as in my case, moving it off screen:
class SlideTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromView = transitionContext.view(forKey: .from),
let toView = transitionContext.view(forKey: .to) else {return }
let containerView = transitionContext.containerView
containerView.addSubview(toView)
switch direction {
case .left:
toView.frame.origin.x = toView.frame.size.width
default:
toView.frame.origin.x = -toView.frame.size.width
}
UIView.animate(withDuration: 1,delay: 0, options: .curveEaseInOut,
animations: {
fromView.alpha = 0
switch self.direction {
case .left:
fromView.frame.origin.x -= fromView.frame.size.width
default:
fromView.frame.origin.x += fromView.frame.size.width
}
toView.alpha = 1.0
toView.frame.origin.x = 0
},
completion: { _ in
transitionContext.completeTransition(true)
})
}
}
if you don't reverse this animation or reset it when calling popToRootViewController you can end up with a seemingly empty root view controller.

Resources