Open WebView URL in Browser - macos

I made a very simple Swift application that loads a webpage with links on it. Whenever I click the links, they do not open. How would I got about having the links on the loaded .html webpage open in a browser window for OS X?
Here is my implementation:
import Cocoa
import WebKit
class ViewController: NSViewController {
#IBOutlet weak var webView: WebView!
override func viewDidLoad() {
super.viewDidLoad()
let urlString = "URL"
self.webView.mainFrame.loadRequest(NSURLRequest(URL: NSURL(string: urlString)!))
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
}

First, set your WebView's policy delegate and your initial URL as a class variable:
let url = NSURL(string: "http://www.google.com/")!
// ...
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.webView.policyDelegate = self
self.webView.mainFrame.loadRequest(NSURLRequest(URL: self.url))
}
Then, override the delegate methods to intercept navigation.
override func webView(webView: WebView!, decidePolicyForNewWindowAction actionInformation: [NSObject : AnyObject]!, request: NSURLRequest!, newFrameName frameName: String!, decisionListener listener: WebPolicyDecisionListener!) {
println(__LINE__) // the method is needed, the println is for debugging
}
override func webView(webView: WebView!, decidePolicyForNavigationAction actionInformation: [NSObject : AnyObject]!, request: NSURLRequest!, frame: WebFrame!, decisionListener listener: WebPolicyDecisionListener!) {
if request.URL!.absoluteString == self.url.absoluteString { // load the initial page
listener.use() // load the page in the app
} else { // all other links
NSWorkspace.sharedWorkspace().openURL(request.URL!) // take the user out of the app and into their default browser
}
}

Also you can decide what links to open in WebView and what - in browser as easy as to write target attribute in your html page like
external page
And use target check in the decidePolicyForNewWindowAction, menthioned above. I've placed the full answer in this question thread. Hope you can translate it to swift yourself.

Related

Xcode Cocoa: Couldn't read values in CFPrefsPlistSource<0x600002909f10>

I'm trying to implement WKWebView on Cocoa/MacOS but I'm getting this error:
Unable to load Info.plist exceptions (eGPUOverrides)
Couldn't read values in CFPrefsPlistSource<0x600002909f10>
(Domain: com.apple.Accessibility, User: kCFPreferencesCurrentUser,
ByHost: No, Container: kCFPreferencesNoContainer, Contents Need Refresh: Yes): accessing preferences outside an application's container requires user-preference-read or file-read-data sandbox access
Here is my implementation:
import Cocoa
import WebKit
class ViewController: NSViewController {
#IBOutlet var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
if let url = URL(string: "https://www.apple.com") {
let request = URLRequest(url: url)
webView.load(request)
}
}
}
My question is how can I fix this issue on my implementation?

Simple Swift Cocoa app with WebKit: Upload picture doesn't work

I decided to make my own FB chat app that simply shows https://messenger.com on a WebView after trying other 'freemium' apps.
My ViewController.swift has just a few lines of code that loads URL on the web view
import Cocoa
import WebKit
class ViewController: NSViewController {
#IBOutlet weak var webView: WebView!
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string: "https://messenger.com")
let request = NSURLRequest(URL: url!);
webView.mainFrame.loadRequest(request);
}
override var representedObject: AnyObject? {
didSet {
// do nothing
}
}
}
Besides adding NSAppTransportSecurity key to info.plist to unblock HTTP traffic via HTTPS connection, I have not done any other settings.
Question
Please take a look at this image first.
Everything looks fine & working except two things.
Uploading image does not work - I labeled as 1 in the picture.
normally (as in other released apps or from web browsers) if you click that icon, it shows an explorer to upload a picture like below.
My app completely ignores user's click on that icon so I cannot upload any pictures to the chat. Interestingly, if I drag and drops the picture to the webview, it uploads fine.
Shared picture does not show up - I labeled as 2 in the picture.
again, from other browsers or released apps, it shows the pictures that I shared with participants like below. (of course I censored the pictures)
my app tries to load the pics, but does not display them. I can see it trying to load because I see circular progress indicator while loading.
Why?
I suspect that there might be a way to listen to the JavaScript that's triggered within the WebView and link to a file explorer or something like that?
This I have no idea. I'm logged into Messenger (basically Facebook), so I think session is not a problem here. Maybe some jQuery loading issue??
What should I do to solve these issues?
There is indeed a delegate method to open a new panel called runOpenPanelForFileButtonWithResultListener, documentation here.
In the delegate method, just create a new NSOpenPanel like this:
func webView(sender: WebView!, runOpenPanelForFileButtonWithResultListener resultListener: WebOpenPanelResultListener!, allowMultipleFiles: Bool) {
let openDialog = NSOpenPanel()
if (openDialog.runModal() == NSOKButton) {
let fileName: String = (openDialog.URL?.path)!
resultListener.chooseFilename(fileName) // Use chooseFilenames for multiple files
}
}
I just tried to create a WebView from Messagers App and images are loading well.
You should try to enable WebView options like "Autoload Images" or "Enable Animated Images" from interface builder (or by code).
This code works for me and what is nice here is that you make the download of image asynchronous. You can find more about this concept here: http://www.raywenderlich.com/79149/grand-central-dispatch-tutorial-swift-part-1 and: http://www.raywenderlich.com/79150/grand-central-dispatch-tutorial-swift-part-2
Edited
I. You have to create new Class or a new iOS Swift File named ImageLoader with this content:
class ImageLoader {
var cache = NSCache()
class var sharedLoader : ImageLoader {
struct Static {
static let instance : ImageLoader = ImageLoader()
}
return Static.instance
}
func imageForUrl(urlString: String, completionHandler:(image: UIImage?, url: String) -> ()) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {()in
var data: NSData? = self.cache.objectForKey(urlString) as? NSData
if let goodData = data {
let image = UIImage(data: goodData)
dispatch_async(dispatch_get_main_queue(), {() in
completionHandler(image: image, url: urlString)
})
return
}
var downloadTask: NSURLSessionDataTask = NSURLSession.sharedSession().dataTaskWithURL(NSURL(string: urlString)!, completionHandler: {(data: NSData!, response: NSURLResponse!, error: NSError!) -> Void in
if (error != nil) {
completionHandler(image: nil, url: urlString)
return
}
if data != nil {
let image = UIImage(data: data)
self.cache.setObject(data, forKey: urlString)
dispatch_async(dispatch_get_main_queue(), {() in
completionHandler(image: image, url: urlString)
})
return
}
})
downloadTask.resume()
})
}
}
II. In your actual viewController you call the method 'imageForUrl' from ImageLoaded following this lines of code:
ImageLoader.sharedLoader.imageForUrl("http://upload.wikimedia.org/wikipedia/en/4/43/Apple_Swift_Logo.png", completionHandler:{(image: UIImage?, url: String) in
self.myImage.image = image!
})
I took the code from this link: https://teamtreehouse.com/community/does-anyone-know-how-to-show-an-image-from-url-with-swift
Edited for image loaded on webview
Here is the code. It works perfect for me:
override func viewDidLoad() {
super.viewDidLoad()
let myWebView:UIWebView = UIWebView(frame: CGRectMake(0, 0, UIScreen.mainScreen().bounds.width, UIScreen.mainScreen().bounds.height))
myWebView.loadRequest(NSURLRequest(URL: NSURL(string: "https://scontent-vie1-1.xx.fbcdn.net/hphotos-xap1/v/t1.0-9/12144725_10204647881668565_4367944825116750386_n.jpg?oh=5ecdae91f5258ffe0e0355e176f8eb8a&oe=56B007CA")!))
self.view.addSubview(myWebView)
}

Sent email, without confirmation using swift (OSX)

I cannot manage to send an email using Swift on OSX.
Its almost do the work, but don't sent the email, and on my personal computer, its opening Google Chrome for some raison.
Here is my code
import Cocoa
class ViewController: NSViewController, NSSharingServiceDelegate {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
#IBAction func mag(sender: AnyObject) {
let body = "BODY EMAIL"
let shareItems = [body] as NSArray
var service = NSSharingService(named: NSSharingServiceNameComposeEmail)
service?.delegate = self
service?.recipients = ["email#address.com"]
let subject = "Subject!"
service?.subject = subject
service?.performWithItems(shareItems as [AnyObject])
}
}

how do I switch between NSViewControllers using NSPageController?

I have had bad luck finding any examples on the web that closely match what I am trying to do. I am trying to using NSPageController to view and switch between multiple NSPageControllers. My steps.
I create a new OS X swift project
I add an object to the ViewController and make it of NSPageController class.
I add two buttons, one I label "Next" and the other one I label "Back" for the transitions.
I link the buttons to the NSPageController object as navigateForward and navigateBack actions.
I create an outlet in the custom NSViewController class for the NSPageController object and add the specific NSPageController delegate methods.
I add two additional view controllers in storyboard and create an identifier for them to reference back in my custom view controller class: Wizard1, Wizard2.
import Cocoa
class ViewController: NSViewController, NSPageControllerDelegate {
#IBOutlet var myPageController: NSPageController!
override func viewDidLoad() {
super.viewDidLoad()
let vc1: AnyObject? = self.storyboard!.instantiateControllerWithIdentifier("Wizard1")
let vc2: AnyObject? = self.storyboard!.instantiateControllerWithIdentifier("Wizard2")
self.myPageController.arrangedObjects.append(vc1!)
self.myPageController.arrangedObjects.append(vc2!)
// Do any additional setup after loading the view.
}
override init?(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
myPageController = NSPageController()
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil?)
}
required init?(coder aDecoder: NSCoder) {
myPageController = NSPageController()
super.init(coder:aDecoder)
}
override var representedObject: AnyObject? {
didSet {
// Update the view, if already loaded.
}
}
func pageController(pageController: NSPageController, identifierForObject object: AnyObject!) -> String! {
return "View"
}
func pageController(pageController: NSPageController, viewControllerForIdentifier identifier: String!) -> NSViewController! {
let vc1: AnyObject? = self.storyboard!.instantiateControllerWithIdentifier("Wizard1")
return vc1 as NSViewController
}
func pageController(pageController: NSPageController, prepareViewController viewController: NSViewController!, withObject object: AnyObject!) {
viewController.representedObject = object
}
func pageControllerDidEndLiveTransition(pageController: NSPageController) {
pageController.completeTransition()
}
func pageControllerWillStartLiveTransition(pageController: NSPageController) {
self.presentViewControllerAsModalWindow(self.storyboard?.instantiateControllerWithIdentifier("Wizard2") as NSViewController)
}
}
The error I get when pressing the Next button is:
-[NSNib initWithNibNamed:bundle:] could not load the nibName: NSPageController in bundle (null).
Perhaps you are trying to load a nib with the wrong name in AppDelegate.m or wherever you are initializing your page controller.
Otherwise you have missed creating a .xib file and to name it NSPageController. When creating a Cocoa Touch Class there is a checkbox to also create an xib file for your class if needed.
This line is responsible for the error:
myPageController = NSPageController()
You're trying to initialize a view controller without a nib, that's why it does not work. By default the NSViewController's name is taken to identify the nib that corresponds to it. In your case it is "NSPageController".

Progress Bar in Cocoa

I have a very simple application that contains a WebView. This webview loads an HTML5 app and it takes some time while the content is being built inside the webview.
I would like to show a progress bar until the content finishes loading and show the webview when the content is ready. It takes roughly 10 seconds.
??
Swift 2.2
override func viewDidLoad() {
super.viewDidLoad()
webView.frameLoadDelegate = self
webView.addObserver(self, forKeyPath: "estimatedProgress", options: .New, context: nil) // add observer for key path
}
/// Observer listening for progress changes
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if (keyPath == "estimatedProgress") { // listen to changes and updated view
if self.webView.estimatedProgress == 0 { return }
// All UI operations always in main thread
dispatch_async(dispatch_get_main_queue(), {
// *100 because progressIndicator in Mac OS wants values from 0 to 100
self.progressIndicator.doubleValue = self.webView.estimatedProgress * 100
})
}
}
/// Hide progress indicator on finish
func webView(sender: WebView!, didFinishLoadForFrame frame: WebFrame!) {
dispatch_async(dispatch_get_main_queue(), { self.progressIndicator.hidden = true })
}
/// Show progress indicator on start page loading
func webView(sender: WebView!, didStartProvisionalLoadForFrame frame: WebFrame!) {
dispatch_async(dispatch_get_main_queue(), { self.progressIndicator.hidden = false })
}
Swift 3
override func viewWillAppear() {
super.viewWillAppear()
webView.addObserver(self, forKeyPath: "estimatedProgress", options: .new, context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if (keyPath == "estimatedProgress") { // listen to changes and updated view
DispatchQueue.main.async {
// Here you can do set your actions on progress update
// E.g.: someProgressBar.doubleValue = self.webView.estimatedProgress * 100
}
}
}
override func viewWillDisappear() {
super.viewWillDisappear()
webView.removeObserver(self, forKeyPath: "estimatedProgress")
}
Full solution
I've created Swift 3 Cocoa code snippet with NSViewController with embedded WKWebViewController and NSProgressIndicator so you can look at live example.
You need to create a class that conforms to the WebFrameLoadDelegate protocol and set it as the delegate for your WebView.
Delegates in Cocoa are its callback pattern. You make a class that conforms to a protocol, implementing the required messages and whatever optional messages you need, add that class as a delegate to the main class, in your case the WebView, and your delegate gets messages whenever things happen in the main class.
From your delegate you could create a timer that repeats ever 1/10th of a second and sends the main WebView the message - (double)estimatedProgress to update your progress bar. Once the view is loaded invalidate the timer and remove the progress bar.
WebKit posts WebViewProgressEstimateChangedNotification and friends to give you this information

Resources