We have a requirement to disable scrolling of web view and show the complete web view content in a table view cell, and only table view should be scrolling not the web view
Tried the below approach, but it's not giving the correct content size of the web view if we disable scrolling of the WKWebView.
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.updateWebView(for: webView)
}
func updateWebView(for webView: WKWebView) {
let scaleFactor: CGFloat = UIDevice.current.userInterfaceIdiom == .pad ? 1.0 : 0.70
let scaleJavascript = "var style = document.createElement(\"style\"); document.head.appendChild(style); style.innerHTML = \"html{-webkit-text-size-adjust: none; font-size: 25px; word-wrap: break-word;}\";var viewPortTag=document.createElement('meta');viewPortTag.id=\"viewport\";viewPortTag.name = \"viewport\";viewPortTag.content = \"width=device-width; user-scalable=0.0; initial-scale=\(scaleFactor); minimum-scale=\(scaleFactor); maximum-scale=\(scaleFactor); shrink-to-fit=no;\";document.getElementsByTagName('head')[0].appendChild(viewPortTag);"
webView.evaluateJavaScript(scaleJavascript, completionHandler: { [weak self] (_, _) in
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self?.height = webView.scrollView.contentSize.height
self?.onUpdate?(nil)
}
})
}
Any help or lead is highly appreciated.
Related
I have a WKWebView in my swift app. I load the URL, but it has a map website in website. When I show it on the app, I have to pinch to zoom out to get it to be at the desired zoom for my to see the entire route. I am trying to figure out how to zoom out with code, so that I can just cause it to pinch zoom out once the url is loaded, and I don't need to keep manually zooming out by pinching.
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
let contentSize = objWebview.scrollView.contentSize
let viewSize = objWebview.bounds.size
let rw = Float(viewSize.width / contentSize.width)
objWebview.scrollView.minimumZoomScale = CGFloat(rw)
objWebview.scrollView.maximumZoomScale = CGFloat(rw)
objWebview.scrollView.zoomScale = CGFloat(rw)
}
I am having an issue that long press and selecting text on the WKWebView will randomly scroll the web view. This behavior happens when I set the contentInset.top property of the scroll view of the web view.
[Screenshot]
Long press on the text. The green area is a native UIView https://ibb.co/P4LqgBB
After dragging on the text, WKWebView scrolls itself down unexpectedly https://ibb.co/r5KR4JB
Since my application need to show the native view above the web view potion, this behavior really frustrates my users when they need to copy and paste text from the web view.
Below is the minimum code that you can use to reproduce the issue. I tried this on iPhone 8 iOS 12.2 using Xcode 10.2. The issue occurs when webView.scrollView.contentInset.top = 100 is set. Moreover, if you change the value to something like 1000, where the contentInset.top is longer than the phone's screen size, long press will cause the web view to scroll instantly.
override func viewDidLoad() {
super.viewDidLoad()
// Create WKWebView
let webView = WKWebView(frame: .zero)
webView.translatesAutoresizingMaskIntoConstraints = false
webView.scrollView.contentInsetAdjustmentBehavior = .never
webView.clipsToBounds = false
webView.scrollView.bounces = false
// Create Native UIView
let nativeView = UIView(frame: .zero)
nativeView.translatesAutoresizingMaskIntoConstraints = false
nativeView.backgroundColor = .green
// Add WebView to the view
view.addSubview(webView)
webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
// Set contentInset to give blank space for native view
webView.scrollView.contentInset.top = 100
// Add the native view as webView scrollView's child
webView.scrollView.addSubview(nativeView)
nativeView.leadingAnchor.constraint(equalTo: webView.leadingAnchor).isActive = true
nativeView.trailingAnchor.constraint(equalTo: webView.trailingAnchor).isActive = true
nativeView.topAnchor.constraint(equalTo: webView.scrollView.topAnchor,
constant: -100).isActive = true
nativeView.heightAnchor.constraint(equalToConstant: 100).isActive = true
// Load the webpage
let url = URL(string: "https://www.apple.com")!
let request = URLRequest(url: url)
webView.load(request)
}
I expect the long press and scroll behave like when contentInset.top is not set.
Does anyone know how to fix this issue?
Please add first UIScrollViewDelegate,WKNavigationDelegate,WKUIDelegate delegates in your viewcontroller and add below delegates.
// MARK: - WKWebView Delegate
private func webView(webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: NSError) {
print(error.localizedDescription)
}
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
// print("Strat to load")
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// print("finish to load")
let javascriptStyle = "var css = '*{-webkit-touch-callout:none;-webkit-user-select:none}'; var head = document.head || document.getElementsByTagName('head')[0]; var style = document.createElement('style'); style.type = 'text/css'; style.appendChild(document.createTextNode(css)); head.appendChild(style);"
webView.evaluateJavaScript(javascriptStyle, completionHandler: nil)
}
// MARK: - UIScrollViewDelegate
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return nil
}
I have faced same issue and above code working fine for me. Hope it will help to you. #Chui
WKWebView creates a-lot of problems if the css isn't done clean enough.
I sugest moving back to UIWebView, it should fix all your problems
I have read lots of articles about how to get height of the webView. Most of them use KVO to observe the contentSize. I also did that.
1) create wkWebView and forbid webView's scrollView
lazy var webView: WKWebView = WKWebView()
webView.scrollView.bounces = false
webView.scrollView.isScrollEnabled = false
webView.snp.makeConstraints({ (make) in
make.left.right.equalTo(view)
make.top.equalTo(headerView.snp.bottom)
make.height.equalTo(AppDefaults.screenH - 64 - 44)
make.bottom.equalTo(containView)
})
2) add observer
webView.scrollView.addObserver(self, forKeyPath: "contentSize", options: .new, context: nil)
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "contentSize" {
if let scroll = object as? UIScrollView {
if scroll.contentSize.height > 0 {
updateWebViewConstraints(scroll.contentSize.height)
}
}
}
}
func updateWebViewConstraints(_ height: CGFloat) {
webView.snp.remakeConstraints { make in
make.left.right.equalTo(view)
make.top.equalTo(headerView.snp.bottom)
make.height.equalTo(height)
make.bottom.equalTo(containView).offset(-44)
}
}
3) remove observer
deinit {
webView.scrollView.removeObserver(self, forKeyPath: "contentSize")
}
When I run the app, sometimes I can get the correct height of the webView which was show right. Like below images
But sometimes ,the height of the webView over the correct height , it cause webView have part of empty space. We can see the below images, it get the wrong contentSize,
And when I tap the nextButton to load the new content, it also keep the previous height. That's not good. Just like if the contentSize's height over the new content's height. It will not change.
In order to compare wkWebView, so I have tried UIWebView, it was correct. So, I don't know how to solve that problem. Please give me some suggestions. Thanks
If the contentSize of the page you're rendering is less than the bounds of the WKWebView, the contentSize will be at least the same as the bounds, which results in a blank space.
The only way you could get around this was to set the bounds of your WKWebView to something less than the size of the content. Once the page renders you will know the size of the WKWebView.scrollView's contentSize and you can size it using your logic above.
extension ViewController: WKNavigationDelegate {
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
self.webViewHeightConstraint?.constant = 0
self.view.layoutIfNeeded()
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
self.webViewHeightConstraint?.constant = webView.scrollView.contentSize.height
self.view.layoutIfNeeded()
}
}
I faced the same issue. Based on runmad's solution, setting the height to zero before load and setting the correct height afterward works.
Am I doing something wrong...?
On iOS, I am wanting to take a snapshot of a full web page. Until iOS 11, this previously wasn't possible without dealing with a whole bunch of logic to scroll the WKWebView's scrollView, etc.
The following bug was reported to Webkit and fixed: https://bugs.webkit.org/show_bug.cgi?id=161450
Resulting in this change: https://trac.webkit.org/changeset/212929/webkit
Which now gives us a nice API on iOS 11 to take snapshot of WKWebViews content: https://developer.apple.com/documentation/webkit/wkwebview/2873260-takesnapshotwithconfiguration
This is all pretty great, except, I simply cannot get a snapshot when using this method on a device. When running the app in Simulator it work's great.
I've added two images below for comparison. Both are the images that are returned with takeSnapshotWithConfiguration:completionHandler:, however one is blank for the most part.
I've tried playing around with the web view's frame, the configuration, etc. but with no luck.
I am thinking that perhaps Simulator links against the macOS version of WebKit and somehow it works fine there. One issue, as well is that the WKSnapshotConfiguration header also doesn't appear to be exposed in Swift, so I had to add a bridging header that imports it.
Here's a sample project for anyone who's curious: https://github.com/runmad/WebKitSnapshotTest
UPDATE 10/20:
So this fixes it but is a nasty hack:
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// webView.scrollView.contentSize == .zero initially, so put in a delay.
// It still may be .zero, though. In that case we'll just call the
// delegate method again.
let deadlineTime = DispatchTime.now() + .milliseconds(300)
DispatchQueue.main.asyncAfter(deadline: deadlineTime) {
guard webView.scrollView.contentSize.height > 0 else {
self.webView(webView, didFinish: navigation)
return
}
// So this works... If you set the .frame of the webView
// to that of the just loaded .scrollView and then load the
// same URL again, the resulting .frame and .scrollView will
// be the same size and the image render on a device will be
// the correct size.
guard self.hasLoadedOnce else {
webView.frame = CGRect(x: self.view.bounds.width, y: 0, width: self.view.bounds.width, height: webView.scrollView.contentSize.height)
webView.load(URLRequest(url: URL(string: "https://www.apple.com")!))
self.hasLoadedOnce = true
return
}
webView.snapshotWebView(completionHandler: { (image, _) in
self.updateWith(image)
})
}
}
So couple of issues:
(asyncAfter I knew of this one already and had that in there) Have to add a delay for the snapshot, since the scrollView still might be .zero for a little bit
Setting the size of the frame of the WKWebView to that of the correct scrollView size wasn’t working
Setting the frame and THEN RELOADING the WKWebView in what will be it’s true size works
UPDATE 03/11:
My radar (35094298) was duped today to 33812691 and closed, which means Apple is aware of the issue, at least.
Swift 4
Here's what worked for me (note: I used the extension from the sample project provided).
Inside the webView(didFinish navigation) I used JS to find the content width and height. I then set a global rect variable in order to pass it to snapshot() later in a button press. I could not get the snapshot() to work when included in the following code after the constraint is set.
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
// To get the constraints properly set for preparation for the snapshot, the setup must be performed here.
self.webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in
if complete != nil {
self.webView.evaluateJavaScript("document.body.scrollHeight", completionHandler: { (height, error) in
guard let contentHeight = height as? CGFloat else { print("Content height could not be obtained"); return }
self.webView.evaluateJavaScript("document.body.scrollWidth", completionHandler: { (width, error) in
let contentWidth = width as! CGFloat
self.rect = CGRect(x: 0, y: 0, width: contentWidth, height: contentHeight)
self.heightConstraint.constant = contentHeight
// Because it is known that the web page has completely loaded, it is safe to enable a snapshot to be taken
self.takeSnapButton.isEnabled = true
})
})
}
})
}
For me, taking a snapshot() had to be a new action, in my case, a separate button press. My conclusion is that the constraint needs to be set and the view reloaded in order for the content to properly resize.
#IBAction func takeSnapButton(_ sender: Any) {
// It is probably safe to assume that there is definitely an image at this point but to be extra
guard let image = self.webView.snapshot(of: self.rect) else { print("An image could not be taken"); return}
}
I tried this, which should work but doesn't:
webView.scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: false)
I am working on a browser and have a couple of webViews in kept in stack. Some sites work as expected, start at the top, some do not.
Starts at top: http://leagueoflegends.com/
This one starts a little down, have to manually scroll up: http://euw.leagueoflegends.com/
Extra note, this problem never happens to the first webView. Even if I load the 2nd link and show it as first webView, it doesn't scroll to the middle.
I had a very similar problem when rotating my WKWebView.
After rotation to landscape the view is automatically scrolled to the middle.
I use following workaround to scroll to the top of the page again after rotation:
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
coordinator.animate(alongsideTransition: nil, completion: { (context) in
self.webKitView.evaluateJavaScript("window.scrollTo(0,0)", completionHandler: nil)
})
}
In your case it should work to call this directly after you load your URL.
webKitView.evaluateJavaScript("window.scrollTo(0,0)", completionHandler: nil)
Or if your problem occurs when navigating within the WKWebView, you could use a WKNavigationDelegate (I have not tested this) :
override func viewDidLoad() {
super.viewDidLoad()
// load content of webKitView here and set as view or add as subview
webKitView.navigationDelegate = self
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webKitView.evaluateJavaScript("window.scrollTo(0,0)", completionHandler: nil)
}
Our app uses multiple WKWebView instances and we have also experienced this problem of some pages randomly scrolling to some location down the page.
We were able to work around the problem by having the next webView which will be loading the next page attached to the window hierarchy before loading the URL.
WKWebView* nextWebView = [WKWebView new];
[[[UIApplication sharedApplication] keyWindow] addSubview:webView.view];
[[[UIApplication sharedApplication] keyWindow] sendSubviewToBack:webView.view];
Then when you load your URL:
[nextWebView.view removeFromSuperview];
[controller.view addSubview: nextWebView.view];
I encountered this too. Finally, I fixed this way:
if #available(iOS 11, *) {
webView.scrollView.contentInsetAdjustmentBehavior = .never
}
If you have this issue on iOS 11, try this.
Before iOS 11.0, UIWebView was avilable with delegates to indicate that loading of webView is finished and we can do any operation after loading has been finished.
Now we have WKWebView which doesn't provide any delegate method to notify once your webView has finished loading. Due to this if we do following piece of code then it will not work:
let myURL = URL(string: "https://www.google.com")
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest)
webView.scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: false)
In this case, webView.load() is a asynchronous function which gets called and code moves further without waiting for its completion. Due to this your call for setContentOffset work but after that when webView finish loading it reset the ContentOffset to default, which is actually (0,0) but I am not sure why your WebView doesn't load from top. But doing code in following manner will may help you:
Extend to WKNavigationDelegate and implement didFinish navigation method in your ViewController and call setContentOffset in that method.
class ViewController: UIViewController, WKNavigationDelegate {
override func viewDidLoad() {
let myURL = URL(string: "https://www.google.com")
let myRequest = URLRequest(url: myURL!)
webView.load(myRequest)
webView.navigationDelegate = self
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
webView.scrollView.setContentOffset(CGPoint(x: 0, y: 0), animated: false)
}
}
I would try to use javascript doc for scrolling to top:
NSString *script = #"window.scrollTo(0, 0)";
// UIWebView
// [webView stringByEvaluatingJavaScriptFromString:script];
// WKWebView
[webView evaluateJavaScript:script completionHandler:NULL];
Swift 4
This function is part of the WKWebView's UIScrollView and scrolls to a given rectangle.
scrollRectToVisible(_ rect: CGRect, animated: Bool)
A code example would look like:
let rect: CGRect = CGRect(x: 0, y: 0, width: 1, height: 1)
webView.scrollView.scrollRectToVisible(rect, animated: true)
This seems to happen if the WKWebView is not fully initialized or sized when asked to display a web page.
In my case, I created the web view during loadView in a UIViewController. If I then requested to navigate to a web page during viewDidLoad, the web view appeared scrolled partway down the page. I was able to fix the problem by moving the html request into viewWillAppear: (which is later in the view controller lifecycle).