I'm experiencing a few issues when trying to call a method from another class. Here's my code
Appdelegate.m
- (BOOL)application:(UIApplication *)application handleOpenURL:(NSURL *)url{
if ([url isFileURL]){
RootViewController *theview = [[RootViewController alloc] init];
[theview handleOpenURL:url];
}
return YES;
}
RootViewController.h
#interface RootViewController : UIViewController <UIWebViewDelegate>{
IBOutlet UIWebView* mainwebView;
}
- (void)handleOpenURL:(NSURL *)url;
#property (nonatomic, retain) UIWebView* mainWebView;
RootViewController.m
- (void)handleOpenURL:(NSURL *)url{
NSLog(#"Method handleOpenURL completed")
[mainwebView setDelegate:self];
[self.mainwebView loadRequest:[[NSurLRequest alloc] initWithURL:url];
[self performSelector:#selector(handleIt) withObject:nil afterDelay:3.0];
}
- (void)handleIt{
docView *docController = [[docView alloc] initWithNibName:nil bundle:nil];
[self presentModalViewController:docController animated:YES];
}
So, basically I'm trying to update a UIWebView and then present a modal view controller once the appdelegate calls handleOpenURL. The problem is that both of these things (updating webview and presenting view controller) refuse to work in the method handleOpenURL. They work perfectly fine in viewdidload, which makes me think it has something to do with the method being called from appdelegate.
The webview simply refuses to respond when accessed from the handleOpenURL method, and when I present the modal view controller I get the error
Warning: Attempt to present ... whose view is not in the window hierarchy!
But, again the modalview works fine when presented from viewdidload (using the time delay selector)
An explanation to why this is happening or a workaround would be great, thanks for your help.
You have to #import "docView.h" in your RootViewController.m.
Warning: Attempt to present ... whose view is not in the window hierarchy!
The warning states the view is not recognized, meaning you don't have access to the class properly, if it's not importing, then you didn't set up the code in docView.m properly.
If that doesn't work, than i need more info to help solve your problem.
Edit for Clarification:
The app-delegate is called at the start and when pointed to, not
constantly.
You should call it from when the modal view loads, a method
pointer([self myFunctionNameHere];) would work just fine.
So it's that your not accessing the app-delegate properly.
I don't think you should even be calling it from the app-delegate,
call it from your .m.
Calling it from your main(.m) file would solve everything.
Related
I was trying to push a viewcontroller B into navigation controller from A and then assigning some properties of B in A.
In this case, the assigning of properties was done and then viewDidLoad of viewcontroller A was executed.
Here, assigning properties in A should be done only after viewDidLoad of A has done.
For example,
[b.navController pushViewController:a animated:YES];
a.status = #"loaded";
Here, status was assigned first and then viewDidLoad of A was executed.
This happens only in iOS 7 whereas in iOS6 it works fine.
Can anyone please let me know where the problem is?
UPDATE: For me in some cases in iOS7, Push view is not working. How cna I debug and fix it?
Just access the viewcontroller.view (set any thing immediately after the alloc) property after the alloc init;
Which will loadview/viewdidload.
See Apple Documentation
In my experience, a UIViewController view is loaded lazily, no matter which iOS version you're working on. If you want to trigger a view load, and therefore its UIViewController viewDidLoad, you should access the view after the UIViewController is allocated. For example:
UIViewController *aViewController = [[CustomViewController alloc] init];
[aViewController view];
Make sure you don't code it as
aViewController.view
since that would generate a compiler warning.
So, in your case it would have to be something like this:
...
CustomViewController *a = [[CustomViewController alloc] init];
[b.navController pushViewController:a animated:YES];
[a view];
a.status = #"loaded";
Let me know if you have further problems with it.
You can know when a View Controller has been pushed onto the stack by implementing the UINavigationControllerDelegate and setting yourself as the delegate self.navigationController.delegate = self; then you will get this callback after every push
navigationController:didShowViewController:animated:
So you can check if the shown viewController is the one your interested in, then set your a.status.
I would suggest you call a delegate method once the view is loaded.
Set the delegate to be controller B.
and after viewDidLoad finishes (in controller A) call the delegate method. You can even pass parameters as you wish to the delegate.
Here's some example code:
Controller B:
a.delegate = self;
[b.navigationController pushViewController:a animated:YES];
Implement the delegate method:
- (void)controllerIsLoaded:(ControllerA *)controllerA status:(NSString *)status
{
a.status = status;
}
Controller A .h file:
#class ControllerA;
#protocol ControllerADelegate <NSObject>
- (void)controllerIsLoaded:(ControllerA *)controllerA status:(NSString *)status;
#end
#interface ControllerA : UIViewController
#property (nonatomic, weak) id <ControllerADelegate> delegate;
Controller A .m file:
- (void)viewDidLoad:(BOOL)animated
{
[super viewDidLoad:animated];
/* your viewDidLoad code here... */
if ([_delegate respondsToSelector:#selector(controllerIsLoaded:status)])
[_delegate controllerIsLoaded:self status:#"Loaded"];
}
Turn off animation for ios7, in my case its work
[b.navController pushViewController:a animated:NO];
a.status = #"loaded";
No documentation provides enough information to know exactly when viewDidLoad would be called.
UIViewController's documentation just says this
This method is called after the view controller has loaded its view hierarchy into memory
I would suggest that you create a custom initializer like this
- (id)initWithStatus:(NSString *)status {
}
But, if you are trying to use this variable to check if the viewController's view has 'loaded' or not, it may not be possible to do that because the pushViewController or presentViewController methods are not guaranteed to be synchronous.
Even in iOS 6, there was no explicit guarantee that the view would be loaded as soon as that method returned.
Please write the code in viewWillAppear method instead of viewDidLoad in next class i.e. where you are pushing the object to
-(void)viewWillAppear:(BOOL)animated
{
}
I'm understand of your question like this:
B *b = [[B alloc] init];
b.status = #"loaded";
[self.navigationController pushViewController:b animated:Yes];
If you want to pass a value from one controller to another means, you have to assign a value before using pushViewController method.
I'm trying to call a method in the view controller from the app delegate, but Xcode says No known class method for selector 'myMethodHere'. Here's my code:
AppDelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[..]
[MainViewController myMethodHere];
[..]
return YES;
}
MainViewController.m:
-(void) myMethodHere {
[..]
}
I would try
MainViewController * vc = [[MainViewController alloc]init];
[vc myMethodHere];
[vc release];
Make sure to import your MainViewController in your app delegate .m file
make sure you add "myMethodHere" to your MainViewController .h file
You are trying to call a class method when you want to call an instance method. If the view controller is the root view controller, then you should be able to call it thus:
UIWindow *window = [UIApplication sharedApplication].keyWindow;
MainViewController *rootViewController = window.rootViewController;
[rootViewController myMethodHere];
If it's not the root view controller then you'll have to find some other way of getting hold of the instance and then calling the method as in the last line above.
If you want to access to a view controller on a story board, you may use this block of code from the AppDelegate:
MainViewController *rootViewController = (MainViewController*)self.window.rootViewController;
[rootViewController aMethod];
Remember to add the import.
In Swift, you can write it like this
UIApplication.sharedApplication().keyWindow?.rootViewController?.yourMethodName()
Try to write
-(void) myMethodHere;
in MainViewController.h
so in my app delegate I am trying to present a modalViewController from a UITabBarController, by doing the following:
self.tabBarController = [[UITabBarController alloc] init];
LoginViewController* loginViewController = [[LoginViewController alloc] init];
loginViewController.delegate = self;
[self.tabBarController presentModalViewController:loginViewController animated:NO];
[loginViewController release];
and the delegate defined in the app delegate is:
- (void)userDidLogin:(LoginViewController *) loginViewController
{
NSLog(#"DELEGATE CALLED, DISMISSING");
[self.tabBarController dismissModalViewControllerAnimated:NO];
}
Here's my LoginViewController:
protocol LoginViewControllerDelegate;
#interface LoginViewController : UIViewController <MBProgressHUDDelegate>
{
id<LoginViewControllerDelegate> delegate;
}
#property (assign) id<LoginViewControllerDelegate> delegate;
#end
#protocol LoginViewControllerDelegate
- (void)userDidLogin:(LoginViewController *) loginViewController;
#end
The issue is that this (userDidLogin:(LoginViewController *) loginViewController) is never called... why is this?
I have called the following in my LoginViewController implementation and this is called
[self.delegate userDidLogin:self];
UPDATE:
I got the delegate called now. The issue now is that when I call [self.tabBarController dismissModalViewControllerAnimated:YES] it doesn't dismiss the modal view controller.
You didn't post any code from LoginViewController, but within that class's code you need to add the following lines when you are ready to dismiss it (perhaps when the user clicks the "Login" button and the login is successful).
if (delegate && [delegate respondsToSelector:#selector(userDidLogin:)])
[delegate performSelector:#selector(userDidLogin:) withObject:self];
UPDATE:
I think I understand what the issue is here. According to Apple's documentation, when you call presentModalViewController:animated: the method sets the value of the "modalViewController" property of UIViewController (in this case your UITabBar). However that property only maintains a weak reference to the modalViewController. That's important because you initialize the LoginViewController, pass it in to presentModalViewController:animated: and then you release it. Since presentModalViewController:animated: is not retaining a strong reference to the LoginViewController, the UITTabBar is unable to dismiss it later on. In fact I'm surprised what you have done is not resulting in an EXC_BAD_ACCESS crash. I suggest you remove the "[loginViewController release]" statement and instead release it after you call "[self.tabBarController dismissModalViewControllerAnimated:NO]"
Is there a way to call code when a modal view is finished dismissing?
EDIT:
I'm sorry, I didn't clarify earlier. I'm trying to dismiss a UIImagePickerController and then show a MFMailComposeViewController and attach the image data to the email. When I try to call
[self presentModalViewController: mailController]
right after
[self dismissModalViewController];
I get errors and such.
You use a delegate pattern for the modal view to inform whoever presented it when it's finished.
MyModalViewController.h:
#protocol MyModalViewControllerDelegate;
#interface MyModalViewController : UIViewController
{
id<MyModalViewControllerDelegate> delegate;
}
#property (nonatomic, assign) id<MyModalViewControllerDelegate> delegate;
#end
#protocol MyModalViewControllerDelegate
- (void)myModalViewControllerFinished:(MyModalViewController*)myModalViewController;
#end
MyModalViewController.m:
#synthesize delegate;
// Call this method when the modal view is finished
- (void)dismissSelf
{
[delegate myModalViewControllerFinished:self];
}
ParentViewController.h:
#import "MyModalViewController.h"
#interface ParentViewController : UIViewController <MyModalViewControllerDelegate>
{
}
ParentViewController.m:
- (void)presentMyModalViewController
{
MyModalViewController* myModalViewController = [[MyModalViewController alloc] initWithNibName:#"MyModalView" bundle:nil];
myModalViewController.delegate = self;
[self presentModalViewController:myModalViewController animated:YES];
[myModalViewController release];
}
- (void)myModalViewControllerFinished:(MyModalViewController*)myModalViewController
{
[self dismissModalViewControllerAnimated:YES];
}
EDIT:
I haven't used UIImagePickerController, but looking at the docs, it looks like you already have most of the code done for you, as there is an existing UIImagePickerControllerDelegate class that has three different "dismissal" delegate callbacks (although one is deprecated). So you should make your ParentViewController class (whatever that is) implement the UIImagePickerControllerDelegate pattern and then implement those methods. While each method will do something different (since you have to handle when the user actually selects an image, or if they cancel), they each will do the same thing at the end: call dismissModalViewControllerAnimated: to dismiss the picker.
You have to dismiss the modalViewController somehow right? Either a UIButton, or by code:
- (void)dismissModalViewControllerAnimated:(BOOL)animated
In the IBAction (e.g. delegate) for the UIButton or in the method above, call whatever code you want.
I don't think there is a specific notification yet can subscribe to, to know when dismiss animation is done,...BUT. You can implement viewDidAppear: in the view controller that presented the modal view. This is what I do, when I use the (to UIImagePickerController quite similar) ABPeoplePickerNavigationController.
In the callback from people picker, I remember the person tapped in the picker on an instance variable, like this:
- (void)callbackFromModalView:(id)dataFromModalView {
// remember dataFromModalView as I need it when dismissed
self.dataFromModalView = dataFromModalView;
// now initiate dismissal
[self dismissModalViewControllerAnimated:YES];
}
then, in your view controller, implement this:
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
if (self.dataFromModalView) {
//...present now view here
// don't forget to reset this one
self.dataFromModalView = nil;
}
}
in effect, you are using the combination of viewWillAppear: and the dataFromModalView property as the "notification about modal view dismissed".
I have an app with tabbar and webview. I'm trying to make the app come back to default url each time user taps the bar. Right now I'm intercepting taps and launching a method, however it's not affecting my webview (it's not loading the page). The method works properly when called from the class, but not when it's called from my app delegate, where I'm intercepting taps.
I suspect it's something with the way I create the SecondViewController object that it's not pointing to the webview, but I have no clue what I'm doing wrong.
Here is the code:
Second view header (where the WebView is located)
#interface SecondViewController : UIViewController {
IBOutlet UIWebView *secondView;
}
- (void) goToPage;
Second view implementation
#import "SecondViewController.h"
#implementation SecondViewController
- (void)awakeFromNib
{
[self goToPage];
}
- (void) goToPage
{
NSLog(#"go to page");
NSString *newURL = [NSString stringWithFormat:#"http://pageurl"];
[secondView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:newURL]]];
}
My app delegate, where I call the SecondViewController class method:
#import "RedDragonAppDelegate.h"
#import "SecondViewController.h"
#implementation RedDragonAppDelegate
#synthesize window;
#synthesize rootController;
- (void)applicationDidFinishLaunching:(UIApplication *)application {
// Override point for customization after application launch
[window addSubview:rootController.view];
}
- (void)tabBarController:(UITabBarController *)tabBarController didSelectViewController:(UIViewController *)viewController {
NSLog(#"didSelectViewController %d", rootController.selectedIndex);
SecondViewController * sv = [[SecondViewController alloc] init];
if (rootController.selectedIndex == 0){
//NSLog(#"if in didSelectViewController 0");
} else if (rootController.selectedIndex == 1) {
//NSLog(#"if in didSelectViewController 1");
[sv goToPage];
}
}
Thanks fot your help!
If I understand correctly, you've got an instance of SecondViewController with secondView connected to an instance of UIWebView in Interface Builder. What you want to do is call goToPage on that instance of SecondViewController from RedDragonAppDelegate. (In particular, note that I'm talking about instances of these--I believe this is the underlying issue.)
In tabBarController:didSelectViewController:, when you do SecondViewController * sv = [[SecondViewController alloc] init];, you are creating a new instance of SecondViewController and you can call its goToPage method, but sv is not the same instance of SecondViewController that appears in Interface Builder and has secondView connected to the UIWebView (that is, when you create a new instance of SecondViewController, the ivar secondView is unset and seems to be nil, but I don't know that it's guaranteed to be nil).
What you probably(*) want to do is add IBOutlet SecondViewController *sv; to the #interface of RedDragonAppDelegate, make sure that you have an instance of RedDragonAppDelegate in Interface Builder, connect the new IBOutlet sv of RedDragonAppDelegate to the instance of SecondViewController in Interface Builder, and delete the line in tabBarController:didSelectViewController: that defines and initializes sv.
(*) I'm not 100% certain on this because I don't do iPhone stuff and I don't know how your various views/objects/etc. are arranged in XIB/NIB files, but if everything's in one XIB/NIB file, I'm pretty sure it'll work.