WKWebView Dark and Light Mode with dynamic URL in Swift 5 - swift

I created WKWebView that doesn't have one url. User use this WKWebView as Safari means user can search anything on WKWebView. I am facing one issue when I change dark and light mode my web view will show me only white(Light) mode. My app is working on both mode all things working fine except WKWebView.
Already search on SO not find any related question on this.
how to use iOS 13 darkmode for wkwebview
I refer this blog but it's static url so it will not help me out
https://useyourloaf.com/blog/using-dynamic-type-with-web-views/
Also checked opaque and background property but not working for me!
IMPORTANT
User can search anything like google.com, photos or any surfing etc.
class DownloadViewController: UIViewController {
#IBOutlet weak var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView.uiDelegate = self
webView.navigationDelegate = self
webView.load(URLRequest(url: URL(string: "https://www.google.com")!))
webView.allowsBackForwardNavigationGestures = true
webView.allowsLinkPreview = true
}
}
First I am loading Google site then depend on user(Totally dynamic not one url here).
Please suggest me! Thank you.

You can use injecting CSS technic to add Light/Dark feature to your loaded web pages e.g:
// Load a web page
webView?.load(URLRequest(url: URL(string: "https://google.com")!))
// Inject Light/Dark CSS
let lightDarkCSS = ":root { color-scheme: light dark; }"
let base64 = lightDarkCSS.data(using: .utf8)!.base64EncodedString()
let script = """
javascript:(function() {
var parent = document.getElementsByTagName('head').item(0);
var style = document.createElement('style');
style.type = 'text/css';
style.innerHTML = window.atob('\(base64)');
parent.appendChild(style);
})()
"""
let cssScript = WKUserScript(source: script, injectionTime: .atDocumentEnd, forMainFrameOnly: false)
webView?.configuration.userContentController.addUserScript(cssScript)
Result:
To support page changing you can move insertion code to didCommitNavigation notification:
func webView(_ webView: WKWebView, didCommit navigation: WKNavigation!) {
...
// Inject CSS here
}
NOTE: It doesn't work with any web page in general because some of web sites can hardcode colors of background, fonts etc. but you can tune specific pages as well by overriding its CSS.

Already search on SO not find any related question on this.
Interesting that you mention this, as this has been a question addressed quite a few times already. A quick search will show many good answers and threads, for example: this one.
That being said, the quick answer is:
WKWebView can adopt light and dark mode, like any other UIView/NSView. You can manually set the interfaceStyle on the appearance, or it will inherit it. More details here overrideUserInterfaceStyle.
But
Probably the problem you are facing is that even though the WKWebView is using the dark interfaceStyle, websites still show on light mode. That is because those websites (google and any other website/URL) are independent and handle their style on their own. Some of them adjust to match your device's interface style, but not all. In short, even if WKWebView is on dark mode, Google will load a white background if they want to do it.
As mentioned by others, you can change this behaviour by manually injecting CSS, please check those answers if that is what you want to do, check also the link I posted at the beginning of this answer where they discuss that approach.

As in android web views(force_darkmode), Apple has not provided support for dark mode web-views yet. you can explore safari with a few reputed websites, even though they don't convert to dark mode.
so we should wait for Webkit dark mode support till then. These CSS injections are not the best practice or robust solution.

Related

SwiftUI Universal Links not working for NFC

Our app uses universal links and the AASA file seems to be working fine because hyperlinks in emails, as well as QR coded URLs both launch the app. They both also cause the SwiftUI .onOpenURL(perform:) function to be called.
Things are a bit more confusing with NFC. The URL is recognised and the app launches, suggesting the issue is not related to AASA. However, the onOpenURL function is not called. Can anyone tell me how to handle universal links from NFC? Ideally I'd like to keep it pure SwiftUI but if I need to use the AppDelegate so be it.
The code in the main SwiftUI file is:
import SwiftUI
#main
struct MyApp: App {
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL(perform: { url in
print("url opened:\(url)")
// prints when opened from URL in email,
// Notes or QR code, but not when opened
// from URL embedded in NFC NDEF
})
}
}
}
I have also tried defining the application(_:continue:restorationHandler:) function in the AppDelegate, but that doesn't get called either.
Thanks for any help.
The solution is to add a continue user activity handler on a suitable view:
.onContinueUserActivity(NSUserActivityTypeBrowsingWeb) { userActivity in
print("Continue activity \(userActivity)")
guard let url = userActivity.webpageURL else {
return
}
print("User wants to open URL: \(url)")
// TODO same handling as done in onOpenURL()
}
It's not obvious why the same Universal Link received through a click on a link in Safari should be handled differently than the same link read from a tag, but apparently it's not the same thing.
In case it helps anyone with the same issue, I solved the issue by registering a URL schema for the app and then using .onOpenURL(perform:).
Seems a strange way to have to do it but it works as required, so happy days!

Open new tab from WKWebView html content in safari app extension popover

I'm developing a browser extension that share code between chrome and safari, basically it's an html page that I load up in a WKWebView on safari app extension (and an iframe on chrome extension).
Everything works fine apart from links that won't open in the safari app extension popover if they have target="_blank" or are popups like facebook login.
Anyone know if it is possible to have safari open them up as if clicked on in an ordinary safari tab?
I've tried to see if the WKWebView sets a global safari object in my html content, but it seems not, so this Safari Extension Popover Links does not work (results in ReferenceError: Can't find variable: safari).
This is how I load the web view in my SafariExtensionViewController:
override func viewDidLoad() {
super.viewDidLoad()
...
self.view.addSubview(webView)
webView.navigationDelegate = self
...
webView.load("\(baseUrl)?url=\(currentUrl)")
}
and an extension to make the load part work:
extension WKWebView {
func load(_ urlString: String) {
if let url = URL(string: urlString) {
let request = URLRequest(url: url)
load(request)
}
}
}
Safari Technology Preview 77 (6March 2019) and likely 12.1.x of regular Safari.
Made the window.safari object available in frames opened to safari-extension:// resources
https://webkit.org/blog/8658/release-notes-for-safari-technology-preview-77/
So if you go by that method you should be OK.

Programmatically minimize all windows or besides my Cocoa application?

I haven't been able to find anything about this in Swift. Is there a way to programmatically make my application minimize all other windows open in the background, or even just minimize Safari? I basically want my application to run against the desktop, without any clutter in the background. Is there a way to programmatically do this for a Cocoa app? I'm pretty new to swift, so any help would be appreciated.
You can use api on NSWorkspace which allows you to hide all visible app in the background.
You can find more about NSWorkspace here: link
Hides all applications other than the sender. This method must be called from your app’s main thread.
NSWorkspace.shared().hideOtherApplications()
If you only want to hide Safari,
let appPath = NSWorkspace.shared().fullPath(forApplication: "Safari")
let identifier = Bundle.init(path: appPath!)?.bundleIdentifier
if let bundleID = identifier {
let runningApps = NSRunningApplication.runningApplications(withBundleIdentifier:bundleID )
if !runningApps.isEmpty {
runningApps.first?.hide()
}
}

offline application cache support in mac os x embedded webkit webview

I am writing OS X HTML5 app which has to function both online and offline. Offline application cache was prototyped based on this and works well in Safari. When i do a simple test using webview in Xcode, the manifest file seems never to be fetched by the webview. A simple way to test uses this minimal html file:
<html manifest="example.manifest"><head> <title>Test with
manifest</title> </head> <body> Test with manifest<br> <br>
</body></html>
the manifest file example.manifest contains:
CACHE MANIFEST
# ver 1
CACHE:
When i view this page in Safari, the page loads, and the manifest is read. If i quit safari, go offline, then start Safari and refresh this page, it loads from cache. the offline cache here seems to work fine.
when i repeat this with web view, in Xcode 7.3 Mac OS 10.11, using the following code (storyboard has a web view in a view controller inside of the window):
class ViewController: NSViewController {
#IBOutlet var webview: WebView!
let THEURL:String = "http://192.168.23.180/WithManifest/"
override func viewDidLoad() {
super.viewDidLoad()
let url = NSURL(string: THEURL)
let request = NSURLRequest(URL: url!, cachePolicy:NSURLRequestCachePolicy.UseProtocolCachePolicy ,timeoutInterval: 10)
webview.mainFrame.loadRequest(request)
}
}
In the web view case, the page loads fine, but after quitting and restarting offline, the page fails to load. looking at the network traffic, it is clear that the manifest file is never requested from the client. I have tried all of the different cache policies without success. I have also tried creating my own shared URL cache as has been suggested elsewhere with no success.
let cacheSizeMemory:Int = 4*1024*1024; // 4MB
let cacheSizeDisk:Int = 32*1024*1024; // 32MB
let sharedCache:NSURLCache = NSURLCache(memoryCapacity:cacheSizeMemory, diskCapacity:cacheSizeDisk, diskPath:"nsurlcache")
NSURLCache.setSharedURLCache(sharedCache)
My questions:
1) does webkit webview in OS X support offline application cache as described in html5?
2) if yes, what has to be done to make it work?
My workaround is to implement a subclass of NSURLProtocol that implements custom caching and doing the loading logic (offline vs. online) in the app. I would much rather use the standard approach if at all possible.
I was able, with help from different places and people, to find an answer. There is an Objective-C webview preference called setOfflineWebApplicationCacheEnabled, which is for some reason not exposed in swift. To make it work, first make a bridging header file. Use Xcode to create a header file and put this into it:
#import <Foundation/Foundation.h>
#import <WebKit/WebKit.h>
#interface WebPreferences (WebPreferencesPrivate)
- (void)_setLocalStorageDatabasePath:(NSString *)path;
- (void) setLocalStorageEnabled: (BOOL) localStorageEnabled;
- (void) setOfflineWebApplicationCacheEnabled:(BOOL)offlineWebApplicationCacheEnabled;
- (void) setDatabasesEnabled:(BOOL)databasesEnabled;
#end
and save it in your project. Go to Build Settings->Swift Compiler - Code Generation and select 'Objective C Bridging Header'. double click on the right side and put in the relative path (based on the file system, not the project organization) to the header file. In my case it was testwebviewmanifest/Header.h, where testwebviewmanifest folder is at the same level as the .xcodeproj file.
Once that is done and you can build successfully (it may complain it can't find your header file if the path is wrong), put this line at a startup place where you have a handle to your webview. For me it was easy to put it in the ViewController.
webview.preferences.setOfflineWebApplicationCacheEnabled(true)
Once that is done, the cache manifest will be downloaded, and application caching appears to work flawlessly. Extensive testing has not been done yet. I will update if any gotchas are found...
A demo project can be found here: https://github.com/graySquirrel/testwebviewmanifest

Take screenshot of host app using iOS share/action extensions?

I will like to know how to take a screenshot of the iOS host app with the use of a share/action extension.
My use case is as follows:
use the Safari browser to access a webpage (https such as gmail)
tap on the Share button and select the extension
the extension will take a screenshot of the current webpage
An working example for this use case is the Awesome Screenshot iOS app.
I had tried the following methods
reload the baseURI (loadRequest) on a UIWebView/WKWebkit (would not be able to access https contents such as Gmail). So not doing any reload (Awesome Screenshot is not doing a reload btw)
Used the ExtensionPreprocessingJS to obtain the DOM contents through the arguments.completionFunction function. I could not extract the document.body here although i could get the source etc. loadHTMLString with the baseURI will mess up the presentation layer.
Used html2canvas in the ExtensionPreprocessingJS to obtain an image and appended it to the host app's webpage as a child but do not know how to extract it. Also, the image got lost for some webpages such as Gmail.
Mobile Safari does not have the visibleContentsAsDataURL method.
I think a viable approach will be to use the html2canvas in the ExtensionPreprocessingJS but how do I save this image somehow?
Edit: So the below works in the Simulator but does not work on the device. I'm presently looking for a solution as well.
Here's a solution that I think the Awesome Screenshot app uses:
func captureScreen() -> UIImage
{
// Get the "screenshot" view.
let view = UIScreen.mainScreen().snapshotViewAfterScreenUpdates(false)
// Add the screenshot view as a subview of the ShareViewController's view.
self.view.addSubview(view);
// Now screenshot *this* view.
UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, false, 0);
self.view.drawViewHierarchyInRect(view.bounds, afterScreenUpdates: true)
let image: UIImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
// Finally, remove the subview.
view.removeFromSuperview()
return image
}