Detecting the previous view controller - iphone

I am currently intending to detect the previous view controller in the -viewDidLoad method, and my intended result would be something like this:
-(void)viewDidLoad
{
if (lastViewController==firstViewController)
{
//do something
}
else
{
//do something else
}
I did previously read about utilizing the viewControllers property of UINavigarioController (and in this case I am using a UINavigationController). However, I don't fully understand how are they supposed to implement such a thing in a if statement.

Do you mean to say you're trying to determine which VC is behind your current VC in the navigationController viewControllers stack? If so, you could use:
if ([self.navigationController.viewControllers[self.navigationController.viewControllers.count - 2]
isEqual:firstViewController]) {
//...

Related

How to identify that an UIViewController is presented

I have created an UIViewController sub class which can either be pushed in a navigation stack of a UINavigationController or presented(modally) from any UIViewController. I need to identify whether my view controller is presented, if it is presented I need to add a toolbar with a close button at the top of the view controller. (else if it is pushed in navigation stack then the default close button will get added, by using that the user can go back.)
In all the available versions say 4.3, 5.0, till 6.0, from inside an UIViewController sub class, Can I assume that the view controller is presented(modally) if the following condition is satisfied.
if(self.parentViewController == nil || self.navigationController == nil)
With iOS 5, UIViewController gained a readonly property named presentingViewController, that replaces the older semantics of parentViewController (which now describes containment). This property can be used when a view controller needs to get at the view controller that’s presenting it — note: this will often be something else than what you’d expect, if you’re new to the API!
In addition, the isBeingPresented property had been introduced to pretty much solve the class of situations you’re currently in. Check for this property in your view controller’s viewWillAppear:.
Update
I overread that you seem to target iOS 4.3 as well:
In that case, you need to guard the call to isBeingPresented with an if ([self respondsToSelector:…]) you can then in the else block check for whether the parentViewController is not nil.
Another approach to backwards compatibility might be to override +resolveInstanceMethod: to add an implementation for -isBeingPresented at runtime. This will leave your calling sites clean, and you’d get rid of runtime-magic as soon as you let go of ancient iOS support ;-)
Note, though, that there are edge cases to this, and you initial approach as well, when running on iOS <5:
The view controller can be presented contained in any other view controller—including navigation controllers. When that last case happens, you’re out of luck: parentViewController will be nil, while navigationController will not. You can try to add gobs of unwieldy code to mitigate this limitation in older iOSes…or you could just let it go.
I use the this code to check whether the UIViewController is presented.
if (uiviewcontroller.presentingViewController != nil) {
// do something
}
I had a similar case, however the view controller that I presented is wrapped in it's own navigation controller. So in that view controller when I need to determine whether or not to add the close button vs a back button, I just check the navigation controllers stack size. If the screen is presented, the stack size should be one (needs close button)... and if it is pushed using an existing navigation controller, then stack size will be larger than one (needs back button).
BOOL presented = [[self.navigationController viewControllers] count] == 1;
To handle this kind of behavior, I usually set/reset a BOOL toggling it in viewWillAppear/viewWillDisappear methods.
By the way, your test condition seems incorrect. I think you should use
if(self.parentViewController != nil || self.navigationController != nil)
Why can't you just always add the toolbar to your view controller? Is there any case the view is loaded but never presented?
In Swift on iOS 9 (or later):
if viewController.viewIfLoaded?.window != nil {
// viewController is visible
}
#saikamesh.
As you use UINavigationController to navigate your viewControllers, I think you can use topViewController (Doc here) and visibleViewController (Doc again) to reach your intention.
You mention that :
when it is pushed in navigation stack then the default close button
will get added, by using that the user can go back
If the instance of the the specific UIViewController is important, I think it better to create a shared singleton instance and provide a global presented flag:
id specificVC = [SpecificViewController sharedInstance];
if (specificVC.isPushed) {
[self.navController popToViewController:specificVC animated:YES];
}
and to check if it is presented:
if ([self.navController.visibleViewController isKindOfClass:[SpecificViewController class]]) {
// Hide or add close button
self.isPresented = YES;
}
Or, you can read the much accepted answer.
:) Hope helps.
Please check this way:
for (UIViewController*vc in [self.navigationController viewControllers]) {
if ([vc isKindOfClass: [OffersViewController class]]){ //this line also checks OffersViewController is presented or not
if(vc.isViewLoaded){
NSLog(#"Yes");
}
}
}
You could do it like this, it's fast and safe
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
// Find the top controller on the view hierarchy
while (topController.presentedViewController) {
topController = topController.presentedViewController;
}
// If the top controller it is not already presented
if (![topController isKindOfClass:[YourViewController class]]) {
// Present it
[topController presentViewController:yourViewController animated:YES completion:nil];
}
else {
// do some stuff here
}
You can at any point check whether you have a modal view controller presented or not by using the modalViewController property from your navigation controller.
Ex:
UIViewController *presentedController = self.navigationController.modalViewController;
if (presentedController) {
// At this point, you have a view controller presented from your navigation controller
if ([presentedController isKindOfClass:[controllerYouWantToCheck class]]) {
// add your toolbar/buttons/etc here
}
}
One elegant answer that I haven't seen here:
// Edit: Added 2 other modal cases
extension UIViewController {
var isModal: Bool {
return self.presentingViewController?.presentedViewController == self
|| (navigationController != nil && navigationController?.presentingViewController?.presentedViewController == navigationController)
|| tabBarController?.presentingViewController is UITabBarController
}
}
credit: based on this gist
As Martin Reed said, this is the best way
BOOL presented = [[self.navigationController viewControllers] count] == 1;
if (presented) {
[self dismissViewControllerAnimated:YES completion:^{
// do whatever you need here
}];
}
else {
[self.navigationController popViewControllerAnimated:YES];
}
If it was me I'd have a custom init method and use that when creating the vc.
vc = [[[MyUIViewControllerSubClass alloc] init] initWithToolbarAndCloseButton:YES];
Small modification in #AmitaiB answer to create a function,
func isModallyPresented(tmpVC:UIViewController) -> Bool {
return tmpVC.presentingViewController?.presentedViewController == tmpVC
|| (tmpVC.navigationController != nil && tmpVC.navigationController?.presentingViewController?.presentedViewController == tmpVC.navigationController)
|| tmpVC.tabBarController?.presentingViewController is UITabBarController
}
Just check by calling:
if(isModallyPresented(tmpVC:myTopVC)){
//return true if viewcontroller is presented
}

Using one ViewController for multiple scenes

I want to use the same ViewController for multiple scenes. Now I want to implement slightly different behavior depending on which scene is used. I guess this is possible using the identifier. Like (pseudo-code)
if (self.identifier == scene1)
{
// do this
}
else if (self.identifier == scene2)
{
// do that
}
How can I call the identifier from the ViewController?
EDIT:
I mean this identifier from the Inpector - how can I call it in code?
Thanks in advance.
I fixed it. One possible approach is to name the Segway Identifier and then check for equal string in the prepareForSegue method.
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"HelpSegue"])
{
// do this
}
else
{
// do that
}
}
I think the correct design here would be to use an enumerated value, such as:
typedef enum{
kViewControllerStyle1,
kViewControllerStyle2,
kViewControllerStyle3
} ViewControllerStyle;
Hook up all of the view elements that need adaptation to this style through interface builder outlets and add a switch-case in your "viewDidLoad" method to make the correct adjustments, relying on the current view controller style (also add a "ViewControllerStyle" property to your view controller class).

One ViewController vs. many ViewControllers

I'm building an app which consists of different views which are closely related to each other. So far, I only have one UIViewController which controls these different views. View 1 and 2 share the same background, for instance, and the transition between view 1 and 2 is a custom animation.
My problem is that both view 1 and 2 have an UIScrollView. My UIViewController is their delegate and I could have the following scrollViewDidScroll to distinguish between the two scrollviews:
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView.tag == 1)
//handle a
else if (scrollView.tag == 2)
//handle b
else if (scrollView.tag == 3)
//handle c
}
As a lot happens with scrollView 1 and different things happen with scrollView2, the code will become really messy. Ideally, I'd like to define in a separate file what happens if scrollView1 is scrolled etc.. Yet I don't want to have another UIViewController as then transitions become more difficult. I don't have a NavBar or ToolBar, so neither UINavigationController nor UITabBarController would work very well in my case.
What should I do?
I posted a similar question here.
If you don't want two view controllers, just create a separate delegate for each scroll view. Make it an NSObject which conforms to UIScrollViewDelegate and create it at the same time as the scroll view.
Seems to combine the results you seek: one view controller, but encapsulated scroll view code.
You could have a base controller class that handles the common functionality. Each different controller can inherit from this and override with their specific functionality as required.
Aka the template pattern
Edit
To expand. You say you want only one view controller. So you should create a separate class to handle the individual functionality. The View Controller has a base class pointer which gets swapped around according to the current view.
In pseudo code :
class BaseFunctionality
-(void) handleDidScroll {}
end
class ScrollViewAFunctionality : BaseFunctionality
-(void) handleDidScroll {
// Lots of interesting technical stuff...
}
end
class ScrollViewBFunctionality : BaseFunctionality
-(void) handleDidScroll {
// Lots of interesting technical stuff...
}
end
class TheViewController : UIViewController
BaseFunctionality *functionality;
-(void) swapViews {
// Code to swap views
[this.functionality release];
if (view == A)
this.functionality = [[ScrollViewAFunctionality alloc] init]
else if ( view == B)
this.functionality = [[ScrollViewBFunctionality alloc] init]
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[this.functionality handleDidScroll];
}
end

Strange behaviour in viewWillAppear

I have a TabBar Controller with some tab bar item in it.
The first time that a user tap on a tab bar item, I want that a alertview is opened, so that the user can read some little instruction tips.
I have a global variable (say CONFIG), that hold some boolean valeus (CONFIG.tip1AlreadySeen, CONFIG.tip1AllreadySeen, etc.). All these boolean values are initializated to NO.
When the user tap a tab bar item, the viewWillAppear method in its viewcontroller is executed. In this method I put a code like this one:
-(void) viewVillAppear: (BOOL) animated {
extern CONFIG; // <- it's not the actual code but it indicates that a global variable must be used
[super viewWillAppear: animated];
if(CONFIG.tip1AlreadySeen == NO) {
CONFIG.tip1AlreadySeen = YES;
// code for showing the alertview
}
}
The strange thing is that this piece of code works perfectly in one viewcontroller but doesn't work in one another.
With some debug, I fidd out that in the another viewcontroller the code is executed but the assigment CONFIG.tipAlreadySeen = YES doesn't modify the actual value of CONFIG.tipAlreadySeen. This value is still NO. Unbelievable!!!
A little workaround was using the viewDidAppear method for changing the value:
-(void) viewVillAppear: (BOOL) animated {
extern CONFIG; // <- it's not the actual code but it indicates that a global variable must be used
[super viewWillAppear: animated];
if(CONFIG.tip1AlreadySeen == NO) {
// code for showing the alertview
}
}
-(void) viewDidAppear: (BOOL) animated {
extern CONFIG;
CONFIG.tip1AlreadySeen = YES;
}
...But I really did not understand what happened!!! Someone of you could explain this behaviour?
Thanks in advance!
Marco
Why must this be global and not contained in the view controller itself? Just a simple BOOL #property on your view controller that is toggled. And, to maintain this persistent across multiple runs of your application, save out the result to NSUserDefaults, which you in turn check each time you init your view controller.

iPhone: How do I get the the UINavViewController to not pop the view controller when the user presses the tab that is already selected

I've got a Tab Bar, and each of the tabs' view controllers is a nav controller. If you press on a tab that is already selected, it pops the view controller back. for one the tabs, i want this not to happen. What do i make a delegate of? I tried overriding
-(UIViewController *)popViewControllerAnimated:(BOOL)animated
in the nav controller to return 0, but that doesn't stop it from popping!
user74574 is close, but you shouldn't return nil, you should return NO. Yes, technically they are both the same in term of the bits, bit types have meaning and ignoring that will (depending on the situation) result in warnings and/or bugs that could be detected via static analysis. The could you want to implement in your delegate should be something like this:
- (BOOL)tabBarController:(UITabBarController *)tabBarController_ shouldSelectViewController:(UIViewController *)viewController {
if (viewController == tabBarController_.selectedViewController) {
return NO;
} else {
return YES;
}
}
You can accomplish this by subclassing UITabBarController, and setting it as the UITabBarControllerDelegate for itself.
Implement tabBarController:shouldSelectViewController:, testing whether it is selecting the view controller you are concerned about, and if it is already selected. return nil if it satisfies the above.
If you'd like, I can put together some actual code, but this should get you in the right direction.