I've made a simple app for mac, however when it laods in content(via JS) it redirects due to adds or something, is there any way to prevent WebView to going to any other URL than the one defined in the code?
Currently trying to stop it with (UPDATED)
webberAppDelegate.h
#import <Cocoa/Cocoa.h>
#import <WebKit/WebKit.h>
#interface webberAppDelegate : NSObject <NSApplicationDelegate> {
WebView *webview;
}
#property (assign)IBOutlet WebView *webview;
#end
webberAppDelegate.m
#import "webberAppDelegate.h"
#implementation webberAppDelegate
//#synthesize window;
//#synthesize webber;
#synthesize webview;
- (void)applicationDidFinishLaunching:(NSNotification *)Notification
{
NSURL*url=[NSURL URLWithString:#"http://simplyshows.com/dev/forums/"];
NSURLRequest*request=[NSURLRequest requestWithURL:url];
[[webview mainFrame] loadRequest:request];
webview.resourceLoadDelegate = self;
}
-(NSURLRequest*) webView:(WebView*)webview resource:(id)sender willSendRequest:(NSURLRequest*)request redirectResponse:(NSURLResponse*)redirectresponse fromDataSource:(WebDataSource*)dataSource {
NSLog(#"willSendRequest delegate method called");
}
#end
See here for details on connection:willSendRequest:redirectResponse:
UPDATED: sorry, I breezed right over the fact that this is a Mac app not an iPhone app. Limited experience with the former; however, I believe that the same concepts apply, just instantiated differently.
Looking at the documentation, it looks like you want webView:resource:willSendRequest:redirectResponse:fromDataSource (see here for details). You'll also need to set webberAppDelegate's resourceLoadDelegate to self.
UPDATED: okay, here's a bit more detail on the process. You need to understand a bit about how protocols and delegates work in Objective-C. Read that material in support of what follows.
Protocols function like interfaces in Java or C#, or abstract functions in C++. They are basically a form of contract. They are mandatory by default but can be marked explicitly as #optional, meaning the compiler won't choke if one is omitted. If you look at the documentation for WebResourceLoadDelegate, you'll see that all the methods are optional. We want just one here, the webView:resource:willSendRequest:redirectResponse:fromDataSource method.
The other part of this is the concept of the delegate. Delegates function as callbacks. This involves both an object that will carry out the callback logic and an implementation of the logic (i.e., the implementation of the method from the protocol above). This can be implemented in a number of ways, but Cocoa has a more-or-less standardized way of doing it. You need to provide the implementation of the method, and you need to identify which object is going to carry out the logic. Note that WebView has a bunch of different delegate protocols. Adopting all or part of one of them is called conforming to the protocol. This is stated in code as (I am providing a skeleton of your class here):
#interface webberAppDelegate : NSObject<WebResourceLoadDelegate> {
WebView* webview;
}
#end
here I'm assuming you derive from NSObject; substitute whatever your base class is. This lets the compiler know to expect that you'll provide implementations of the mandatory methods as well as whichever optional methods you require. It will complain if you do not implement the mandatory methods.
The more critical piece is to establish that the Webview ivar has a delegate that is going to provide implementations for one or all of methods declared by WebResourceLoadDelegate. You have a property for this ivar -- webview. Somewhere (e.g., in the viewDidLoad -- that's where I'd do it in iOS) you need to declare:
webview.resourceLoadDelegate = self;
assuming you want the callback handled by webberAppDelegate. Then in webberAppDelegate's implementation you need to provide the definition for the method webView:resource:willSendRequest:redirectResponse:fromDataSource:
-(NSURLRequest*) webView:(WebView*)webview resource:(id)sender willSendRequest:(NSURLRequest*)request redirectResponse:(NSURLResponse*)redirectresponse fromDataSource:(WebDataSource*)dataSource {
NSLog(#"willSendRequest delegate method called");
}
If you implement this much, you should see the log statement echoed in the console when you run your application. It's in that method that you need to handle the redirect. As I said before, I'm not well versed in the implementation of this protocol. According to this thread, it may actually be the WebPolicyDelegate protocol that you want. The principles involved will be the same.
As far as I can say, these two delegate methods can catch URLs for other frames/content. You should try around and test them on your pages. Read the Help for these methods to see how to e.g. prevent the client redirect.
// WebView delegate methods
// Used to indicate that we've started loading (so that we can update our progress indicator
// and status text field)
- (void)webView:(WebView *)sender didStartProvisionalLoadForFrame:(WebFrame *)frame
{
if( frame == [myWebView mainFrame] )
NSLog(#"EntryController: Started loading mainFrame");
else
NSLog(#"EntryController: Started loading Frame : %#", [[frame.dataSource request] URL]);
}
- (void)webView:(WebView *)sender willPerformClientRedirectToURL:(NSURL *)URL
delay:(NSTimeInterval)seconds fireDate:(NSDate *)date
forFrame:(WebFrame *)frame
{
NSLog(#"EntryController: willPerformClientRedirectToURL: %#",URL);
}
greetings
Jimmy
Although jstevenco's answer explains how you can know if a webview is trying to go to another url, it doesn't answer how you can prevent the webview from going to such url.
If you want to prevent WebView to going to any other URL you must implement the WebPolicyDelegate Protocol. It's important to note that this is an informal protocol so you cannot write #interface MyDelegate : NSObject <WebPolicyDelegate>.
Instead in your webberAppDelegate.h import Webkit:
#import <WebKit/WebPolicyDelegate.h>
and in webberAppDelegate.m implement the method webView:decidePolicyForNavigationAction:request:frame:decisionListener:, this method "is invoked every time a server redirect is encountered unless blocked by an earlier policy decision". (WebPolicyDelegate Protocol Documentation)
In this method you need to tell the decisionListener that it must ignore any redirection. This is the implementation:
- (void)webView:(WebView *)sender decidePolicyForNavigationAction:(NSDictionary *)actionInformation
request:(NSURLRequest *)request
frame:(WebFrame *)frame
decisionListener:(id <WebPolicyDecisionListener>)listener {
[listener ignore];
}
However, beware with this implementation because you will ignore ANY redirect, and I don't think that's what you want (it would be a static webpage); more likely, what you want it's to prevent redirections that leave your domain so probably what you really want is:
- (void)webView:(WebView *)sender decidePolicyForNavigationAction:(NSDictionary *)actionInformation
request:(NSURLRequest *)request
frame:(WebFrame *)frame
decisionListener:(id <WebPolicyDecisionListener>)listener {
if (![[[request URL] absoluteString] hasPrefix:#"yourdomain.com"]) {
[listener ignore];
}
}
Yes you can do this. Implement
– webView:shouldStartLoadWithRequest:navigationType:
This delegate . This method gets called whenever your webview is about to make a request. So now when someone clicks a button on your webpage, you will get a call to this method. After you catch this call, you can choose to do whatever you want with it. Like redirect the link through your own servers, or log a request to your server about user activity etc.
Example - here you are trying to intercept any links clicked on your webpage & pass it through myMethodAction first.
- (BOOL)webView:(UIWebView*)webView shouldStartLoadWithRequest:(NSURLRequest*)request navigationType:(UIWebViewNavigationType)navigationType
{
if(navigationType == UIWebViewNavigationTypeLinkClicked)
{
if(overrideLinksSwitch.on == TRUE)
{
[self myMethodAction];
[myWebView stopLoading];
return YES;
}
else
{
return YES;
}
}
return YES;
}
Hope this helps...
Related
I am having some difficulty with key-value-observing logic in conjunction with XCTest (the original code is being retrofitted with test coverage). The logic works fine in the normal (non-test) context, but blows out with an exception every time in the context of a test.
The gist of the logic is this -- I have two classes, call them Service and Helper. Scaffold implementations are:
interface Service : NSObject {
BOOL svcCallComplete;
}
#end
#implementation Service
- (id) init {
if ((self=[super init])==nil) {
return nil;
}
return self;
}
#end
interface Helper : NSObject {
}
#end
#implementation Helper
- (id) init {
if ((self=[super init])==nil) {
return nil;
}
return self;
}
#end
Helper is an observer of an attribute in Service. In the context of my normal runtime logic I do this with a call to a Service instance method addSvcObserver:
Service.m:
- (void) addSvcObserver:(id)observer {
[self addObserver:observer
forKeyPath:#"svcCallComplete"
options:NSKeyValueObservingOptionNew
context:nil];
}
Helper complies with the KVO observing pattern thus:
Helper.m:
- (void) observeValueForKeyPath:(NSString*)keyPath
ofObject:(id)object
change:(NSDictionary*)change
context:(void*)context {
}
Pretty straight-forward, and I won't go into the logic for monitoring the attribute change as the problem occurs way before that -- if I have a code excerpt like:
Service* service = [[Service alloc] init];
Helper* helper = [[Helper alloc] init];
[service addSvcObserver:helper];
there are no problems in the non-test case (i.e., this and associated KVO logic works as expected). However the addSvcObserver call when performed in the context of an XCTest test method produces an immediate access denied exception.
I've included an exception "break on all" breakpoint -- the problem seems to be occurring in objc_registerClassPair during the addObserver:forKeyPath:options:context: call. The test target has ARC explicitly disabled as the project for which it is providing test coverage (the target is iOS7) is non-ARC for legacy reasons; this does not seem to cause any problems with other tests.
Thoughts?
interface Service : NSObject {
}
#property (nonatomic) BOOL svcCallComplete;
You should declare svcCallComplete as a property.
because The observed class must be key-value observing compliant for the property that you wish to observe
The reason you get the objc_registerClassPair i think maybe because KVO dynamic register a subclass of your Service , but could not find a setter method of svcCallComplete.which the dynamic subclass needs to override that setter method and send notification.
For more detail read this.
The cause of this turned out to be that my implementation of the KVO logic was incomplete. According to the guide here, you must override the NSObject implementation of automaticallyNotifiesObserversForKey: when using manual change notification -- I somehow missed that in my initial read of the text. I added the following to my Service class:
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey {
BOOL automatic = NO;
if ([theKey isEqualToString:#"svcCallComplete"]) {
automatic = NO;
} else {
automatic = [super automaticallyNotifiesObserversForKey:theKey];
}
return automatic;
}
and all is now correct in the test case. Anyone care to hazard a guess as to why this was not blowing out in the normal (non-test) case?
So, I have a decent idea of what a delegate does, why use it, how to implement it etc. and I'm working on implementing it in one of my projects. The problem I'm trying to solve is to decouple my Controller objects from my Network Access class. In this context, the ideas get a little messy in my head.
I somehow intuitively feel that the NetworkAccessClass should be the delegate for a Controller object, because the NetworkAccessClass is acting as a helper for the Controller object. But it seems to work in a reverse fashion, because the following is apparently the right way to do it:
NetworkaccessClass.h
#protocol NetworkAccessDelegate
-(void) requestSucceded:(NSData *) data
-(void) requestFailed:(int) responseCode;
#end
#interface NetworkAccessClass : NSObject
{
id<NetworkAccessDelegate> networkDelegate;
}
#property(nonatomic, assign) id networkDelegate;
-(void) initWithDelegate:(id) delegate; //
#end
NetworkAccessClass.m
#implementation
#synthesize networkDelegate
-(void) initWithParams:(id) delegate
{
networkDelegate = delegate;
// Assign GET/POST vals, create request etc
[request startAsynchronous];
}
-(void) requestSucceded:(ASIHTTPRequest *) request
{
if([networkDelegate respondsToSelector:#selector(requestSucceded:)]) {
// Send the data to the controller object for it to use
...
}
}
-(void) requestFailed:(ASIHTTPRequest *) request
{
// Same as above. Send to request failed.
}
#end
And finally in my FirstViewController.h
#import "NetworkAccessClass.h"
#interface FirstViewController<NetworkAccessDelegate>
{
}
-(void) requestSucceded:(NSData *) data;
-(void) requestFailed:(int) responseCode;
#end
And the same in SecondViewController.h and so on.
Although this does decouple my Controllers from my Network class, I can't help feel it's wrong because the controllers in this case are acting as delegates or helper methods to the Network Class and not the other way round. Am I missing something basic? Or is this how it is?
Thanks,
Teja.
Delegates aren't "helper methods". Think of them as objects that get notified when something happens. (Although don't confuse them with "Notifications"--that's a different thing entirely.) In this case, your network class does it's stuff and then calls its delegate method on the View Controller that instantiated and fired, it to report the contents of that response to the view controller. The controller will then, presumably, update the view with the data that the network connector got. Classic delegate pattern, right there.
- (void)someMethod
{
if ( [delegate respondsToSelector:#selector(operationShouldProceed)] )
{
if ( [delegate operationShouldProceed] )
{
// do something appropriate
}
}
}
The documentation says:
The precaution is necessary only for optional methods in a formal protocol or methods of an informal protocol
What does it mean? If I use a formal protocol I can just use [delegate myMethod]?
You use it pretty much just when you think you need to: to check to see if an object implements the method you are about to call. Usually this is done when you have an optional methods or an informal protocol.
I've only ever used respondsToSelector when I'm writing code that must communicate with a delegate object.
if ([self.delegate respondsToSelector:#selector(engineDidStartRunning:)]) {
[self.delegate engineDidStartRunning:self];
}
You sometimes would want to use respondsToSelector on any method that returns and id or generic NSObject where you aren't sure what the class of the returned object is.
Just to add to what #kubi said, another time I use it is when a method was added to a pre-existing class in a newer version of the frameworks, but I still need to be backwards-compatible. For example:
if ([myObject respondsToSelector:#selector(doAwesomeNewThing)]) {
[myObject doAwesomeNewThing];
} else {
[self doOldWorkaroundHackWithObject:myObject];
}
As kubi mentioned respondsToSelector is normally used when you have a an instance of a method that conforms to a protocol.
// Extend from the NSObject protocol so it is safe to call `respondsToSelector`
#protocol MyProtocol <NSObject>
// #required by default
- (void) requiredMethod;
#optional
- (void)optionalMethod;
#end
Given and instance of this protocol we can safely call any required method.
id <MyProtocol> myObject = ...
[myObject requiredMethod];
However, optional methods may or may not be implemented, so you need to check at runtime.
if ([myObject respondsToSelector:#selector(optionalMethod)])
{
[myObject optionalMethod];
}
Doing this will prevent a crash with an unrecognised selector.
Also, the reason why you should declare protocols as an extension of NSObjects, i.e.
#protocol MyProtocol <NSObject>
Is because the NSObject protocol declares the respondsToSelector: selector. Otherwise XCode would think that it is unsafe to call it.
Old question, but I have learned to be very cautios with using stuff like addTarget:#selector(fu:) because the method name is not checked nor included in refactoring by XCODE. This has caused me quite some trouble already. So now I made it a habbit to always embed stuff like addTarget or addObserver in a respondsToSelector-Check like so:
if([self respondsToSelector:#selector(buttonClicked:)]){
[self.button addTarget:self action:#selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
}else{
DebugLog(#"Warning - a class or delegate did not respond to selector in class %#", self);
}
I know its not super elegant, but i'd rather add some boilerplate code than have an unexpected crash of my apps in the App Store.
i working with geocoding at the moment. The geocoding service allways works with delegates.
So let's say, I've got a
AskingClass and AnsweringClass(geocoding)
The AskingClass calls a function in the AnsweringClass to return the adress of the current location.
AnsweringClass should handle and capsulate the geocoding stuff. My Problem is, with all these delegates, I do not manage to come back to the orginal function, which the asking class has called. So I cannot give easily the adress back:
AskingClass.Adress= [AnsweringClass giveAdress];
I managed it, doing it with delegates, so the result comes back in a delegate function (somewhere) in the askingClass. But I'm not happy with that. It's seems oversized and complex.
with best regards
Klaus-Dieter
It is unclear why you are using a delegate pattern at all. Why not just use straight up classes?
Something like this (assuming that you are using a PCH file for your header files or otherwise importing 'em as needed):
AnsweringClass.h
#interface AnsweringClass:NSObject
- (MyAnswer *)answerThisDude;
#end
AskingClass.h
#class AnsweringClass; // just in case you including AskingClass.h before AnsweringClass.h
#interface AskingClass : NSObject
// {
// declare the ivar if you need support for 32 bit "classic" ABI
// AnsweringClass *theThingThatAnswers;
// }
#property(retain) AnsweringClass *theThingThatAnswers;
#end
Then you can do this:
AskingClass.m
#implementation AskingClass
#synthesize theThingThatAnswers;
- (void) setUpMyStuff // probably invoked by your designated initializer or app launch handler
{
self.theThingThatAnswers = [AnsweringClass new];
MyAnswer *theFirstAnswer = [self.theThingThatAnswers answerThisDude];
}
// don't forget a -dealloc if you aren't running GC'd
#end
No delegation necessary.
I have two classes that can act as a delegate of a third class, and both implement a formal protocol made entirely of optional methods. One of the classes implements everything while another only implements a couple methods that i care about. However, at runtime when i have the second class act as the delegate to the third class, and the third class ends up calling one of the unimplemented optional methods on that delegate, i get a runtime error essentially saying "Target does not respond to this message selector." I thought that objective-c handled this case correctly, and that it would just do nothing if that method wasn't actually defined on the class. Might there be something i'm missing?
When you call an optional method of your delegate, you need to make sure it responds to the selector before calling it:
if ([delegate respondsToSelector:#selector(optionalMethod)])
[delegate optionalMethod];
Optional protocol methods simply mean the object implementing the protocol does not have to implement the method in question - the callee then absolutely must check whether the object implements the method before calling (otherwise you'll crash, as you noticed). These NSObject HOM categories can be helpful:
#implementation NSObject (Extensions)
- (id)performSelectorIfResponds:(SEL)aSelector
{
if ( [self respondsToSelector:aSelector] ) {
return [self performSelector:aSelector];
}
return NULL;
}
- (id)performSelectorIfResponds:(SEL)aSelector withObject:(id)anObject
{
if ( [self respondsToSelector:aSelector] ) {
return [self performSelector:aSelector withObject:anObject];
}
return NULL;
}
#end
Then you can simply do:
[delegate performSelectorIfResponds:#selector(optionalMethod)];
This Blocks solution works well, once you get your head wrapped around what is going on. I added a BOOL result because I wanted to be able to conditionally run one of several optional methods. Some tips if you are trying to implement this solution:
First, if you haven't encountered Extension/Categories yet, you simply add this to the top of your class, OUTSIDE the existing class definition. It will be a public or private extension based on where you put it.
#implementation NSObject (Extensions)
// add block-based execution of optional protocol messages
-(BOOL) performBlock:(void (^)(void))block ifRespondsTo:(SEL) aSelector
{
if ([self respondsToSelector:aSelector]) {
block();
return YES;
}
return NO;
}
#end
Second, here's how you call it from your code:
BOOL b = [(NSObject*)self.delegate performBlock:^{
// code to run if the protocol method is implemented
}
ifRespondsTo:#selector(Param1:Param2:ParamN:)];
Replace Param1:Param2:ParamN: with the names of each parameter for your protocol method. Each one should end with a colon.
So if your protocol method looks like:
-(void)dosomething:(id)blah withObj:(id)blah2 andWithObj(id)blah3;
the last line would look like this:
ifRespondsTo:#selector(dosomething:withObj:andWithObj:)];
Blocks might provide a better solution. They allow to conditionally perform any code based on the existence of an implementation of a given method:
-(void) performBlock:(void (^)(void))block ifRespondsTo:(SEL) aSelector{
if ([self respondsToSelector:aSelector]) {
block();
}
}
By using this addition to NSObject, you can conditionally execute any #optional method, no matter how many parameters it might have.
See How to safely send #optional protocol messages that might not be implemented