I have a iframe of youtube player in my webview macos application, and most of the links (the <a> element) inside the iframe are not triggering the decidePolicyForNewWindowAction delegate.
The only working <a> element is the channel link, others like video title, the youtube icon are all silent, and I can't tell the differences between these <a>s.
So why are some links cannot trigger decidePolicyForNewWindowAction?
Documentation of the delegate: https://developer.apple.com/documentation/webkit/webpolicydelegate/1536381-webview?language=objc
Documentation of the iframe youtube player: https://developers.google.com/youtube/iframe_api_reference
For someone maybe interested:
Turns out it's not a iframe problem, it's because some <a> are not directly changing the url, it calls some javascript to do the job for it. This was inspected from the callstack.
In such case, the first thing is that one more delegate needs to be hooked up, the createWebViewWithRequest. This delegate returns nil by default. So change it like this:
- (WebView*)webView:(WebView *)sender createWebViewWithRequest:(NSURLRequest *)request
{
return sender;
}
Not doing much but let the following codes continue.
The second thing is that the javascript url request goes to decidePolicyForNavigationAction instead. So still not firing decidePolicyForNewWindowAction but at least there's a chance to know it.
In my case I add some condition inside decidePolicyForNavigationAction to distinguish my request and iframe's request, like this:
- (void) webView: (WebView*) sender decidePolicyForNavigationAction: (NSDictionary*) actionInformation
request: (NSURLRequest*) request
frame: (WebFrame*) frame
decisionListener: (id <WebPolicyDecisionListener>) listener
{
NSString* mainDocumentURL = [[request mainDocumentURL] absoluteString];
NSString* fromYT = #"https://www.youtube.com";
if ([mainDocumentURL hasPrefix:fromYT]) {
[[NSWorkspace sharedWorkspace] openURL:url]; // fires Safari
[listener ignore];
return;
}
// normal flow here, you could open it inside the webview or something
}
Related
I'm implementing a PDF viewer on the Mac and I want to let the user add annotations.
I've added a PDFAnnotationText to the page, and it appears just fine, but when the user clicks on it, the whole document is shrunk and an annotation list appears down the left side.
I want to customize this to display the annotation as a pop-up, similar to what Preview does. The PDFAnnotationText class reference says I can do this:
Each PDFAnnotationText object has a PDFAnnotationPopup object associated with it. In its closed state, the annotation appears as an icon. In its open state, it displays as a pop-up window containing the text of the note. Note that your application must do the work to put up a window containing the text in response to a PDFViewAnnotationHitNotification.
But when I add an observer for PDFViewAnnotationHitNotification, no notification is delivered when I click on the annotation.
I've contacted Apple about this, and the answer I received back was that it's a bug. A workaround is to handle the mouse click yourself, walk the annotations and look for a hit.
Something like this (code which runs in a mouseDown handler in a PDFView subclass):
NSPoint windowPoint = [self.window convertScreenToBase:[NSEvent mouseLocation]];
NSPoint viewPoint = [self convertPoint:windowPoint fromView:nil];
PDFPage *page = [self pageForPoint:viewPoint nearest:NO];
if (page != nil) {
NSPoint pointOnPage = [self convertPoint:viewPoint toPage:page];
for (PDFAnnotation *annotation in page.annotations) {
NSRect annotationBounds;
// Hit test annotation.
annotationBounds = [annotation bounds];
if (NSPointInRect(pointOnPage, annotationBounds))
{
NSLog(#"Annotation hit: %#", annotation);
}
}
}
I am making a web browser. Created a URL Text Bar and a WebView. But I had always wanted to know how to make a progress/loading bar that tells the user how much of the webpage loaded, how do I do this?
I have checked Apple's WebKit Development Guide but did not explain anything about loading bars.
Here's a working implementation, assuming you have an #property for the web view, a URL text field, and a progress indicator.
Your controller needs to be the resource delegate and policy delegate:
- (void)awakeFromNib
{
self.webView.resourceLoadDelegate = self;
self.webView.policyDelegate = self;
}
Make sure the URL field has the same URL as the page. We use this for tracking later.
- (void)webView:(WebView *)webView decidePolicyForNavigationAction:(NSDictionary *)actionInformation request:(NSURLRequest *)request frame:(WebFrame *)frame decisionListener:(id<WebPolicyDecisionListener>)listener
{
self.urlField.stringValue = request.URL.absoluteString;
[listener use];
}
Detect when a page starts loading.
- (id)webView:(WebView *)sender identifierForInitialRequest:(NSURLRequest *)request fromDataSource:(WebDataSource *)dataSource
{
if ([request.URL.absoluteString isEqualToString:self.urlField.stringValue]) {
[self.loadProgress setIndeterminate:YES];
[self.loadProgress startAnimation:self];
}
return [[NSUUID alloc] init];
}
This will be called as new data comes in. Note the "length" parameter is the amount of length received in this chunk of data. You need to use dataSource to find the actual amount received so far. Also note that most web servers (as in, almost any dynamic webpage) will not return a content-length header, so you have to take a wild guess.
- (void)webView:(WebView *)sender resource:(id)identifier didReceiveContentLength:(NSInteger)length fromDataSource:(WebDataSource *)dataSource
{
// ignore requests other than the main one
if (![dataSource.request.URL.absoluteString isEqualToString:self.urlField.stringValue])
return;
// calculate max progress bar value
if (dataSource.response.expectedContentLength == -1) {
self.loadProgress.maxValue = 80000; // server did not send "content-length" response. Pages are often about this size... better to guess the length than show no progres bar at all.
} else {
self.loadProgress.maxValue = dataSource.response.expectedContentLength;
}
[self.loadProgress setIndeterminate:NO];
// set current progress bar value
self.loadProgress.doubleValue = dataSource.data.length;
}
And this delegate method is called when the request has finished loading:
- (void)webView:(WebView *)sender resource:(id)identifier didFinishLoadingFromDataSource:(WebDataSource *)dataSource
{
// ignore requests other than the main one
if (![dataSource.request.URL.absoluteString isEqualToString:self.urlField.stringValue])
return;
[self.loadProgress stopAnimation:self];
}
Apple has an example on how to track resources as they load on this page.
https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/DisplayWebContent/Tasks/ResourceLoading.html#//apple_ref/doc/uid/20002028-CJBEHAAG
Instead of printing out how many resources are left out of how many, you could use the numbers to set a progress bar.
BTW: This counting seems to be the only way to tell if a given page/URL has really finished loading.
I have one button in sample.xib named "Google", and I want to load google page in sample1.xib using webview. I may have more buttons like fb,youtube,etc. Now, I want that which button is clicked and according to that, sample1.xib display the webpage. What should I do to get text or id of button ?
Using tags is a little clunky. You have another thing to keep track of. There are two ways of doing this.
First Way
Use a different action method for each button. This may seem like a lot of repeated code, but I believe in writing code that is easier for the human to read, and letting the compiler take care of optimising (at least until I profile) It makes it a lot easier to see exactly what each method does and an action name like openGoogleInWebview is a lot more informative than buttonAction
So with this - wire up each buttons action (it's quite easy in Interface Builder) to each action:
- (IBAction)openGoogleInWebview;
- (IBAction)openYouTubeInWebview;
etc.
See - and if you want to optimise this you can write a method that takes a URL and opens it in the webview and send the URL to that: For example
- (IBAction)openGoogleInWebview {
NSURL *url = // Your URL here;
[self openWebviewWithURL:url];
}
...
- (void)openWebviewWithURL:(NSURL *)url {
// load your webview with the url
}
Keeps things compartmentalised, readable and easy to maintain.
Second Way
If you really want to go with the single method that depends on the button that is pressed, there is no reason to use the tag of the button. Which isn't maintainable. You already have a unique identifier for each button; it's pointer address.
If you create IBOutlets for your buttons in your view controller, all you need to do is check the pointer:
.h
#property (weak, nonatomic) IBOutlet UIButton *googleButton;
#property (weak, nonatomic) IBOutlet UIButton *youTubeButton;
.m
- (IBAction)buttonClicked:(id)sender {
if (sender == googleButton) {
// load Google
} else if (sender == youTubeButton) {
// load YouTube
} // etc
...
}
Again - more maintainable; you don't have to keep track of the button's tag. And the code is much more readable because you can see that the google button loads the google page, which is cleaner than saying that tag1 opens the google page and tag 1 is supposed to be the tag for the google button.
Set tag for your buttons and treat it as an ID(Note: only number is valid).
[fbButton setTag:1];
[fbButton addTarget:self action:#selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside];
//...
[youtubeButton setTag:2];
[youtubeButton addTarget:self action:#selector(buttonAction:) forControlEvents:UIControlEventTouchUpInside];
//...
and your button action:
- (void)buttonAction:(id)sender
{
switch (((UIButton *)sender).tag) {
case 1:
// load facebook view
break;
case 2:
// load youtube view
break;
default:
break;
}
}
I have got the solution....declare variable in second view...and use it in first view....and get tag from first view of tag....:)
I load the webview and set allowsScrolling to NO, but webview still shows scroll bars... Banging your head on your computer hurts a lot more now that MacBooks have sharp metal edges.
My code:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
NSString *webFolder = #"file:///<WebFolderPath>";
[[[productWeb mainFrame] frameView] setAllowsScrolling:NO];
[productWeb setFrameLoadDelegate:self];
[[productWeb mainFrame] loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[webFolder stringByAppendingString:#"webpage.html"]]]];
}
I even setup the frame loading delegate to report about the scrolling status:
- (void)webView:(WebView *)sender didFinishLoadForFrame:(WebFrame *)frame
{
NSLog(#"Scrolling %#",[[frame frameView] allowsScrolling] ? #"Allowed" : #"Not Allowed");
[[frame frameView] setAllowsScrolling:NO];
NSLog(#"Scrolling %#",[[frame frameView] allowsScrolling] ? #"Allowed" : #"Not Allowed");
}
Which still gives me the unhappy:
2010-08-24 15:20:09.102 myApp[30437:a0f] Scrolling Allowed
2010-08-24 15:20:09.104 myApp[30437:a0f] Scrolling Not Allowed
And yet the scrollbars continue to show! Hopefully, it is something stupid I'm doing as I don't want to get any more blood on my laptop.
I found I had to edit the HTML of the page I was trying to display to make sure that it was setup to take the full screen (and not more)... there was a DIV that had a minimum width set. Once I made sure the body had height = 100% and that none of the DIV's had a fixed or minimum width set that was smaller then the box I wanted to show it in everything came together :)
Are you trying to prevent the user from scrolling the view at all? You can just set productWeb.userInteractionEnabled = NO. Or are you just trying to prevent the bars from showing when the user is scrolling?
Here's another thing you can try: inject some JavaScript into your UIWebView that disables the touchmove event:
[productWeb stringByEvaluatingJavaScriptFromString:#"document.ontouchmove = function(e){e.preventDefault();}"];
This leaves the user interaction enabled, but should prevent any scrolling.
Try this if you use Swift:
import Foundation
import Cocoa
import WebKit
class WebViewRep: WebView {
//... hidden code
required init?(coder: NSCoder) {
super.init(coder: coder)
//Disable scrolling
self.mainFrame.frameView.allowsScrolling = false
//Sets current object as the receiver of delegates
self.policyDelegate = self
self.frameLoadDelegate = self
//Load homepage
self.loadHomePage()
}
//... hidden code
override func webView(sender: WebView!, didFinishLoadForFrame frame: WebFrame!) {
//Completely disable scroll
frame.frameView.allowsScrolling = false
self.stringByEvaluatingJavaScriptFromString("document.documentElement.style.overflow = 'hidden';")
}
}
Sadly, it's not enough to use allowScrolling. It works sometimes, but not always. So, you have to manipulate loaded page too.
Of course, don't forget to set WebViewRep as custom class for your WebView.
I'm guessing your web view is in a nib that's being loaded before -applicationDidFinishLaunching: is called.
A quick fix:
In your nib, select the window and deselect "Visible at Launch" attribute.
Then after you call -setAllowsScrolling:, call -[self.window makeKeyAndOrderFront:nil] to show the window.
A better fix is to move your window loading code from the app delegate to a window controller subclass, do the call to -setAllowsScrolling: in -[NSWindowController windowDidLoad], and call -[NSWindowController showWindow:] from your app delegate to show the window.
I did try – windowDidExpose: but it didn't work. What do I have to try for this?
My window is a utility window.
-- edit for more clarity --
What I want are:
viewWillAppear
viewWillDisappear
viewDidLoad
viewDidUnload
in Cocoa Touch.
Very old question, but only for documentation purpose:
Track open:
In your windows controller override the method:
-(void)showWindow:(id)sender
{
//add this for track the window close
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(windowWillClose)
name:NSWindowWillCloseNotification
object:nil];
[super showWindow:sender];
//do here what you want...
}
Track close:
Implement the method
-(void)windowWillClose
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
//do here what you want...
}
There is windowDidClose:, but that probably only refers to closing; if you're sending your window an orderOut: message, I don't think that counts.
You probably need to either just track it from whatever code you're ordering the window in and out from, or subclass the window's class and override methods like makeKeyAndOrderFront: and orderOut: (whatever you're using, at least) to post custom notifications before calling up to super.
For Swift
Track open: In your windows controller override the method:
override func showWindow(sender: AnyObject?) {
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(windowWillClose), name: NSWindowWillCloseNotification, object: nil)
}
Track close: Implement the method:
func windowWillClose() -> Void {
NSNotificationCenter.defaultCenter().removeObserver(self);
//Do here what you want..
}
I came up with a hack for dealing with this. There is no notification that signals that a window has been put on screen, but there's a notification that's pretty much guaranteed to be sent when a window is put on screen. I'm speaking of NSWindowDidUpdateNotification, which indicates that a window has refreshed itself.
Of course, it's not only sent when the window appears—it's sent every time the window updates. Needless to say, this notification is sent a lot more than once. So you want to watch for it the first time, do your thing, and ignore any subsequent notifications. In my case, I wanted to add a sheet to a window that another part of my app would order in later. So I did something like this:
__block id observer = [NSNotificationCenter.defaultCenter addObserverForName:NSWindowDidUpdateNotification object:window queue:nil usingBlock:^(NSNotification *note) {
[self showSetupSheet];
[NSNotificationCenter.defaultCenter removeObserver:observer];
}];
There's no particular reason you would have to use a block-based observer—a method-based observer would work just as well.