iOS - accessing a property in an unknown view controller - ios

In the appDelegate, I want to do something in case the visible view controller is kind of class MyViewController. Then I want to check the property myVar that is defined in the MyViewController class. This is my code:
UIViewController *vc = [self visibleViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
if ([vc isKindOfClass:[MyViewController class]]) {
if (vc.myVar == "foo") {
// do something
}
}
I have an error saying: Property myVar not found on object type of 'UIViewController *'
How do I tell the code that I'm sure now that vc is a type of MyViewController class?

What you need to do is casting
UIViewController *vc = [self visibleViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
if ([vc isKindOfClass:[MyViewController class]]) {
if ([(MyViewController *)vc myVar] == "foo") {
// do something
}
}

You are sure that vc is an MyViewController object, but in the next line vc is still considered as a UIViewController. You can create a MyViewController pointer or cast it automatically :
UIViewController *vc = [self visibleViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
if ([vc isKindOfClass:[MyViewController class]]) {
MyViewController * myViewControllerVc = (MyViewController *)vc;
if (myViewControllerVc.myVar == "foo") {
// do something
}
}
or
UIViewController *vc = [self visibleViewController:[UIApplication sharedApplication].keyWindow.rootViewController];
if ([vc isKindOfClass:[MyViewController class]]) {
if (((MyViewController *)vc).myVar == "foo") {
// do something
}
}
I tend to use the first solution when i will use more than once the custom class properties, otherwise i use the second one.

Related

iOS use the delegate pass value not trigger the method

I was test the delegate pass the value in objective-c.
I know there are other methods can pass string between UIViewControllers like NSNotifyCenter..etc.
Now I want to try to use the delegate pass value .
But I encounter some problems.
I use the navigation and there have two UIViewController(FirstUIViewcontroller and SecondUIViewController).
Now I want to use manual to change to SecondUIViewController,not use the button drag to the SecondUIViewController at FirstUIViewController.
So I add the code in the FirstUIViewController.m button action.
- (IBAction)pushBtnAction:(id)sender {
SecondViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"SecondViewController"];
[self.navigationController pushViewController:controller animated:YES];
}
Then I want to pass the value from the SecondUIViewcontroller when I pop the view controller.
So I add the delegate implement and se the delegate in the FirstUIViewController.m.
- (void)viewDidLoad {
[super viewDidLoad];
UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
secondVC = (SecondViewController*)[mainStoryboard instantiateViewControllerWithIdentifier:#"SecondViewController"];
secondVC.delegate = self;
}
-(void) passSecondVC:(SecondViewController *)vc didAddValue:(NSString *)str
{
NSLog(#"second str:%#",str);
}
In the SecondUIViewController.h , I had declare delegate method.
#class SecondViewController;
#protocol SecondViewControllerDelegate <NSObject>
#optional
-(void)passSecondVC:(SecondViewController*)vc didAddValue:(NSString*) str;
#end
#interface SecondViewController : UIViewController
#property (nonatomic,assign) id<SecondViewControllerDelegate> delegate;
- (IBAction)passValueDelegatBtnAction:(id)sender;
In the SecondViewController.m ,
when I click the button will pop self uiviewcontroller and pass the value to FirstUIViewController.
- (IBAction)passValueDelegatBtnAction:(id)sender {
if( [self.delegate respondsToSelector:#selector(passSecondVC:didAddValue:)])
{
[self.delegate passSecondVC:self didAddValue:#"this is string from sencond vc"];
}
[self.navigationController popViewControllerAnimated:YES];
}
(My problems)
But in this status , I always can't get the value in the delegate method in the FirstUIViewController.
I had try to other method like below in the FirstViewController.m
- (void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
NSLog(#"segue");
id vc = segue.destinationViewController;
if( [vc isKindOfClass:[SecondViewController class]])
{
SecondViewController *secondVC = vc;
secondVC.delegate = self;
}
}
There are same problem.
I can't get the value from the delegate method.
Have anyone know where the problems?
I post my completely code in here.
Thank you very much.
Alright!
Remove your code from the viewDidLoad: method and set the delegate when you push the secondViewController.
- (IBAction)pushBtnAction:(id)sender {
SecondViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"SecondViewController"];
[controller setDelegate:self];
[self.navigationController pushViewController:controller animated:YES];
}
So what was going wrong?
Ans: You create a new object in your viewDidLoad: method and set your firstViewController as delegate to it. Now while pushing you are creating another object of SecondViewController whose delegate is not set.
I downloaded your code & fixed it.
You don't want this line. so comment it,
// UIStoryboard *mainStoryboard = [UIStoryboard storyboardWithName:#"Main" bundle: nil];
// secondVC = (SecondViewController*)[mainStoryboard instantiateViewControllerWithIdentifier:#"SecondViewController"];
// secondVC.delegate = self;
Edit this method as below,
- (IBAction)pushBtnAction:(id)sender {
SecondViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"SecondViewController"];
controller.delegate = self;
[self.navigationController pushViewController:controller animated:YES];
}
Also edit this method as below,
- (IBAction)passValueDelegatBtnAction:(id)sender {
// if( [self.delegate respondsToSelector:#selector(passSecondVC:didAddValue:)])
// {
[self.delegate passSecondVC:self didAddValue:#"this is string from sencond vc"];
// }
[self.navigationController popViewControllerAnimated:YES];
}
you forgot some code in pushBtnAction..
- (IBAction)pushBtnAction:(id)sender {
SecondViewController *controller = [self.storyboard instantiateViewControllerWithIdentifier:#"SecondViewController"]; // It's different to VC you init in viewDidLoad
controller.delegate = self; // you forgot....
[self.navigationController pushViewController:controller animated:YES];
}
prepareForSegue be call when you use segue to navigation.

Can anybody explain me why popToViewController: method doesn't work?

I use this code:
MainViewController *mvc = [[MainViewController alloc] init];
[self.navigationController popToViewController:mvc animated:YES];
and it crash and I don't know why or where I am wrong. The error is:
reason: 'Tried to pop to a view controller that doesn't exist.' , but my view controller exists.
If somebody can help me..
I would recommend writing a method similar to this in the view controller you are popping.
- (void) popCurrentViewController
{
UIViewController *popDestination = nil;
for (UIViewController *viewController in self.navigationController.viewControllers)
{
if ([viewController isKindOfClass:[MainViewController class]])
{
popDestination = viewController;
}
}
if (popDestination)
{
NSLog(#"%# Popping...", self);
[self.navigationController popToViewController:popDestination animated:true];
}
else
{
// Handle if it can not find the specific view controller.
}
}

Find ViewController in NavigationController with special key

I have a NavigationController with different ViewControllers. Sometimes I want to push a ViewController to NavigationController, which is already included.
In this case I want to move to the existing from TopController, and pop all other ViewControllers between top and the existing one.
Is there a way to give a ViewController a special id (for example a NSString), to find him later in "NavigationController.viewControllers"?
Or should I use a seperate Dictionary to manage my ViewControllers?
Or is there a better way, I dont consider.
best regards
On your appDelegate, you can store all your ViewController and when you want to pop call :
[self.navigationController popToViewController:yourViewController animated:YES];
Or you can try to search
NSArray *viewControllers = self.navigationController.viewControllers
for (UIVIewController *anVC in viewControllers) {
if (anVC isKindOfClass:[yourController class] {
[self.navigationController popToViewController:anVC animated:YES];
break;
}
}
Hope it will help you.
Note : As the view controllers are the same class, most times
Add strUniqueID property on each controller while pushing view controller to navigation controller don't forget to set it
Now you know which unique ID's you want remove, so find it
//Firstly find your viewController
for(id viewcontroller in self.navigationController.viewControllers)
{
//For finding specific viewController use isKindOfClass
if(viewcontroller isKindOfClass:[YourViewControllerNameHere Class])
{
//Now find UniqueIDHere
YourViewController *objYourViewController = (YourViewController *)viewcontroller
if(YourViewControllerNameHere.strUniqueID isEqualToString:removeUniqueIDHere])
{
//Now pop to YourViewController
[self.navigationController popToViewController:viewcontroller animated:YES];
break;
}
}
else if(viewcontroller isKindOfClass:[YourDifferentViewControllerNameHere Class]) //Different ViewControllers here like this
{
//Now find UniqueIDHere
YourDifferentViewController *objYourDifferentViewController = (YourViewController *)viewcontroller
if(YourDifferentViewController.strUniqueID isEqualToString:removeUniqueIDHere])
{
//Now pop to YourViewController
[self.navigationController popToViewController:viewcontroller animated:YES];
break;
}
}
}
Try this :
//Firstly find your viewController
for(id viewcontroller in self.navigationController.viewControllers)
{
//For finding specific viewController use isKindOfClass
if(viewcontroller isKindOfClass:[YourViewControllerNameHere Class])
{
//Now pop to YourViewController
[self.navigationController popToViewController:viewcontroller animated:YES];
break;
}
}
Swift 5
let vc = navVC.viewControllers.first(where: { $0.hasKey })

Popping to a specific viewcontroller in a navigation stack

I have a come across a piece of code to pop to a specific viewcontroller in a navigation stack as below
for (UIViewController* viewController in self.navigationController.viewControllers) {
if ([viewController isKindOfClass:[MyGroupViewController class]] ) {
MyGroupViewController *groupViewController = (MyGroupViewController*)viewController;
[self.navigationController popToViewController:groupViewController animated:YES];
}
}
The objective is to pop to MyGroupViewController. But I am not understanding this line of code.
MyGroupViewController *groupViewController = (MyGroupViewController*)viewController;
Whats exactly happening here. I don't think a new instance of MyGroupViewController is being created here.
//This for loop iterates through all the view controllers in navigation stack.
for (UIViewController* viewController in self.navigationController.viewControllers) {
//This if condition checks whether the viewController's class is MyGroupViewController
// if true that means its the MyGroupViewController (which has been pushed at some point)
if ([viewController isKindOfClass:[MyGroupViewController class]] ) {
// Here viewController is a reference of UIViewController base class of MyGroupViewController
// but viewController holds MyGroupViewController object so we can type cast it here
MyGroupViewController *groupViewController = (MyGroupViewController*)viewController;
[self.navigationController popToViewController:groupViewController animated:YES];
}
}
Also you can do like this
if ([viewController isKindOfClass:[MyGroupViewController class]] ) {
[self.navigationController popToViewController:viewController animated:YES];
}
Swift code
//Itrate through all the view controllers in navigation stack
for vc in self.navigationController!.viewControllers {
// Check if the view controller is of MyGroupViewController type
if let myGropupVC = vc as? MyGroupViewController {
self.navigationController?.popToViewController(myGropupVC, animated: true)
}
}
The view controllers of a navigation controller stack are being enumerated. Since these view controllers can be of any kind (but will always inherit from UIViewController), the generic UIViewController is used. However, the compiler will not know what type that view controller is, so it is being casted to a MyGroupViewController. When that happens, the compiler knows what the type of class and you can send it messages that only apply to that class.
In this case it is kind of unnecessary, as it could be simplified to this:
(UIViewController* viewController in self.navigationController.viewControllers) {
if ([viewController isKindOfClass:[MyGroupViewController class]] ) {
[self.navigationController popToViewController:viewController animated:YES];
}
}
Short answer: it changes a variable type to the type specified in the parentheses to avoid compiler warnings.
for (int m=0; m<[self.navigationController.viewControllers count]; m++) {
if([[self.navigationController.viewControllers objectAtIndex:m] isKindOfClass:[MyGroupViewController class]]) {
MyGroupViewController * groupViewController = [self.navigationController.viewControllers objectAtIndex:m];
[self.navigationController popToViewController:groupViewController animated:YES];
}
}
- (void) RetunToSpecificViewController{
for (UIViewController *controller in self.navigationController.viewControllers) {
if ([controller isKindOfClass:[AnOldViewController class]]) {
//Do not forget to import AnOldViewController.h
[self.navigationController popToViewController:controller
animated:YES];
break;
}
}
On Swift
func RetunToSpecificViewController()
{
let viewControllers: [UIViewController] = self.navigationController!.viewControllers as [UIViewController]
self.navigationController!.popToViewController(viewControllers[viewControllers.count
- 5], animated: true)
}
We have write a better tutorial on that , You can check
https://appengineer.in/2014/03/13/pop-to-specific-view-controller-in-ios/

Problem implementing UINavigationControllerDelegate

I may have some misunderstanding regarding the use of the UINavigationControllerDelegate protocol. Here is my situation:
I have a ViewController, let's call it, BViewController that may display a PopoverViewController. BViewController is the second ViewController in a NavigationContoller's stack, after AViewController. I need to dismiss the PopoverViewController when the user hits a button in BViewController and the app takes us back to the previous view--AViewController.
To do that, I have implemented the following in BViewController
- (void)viewWillDisappear:(BOOL)animated {
NSLog(#"BViewController will disappear");
// Check whether the popoverViewController is visible
if (self.popoverController.popoverVisible==YES) {
[self.popoverController dismissPopoverAnimated:NO];
}
}
However, that is not being called directly by the framework as BViewController is inside a NavigationController. Hence, I register a UINavigationControllerDelegate with my NavigationController and implement the following two methods:
- (void)navigationController:(UINavigationController *)navigationController willShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
// Pass the message on to the viewController in question
[viewController viewWillAppear:animated];
}
- (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
// Pass the message on to the viewController in question
[viewController viewWillDisappear:animated];
}
However, it seems that the passed in viewController parameter in both methods is the one that is about to be shown. I would have expected that the second method gives me access to the one that is about to disappear. So, when the user hits aforementioned button viewWillDisappear gets called on AViewController (which is about to be shown) and not on BViewController (which is about to disappear). Does that sound right? The apple documentation refers in both cases to
The view controller whose view and navigation item properties are being shown.
...which is not quite clear, I think. Thank you for some help, guys.
The two delegate method are both called for the same action (showing a view controller). The navigationController: willShowViewController:animated: is called before the new view controller is visible in the gui. The navigationController:navigationController didShowViewController:animated: is called after the new view controller is shown.
You will find this pattern in a lot of delegate protocols from apple. Unfortunately you do not have a delegate method in the NavigationViewController which tells you if the action was a pop or push.
I hook in my own protocol, which will know about the TO and FROM sides:
NavigationControllerDelegate.h:
#protocol NavigationControllerDelegate <NSObject>
#required
-(void) navigationController: (UINavigationController*) navController
willMoveFromViewController: (UIViewController*) from
toViewController: (UIViewController*) to;
#end
Instead of the regular UINavigationViewController, I then use a little helper class which keeps track of the view controllers:
NavigationHandler.h:
#interface NavigationHandler : NSObject <UINavigationControllerDelegate> {
NSMutableArray* m_viewControllers;
}
In my app delegate, I create one of these objects and set it as the delegate of the navigation controller:
...
m_navigationHandler = [[NavigationHandler alloc] init];
navigationController = [[UINavigationController alloc] initWithRootViewController: mainMenuViewController];
navigationController.delegate = m_navigationHandler;
...
And from then on its a simple case of comparing my own list of view controllers with what the navigation controller has:
NavigationHandler.m
#import "NavigationHandler.h"
#import "NavigationControllerDelegate.h"
#implementation NavigationHandler
-(id) init {
if ((self = [super init])) {
m_viewControllers = [[NSMutableArray alloc] init];
}
return self;
}
-(void) dealloc {
[m_viewControllers release];
[super dealloc];
}
- (void)navigationController:(UINavigationController *)navController
willShowViewController:(UIViewController *)viewController
animated:(BOOL)animated {
// Find out which viewControllers are disappearing and appearing
UIViewController* appearingViewController = nil;
UIViewController* disappearingViewController = nil;
if ([m_viewControllers count] < [navController.viewControllers count]) {
// pushing
if ([m_viewControllers count] > 0) {
disappearingViewController = [m_viewControllers lastObject];
}
appearingViewController = viewController;
[m_viewControllers addObject: viewController];
} else if ([m_viewControllers count] > [navController.viewControllers count]) {
// popping
disappearingViewController = [m_viewControllers lastObject];
appearingViewController = viewController;
[m_viewControllers removeLastObject];
} else {
return;
}
// Tell the view that will disappear
if (disappearingViewController != nil) {
if ([disappearingViewController conformsToProtocol: #protocol(NavigationControllerDelegate)]) {
if ([disappearingViewController respondsToSelector: #selector(navigationController:willMoveFromViewController:toViewController:)]) {
UIViewController<NavigationControllerDelegate>* vcDelegate = (UIViewController<NavigationControllerDelegate>*)disappearingViewController;
[vcDelegate navigationController: navController willMoveFromViewController: disappearingViewController toViewController: appearingViewController];
}
}
}
// Tell the view that will appear
if ([appearingViewController conformsToProtocol: #protocol(NavigationControllerDelegate)]) {
if ([appearingViewController respondsToSelector:#selector(navigationController:willMoveFromViewController:toViewController:)]) {
UIViewController<NavigationControllerDelegate>* vcDelegate = (UIViewController<NavigationControllerDelegate>*)appearingViewController;
[vcDelegate navigationController: navController willMoveFromViewController: disappearingViewController toViewController: appearingViewController];
}
}
}
#end

Resources