I have some code inside the webViewDidFinishLoad delegate method, and I need some way to tell that this code has finished executing from within another method in a completely different part of my code. Is this possible? Basically what I want to do is something like this:
-(void) myMethod
{
// do stuff that causes webViewDidFinishLoad to be called
if (webViewDidFinishLoad has finished executing)
{
// do stuff with outputString
}
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
outputString = [webView stringByEvaluatingJavaScriptFromString:[NSString stringWithFormat:#"myJSFunction('%#')",jsInputString]];
}
I looked at this answer already and it made no sense to me.
I should add that inside myMethod, I can be sure that webViewDidFinishLoad will only be called once.
You can always set a boolean to indicate whether the webview has finished loading.
And the value of the boolean will be changed in the webview delegate method:
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
loadFinished = YES;
}
Then in your own method, check "loadFinished" will do the task.
You have to set <UIWebViewDelegate> for interface to use this delegate method
Try to avoid waiting for delegates and notifications in other methods - do the work that depends on them when they call back to your class. Don't call myMethod until you get the delegate message. If myMethod is called from user input, disable the associated controls until after the web view loads.
You could use UIWebView's iVar loading
You could use your own shared boolean flag as iVar and set it in webViewDidFinishLoad.
However, maybe you could reconsider your design and do in webViewDidFinishLoad what to do after loading is finished.
Why not try GCD?
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// WebView load content here
dispatch_async( dispatch_get_main_queue(), ^{
// load myMethod
});
});
use a Boolean and set its value to YES in the last line of webViewDidFinishLoad.
then check in your myMethod like this:
-(void) myMethod
{
// do stuff that causes webViewDidFinishLoad to be called
if (yourBoolean == YES)
{
// do more stuff
}
}
that should do your work.
In webViewDidFinishLoad, you post a notification to your "myMethod" code part.
Something like this:
in UIWebView:
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"webview_finished" object:self userInfo:nil];
}
in your "myMethod" code part:
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(myMethod) name:#"webview_finished" object:nil];
}
Related
When I call performSegueWithIdentifier before viewDidLoad and viewDidAppear sometimes it works and sometimes not.
AppDelegate
- (void)applicationDidBecomeActive:(UIApplication *)application {
NSLog(#"applicationDidBecomeActive");
[self abcNotif]; // the method post the notification.
}
VC1:
-(void)awakeFromNib {
NSLog(#"awakeFromNib");
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(theNotif:) name:#"abcNotif" object:nil];
}
-(void)theNotif:(UILocalNotification*)notif {
if([[[NSUserDefaults standardUserDefaults]valueForKey:#"flag"]isEqualToString:#"YES"]) {
[self performSegueWithIdentifier:#"seg1" sender:self];
NSLog(#"theNotif = %#", [[notif userInfo]valueForKey:#"notif1Key"]);
}
}
Logs in console
awakeFromNib
didFinishLaunchingWithOptions
applicationDidBecomeActive
theNotif = notif1Value
viewDidLoad
All is working fine although performSegueWithIdentifier is called before before viewDidLoad and viewDidAppear. But in some scenarios this don't work. Why is this behavior. Peoples have also asked theses kind of questions Why doesn't performSegueWithIdentifier work inside viewDidLoad?
The key thing to understand is that UIViewController subclasses load (create) their view property lazily. So viewDidLoad will be called after the view is loaded, but when is that? Certainly the normal cycle of launching your app causes it at some point, adding it to the window, but you can cause it to happen earlier by making a call on the view. Try inserting [self view]; immediately before your call to performSegue.. , this will ensure the view is loaded. Even so, it does not ensure that the view has been added to the window, and if that hasn't yet happened then I don't see how a modal can be pushed.
First, thanks to everyone that takes the time to answer questions. I have gotten so many quick answers to problems over the years from StackOverflow.
I'm new to Object C and iOS programming, but starting with what I think should be a super simple app. It receives a push notification (which works fine) and redirects to a webpage when it has figured out its appid.
The problem is that the while I can get my UIWebView to loadRequest in the viewDidLoad, the same code will not execute in another function.
Here's the code:
AppDelegate.m
// UFAppDelegate.m
#import "UFAppDelegate.h"
#import "UFViewController.h"
#implementation UFAppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Add registration for remote notifications
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:
(UIRemoteNotificationType)(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound)];
return YES;
}
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)devToken {
// ....
NSString *urlString = #"http://www.google.com";
NSURL *url = [NSURL URLWithString:urlString];
NSLog(#"Navigating to URL: %#", url);
UFViewController *theview = [[UFViewController alloc] init];
[theview handleOpenURL:url];
}
#end
ViewController.h:
// UFViewController.h
#import <UIKit/UIKit.h>
#interface UFViewController : UIViewController
- (void)handleOpenURL:(NSURL *)url;
#end
ViewController.m:
// UFViewController.m
#import "UFViewController.h"
#interface UFViewController ()
#property (weak, nonatomic) IBOutlet UIWebView *UFHWebView;
- (void)handleOpenURL:(NSURL *)url;
#end
#implementation UFViewController
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"UFViewController.viewDidLoad started");
// This block that assigns the url and loads it works perfectly here... but not down below.
NSString *urlString = #"http://search.yahoo.com/";
NSURL *url = [NSURL URLWithString:urlString];
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
[_UFHWebView loadRequest:requestObj];
NSLog(#"UFViewController.viewDidLoad completed");
}
- (void)handleOpenURL:(NSURL *)url
{
NSLog(#"UFViewController.handleOpenURL started");
NSLog(#"url = %#",url);
// The below loadRequest does not load!
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
[_UFHWebView loadRequest:requestObj];
NSLog(#"UFViewController.handleOpenURL completed");
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
NSLog(#"Error - %#", error);
}
- (BOOL) webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
return YES;
}
#end
When run as coded here, the yahoo page shows from the loadRequest in the didload, but the handleOpenURL one still does not fire. If I comment out the loadRequest out of viewDidLoad, a blank page shows up and the handleOpenURL still does not fire.
Here's the debug sequence. viewDidLoad fires and completes before receiving the AppID and manually firing handleOpenURL:
2013-12-12 15:28:32.606 UF[5896:60b] UFViewController.viewDidLoad started
2013-12-12 15:28:32.608 UF[5896:60b] UFViewController.viewDidLoad completed
2013-12-12 15:28:32.765 UF[5896:60b] Navigating to URL: http://www.google.com
2013-12-12 15:28:32.769 UF[5896:60b] UFViewController.handleOpenURL started
2013-12-12 15:28:32.773 UF[5896:60b] url = http://www.google.com
2013-12-12 15:28:32.775 UF[5896:60b] UFViewController.handleOpenURL completed
Any help appreciated!
Are you sure you have just one instance of UFViewController here?
-didRegisterForRemoteNotificationsWithDeviceToken initializes a new web view controller in a local variable, but I can't see how it's getting shown to the user - you aren't doing anything with the vc once it's created.
Here's what I think might be happening:
App starts. You have an instance of UFViewController in a storyboard. It loads, its -viewDidLoad is called, the log statements print and the webview loads correctly.
A little later, the -didRegisterForRemoteNotificationsWithDeviceToken: method is called. This instantiates a UFViewController, and calls the -handleOpenUrl method on it. The -handleOpenURL log methods fire. However, this controller has not been loaded into the view hierarchy, so its view is never instantiated, and its -viewDidLoad is never called.
This means that the webview that the -loadRequest: is being called on is nil. In Objective-C, it is valid to send a message to nil- unlike most other languages, no error or exception will be thrown. Instead, absolutely nothing happens, hence no web load.
The UFViewController in -handleOpenUrl is only referred to by a local variable within the method, so when the method completes, the last strong reference to it vanishes and it is released.
To check this is correct:
-NSLog(#"%#",[self description]); in your vc methods - this will log the address of the objects and tell you whether they are the same.
-NSLog the web controller's view, to check whether it's nil in handleOpenURL
To solve your problem, I suggest using an NSNotification posted by the AppDelegate and listened for by the UFViewController that is fully instantiated. So in the vc you'd do:
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(handleRegistration:) name:#"HandleRegistrationNotification" object:nil];
and then implement the handleRegistration method:
-(void)handleRegistration:(NSNotification*)notification
{
//your handleOpenUrl code
}
and also
-(void)dealloc
{
[[NSNotificationCenter defaultCenter]removeObserver:self name:#"HandleRegistrationNotification" object:nil];
}
(this bit is important because otherwise you may get a crash when the vc is dealloc'd)
Then, in the -didRegisterForRemoteNotifications: method:
[[NSNotificationCenter defaultCenter]postNotificationName:#"HandleRegistrationNotification" object:nil];
If you need to pass any information through the notification (eg urls) you can use the object parameter - this is accessible in the method called on the vc via the object property of the NSNotification parameter.
Full docs on notifications in Objective-C can be found here.
Some minor points - it's generally a bad idea to call your view controllers things like theView rather than theViewController since other people (or you in 6 months time) will end up thinking they're UIView s. Also the webview property should not start with a capital letter- that's generally only used for class names.
This should be called, "How to call a storyboard viewcontroller method from the appdelegate". And the answer is:
in AppDelegate.m:
#import "UFViewController.h"
...
UFViewController *rootViewController = (UFViewController*)self.window.rootViewController;
[rootViewController handleOpenURL:url];
In my code snippets above, this means replacing these lines
UFViewController *theview = [[UFViewController alloc] init];
[theview handleOpenURL:url];
with the ones above. The difference is my original code instantiates a new UFViewController object into a local variable, while the correct code just gets a handle to the view controller already created in the storyboard and calls its method.
I have two methods in viewDidLoad of the app and I want that if the notification method is called then the other method should not be called.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(actionNotificationDataA:)
name:#"reloadDataActivity"
object:nil];
Below is the other method. I want that if the notification method is not called, then this method should be called:
[NSThread detachNewThreadSelector:#selector(allData:) toTarget:self withObject:nil];
Otherwise, this method shouldn't be called.
First one will get called only when post that notification somewhere. But the second will detach the new thread suddenly when the code runs. That may create a problem look at it.
For an idea to your requirement:
Keep a BOOL with default to NO.
Then in the both methods check if the boolValue is NO, then run the code only if boolValue is NO and change the boolValue to YES.
Put BOOL isNotifCall; in your .h file.
In starting of viewDidLoad Method, give NO to isNotifCall, such like,
- (void)viewDidLoad
{
[super viewDidLoad];
isNotifCall = NO;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(actionNotificationDataA:)
name:#"reloadDataActivity"
object:nil];
[NSThread detachNewThreadSelector:#selector(allData:) toTarget:self withObject:nil];
}
Method of your NSThread (I don't know about parameter so i take id)
-(void)actionNotificationDataA:(id)Sender
{
isNotifCall = YES;
.
.
.
/// your Stuuf;
}
Method of your notification (I don't know about parameter so i take id)
-(void) allData:(id)Sender
{
if(!isNotifCall)
{
/// your allData method's Stuuf;
}
}
I'm getting data off the server via JSON and displaying it on Labels.
I've added that method in viewDidLoad.
I want to refresh the data when the user opens the app again. Currently, even if I kill the app in the simulator and start the app again, it doesn't refresh.
I tried the viewDidAppear method, but it isn't being executed for some reason.
-(void)viewDidAppear{
NSLog(#"Called viewDidAppear");
}
This is never called. I tried to minimize the app but it didn't work.
You can listen for notifications and respond appropriately. Try using these and decide what works for your intended workflow.
UIApplicationDidBecomeActiveNotification
UIApplicationWillEnterForegroundNotification
You can use respond to the notification like this.
[[NSNotificationCenter defaultCenter] addObserverForName: UIApplicationDidBecomeActiveNotification object: nil queue: [NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
// LOAD JSON
}];
I followed this tutorial - http://leejon.es/notifying-a-viewcontroller-with-uiapplicationdidbecomeactivenotification/
First, attach to the notification in the viewWillAppear method of the target view controller:
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector( appActivated: )
name: UIApplicationDidBecomeActiveNotification
object: nil];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter] removeObserver:self ];
}
- (void)appActivated:(NSNotification *)note
{
[self update];
}
The viewDidAppear: method takes a bool parameter wether the view was displayed with an animation which you are missing. Also you have to call the implementation of the superclass:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear: animated];
NSLog(#"Called viewDidAppear");
}
In your app delegate implementation, there is a method called:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
This method is called each time the app is launched, so I think it fits your needs. If you place your code here, it should work.
Also, be aware you should not perform a synchronous call here, because you will delay the app launch.
EDIT:
This method will be only called when the app launches. You could place your code inside a method, and call it from application didFinishLaunchingWithOptions, and then also call it from the method:
- (void)applicationWillEnterForeground:(UIApplication *)application;
This method will be called when the application enters the foreground, but not after the first launch, so beware.
I also think you should check the UIApplicationDelegate methods from apple developer page: http://developer.apple.com/library/ios/#documentation/uikit/reference/UIApplicationDelegate_Protocol/Reference/Reference.html
Also, check out the application state changes:
http://developer.apple.com/library/ios/#documentation/iphone/conceptual/iphoneosprogrammingguide/ManagingYourApplicationsFlow/ManagingYourApplicationsFlow.html
I have 3 screens on my app.First is login. Second is search and third is process the task.
On login i retrieve data from a web service. It returns data in XML format. So the data is considerably large. So i am doing that task on a background thread like this to stop Mainthread freezing up on me:
-(BOOL)loginEmp
{
.....some computation
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,
(unsigned long)NULL), ^(void) {
[self getAllCustomerValues];
});
}
-(void)getAllCustomerValues
{
....more computation.Bring the data,parse it and save it to CoreData DB.
//notification - EDIT
NSNotification *notification =[NSNotification notificationWithName:#"reloadRequest"
object:self];
[[NSNotificationCenter defaultCenter] postNotification : notification];
}
//EDIT
//SearchScreenVC.m
- (void)viewDidLoad
{
....some computation
[self.customerActIndicator startAnimating];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(stopActivityIndicator)
name:#"reloadRequest"
object:nil];
}
- (void)stopActivityIndicator
{
[self.customerActIndicator stopAnimating];
self.customerActIndicator.hidesWhenStopped = YES;
self.customerActIndicator.hidden =YES;
NSLog(#"HIt this at 127");
}
So on condition that login was successful, i move to screen 2. But the background thread is still in process( i know because i have logs logging values) . I want an activity indicator showing up here (2nd screen)telling user to wait before he starts searching. So how do i do it?How can i make my activity indicator listen/wait for background thread. Please let me know if you need more info.Thanks
EDIT: so I edited accordingly but the notification never gets called. I put a notification at the end of getAllCustomerValues and in viewDidLoad of SearchScreen i used it. That notification on 2nd screen to stop animating never gets called. What is the mistake i am doing.?Thanks
EDIT 2: So it finally hits the method. I dont know what made it to hit that method. I put a break point. I wrote to stop animating but it wouldn't. I wrote hidesWhenStoppped and hidden both to YES. But it still keeps animating.How do i get it to stop?
Ok, if it is not the main thread, put the following in and that should fix it.
- (void)stopActivityIndicator
{
if(![NSThread isMainThread]){
[self performSelectorOnMainThread:#selector(stopActivityIndicator) withObject:nil waitUntilDone:NO];
return;
}
[self.customerActIndicator stopAnimating];
self.customerActIndicator.hidesWhenStopped = YES;
self.customerActIndicator.hidden =YES;
NSLog(#"HIt this at 127");
}
Could you put your background operation into a separate class and then set a delegate on it so you can alert the delegate once the operation has completed?
I havent tried this, its just an idea :)
You could use a delegate pointing to your view controller & a method in your view controller like:
- (void) updateProgress:(NSNumber*)percentageComplete {
}
And then in the background thread:
float percentComplete = 0.5; // for example
NSNumber *percentComplete = [NSNumber numberWithFloat:percentComplete];
[delegate performSelectorOnMainThread:#selector(updateProgress:) withObject:percentageComplete waitUntilDone:NO];