Swift 5.3 macOS - Load Local HTML - swift

I am trying to load a local html based website into my macOS app and I am hitting issues, I have this working in iOS but not macOS.
My ViewController.swift looks like this
import Cocoa
import WebKit
class ViewController: NSViewController, WKUIDelegate, WKNavigationDelegate {
var webView: WKWebView!
override func loadView() {
let webConfig = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration: webConfig);
webView.uiDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
let htmlURL = Bundle.main.url(forResource: "index", withExtension: "html", subdirectory: "website")!
webView.loadFileURL(htmlURL, allowingReadAccessTo: htmlURL.deletingLastPathComponent())
}
}
The App runs but then I get this output in the console in Xcode:
WebPageProxy::tryReloadAfterProcessTermination: process crashed and the client did not handle it, not reloading the page because we reached the maximum number of attempts
The documentation for what I am trying to do seems to be lacking I wonder if anyone can help me sort this out please.

After some more digging I needed to do 2 things:
Amend my viewDidLoad() code:
let htmlURL = Bundle.main.url(forResource: "index", withExtension: "html", subdirectory: "polarix-website")!
webView.loadFileURL(htmlURL, allowingReadAccessTo: htmlURL.deletingLastPathComponent())
view = webView
Enable Outgoing Connections (Client) in the Signing & Capabilities

Related

Shared cookies with WKProcessPool for WKWebView in Swift

Can anyone please tell me how to create a WKProcessPool in Swift? I'm not familiar with Objective-C.
I have to create a WKProcessPool in order to have shared cookies with all WKWebViews. I want to keep cookies even when showing another viewcontroller with same class. I tried the following but it's not working.
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
var webView = WKWebView()
override func viewDidLoad() {
super.viewDidLoad()
let processPool = WKProcessPool()
let configuration = WKWebViewConfiguration()
configuration.processPool = WKProcessPool()
webView.navigationDelegate = self
view.addSubview(webView)
}
}
You need to use configuration.websiteDataStore property instead of processpool.
For the stored cookies use WKWebsiteDataStore.default() value.
For the private browsing use WKWebsiteDataStore.nonPersistent().
The apple site say:
If your app creates multiple web views, assign the same WKProcessPool
object to web views that may safely share a process space. Instantiate
an instance of this class and assign it to the processPool property of
each web view’s WKWebViewConfiguration object.
However, you set your processProtocol into class ViewControler. Then, this is redefined each time the view is instantiate. Do it:
import UIKit
import WebKit
let processPool = WKProcessPool()
class ViewController: UIViewController, WKNavigationDelegate { var webView = WKWebView()
override func viewDidLoad() {
super.viewDidLoad()
let configuration = WKWebViewConfiguration()
configuration.processPool = WKProcessPool()
webView.navigationDelegate = self
view.addSubview(webView)
}
}

How to embed and stream a video from the web within an Xcode macOS Cocoa Application?

I am trying to get my macOS Cocoa Application Xcode project to play some video when I feed it a specified URL.
To simplify the question lets just use an empty Xcode project.
I want to achieve the same level of control as I am able to get using AVPlayerViewController() in my iOS Single View Application. Within that app I am currently using the solution offered here.
However, only the second example works in my macOS Cocoa Application when adapted like so:
import Cocoa
import AVFoundation
import AVKit
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let videoURL = URL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")
let player = AVPlayer(url: videoURL!)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.view.bounds
self.view.layer?.addSublayer(playerLayer)
player.play()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
This option however has 2 downsides:
It creates a repetitive error message in the console:
2017-06-27 18:22:06.833441+0200 testVideoOnmacOS[24456:1216773] initWithSessionInfo: XPC connection interrupted
2017-06-27 18:22:06.834830+0200 testVideoOnmacOS[24456:1216773] startConfigurationWithCompletionHandler: Failed to get remote object proxy: Error Domain=NSCocoaErrorDomain Code=4097 "connection to service named com.apple.rtcreportingd" UserInfo={NSDebugDescription=connection to service named com.apple.rtcreportingd}
This AVPlayer solution does not offer the same control as the AVPlayerViewController() solution.
So I attempted to get the following AVPlayerViewController() solution to work. But it fails:
import Cocoa
import AVFoundation
import AVKit
class ViewController: NSViewController {
override func viewDidLoad() {
super.viewDidLoad()
let videoURL = URL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")
let player = AVPlayer(url: videoURL!)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
self.present(playerViewController, animated: true) {
playerViewController.player!.play()
}
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
The error I am getting is:
Use of unresolved identifier 'AVPlayerViewController' - Did you mean
'AVPlayerViewControlsStyle'?
I have tried to work around this in many different ways. It will be confusing if I spell them all out. My prominent problem seems to be that the vast majority of online examples are iOS based.
Thus, the main question:
How would I be able to successfully use AVPlayerViewController within my Xcode macOS Cocoa Application?
Yes, got it working!
Thnx to ninjaproger's comment.
This seems to work in order to get a video from the web playing / streaming on a macOS Cocoa Application in Xcode.
First, create a AVKitPlayerView on your storyboard and link it as an IBOutlet to your NSViewController.
Then, simply apply this code:
import Cocoa
import AVFoundation
import AVKit
class ViewController: NSViewController {
#IBOutlet var videoView: AVPlayerView!
override func viewDidLoad() {
super.viewDidLoad()
let videoURL = URL(string: "https://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4")
let player = AVPlayer(url: videoURL!)
let playerViewController = videoView
playerViewController?.player = player
playerViewController?.player!.play()
}
override var representedObject: Any? {
didSet {
// Update the view, if already loaded.
}
}
}
Update august 2021
Ok, it's been a while since this answer has been up and I am no longer invested in it. But it seems others still end up here and the solution needs a bit more work to work properly at times.
With thanks to #SouthernYankee65:
What is needed sometimes is to go to the project file and on the Signing & Capabilities tab check Outgoing Connection (Client). Then in the info.plist add App Transport Security Settings dictionary property, then add Allows Arbitrary Loads boolean property and set it to YES.
The video now plays. However, some errors in the console might occur.

Input type=file not working in WebView of OS X application

I have created an OSX application in Swift and embedded a WebView in it using Xcode 7. I have a file selector on the web page loaded by the WebView which asks the user to browse for a particular file from the user's computer. The problem I am facing is that nothing happens when the user clicks on the browse button.
The same file selector control is working fine if i open the same web page in Safari.
I am relatively new in swift, so help in this case would be appreciated.
Here is my viewDidLoad function:
func viewDidLoad() {
super.viewDidLoad()
let url=NSURL(string: "http://video.online-convert.com/convert-to-mp4")
let request=NSURLRequest(URL:url!)
webView.frameLoadDelegate=self
webView.mainFrame.loadRequest(request)
webView.shouldCloseWithWindow = true
webView.drawsBackground = true
}
Thanks.
I have not worked with this myself, but from puzzling bits and pieces together this seems to do what you'd like.
First...I'm using a WKWebView. That is declared and initialised with a local HTML file like so:
import Cocoa
import WebKit
class ViewController: NSViewController {
let webview: WKWebView = WKWebView()
override func viewDidLoad() {
super.viewDidLoad()
webview.autoresizingMask = [.viewWidthSizable, .viewHeightSizable]
webview.frame = view.bounds
webview.uiDelegate = self
view.addSubview(webview)
let fileURL = URL(fileURLWithPath: "/Users/myuserhere/Desktop/index.html")
webview.loadFileURL(fileURL, allowingReadAccessTo: fileURL)
}
}
The interesting part is webview.uiDelegate. This promises that we will conform to the WKUIDelegate protocol documented here. As it says:
The WKUIDelegate class provides methods for presenting native user interface elements on behalf of a webpage.
One of the methods you can implement is runOpenPanelWithParameters:
If you implement this method, you promise that you will present a file upload panel and call the callback method of this method with the outcome of what the user selected. Remember to also call the callback method when the user presses cancel.
Here is a quick and dirty example:
extension ViewController: WKUIDelegate {
func webView(_ webView: WKWebView, runOpenPanelWith parameters: WKOpenPanelParameters, initiatedByFrame frame: WKFrameInfo, completionHandler: #escaping ([URL]?) -> Void) {
let openPanel = NSOpenPanel()
openPanel.canChooseFiles = true
openPanel.begin { (result) in
if result == NSApplication.ModalResponse.OK {
if let url = openPanel.url {
completionHandler([url])
}
} else if result == NSApplication.ModalResponse.cancel {
completionHandler(nil)
}
}
}
}
Hopefully that gives you something to get started with.
Bonus Material
Here are some links that helped me:
How to upload files from WKWebView
How to implement the delegate method
How to create a NSOpenPanel in Swift
For iOS (asked by #DarshanMothreja)
I tried gluing together a simple program to do the same on iOS. I hope it is useful to you #DarshanMothreja
HTML
A file called index.html is added to the Xcode project. The content looks like this:
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
<meta charset="utf-8">
<title>Test</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<label for="file">File goes here</label>
<input type="file" name="file" value="File">
</body>
</html>
Swift
Here is the ViewController
import UIKit
import WebKit
class ViewController: UIViewController {
let webview: WKWebView = WKWebView()
override func viewDidLoad() {
super.viewDidLoad()
webview.autoresizingMask = [ .flexibleWidth, .flexibleHeight ]
webview.frame = view.bounds
webview.uiDelegate = self
view.addSubview(webview)
if let path = Bundle.main.path(forResource: "index", ofType: "html") {
let fileURL = URL(fileURLWithPath: path)
webview.loadFileURL(fileURL, allowingReadAccessTo: fileURL)
}
}
}
extension ViewController: WKUIDelegate { }
If I run the above, I get this result when I tap the "Choose File" button, no need to add any delegate methods.
Hope that gives you something to work with.

Injecting a new stylesheet into a website via uiwebview using iOS8 Swift XCode 6

I've seen a few options on here using just Objective-C, but I'm having trouble doing this with Swift iOS8 in XCode 6. I'm using the uiwebview to load a website. For sake of example, let's say its google.com.
#IBOutlet var website: UIWebView!
var url = "http://www.google.com"
func loadUrl() {
let requestURL = NSURL(string: url)
let request = NSURLRequest(URL: requestURL!)
website.loadRequest(request)
}
override func viewDidLoad() {
super.viewDidLoad()
website.delegate = self
loadUrl()
}
func webViewDidFinishLoad(website: UIWebView) {
var jsscript = "some script here"
website.stringByEvaluatingJavaScriptFromString(jsscript)
}
Using Swift, how would I inject a whole new stylesheet into the website so I can overwrite many of the styles?
Also, I'd like the new stylesheet to exist in one of my app's directories. Where is the best directory for that to be? Under "Supporting files?" And, how would I call that path in my code?
It would also be nice if the website wouldn't load until the styles had been applied.
So, I think I found at least one way to accomplish appending styles to the webpage being loaded using Swift:
var loadStyles = "var script =
document.createElement('link');
script.type = 'text/css';
script.rel = 'stylesheet';
script.href = 'http://fake-url/styles.css';
document.getElementsByTagName('body')[0].appendChild(script);"
website.stringByEvaluatingJavaScriptFromString(loadStyles)
Using the now preferred WKWebView you can put the external css file, say tweaks.css in the root of your project or in a sub-folder, then you can inject it into your page like this:
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
guard let path = Bundle.main.path(forResource: "tweaks", ofType: "css") else { return }
let css = try! String(contentsOfFile: path).replacingOccurrences(of: "\\n", with: "", options: .regularExpression)
let js = "var style = document.createElement('style'); style.innerHTML = '\(css)'; document.head.appendChild(style);"
webView.evaluateJavaScript(js)
}
To make the file available:
Click your project
Click your target
Select Build Phases
Expand Copy Bundle Resources
Click '+' and select your file.
Also make sure that your controller implements WKNavigationDelegate:
class ViewController: UIViewController, WKNavigationDelegate

Load URL on window load in Swift

I'm trying to load a URL in my WebView in Swift as soon as the main dialog loads. I'm not sure what I'm doing wrong:
class WebViewController: NSViewController {
#IBOutlet var webView: WebView!
// constants
let webUrl = "https://www.google.com"
let webUA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/38.0.2125.111 Safari/537.36"
override func viewDidLoad() {
super.viewDidLoad()
let request = NSMutableURLRequest(URL: NSURL(string: webUrl)!)
request.setValue(webUA, forHTTPHeaderField: "User-Agent")
webView.mainFrame.loadRequest(request)
}
}
When debugging there are no errors or warnings. I think I linked the nib with the objects properly:
I've been playing around with delegations for a few hours now. The page simply doesn't load.
At first I attempted to use applicationDidFinishLaunching, but that went even worse, because the WebView was never finished being created and was nil.
Alright, so change your ViewController to look like this:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var webView: UIWebView!
var urlPath = "http://www.google.com"
func loadAddressUrl() {
let requestUrl = NSURL(string: urlPath)
let request = NSURLRequest(URL: requestUrl!)
webView.loadRequest(request)
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
loadAddressUrl()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
And link the #IBOutlet to your web view controller. Example project available if you want.
Source: https://www.youtube.com/watch?v=Rva9ylPHi2w
Turns out I was pointing to the wrong WebView?!
In the hierarchy, it shows up like this:
I needed to use the one that's highlighted, not the inner one. The inner one was used when I control-dragged from Objects into the physical view object in the layout builder. Somehow dragging to this one fixed it.
Swfit 4 version of loadAddressUrl()
private func loadAddressUrl() {
let requestUrl = NSURL(string: url.text!)
let request = NSURLRequest(url: requestUrl! as URL)
webView.load(request as URLRequest)
}