I have an iPhone app which uses a standard implementation of UINavigationController
for the app navigation.
I am trying to figure out a way to replace a view controller in the hierarchy. In other words, my app loads into a rootViewController
and when the user presses a button, the app pushes to firstViewController
. Then the user pushes another button to navigate the app to secondViewController
. Again the use navigates down to another view controller, thirdViewController. However, I want the BackButton of the thirdViewController
to pop back to firstViewController.
Essentially, when the user pushes to thirdViewController
, I would like it to replace secondViewController
in the navigation hierarchy.
Is this possible? I know it is using Three20, but I'm not in this case. Nevertheless, if it's possible in Three20, then it certainly should be using straight SDK calls. Does anyone have any thoughts?
Cheers, Brett
Pretty Simple, when about to push the thirdViewController instead of doing a simple pushViewController
do this:
NSArray * viewControllers = [self.navigationController viewControllers];
NSArray * newViewControllers = [NSArray arrayWithObjects:[viewControllers objectAtIndex:0], [viewControllers objectAtIndex:1], thirdController,nil];
[self.navigationController setViewControllers:newViewControllers];
where [viewControllers objectAtIndex:0]
and [viewControllers objectAtIndex:1]
are your rootViewController and your FirstViewController.
NSMutableArray *viewController = [NSMutableArray arrayWithArray:[navController viewControllers]];
[viewController replaceObjectAtIndex:1 withObject:replacementController];
[navController setViewControllers:viewController];
See the UINavigationController class reference for more information.
A cleaner way in Swift should be:
extension UINavigationController {
func replaceTopViewController(with viewController: UIViewController, animated: Bool) {
var vcs = viewControllers
vcs[vcs.count - 1] = viewController
setViewControllers(vcs, animated: animated)
}
}
It is not possible to animate the transition if you simply replace the viewController
in the navigation controllers view controller array. I would recommend doing the following in the viewWillAppear
method of the 3rd view controller.
-(void) viewWillAppear:(BOOL)animated
{
NSArray *vCs=[[self navigationController] viewControllers];
NSMutableArray *nvCs=nil;
//remove the view controller before the current view controller
nvCs=[[NSMutableArray alloc]initWithArray:vCs];
[nvCs removeObjectAtIndex:([nvCs count]-2)];
[[self navigationController] setViewControllers:nvCs];
[super viewWillAppear:animated];
}
Swift 4 version:
if var viewControllers = navigationController?.viewControllers {
viewControllers[viewControllers.count - 1] = newViewController
navigationController?.viewControllers = viewControllers
}
Since you are just trying to pop twice from the ViewControllers stack, you can probably get the same result by calling
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated
from the back button of the thirdViewController
- (void)swapTopViewController:(UIViewController *)topViewController{
NSArray *viewControllers = [self.navigationController viewControllers];
NSMutableArray *editableViewControllers = [NSMutableArray arrayWithArray:viewControllers];
[editableViewControllers removeLastObject];
[editableViewControllers addObject:topViewController];
[self.navigationController setViewControllers:editableViewControllers];
}
extension UINavigationController {
func setTop(viewController: UIViewController) {
viewControllers = [viewController]
}
}
Swift 5 UINavigationController extension, which supports Fade
animation as well when replacing.
extension UINavigationController {
func find(of type: UIViewController.Type) -> UIViewController? {
for controller in self.viewControllers {
if controller.isKind(of: type) {
return controller
}
}
return nil
}
func go(to controller: UIViewController, animated: Bool = true) {
if let old = self.find(of: type(of: controller)) {
self.popToViewController(old, animated: animated)
}
else {
self.pushViewController(controller, animated: animated)
}
}
fileprivate func prepareFade() {
let transition: CATransition = CATransition()
transition.duration = 0.3
transition.type = CATransitionType.fade
view.layer.add(transition, forKey: nil)
}
func replace(to controller: UIViewController, animated: Bool = true, fade: Bool = true) {
guard let current = self.topViewController else {
return
}
var controllers = self.viewControllers
controllers.remove(current)
controllers.append(controller)
if fade {
prepareFade()
self.setViewControllers(controllers, animated: false)
}
else {
self.setViewControllers(controllers, animated: animated)
}
}
}
extension Array where Element: Equatable {
mutating func remove(_ element: Element) {
if let index = self.firstIndex(of: element) {
self.remove(at: index)
}
}
}
This answer from @duan is perfect and will literally replace the top item with a new item:
extension UINavigationController {
func replaceTopViewController(with vc: UIViewController, animated: Bool) {
var vcs = viewControllers
vcs[vcs.count - 1] = vc // NB watch for bizarre zero size
setViewControllers(vcs, animated: animated)
}
}
However very often, you'll have a "Base" controller which you want to always be there.
If you want to clear everything safely to the Base and change to the new one (WITHOUT viewWillAppear running in Base), then:
extension UINavigationController {
func popToRoot(andPushOnly: UIViewController) {
var vcs = viewControllers
while vcs.count > 1 { vcs.removeLast() }
if vcs.count < 1 { return }
vcs.append(andPushOnly)
setViewControllers(vcs, animated: false)
}
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.