Because I could just find some outdated information / not working solutions to my problem I decided to ask this kind of question again. (See outdated / wrong solutions:)
WKWebView: Is it possible to preload multiple URLs?
(Xcode, Swift) Is it possible to load multiple WKWebViews simultaneously if they are on different viewControllers?
Swift 3, iOS 10.3 - Preload UIWebView during Launch Screen
My app is separated in one native part and one HTML part. The HTML is saved as a local file (index.html) and should be load into the myWebView view.
#IBOutlet weak var myWebView: UIWebView!
func loadWebview() {
let url = Bundle.main.url(forResource: "index", withExtension: "html")
let request = URLRequest(url: url!)
myWebView.loadRequest(request)
myWebView.scrollView.isScrollEnabled = false
myWebView.allowsLinkPreview = false
myWebView.delegate = self
}
Because my DOM tree is very large, switching from the native part to the web part (on button click) takes quite a long time at -least for the first time switching- because afterward, I'm sure the webView-request gets cached.
To my question: How can I preload the WebView on app init to avoid the white screen (maybe 0.5s - 1s duration) when switching from the native to the Web part?
EDIT:
The WKWebView is displaying the scrollbar while the UIWebView was not!
Using (like with UIWebView) this styles:
::-webkit-scrollbar {
display: none;
}
is not working and adding these lines:
webview.scrollView.showsHorizontalScrollIndicator = false
webview.scrollView.showsVerticalScrollIndicator = false
is also not working at all.
Firstly, you should switch to WKWebView,UIWebView is no longer recommended to be used by Apple.
Secondly, you can create a pool of web views that get created and asked to load when the app starts. This way by the time the user switches to the web interface the web view might've got a chance to fully load.
For this you can use a class like this:
/// Keeps a cache of webviews and starts loading them the first time they are queried
class WebViewPreloader {
var webviews = [URL: WKWebView]()
/// Registers a web view for preloading. If an webview for that URL already
/// exists, the web view reloads the request
///
/// - Parameter url: the URL to preload
func preload(url: URL) {
webview(for: url).load(URLRequest(url: url))
}
/// Creates or returns an already cached webview for the given URL.
/// If the webview doesn't exist, it gets created and asked to load the URL
///
/// - Parameter url: the URL to prefecth
/// - Returns: a new or existing web view
func webview(for url: URL) -> WKWebView {
if let cachedWebView = webviews[url] { return cachedWebView }
let webview = WKWebView(frame: .zero)
webview.load(URLRequest(url: url))
webviews[url] = webview
return webview
}
}
and ask it to preload the url sometimes during the app startup:
// extension added for convenience, as we'll use the index url in at least
// two places
extension Bundle {
var indexURL: URL { return self.url(forResource: "index", withExtension: "html")! }
}
webviewPreloader.preload(url: Bundle.main.indexURL)
Thirdly, you might need to use a container view instead of the actual web view, in your controller:
#IBOutlet weak var webviewContainer: UIView!
What remains is to add the preloaded web view to the container when needed:
func loadWebview() {
// retrieve the preloaded web view, add it to the container
let webview = webviewPreloader.webview(for: Bundle.main.indexURL)
webview.frame = webviewContainer.bounds
webview.translatesAutoresizingMaskIntoConstraints = true
webview.autoresizingMask = [.flexibleWidth, .flexibleHeight]
webviewContainer.addSubview(webview)
}
And not lastly, be aware that keeping alive instances of web views, might carry performance penalties - memory and CPU-wise.
Related
Using swift 4 and IOS 12 ,i am using a webViewKit to load a website. One of the features of the web app is to scan qrcode, i have successfully loaded the website but i am still struggling how to access the device camera to scan qr code, in android everything works fine including this qr code scanner using webView onPermissionRequest method. I have already tried this
Key : Privacy - Camera Usage Description in info.plist
and also adding these lines of codes here AVCaptureDevice.authorizationStatus(for: .video) on url load before pressing the web button to scan qr code. According to our web developer this button press executes a javascript undefined function, but i don't know how to make webViewKit respond to this button click event and the hardest part is that there are no logs or NSLogs to evaluate. I am new to IOS development for about almost a month. Thank you
var WebCodeCamJS = function(element) {
'use strict';
this.Version = {
name: 'WebCodeCamJS',
version: '2.7.0',
author: 'Tóth András',
};
var mediaDevices = window.navigator.mediaDevices;
mediaDevices.getUserMedia = function(c) {
return new Promise(function(y, n) {
(window.navigator.getUserMedia || window.navigator.mozGetUserMedia || window.navigator.webkitGetUserMedia).call(navigator, c, y, n);
});
}
HTMLVideoElement.prototype.streamSrc = ('srcObject' in HTMLVideoElement.prototype) ? function(stream) {
this.srcObject = !!stream ? stream : null;
} : function(stream) {
if (!!stream) {
this.src = (window.URL || window.webkitURL).createObjectURL(stream);
} else {
this.removeAttribute('src');
}
};
Above code is a js function named webcodecamjs.js that is being executed from a button click event on the web page. I found similar problem with solution here , but i'm not quite sure how to implement it, it says
Now Add JS file (WebRTC.js) that defines various WebRTC classes, functions & passes the calls to the WKWebView.
In the WKWebView inject the script at the document start:
where do i put this WebRTC.js file? what i did was to create a new file in my ios project and named it WebRTC.js , i also tried renaming it to webcodecamjs.js like what we have in web js files, how i did it in func viewDidAppear
let contentController = WKUserContentController()
contentController.add(self, name: "callbackHandler")
let script = try! String(contentsOf: Bundle.main.url(forResource: "webcodecamjs", withExtension: "js")!, encoding: String.Encoding.utf8)
contentController.addUserScript(WKUserScript(source: script, injectionTime: WKUserScriptInjectionTime.atDocumentStart, forMainFrameOnly: true))
let preferences = WKPreferences()
preferences.javaScriptEnabled = true
let configuration = WKWebViewConfiguration()
configuration.preferences = preferences
configuration.userContentController = contentController
configuration.allowsPictureInPictureMediaPlayback = true
webView.navigationDelegate = self
webView.configuration.preferences = configuration.preferences
WKWebView.load(webView)(NSURLRequest(url: NSURL(string: encodedLoginUrl)! as URL) as URLRequest)
webView is a
#IBOutlet weak var webView: WKWebView
not declared as below
var webView: WKWebView
that's why i can't do something like below, cause it gives me 'weak' warning
webView = WKWebView(frame: CGRect.zero, configuration: config)
Could you add code snippet to check?
A web view with POST data does not POST the body, so I'm missing required parameters of the web view. It works fine in later versions, but does not work with 9.1.
Here is how I'm setting up my webview:
lazy var webView:WKWebView = {
let view = WKWebView()
let url:URL = URL(string:<MyURL>)!
let body:String = String(format: "param1=%#¶m2=%#", arguments: [getParam1(), getParam2()])
var request = URLRequest(url:url)
request.httpMethod = "POST"
request.httpBody = body.data(using: .utf8)
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "content-type")
view.navigationDelegate = self
view.load(request)
view.backgroundColor = .white
view.scrollView.delegate = self;
return view
}()
I've confirmed that my parameters exist when I create body. I get to the page, but it displays an error message about both parameters missing.
Any idea how I can get this working with older versions?
Edit: When I add a breakpoint at the line view.navigationDlegate... and then print the description of the request, I notice requests from later and earlier versions are the same except for some expected bytes which change due to the session token on the device. The range is the exact range I would expect to differ.
Ahh, this appears to be a bug with the Web Kit that was fixed in iOS 11... pretty major bug, surprised they wouldn't have patched it in earlier versions.
I was really hoping to use the WKWebView instead of UIWebView in order to get better javascript performance, but looks like I'll have to settle.
https://bugs.webkit.org/show_bug.cgi?id=167131
To add on, I resolved this by adding an instance var, var webView:UIView, and in viewDidLoad, I did this:
if #available(iOS 11.0, *) {
webView = _webView
}
else {
webView = _webView2
}
Changed my WKWebView lazy var to _webView, added _webView2 lazy var as UIWebView, and implemented the delegates for both. This way, later iOS users get the performance benefits of WKWebView without having to increase the minimum target to 11.0 and losing iOS 9-10 users.
A Mac app requires that a HTML file be called in a WebView (the legacy type, not the newer WKWebView) in a localized form to present the user with some content.
As I side note, I realize that WebView should not be used today, and WKWebView is preferred, however this is a legacy app that currently needs support.
I've used a similar method for the iOS version, however it does not seem to be working. The HTML files are simply called "Term.HTML" and are placed in each localization folder alongside the localized string and all other localized content. This is the code I tried to use:
NSString *htmlFile = [[NSBundle mainBundle] pathForResource:NSLocalizedString(#"fileTerm", nil) ofType:#"html"];
htmlString = [NSString stringWithContentsOfFile:htmlFile encoding:NSUTF8StringEncoding error:nil];
[termsView takeStringURLFrom:htmlString];
Where my localized strings file each contain a line that says:
"fileTerm" = "Term";
This is what links the declaration of the first line to the actual file. It works in iOS. However, when running the app and the view containing the WebView attempt to the run, XCode will automatically create a breakpoint on the third line when I actually attempt to give the HTML file to "termsView" which is my WebView. After skipping this breakpoint, and forcing the app to run, the whole view containing the WebView will simply not appear. I would be thankful if anyone knew why this was or if there was a better way to do this? Thank you everyone!
may be someone needs in SWift: I solved this problem with saving 3 html file for every language, and then in ViewController class checked current app language. And called up the html file for current language
func loadHtmlFile() {
let preferredLanguage = NSLocale.preferredLanguages[0]
if preferredLanguage == "kz" {
let url = Bundle.main.url(forResource: "aboutUs_kz", withExtension:"html")
let request = URLRequest(url: url!)
webView.load(request)
}
if preferredLanguage == "ru" {
let url = Bundle.main.url(forResource: "aboutUs_ru", withExtension:"html")
let request = URLRequest(url: url!)
webView.load(request)
}
if preferredLanguage == "en" {
let url = Bundle.main.url(forResource: "aboutUs_en", withExtension:"html")
let request = URLRequest(url: url!)
webView.load(request)
}
}
in viewDidLoad()
override func viewDidLoad() {
super.viewDidLoad()
loadHtmlFile()
}
I am working on an app (swift) where i need to load a web page inside UIWebView. Inside that UIWebView there's an <img src="http://www.example.com/uploads/43454.jpg" /> element.
All works fine in this scenario, but the problem is that my 43454.jpg image can be of 5-10 megabytes everytime. So when the UIWebView loads it keeps on loading image for about 2 minutes. Plus this <img> tag can have random sources i.e. 22234.jpg, 98734.jpg, 33123.jpg, and so on.
So to tackle this situation I am trying to use following approach:
List all possible images that we need to show in UIWebView, download and cache them (used Kingfisher library for this purpose) at aplication's startup.
When my UIWebView loads my URL initially, it has nothing in it's <img> elements src attribute, but have a data-image-name="22234.jpg" attribute-value pair.
Now when UIWebView finishes loading its contents, get the image name value from data-image-name attribute.
Check for that image in cache, and update the <img> element's src attribute with that image from cache.
This way UIWebView won't be downloading the image over and over again.
Note: Assuming that UIWebView automatically manages resource cache. All other file types *.js, *.css are being properly cached and not being loaded over and over again. The same doesn't go for images, don't know why.
If this approach seems okay, then how should I accomplish it (Swift 2.2)?
Any quick help will be much appreciated. Thanks
This seems to be the same situation in one of my projects. I had exactly this same scenario. I am going to paste my code here, may be it helps you figure out your solution.
As soon as my app loads I create an array of image URLs and pass it to Kingfisher library to download and cache all images (to disk).
for url in response {
let URL = NSURL(string: url as! String)!
self.PlanImagesArray.append(URL)
}
let prefetcher = ImagePrefetcher(
urls: self.PlanImagesArray,
optionsInfo: nil,
progressBlock: {
(skippedResources, failedResources, completedResources) -> () in
print("progress resources are prefetched: \(completedResources)")
print("progress resources are failedResources: \(failedResources)")
print("progress resources are skippedResources: \(skippedResources)")
},
completionHandler: {
(skippedResources, failedResources, completedResources) -> () in
print("These resources are prefetched: \(completedResources)")
print("These resources are failedResources: \(failedResources)")
print("These resources are skippedResources: \(skippedResources)")
self.activityLoadingIndicator.stopAnimating()
})
prefetcher.start()
At my web view screen I initially loaded my web view and after that used following code to check for particular image in cache and if found converted it into a base64 string and put it back in src attribute of the element.
func webViewDidFinishLoad(webView : UIWebView) {
//UIApplication.sharedApplication().networkActivityIndicatorVisible = false
print("webViewDidFinishLoad")
let xlinkHref = webView.stringByEvaluatingJavaScriptFromString("document.getElementById('background_image').getAttribute('xlink:href')")
//print("xlink:href before = \(webView.stringByEvaluatingJavaScriptFromString("document.getElementById('background_image').getAttribute('xlink:href')"))")
if xlinkHref == "" {
let imageCacheKey = webView.stringByEvaluatingJavaScriptFromString("document.getElementById('background_image').getAttribute('data-cache-key')")
let imageCachePath = ImageCache.defaultCache.cachePathForKey(imageCacheKey! as String)
var strBase64:String = ""
webView.stringByEvaluatingJavaScriptFromString(
"var script = document.createElement('script');" +
"script.type = 'text/javascript';" +
"script.text = \"function setAttributeOnFly(elemName, attName, attValue) { " +
"document.getElementById(elemName).setAttribute(attName, attValue);" +
"}\";" +
"document.getElementsByTagName('head')[0].appendChild(script);"
)!
if(imageCachePath != "") {
ImageCache.defaultCache.retrieveImageForKey(imageCacheKey! as String, options: nil) { (imageCacheObject, imageCacheType) -> () in
let imageData:NSData = UIImageJPEGRepresentation(imageCacheObject!, 100)!
strBase64 = "data:image/jpg;base64," + imageData.base64EncodedStringWithOptions(.EncodingEndLineWithCarriageReturn)
webView.stringByEvaluatingJavaScriptFromString("setAttributeOnFly('background_image', 'xlink:href', '\(strBase64)');")!
}
} else {
webView.stringByEvaluatingJavaScriptFromString("setAttributeOnFly('background_image', 'xlink:href', '\(imageCacheKey)');")!
}
}
Hope it helps.
It sounds like you're basically saying "I know what the images are ahead of time, so I don't want to wait for the UIWebView to load them from the server every time. I want the html to use my local copies instead."
While it's a hack, my first thought was:
Have 2 UIWebViews: one is visible to the user and the other is hidden.
"Trigger" the real page in the hidden UIWebView. When the HTML reaches you...
Create a new string, that is a copy of that HTML, but with the image tags that say <img src="http://... replaced with <img src=\"file://... and pointing to the correct image(s) on disk.
Now tell the visible UIWebView to load that HTML string you built in step 3.
Problem :
actually i am getting url link from api response. and by that link i am loading webview. but when webview load its also showing advertisement so is there any possible way to remove that ad from my webview?
here is my code
override func viewDidLoad() {
super.viewDidLoad()
let url : NSURL = NSURL(string: webviewurl)!
let request : NSURLRequest = NSURLRequest(URL: url)
myweb.loadRequest(request)
}
let me know if is there any possible way to remove ad from webview or may be from url
Usually, you can't change the content of webview you get because what you actually get is a HTML file and then rendered as a webpage.
If the ad only exists in mobile phone, there may be a DNS hijacking,