iOS how can I add a completion block to UIWebView loadRequest:? - ios

I'm working with a UIWebView and am already using webViewDidFinishLoad: method with an optional block that gets executed after loading complete:
-(void)webViewDidFinishLoad:(UIWebView *)webView
{
[super webViewDidFinishLoad:webView];
//... bunch of other code
if(self.webViewFinishLoadBlock != nil)
{
self.webViewFinishLoadBlock();
}
}
Now I'm working with an even more complicated sequence of loading pages and redirects that makes the logic above not sufficient. I don't want to register myself as a delegate of dummyWebView and have to juggle multiple completion blocks stored in my view controller's properties:
dummyWebView = [[UIWebView alloc] initWithFrame:CGRectZero];
[dummyWebView loadRequest:[NSURLRequest requestWithURL:logoutURL]];
//Ideally here I would know when dummyWebView finishes loading, because there's some code I want to execute once it is done
My question is:
Is there some kind of third party framework that would allow me to use loadRequest:withCompletion: to simplify writing callback code?

You can just:
Subclass UIWebView with a property to hold the webViewDidFinish completion block;
Make sure it specifies its delegate;
Implement the webViewDidFinish much like you wrote it (though I'd suggest the block return both the web view as well as the NSError object, if any); and
Implement the webView:didFailLoadWithError:, too.
Thus:
// MyWebView.h
#import <UIKit/UIKit.h>
typedef void(^WebViewFinishLoadBlock)(UIWebView *, NSError *);
#interface MyWebView : UIWebView
#property(nonatomic, copy) WebViewFinishLoadBlock webViewFinishLoadBlock;
- (void)loadRequest:(NSURLRequest *)request withCompletionHandler:(WebViewFinishLoadBlock)completionHandler;
#end
And
// MyWebView.m
#import "MyWebView.h"
#interface MyWebView () <UIWebViewDelegate>
#end
#implementation MyWebView
- (void)loadRequest:(NSURLRequest *)request withCompletionHandler:(WebViewFinishLoadBlock)completionHandler
{
self.delegate = self;
self.webViewFinishLoadBlock = completionHandler;
[self loadRequest:request];
}
#pragma mark - UIWebViewDelegate
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
if (self.webViewFinishLoadBlock) {
self.webViewFinishLoadBlock(webView, nil);
self.webViewFinishLoadBlock = nil;
}
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
if (self.webViewFinishLoadBlock) {
self.webViewFinishLoadBlock(webView, error);
self.webViewFinishLoadBlock = nil;
}
}
#end
And then:
MyWebView *webView = [[MyWebView alloc] init];
webView.frame = self.view.bounds;
[self.view addSubview:webView];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
[webView loadRequest:request withCompletionHandler:^(UIWebView *webView, NSError *error) {
if (error) {
NSLog(#"failed: %#", error);
} else {
NSLog(#"succeeded");
}
}];

- (void)webViewDidFinishLoad:(UIWebView *)webView
is a delegate method. By convention delegate methods require the object pass itself back to the delegate:
(UIWebView*)webView
Through a parameter.
If we want to get last request parameter using property request: that means webView.request.URL
The parent object can be the delegate for multiple objects, and it can identify which it is getting a response from though that parameter. Either switch on what responds to you or build some infrastructure to handle it more elegantly.

Related

How to create a simple NSURLRequest Callback?

I have created a class that will collect data from url data asynchronously, however my understanding of callbacks or whatever is not clear and I'm trying to find a simple way to reuse my class by having the calling method wait for data to be returned or set within the ApiManager class. I just need something to wakeup in another class when that process has been completed. Some processes have single request and others have multiple, why you will notice that I'm using [connection description] within the ApiManager class.
ApiManager.h
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
#interface ApiManager : NSObject<NSURLConnectionDelegate>
{
NSMutableDictionary *_dataDictionary;
}
- (void)urlRequest:(NSURLRequest *)url;
#property (strong, nonatomic) NSMutableArray *results;
#end
ApiManager.m
#import "ApiManager.h"
#implementation ApiManager
- (void)urlRequest:(NSURLRequest *)url {
[[NSURLConnection alloc] initWithRequest:url delegate:self];
}
// basic connection classes
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSMutableData *responceOjb = _dataDictionary[ [connection description] ];
[_dataDictionary setObject:responceOjb forKey:[connection description]];
}
// append any data we find
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSMutableData *responceOjb = _dataDictionary[ [connection description] ];
[responceOjb appendData: data];
[_dataDictionary setObject:responceOjb forKey:[connection description]];
}
// --
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse*)cachedResponse {
// Return nil to indicate not necessary to store a cached response for this connection
return nil;
}
// wrap up and close the connect, move objects over to results or something
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[_results addObject:[connection description]];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
// The request has failed for some reason!
// Check the error var
NSLog(#"%#",error);
}
#end
The main View Controller test:
#import "ViewController.h"
#import "ApiManager.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
[self DoThisTest];
}
-(void)DoThisTest {
ApiManager *api = [[ApiManager alloc] init];
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"%#",#"http://google.com"]]];
[api urlRequest:request];
if([api results]) {
NSLog(#"GOT DATA");
}
}
Well, there are a few options. You could add a block property onto your ApiManager class:
#property (copy, nonatomic) void (^doneHandler)();
And then invoke that block like so:
self.doneHandler();
You would invoke the block when you deem it appropriate (say, in your connectionDidFinishLoading: method).
With this approach, the definition of the block (callback) would happen in your view controller and look something like:
ApiManager *apiManager = [[ApiManager alloc] init];
apiManager.doneHandler = ^{
// Do whatever you need to do here.
};
Alternatively, you could add a method to your ApiManager with a signature like this:
- (void)sendRequestWithURL:(NSURL*)url completion:(void(^)())completion;
And use NSURLConnection's (or, better, NSURLSession's) block-based APIs. Those APIs have callbacks built in and you would simply invoke completion(); inside of the completion block of -[NSURLSession sendAsynchronousRequest:completion:].
Finally, you could define an ApiManagerDelegate protocol.
- (void)apiManagerDidFinishReceivingData:(ApiManager*)sender;
And add a delegate property to your ApiManager class.
#property (weak, nonatomic) id<ApiManagerDelegate>delegate;
Assign the delegate of your ApiManager in your ViewController:
ApiManager *apiManager = [[ApiManager alloc] init];
apiManager.delegate = self;
Call the delegate method inside of your implementation of NSURLConnectionDelegate's callbacks in ApiManager like so:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[_results addObject:[connection description]];
[self.delegate apiManagerDidFinishReceivingData:self];
}
And implement the delegate method in ViewController:
- (void)apiManagerDidFinishReceivingData:(ApiManager*)sender {
// Do what you want to.
}
As an addendum, there are networking libraries available that do a lot of the heavy lifting and busy-work for you, most notably AFNetworking, if you're just trying to get stuff done. And, even if this is more of an academic exercise where you're trying to understand the patterns, looking at AFNetworking's APIs and implementation (it's open source) would be highly instructive.
Cheers

UIWebview set delegate method in a class

How can i set the delegate method of a UIWebView in a class?
when i do it, the app carsh.
#interface MineWebViewHandle : NSObject<UIWebViewDelegate>
#end
//.m
#implementation MineWebViewHandle
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSString *urlString = [[request URL] absoluteString];
return YES;
}
- (void)webViewDidStartLoad:(UIWebView *)webView
{
NSLog(#"did start load");
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
NSLog(#"did finished ");
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
NSLog(#"webview error:%#",[error localizedDescription]);
}
i use it:
self.m_pWebView = [[UIWebView alloc] initWithFrame:CGRectZero];
MineWebViewHandle *handle = [[MineWebViewHandle alloc]init];
self.m_pWebView.delegate = handle;
self.m_pWebView.backgroundColor = [UIColor whiteColor];
[self.view addSubview:self.m_pWebView];
how do i use it rightly?
Your delegate look like is create in viewDidLoad,and in the end of this it´s release (put to nil).
You need create a property called: MineWebViewHandle *handle.
in your viewController.h add:
#property (nonatomic,strong)MineWebViewHandle *handle;
and change in your code:
self.handle = [[MineWebViewHandle alloc]init];
self.m_pWebView.delegate = self. handle;
And it´s good idea your webView will have dimensions, change this:
self.m_pWebView = [[UIWebView alloc] initWithFrame:self.view.frame];
Webview is the UIView, Not a controler.
Please remove the NSObject and add the UIview
#interface MineWebViewHandle : UIView
#end

nsdata that are return fron NSObject class and used in viewcontroller class

I have an NSObject class that contains 3 methods:
-(void)RequestForData
{
#pragma Mark - ASIHTTPRequest
NSURL *url=[NSURL URLWithString:#"http://srv2.vitaminas.it/pdv"];
ASIHTTPRequest *request=[ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request startSynchronous];
}
pragma Mark - HTTP Delegate
- (NSData*)requestFinished:(ASIHTTPRequest *)request
{
NSData *responseData = [request responseData];
return responseData;
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
NSError *error = [request error];
NSLog(#"%#",error);
}
I have one view controller class, from viewDidLoad method of viewcontroller class call -RequestForData method of NSObject class,
WebServiceMethods *web=[[WebServiceMethods alloc]init];
[web RequestForData];
arr_JsonData=[NSJSONSerialization JSONObjectWithData:web options:NSUTF8StringEncoding error:Nil];
NSLog(#"%#",arr_JsonData);
[self.tableView reloadData];
But I want to use NSData that are returned from NSObject class (i.e return responsedata; ) into view controller class.
I want that NSData into arr_JsonData ( NSMutuableArray )
What can I do ?
Make responseData as class level variable. Do not create local instance of it in requestFinished method.
Your problem is callbacks. You should put your viewController as a delegate of WebServiceMethods ( or using blocks is better) to be informed when the request has finished and the populate your arr_JsonData
#protocol WebServiceMethodsDelegate
- (void)webServiceMethodsDidFinishWithSucess:(NSString *)response; // give this méthode an appropriate name.
- (void)webServiceMethodsDidFailWithError:(NSError *)error;
#end
#interface WebServiceMethods : NSObject
#property (nonatomic,weak) id <WebServiceMethodsDelegate> delegate;
#end
#implemntation WebServiceMethods : NSObject
- (void)requestFinished:(ASIHTTPRequest *)request
{
NSString *responseString = [request responseString];
[self.delegate webServiceMethodsDidFinishWithSucess:responseString];
}
- (void)requestFailed:(ASIHTTPRequest *)request
{
NSError *error = [request error];
[self.delegate webServiceMethodsDidFailWithError:error];
}
#end
Put your viewController conform to the WebServiceMethodsDelegate protocol.
#interface yourViewController : UIViewController <WebServiceMethodsDelegate>
...
#end
and know in your the viewDidLoad of your viewController :
WebServiceMethods *web=[[WebServiceMethods alloc]init];
web.delegate = self;
[web RequestForData];
Put also the delegate methods in viewController.m
- (void)webServiceMethodsDidFinishWithSucess:(NSString *)response {
// here you can parse the response and reload your tableView
.....
[self.tableView reloadData];
}
- (void)webServiceMethodsDidFailWithError:(NSError *)error {
// handle the errors
}
PS : There are many problems with your code :
Don't use ASHTTPRequest, it's not maintained. You can use AFNetworking.AFNetworking
Put your WebServiceMethods as a shared instance.
....

No visible #interface for 'forum' declares the selector 'backToLastPage'

So am very new to xCode and it would help if someone could help me with this!
I'm creating an app that is very simple for the most part. I have a UIWebView taking me to a mobile page. This page has a log-in for Facebook. The original problem I've been having, is that since it's a mobile site I am getting a blank screen after the login is complete. I need the UIWebView to take me back to the original sign I clicked log-in at. I have copied some code which I think will work, but I I'm getting at error that says
"No visible #interface for 'forum' declares the selector 'backToLastPage'"
Could someone please tell me what I need to do to fix this problem? It's probably something simple but I need some help.
#import "forum.h"
#import "ViewController.h"
#interface forum ()
#end
#implementation forum
-(IBAction)switchback:(id)sender {
[self dismissViewControllerAnimated:YES completion:nil];
}
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (self) {
// Custom initialization
}
return self;
}
- (void)viewDidLoad
{
[super viewDidLoad];
NSURL *myURL = [NSURL URLWithString:#"http://www.moot.it/yopyipcommunity"];
NSURLRequest *myRequest = [NSURLRequest requestWithURL:myURL];
[myWebView loadRequest:myRequest];
}
- (BOOL)webView:(UIWebView *)webView
shouldStartLoadWithRequest:(NSURLRequest *)request
navigationType:(UIWebViewNavigationType)navigationType
{
NSString *url = [[request URL] absoluteString];
//when user status == connected
//(has a access_token at facebook oauth response)
if([url hasPrefix:#"https://m.facebook.com/dialog/oauth"] &&
[url rangeOfString:#"access_token="].location != NSNotFound)
{
[self backToLastPage];
return NO;
}
return YES;
}
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
NSString *url = [[webView.request URL] absoluteString];
if([url hasPrefix:#"https://m.facebook.com/dialog/oauth"])
{
NSString *bodyHTML =
[webView stringByEvaluatingJavaScriptFromString:#"document.body.innerHTML"];
//Facebook oauth response dead end:
// is a blank body and a head with a script that does
//nothing. But if you got back to your last page,
// the handler of authResponseChange
//will catch a connected status
// if user did his login and auth app
if([bodyHTML isEqualToString:#""])
{
[self backToLastPage];
}
}
}
- (void)didReceiveMemoryWarning
{
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
#end
I think forum.h is separate class, backToLastPage method is declared in that class.
#property (nonatomic, strong) forum *forum;
You need to create allocation for that class,
self.forum = [forum alloc]init];
and you call the method like this, [self.forum backToLastPage];
instead of this, [self backToLastPage];

How to get HTML content of a website

In viewDidLoad, I'm using NSURLRequest and NSURLConnection:
NSURLRequest *site_request =
[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.google.com/"]
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:10.0];
NSURLConnection *site_connection =
[[NSURLConnection alloc] initWithRequest:site_request delegate:self];
and then I use
-(void)connection:(NSURLConnection *)site_connection didReceiveData:(NSData *)data
{
site_response = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
}
and I have the whole HTML in site_response.
I want to create an invisible UIWebView which will "open" the page from the NSURLRequest in order to use JavaScript to get content like this:
NSString *myText = [my_webView stringByEvaluatingJavaScriptFromString:
#"document.documentElement......"];
In my .h I have:
UIWebView *my_webview;
#property (nonatomic, retain) UIWebView *my_webview;
and in my .m I have:
#synthesize torrents_webview;
My viewDidLoad after NSURLRequest has
[my_webview loadRequest:site_request];
and I use
-(void)webViewDidFinishLoad:(UIWebView *)webView
{
//an alertview here
}
in order to be sure it works. But nothing happens. It doesn't give an alert view.
What am I doing wrong?
webViewDidFinishLoad: is a method of the UIWebView delegate. You are not setting the delegate anywhere in the code you have shown.
#interface YourClass : UIViewController <UIWebViewDelegate>
...
- (void)loadView
{
self.webView.delegate = self;
}
...
- (void)dealloc
{
self.webView.delegate = nil;
}
Also if you use NSURLRequest you're going to get the page again. But there's no need to use NSURLConnection, just go straight to loading the UIWebVIew using the NSURLRequest.
Or if you must use NSURLConnection then when the file has downloaded save it to disk and use LoadHTMLString to load the contents.
ViewController.h
#interface TopTorrents_ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource,UIWebViewDelegate>
{
UIWebView *torrents_webview;
}
#property (nonatomic, retain) UIWebView *torrents_webview;
ViewController.m
#synthesize torrents_webview;
- (void)viewDidLoad
{
torrents_webview.delegate = self;
NSURLRequest *site_request = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.google.gr/"] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0];
[torrents_webview loadRequest:site_request];
[super viewDidLoad];
}
-(void)webViewDidFinishLoad:(UIWebView *)webView
{
NSString *myText = [torrents_webview stringByEvaluatingJavaScriptFromString:#"document.getElementsByTagName('body')[0]"];
UIAlertView *my_alert = [[UIAlertView alloc] initWithTitle:#"mytitle" message:myText delegate:nil cancelButtonTitle:#"my button" otherButtonTitles:nil,nil];
[my_alert show];
}
this is my updated code... thanks

Resources