Printing from WKWebView in Swift ignores background - swift

I try to print html code to a pdf with the following code:
class PDFPrinter: NSObject, WKNavigationDelegate {
static var shared = PDFPrinter()
let webView = WKWebView()
private var saveURL: URL!
private var completion: () -> () = {}
override init() {
super.init()
self.webView.navigationDelegate = self
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
let printInfo = NSPrintInfo(dictionary: [
.paperSize: CGSize(width: 595.28, height: 841.89),
.jobDisposition: NSPrintInfo.JobDisposition.save,
.jobSavingURL: self.saveURL!,
])
printInfo.horizontalPagination = .automatic
printInfo.verticalPagination = .automatic
let margin = 20.0
printInfo.leftMargin = margin
printInfo.topMargin = margin
printInfo.rightMargin = margin
printInfo.bottomMargin = margin
let pro = self.webView.printOperation(with: printInfo)
pro.showsPrintPanel = false
pro.showsProgressPanel = false
let selector = #selector(self.printOperationDidRun(printOperation: success: contextInfo:))
pro.runModal(for: NSWindow(), delegate: self, didRun: selector, contextInfo: nil)
}
func printHTML(htmlString: String, saveURL: URL, completion: #escaping () -> ()) {
self.saveURL = saveURL
self.completion = completion
webView.loadHTMLString(htmlString, baseURL: nil)
}
#objc func printOperationDidRun( printOperation: NSPrintOperation,
success: Bool,
contextInfo: UnsafeMutableRawPointer?){
self.completion()
}
}
This works except of one thing: The background of the html is completely ignored, my pdf is always completely white. I could not find any setting in WKWebView, NSPrintInfo or NSPrintOperation how to change that. Any ideas?

Related

Pick and play a video in SwiftUI — how convert from UIKit code?

I'm working on a camera app and I've got a problem.
I've never used UIKit to build an app, but a lot of the reference code does.
So I tried to convert it using swiftUI but I failed.
There is UIKit code which I want to convert to SwiftUI.
static func startMediaBrowser(
delegate: UIViewController & UINavigationControllerDelegate & UIImagePickerControllerDelegate,
sourceType: UIImagePickerController.SourceType
) {
guard UIImagePickerController.isSourceTypeAvailable(sourceType)
else { return }
let mediaUI = UIImagePickerController()
mediaUI.sourceType = sourceType
mediaUI.mediaTypes = [kUTTypeMovie as String]
mediaUI.allowsEditing = true
mediaUI.delegate = delegate
delegate.present(mediaUI, animated: true, completion: nil)
}
import AVKit
import MobileCoreServices
import UIKit
class PlayVideoViewController: UIViewController {
#IBAction func playVideo(_ sender: AnyObject) {
VideoHelper.startMediaBrowser(delegate: self, sourceType: .savedPhotosAlbum)
}
}
// MARK: - UIImagePickerControllerDelegate
extension PlayVideoViewController: UIImagePickerControllerDelegate {
func imagePickerController(
_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]
) {
guard
let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String,
mediaType == (kUTTypeMovie as String),
let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL
else { return }
dismiss(animated: true) {
let player = AVPlayer(url: url)
let vcPlayer = AVPlayerViewController()
vcPlayer.player = player
self.present(vcPlayer, animated: true, completion: nil)
}
}
}
// MARK: - UINavigationControllerDelegate
extension PlayVideoViewController: UINavigationControllerDelegate {
}
Here's what I've tried, and the compilation passes, but it only does UIImagePickerController() , and the delegate function I wrote doesn't work.
import SwiftUI
import AVKit
import MobileCoreServices
import UIKit
struct ContentView: View {
#State private var isShowVideoLibrary = false
#State private var image = UIImage()
#State private var isShowCamara = false
var body: some View {
VStack {
HStack{
Button {
isShowVideoLibrary.toggle()
} label: {
Text("Play video")
}
}
}
.sheet(isPresented: $isShowVideoLibrary) {
VideoPicker(sourceType: .photoLibrary)
}
}
struct VideoPicker: UIViewControllerRepresentable {
var sourceType: UIImagePickerController.SourceType = .photoLibrary
#Environment(\.presentationMode) private var presentationMode
func makeUIViewController(context: UIViewControllerRepresentableContext<VideoPicker>) -> UIViewController {
let mediaUI = UIImagePickerController()
mediaUI.sourceType = sourceType
mediaUI.mediaTypes = [kUTTypeMovie as String]
mediaUI.allowsEditing = true
mediaUI.delegate = context.coordinator
return mediaUI
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
final class Coordinator : NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate{
var parent: VideoPicker
init(_ parent: VideoPicker) {
self.parent = parent
}
private func imagePickerController(
_ picker: UIImagePickerController,
didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) -> UIViewController {
guard
let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String,
mediaType == (kUTTypeMovie as String),
let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL
else { return AVPlayerViewController()}
// 2
parent.presentationMode.wrappedValue.dismiss()
//3
let player = AVPlayer(url: url)
let vcPlayer = AVPlayerViewController()
vcPlayer.player = player
return vcPlayer
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
}
The problem you have is that you haven't implemented the correct UIImagePickerControllerDelegate function signature.
Your Coordinator has:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) -> UIViewController
while the correct method is:
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any])
The method won't get called unless the signature matches exactly.
A solution
The UIImagePickerController is just used to select the image or video, you'll need additional cod to play the selected video. Luckily SwiftUI has a VideoPlayer that makes it easy:
import UniformTypeIdentifiers
struct ContentView: View {
#State private var isShowVideoLibrary = false
#State private var url: URL?
var body: some View {
Group {
if let url {
VideoPlayer(player: AVPlayer(url: url))
} else {
VStack {
HStack{
Button {
isShowVideoLibrary.toggle()
} label: {
Text("Play video")
}
}
}
}
}
.sheet(isPresented: $isShowVideoLibrary) {
VideoPicker(sourceType: .photoLibrary) { url in
self.url = url
isShowVideoLibrary = false
}
}
}
}
struct VideoPicker: UIViewControllerRepresentable {
var sourceType: UIImagePickerController.SourceType = .photoLibrary
let didFinish: (URL?) -> Void
func makeUIViewController(context: UIViewControllerRepresentableContext<VideoPicker>) -> UIViewController {
let mediaUI = UIImagePickerController()
mediaUI.sourceType = sourceType
mediaUI.mediaTypes = [UTType.movie.identifier]
mediaUI.allowsEditing = true
mediaUI.delegate = context.coordinator
return mediaUI
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {
}
final class Coordinator : NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate{
let didFinish: (URL?) -> Void
init(didFinish: #escaping (URL?) -> Void) {
self.didFinish = didFinish
}
// This func passes the URL back to the calling View
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
guard
let mediaType = info[UIImagePickerController.InfoKey.mediaType] as? String,
mediaType == UTType.movie.identifier,
let url = info[UIImagePickerController.InfoKey.mediaURL] as? URL
else {
didFinish(nil)
return
}
didFinish(url)
}
}
func makeCoordinator() -> Coordinator {
Coordinator(didFinish: didFinish)
}
}

"_ setUrl: unrecognized selector sent to instance" setting the url property to the Swift Publisher

I want to set the url variable that Published in Swift
Error:
2022-12-15 21:55:27.837982+0100 WebviewIosExample[23481:8227336] [native] Error setting property 'url' of WebView with tag #7: Exception thrown while executing UI block:
-[_TtGC7SwiftUI14_UIHostingViewV24react_native_webview_ios7WebView_ setUrl:]: unrecognized selector sent to instance 0x104417700
//WebViewManager.swift
#available(iOS 14.0, *)
#objc(WebViewManager)
class WebViewManager: RCTViewManager {
public var vc = UIHostingController(rootView: WebView())
#objc var url: String {
set { vc.rootView.props.url = newValue }
get { return vc.rootView.props.url }
}
#objc override func view() -> UIView! {
return vc.view
}
override static func requiresMainQueueSetup() -> Bool {
return true
}
}
//WebViewManager.m
#import <React/RCTViewManager.h>
#interface RCT_EXTERN_MODULE(WebViewManager, RCTViewManager)
RCT_EXPORT_VIEW_PROPERTY(url, NSString)
#end
// Bridgin-Header
#import <React/RCTViewManager.h>
// WebView.Swift
#available(iOS 14.0, *)
struct WebView: UIViewRepresentable {
#ObservedObject var props = WebViewStore()
func makeUIView(context: Context) -> WKWebView {
let prefs = WKWebpagePreferences()
prefs.allowsContentJavaScript = true
let config = WKWebViewConfiguration()
config.defaultWebpagePreferences = prefs
let webView = WKWebView(frame: .zero, configuration: config)
return webView
}
func updateUIView(_ webView: WKWebView, context: Context) {
guard let theUrl = URL(string: props.url) else {
return
}
let request = URLRequest(url: theUrl)
webView.load(request)
}
}
I want to set the WebViewStoreManager url while it passed to from React Native Code
#objc var url: String {
set { vc.rootView.props.url = newValue }
get { return vc.rootView.props.url }
}
Usage in RN:
import { WebView } from 'react-native-webview-ios';
// ...
<WebView url="https://open.spotify.com" />;
I tried:
#objc(setUrl:)
public func setUrl(url: String) {
vc.rootView.props.url = url
}

Method Swizzling with Swift 5.5

I'm trying to overwrite the initWithString method on NSURL, I've looked at these past issues/posts.
Method swizzling in swift 4
How to swizzle init in Swift
https://www.uraimo.com/2015/10/23/effective-method-swizzling-with-swift/
I've tried the following code but I haven't been able to pin the new log_initWithString method, swiftc doesn't flag anything but on run I'm getting index/index.swift:20: Fatal error: Unexpectedly found nil while unwrapping an Optional value.
import AppKit
import WebKit
let app = NSApplication.shared
//example: https://github.com/kickstarter/ios-oss/blob/39edeeaefb5cfb26276112e0af5eb6948865cf34/Library/DataSource/UIView-Extensions.swift
private var hasSwizzled = false
extension NSURL {
public final class func doSwizzle() {
guard !hasSwizzled else { return }
hasSwizzled = true
let original = Selector("initWithString:")
let swizzled = Selector("log_initWithString:")
let originalMethod = class_getInstanceMethod(NSURL.self, original)!
let swizzledMethod = class_getInstanceMethod(NSURL.self, swizzled)!
let didAddMethod = class_addMethod(NSURL.self, original, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
if didAddMethod {
class_replaceMethod(NSURL.self, swizzled, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
#objc internal func log_initWithString(string URLString: String) {
NSLog("Hello from initWithString")
return log_initWithString(string: URLString)
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
let window = NSWindow.init(contentRect: NSRect(x: 0, y: 0, width: 750, height: 600), styleMask: [
NSWindow.StyleMask.titled,
NSWindow.StyleMask.closable,
NSWindow.StyleMask.resizable,
NSWindow.StyleMask.miniaturizable
], backing: NSWindow.BackingStoreType.buffered, defer: false)
func applicationDidFinishLaunching(_ notification: Notification) {
NSURL.doSwizzle()
let webview = WKWebView(frame: window.contentView!.frame)
let request = URLRequest(url: URL(string: "https://google.com")!)
window.contentView?.addSubview(webview)
webview.load(request)
window.makeKeyAndOrderFront(nil)
window.orderFrontRegardless()
window.center()
}
}
let delegate = AppDelegate()
app.delegate = delegate
app.run()
That's because
#objc internal func log_initWithString(string URLString: String)
is exposed to Objective-C as log_initWithStringWithString: and not as log_initWithString:.
Obvious fix is:
...
let swizzled = Selector("log_initWithStringWithString:")
...
To have better compile time checks on that you can use this syntax:
let original = #selector(NSURL.init(string:))
let swizzled = #selector(NSURL.log_initWithString(string:))
This will compile, but there is at least one thing left to fix - swizzled method return value. In your example:
#objc internal func log_initWithString(string URLString: String) {
NSLog("Hello from initWithString")
return log_initWithString(string: URLString)
}
returns nothing, while NSURL's init is supposed to return NSURL, so the fix is:
#objc internal func log_initWithString(string URLString: String) -> NSURL {
...

Swift3 UIWebView delegate method not being called

Code :
let termWebView = UIWebView(frame:CGRect(x:0, y:20, width:320, height:400))
termWebView.delegate = self
self.view.addSubview(termWebView)
if let url = URL(string: “http://www.google.co.in”)
termWebView.loadRequest(request)
}
func webView(_ webView: UIWebView, didFailLoadWithError error: Error)
{
}
func webViewDidFinishLoad(webView: UIWebView!)
{
}
I created a simple webview and set the delegate.but the delegate methods are not being called.what is wrong with code?any help will be appreciated.thanks in advance
Try this -
class TouchUIViewController: UIViewController, UIWebViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
let termWebView = UIWebView(frame:CGRect(x:0, y:20, width:320, height:400))
termWebView.delegate = self
self.view.addSubview(termWebView)
let urlString = "http://www.google.co.in/"
if let url = URL(string: urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!) {
let request = URLRequest(url: url as URL)
termWebView.loadRequest(request)
}
}
func webViewDidStartLoad(_ webView: UIWebView) {
}
func webView(_ webView: UIWebView, didFailLoadWithError error: Error)
{
}
func webViewDidFinishLoad(_ webView: UIWebView)
{
}
}

Get HTML from WKWebview in Swift

I log into a website using WKWebView and now i would like to parse the html of the website. How can I access the websites html in swift? I know how it works for a UIWebView but not for WKWebView.
Thanks for your help!
If you wait until the page has loaded you can use:
webView.evaluateJavaScript("document.documentElement.outerHTML.toString()",
completionHandler: { (html: Any?, error: Error?) in
print(html)
})
You could also inject some javascript that returns you back the HTML.
let script = WKUserScript(source: javascriptString, injectionTime: injectionTime, forMainFrameOnly: true)
userContentController.addUserScript(script)
self.webView.configuration.userContentController.addScriptMessageHandler(self, name: "didGetHTML")
…
func userContentController(userContentController: WKUserContentController,
didReceiveScriptMessage message: WKScriptMessage) {
if message.name == "didGetHTML" {
if let html = message.body as? String {
print(html)
}
}
}
The javascript you could inject looks something like:
webkit.messageHandlers.didGetHTML.postMessage(document.documentElement.outerHTML.toString());
WKWebView
get HTML from WKWebView
wkWebView.evaluateJavaScript("document.body.innerHTML", completionHandler: { (value: Any!, error: Error!) -> Void in
if error != nil {
//Error logic
return
}
//let result = value as? String
//Main logic
})
set HTML into WKWebView
//Do not forget to extend a class from `WKNavigationDelegate`
func someFunction() {
let wkWebView = WKWebView()
wkWebView.loadHTMLString("<html><body></body></html>", baseURL: nil)
wkWebView.navigationDelegate = self as? WKNavigationDelegate
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
//ready to be processed
}
[get/set HTML from UIWebView]
I was here to try to get clues about getting result after asking token form DROPBOX new APIS.
(I am implementing their flow WITHOUT all the stuff of their SDK)
Hope can help someone.
Now Dropbox uses a web page as login, and calls back YOUR url where You can process token.
import WebKit
import SwiftUI
// some code from:
// https://benoitpasquier.com/create-webview-in-swiftui/
// THX pasquier!
let APP_KEY = "YOUR APP KEY"
let REDIRECT_URI = "<YOUR SITE>.dropbox_auth.php"
let DB_URL = "https://www.dropbox.com/1/oauth2/authorize?client_id=APP_KEY&token_access_type=offline&response_type=code&redirect_uri=REDIRECT_URI"
class MyWKDelegate: NSObject, WKNavigationDelegate{
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("End loading")
webView.evaluateJavaScript("document.body.innerHTML", completionHandler: { result, error in
if let html = result as? String {
print(html)
}
})
}
}
struct WebView: UIViewRepresentable {
typealias UIViewType = WKWebView
let webView: WKWebView
func makeUIView(context: Context) -> WKWebView {
return webView
}
func updateUIView(_ uiView: WKWebView, context: Context) { }
}
class WebViewModel: ObservableObject {
let webView: WKWebView
let url: URL!
let delegate = MyWKDelegate()
init() {
webView = WKWebView(frame: .zero)
webView.navigationDelegate = delegate
let urlStr = DB_URL.replacingOccurrences(of: "APP_KEY", with: APP_KEY).replacingOccurrences(of: "REDIRECT_URI", with: REDIRECT_URI)
print(urlStr)
url = URL(string: urlStr)
loadUrl()
}
func loadUrl() {
webView.load(URLRequest(url: url))
}
}
Combining answers 1 and 3 did the trick for me:
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("End loading")
webView.evaluateJavaScript("document.documentElement.outerHTML", completionHandler: { result, error in
if let datHtml = result as? String {
print(datHtml)
// parse datHtml here
}
} )
}