Strange Margin in WKWebView - ios

I have a UIViewController with a few UIViews (built using Interface Builder) including one that I want to use as a WKWebView. I have been able to create the WKWebView and load it as a subview to one of these UIViews -- but when I load the URL I get this strange padding on the top and left. I had the same issue when I use the UIWebView but was able to solve it using
self.automaticallyAdjustsScrollViewInsets = false;
However this does not seem to help at all with the WKWebView that has been loaded dynamically.
I also get the same padding when loading a page from the web so I know its not in my local html.
Edit: I am beginning to wonder whether autolayout in the container UIView is causing this...
Here is the relevant code:
var webView:WKWebView!
#IBOutlet var containerView : UIView?
#IBOutlet weak var webContainer: UIView!
override func loadView() {
super.loadView()
self.webView = WKWebView()
if(self.webView != nil){
self.containerView = self.webView!
self.containerView!.frame = self.webContainer.frame
self.webContainer.addSubview(self.containerView!)
}
}
override func viewDidLoad() {
super.viewDidLoad()
let bundle = NSBundle.mainBundle()
let url = bundle.URLForResource("index", withExtension: "html")
let request = NSURLRequest(URL: url!)
webView.loadRequest(request)
}
Here is what it looks like. The BG color of the UIView container is dark grey -- and you'll also note that the html seems to extend beyond the UIView even though I set the frame of the WebView to be the same as the UIView container:

This is because WKWebView preserves space for the navigation bar by using an appropriate contentInset. However, since your WKWebView is a subview, this adjustment is not necessary anymore.
self.webView.scrollView.contentInset = UIEdgeInsetsMake(0, 0, 0, 0)
It is important to do this after the viewDidLoad method. For example in didFinishedNavigation in the WKNavigationDelegate

self.automaticallyAdjustsScrollViewInsets = NO;
if (#available(iOS 11.0, *)) {
self.webView.scrollView.contentInsetAdjustmentBehavior = UIScrollViewContentInsetAdjustmentNever;
}

Should be used WKNavigationDelegate
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
NSString *cssString = #"body { padding:0; margin:0}";
NSString *jsString = [NSString stringWithFormat:#"var style = document.createElement('style'); style.innerHTML = '%#'; document.head.appendChild(style);", cssString];
[webView evaluateJavaScript:jsString completionHandler:nil];}

Related

How to make transparent background WKWebView in Swift

I want a transparent web page in swift, so I have tried the below code according to this answer. Still, I am not getting a transparent web page. nothing changes in webview colour.. may I know why??
where am I going wrong? please help me in below code.
Total code:
import UIKit
import WebKit
class WebviewViewController: UIViewController {
#IBOutlet weak var testWebView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
guard let url = URL(string: "https://developer.apple.com/swift/") else { return }
let request = URLRequest(url: url)
testWebView.load(request)
// Do any additional setup after loading the view.
self.testWebView = WKWebView()
self.testWebView!.isOpaque = false
self.testWebView!.backgroundColor = UIColor.clear
self.testWebView!.scrollView.backgroundColor = UIColor.clear
}
}
Please help me with the code.
the code to make transparent background is as follow what you already added.
self.testWebView!.backgroundColor = UIColor.clear
now question is you added right code already then why you are not getting reliable output ..?
Also , if you try
self.testWebView!.alpha with any value, it will affect all of WebPages as WkWebView is a single view and changing it's alpha will also affect the components within...
it happened because the page you load in WebViewController has some HTML and CSS code, you make your WebViewController transparent but because of that HTML &CSS you can't see it's transparency as each webpage has it's own background color settings (which is merely impossible to change for each webpage)
I hope you will understand and it will help you ...:)
as I see, you use storyboard (#IBOutlet) and you can use Storyboard for setting your WKWebView:
And about code. This is enough for the result. You shouldn't set again self.testWebView = WKWebView(), because you use storyboard and you can set isOpaque with storyboard. As result:
class ViewController: UIViewController, WKNavigationDelegate {
#IBOutlet var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView.navigationDelegate = self
let url = URL(string: "https://developer.apple.com/swift/")!
webView.load(URLRequest(url: url))
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
let js = "(function() { document.body.style.background='transparent'; })();"
webView.evaluateJavaScript(js) { (_, error) in
print(error)
}
}
}
and evaluateJavaScript helped to add transparency for background:

UIScrollView not working in swift 3

I have made a baseViewController which is derived from UIViewController, SlideMenuDelegate and UIWebViewDelegate
Then I have created subclass which are derived from baseviewcontroller
LoginVC - displays login screen
PlayVC - shows a webview if login is successful.
Everything is fine except that webview is not scrolling at all.
I have tried everything suggested in all posts so far regarding scrolling
Below is my code written in PlayVC.swift
#IBOutlet weak var web1: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
addSlideMenuButton()
let webUrl : URL = URL(string: "http://brainyknights.com")!
let webRequest : URLRequest = URLRequest(url: webUrl)
web1.scalesPageToFit = true
web1.isUserInteractionEnabled = true
web1.scrollView.isScrollEnabled = true
web1.loadRequest(webRequest)
// Do any additional setup after loading the view.
}
remove this line web1.scalesPageToFit = true
#IBOutlet weak var web1: UIWebView!
override func viewDidLoad() {
super.viewDidLoad()
addSlideMenuButton()
let webUrl : URL = URL(string: "http://brainyknights.com")!
let webRequest : URLRequest = URLRequest(url: webUrl)
//web1.scalesPageToFit = true
web1.isUserInteractionEnabled = true
web1.scrollView.isScrollEnabled = true
web1.loadRequest(webRequest)
// Do any additional setup after loading the view.
}
According Apple Documentation
scales​Page​To​Fit
A Boolean value determining whether the webpage scales to fit the view
and the user can change the scale.
if web1.scalesPageToFit is true then the web content will fit in your UIWebView frame and the contentSize will have the same height and width as your UIWebViewframe making imposible to be scrollable
so remove this line and must work
web1.scalesPageToFit = true
I hope this helps you

UIWebView Delegate not changing other views

I have a webView in a simple application which is under a UIImageView. I intend on displaying the UIImageView until webView loads some data and fires a method in the web view delegate. Webview loads the data, delegate method is called fine. However, I am having trouble manipulating other views like the UIImageView.
Here is my code;
class ViewController: UIViewController,UIWebViewDelegate {
//contains the imageView and activityWorkingIndicator
#IBOutlet weak var splash_view: UIView!
#IBOutlet var contentWebView: UIWebView!
//Contains the Webview and other views to be displayed after the splash his hidden
#IBOutlet weak var splash_view_not: UIView!
override func viewWillAppear(animated: Bool)
{
contentWebView?.scrollView.bounces = false;
contentWebView?.delegate = self
let url = NSURL(string: "http://localhost/something/");
let request = NSURLRequest(URL: url!);
contentWebView?.loadRequest(request);
}
....
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if let scheme = request.URL?.scheme {
if scheme == "myRequestScheme"{
let task : String = (request.URL?.host)!;
switch task {
case "systemReady":
print("checkpoint 1");//works fine
splash_view?.hidden = true;//no effect at all
splash_view_not?.hidden = false;//no effect at all
print("checkpoint 2");//works fine
break;
default:
break
}
}
}
return true;
}
}
What am I missing here?
It looks like you are using Auto Layout. If that is the case, using .hidden has more implications than you think. If you are calling it in a method after the view has already been loaded, you need to work with constraints instead of hidden. Some people create constraint outlets and set them to active to hide things. Some people set alphas and bring subviews to front to hide things, etc.
I believe this is the preferred way:
http://candycode.io/hiding-views-with-auto-layout/
And here is a stack overflow thread with A LOT of information on your best options to hide things with AutoLayout:
AutoLayout with hidden UIViews?
You should hide or show any control in delegate method on main thread.
Use like this
DispatchQueue.main.async {
splash_view_not?.hidden = false
}

Unable to display subview added to WKWebView

I am creating an embedded view for an app using Swift and WKWebView. I am stuck in adding and displaying the UIProgressView (my subview).
Whenever I add it on story builder (on top of the view) and then run the app it does not show because of my code (I associate self.view to the instance of WKWebView that I create).
I hence tried an alternative approach and added it programmatically but won't work either.
Here is my code (for the first approach just ignore the programmatic declaration of UIProgressView):
import UIKit
import WebKit
class SupportWebView: UIViewController, WKNavigationDelegate {
#IBOutlet var containerView : UIView? = nil
#IBOutlet var progressView : UIProgressView? = nil
var webView: WKWebView?
dynamic var myProgress: Double = 0
private var myContext = 0
override func loadView() {
super.loadView()
self.webView = WKWebView()
self.view = self.webView
self.webView?.navigationDelegate = self
self.webView?.addObserver(self, forKeyPath: "estimatedProgress", options: .New, context: &myContext)
let progressView = UIProgressView(progressViewStyle: .Bar)
progressView.center = view.center
progressView.progress = 20.0/30.0
progressView.trackTintColor = UIColor.lightGrayColor()
progressView.tintColor = UIColor.blueColor()
self.webView?.addSubview(progressView)
// self.webView.addSubview(progressView)
}
I tried to add the UIProgressView programmatically to the WKWebView view but it won't work.. any suggestion on how to fix this? It seems to display only the main WKWebView and ignore the subviews (they may be rendered before the web view rendering and hence they disappear).
I tested out your code and got it to render the progressView by changing progressView.center = view.center to something like progressView.frame = CGRectMake(100.0, 100.0, 200.0, 5.0). I tested without doing anything on the Storyboard.

Disable magnification gesture in WKWebView

I'm looking for a way to disable the "pinch to zoom" magnification gesture on the iOS implementation of WKWebView. There is a magnification BOOL property available for OS X but it doesn't seem to be available on iOS.
WKWebView.h
#if !TARGET_OS_IPHONE
/* #abstract A Boolean value indicating whether magnify gestures will
change the web view's magnification.
#discussion It is possible to set the magnification property even if
allowsMagnification is set to NO.
The default value is NO.
*/
#property (nonatomic) BOOL allowsMagnification;
I've, also, tried look at the WKWebView's gesture recognizers but that seems to be turning up an empty array. I'm assuming the actual recognizers are bured deeper in the component's structure (fairly complex, by the looks of it) and would rather not go digging for them if at all possible.
I know of possible hacks that could potentially disable the gesture from firing (selectively passing gestures to the WebView, add child view to capture pinch gesture, etc) but I've always found those introduce lag into the event and want to keep the implementation as clean/hack free as possible.
You can prevent your users from zooming by setting the delegate of your WKWebKit's UIScrollView and implementing viewForZooming(in:) as in the following:
class MyClass {
let webView = WKWebView()
init() {
super.init()
webView.scrollView.delegate = self
}
deinit() {
// Without this, it'll crash when your MyClass instance is deinit'd
webView.scrollView.delegate = nil
}
}
extension MyClass: UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return nil
}
}
I have tried setting minimumZoomScale and maximumZoomScale properties of UIScrollView to 1 or isMultipleTouchEnabled property of UIView to false or returning nil from invoking viewForZooming(in:) of UIScrollViewDelegate but none worked. In my case, after several trial and error, the following works in my case [Tested on iOS 10.3]:
class MyViewController: UIViewController {
var webView: WKWebView?
override viewDidLoad() {
super.viewDidLoad()
//...
self.webView.scrollView.delegate = self
//...
}
}
extension MyViewController: UIScrollViewDelegate {
func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
scrollView.pinchGestureRecognizer?.isEnabled = false
}
}
The below answer no longer works in iOS 10 beta.
To improve accessibility on websites in Safari, users can now
pinch-to-zoom even when a website sets user-scalable=no in the
viewport.
WKWebView seems to respect the viewport meta tag the same way Mobile Safari does (as to be expected). So, I found injecting that tag into the DOM through javascript after a page load does the trick. I would be weary of this solution unless you know exactly what HTML is being loaded into the webview, otherwise I suspect it would have unintended consequences. In my case, I'm loading HTML strings, so I can just add it to the HTML I ship with the app.
To do it generically for any webpage:
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
NSString *javascript = #"var meta = document.createElement('meta');meta.setAttribute('name', 'viewport');meta.setAttribute('content', 'width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no');document.getElementsByTagName('head')[0].appendChild(meta);";
[webView evaluateJavaScript:javascript completionHandler:nil];
}
It might be wise to take a look at what kind of navigation has just been completed, since only a new page will need this javascript executed.
Complete working code to disable zooming in WkWebView in Swift.
import UIKit
import WebKit
class ViewController: UIViewController, WKUIDelegate {
var webView : WKWebView!
override func loadView() {
let webConfiguration = WKWebViewConfiguration()
webView = WKWebView(frame: .zero, configuration:webConfiguration)
webView.uiDelegate = self
let source: String = "var meta = document.createElement('meta');" +
"meta.name = 'viewport';" +
"meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';" +
"var head = document.getElementsByTagName('head')[0];" + "head.appendChild(meta);";
let script: WKUserScript = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
webView.configuration.userContentController.addUserScript(script)
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
let myUrl = URL(string: "https://www.google.com")
let myRequest = URLRequest(url: myUrl!)
webView.load(myRequest)
}
}
The native solutions were not working for me, and injecting JS is not ideal. I noticed that when a zoom occurs and my delegate is called, the pinchGestureRecognizer is enabled even though I disabled it when initializing the webview. To fix this, I set it to disabled whenever a zoom starts:
extension ViewController: UIScrollViewDelegate {
// disable zooming in webview
func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
scrollView.pinchGestureRecognizer?.isEnabled = false
}
}
Full Swift 3 / iOS 10 version of Landschaft's answer:
import UIKit
import WebKit
class MyViewController: UIViewController, UIScrollViewDelegate {
var webView = WKWebView()
override func viewDidLoad() {
super.viewDidLoad()
self.view.addSubview(webView)
webView.scrollView.delegate = self
}
// Disable zooming in webView
func viewForZooming(in: UIScrollView) -> UIView? {
return nil
}
}
You can use UIScrollViewDelegate for this. First assign delegate to your webview in viewDidLoad() or any other suitable method as:
class LoginViewController: UIViewController, WKUIDelegate, UIScrollViewDelegate {
override func viewDidLoad() {
webView.scrollView.delegate = self
}
//Add this delegate method in your view controller
func scrollViewWillBeginZooming(_ scrollView: UIScrollView, with view: UIView?) {
scrollView.pinchGestureRecognizer?.isEnabled = false
}
}
In case you display a local html you could just modify this html and put this meta tag to your html:
<head>
<meta content='width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0' name='viewport' />
</head>
A simple way to prevent zooming
override func viewDidLoad() {
super.viewDidLoad()
webView.scrollView.delegate = self
}
func scrollViewDidZoom(_ scrollView: UIScrollView) {
scrollView.setZoomScale(1.0, animated: false)
}
Swift 2.0
extension WKWebView {
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return nil
}
}
If it's not important for you to handle links inside html (say you want to display text only) the simplest would be to turn off user interaction
webView.userInteractionEnabled = false
this is how I disabled zoom for Swift3 view controller for one-webview-only app
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate, UIScrollViewDelegate {
#IBOutlet var webView: WKWebView!
override func loadView() {
webView = WKWebView()
webView.navigationDelegate = self
view = webView
}
override func viewDidLoad() {
super.viewDidLoad()
webView.scrollView.delegate = self
}
func viewForZooming(in: UIScrollView) -> UIView? {
return nil;
}
}
I don't have enough reputation to add comments to answers, but I wanted to mention that Kevin's solution (meta viewport) no longer works in iOS 10 beta. Landschaft's solution is working for me, though!
Since the JS solution uses W3C standards, it should always be supported.
Ah, Kevin, I wish that were how these things worked.
More here: https://stackoverflow.com/a/37859168/1389714
Disable double tap to zoom gesture by require failure of gesture recognizer. It will prevent the browser to take action when user double tap.
import UIKit
class DisableDoubleTapRecognizer : UITapGestureRecognizer, UIGestureRecognizerDelegate{
override init(target: Any?, action: Selector?) {
super.init(target: target, action: action)
}
init() {
super.init(target:nil, action: nil)
self.numberOfTapsRequired = 2;
self.delegate = self;
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true;
}
}
//in your view controller
override func viewDidLoad() {
super.viewDidLoad()
webView.addGestureRecognizer(DisableDoubleTapRecognizer())
}
Here's a slightly modified version of Gulshan Kumar's answer that worked for me in iOS 12.1.2, and it also prevents zooming due to double-taps and rotation. In my example, I just inject the script directly into the web view. I adjusted the scale to 60%, and there's no need to use concatenation in building up the source string.
let source = "var meta = document.createElement('meta'); meta.name = 'viewport'; meta.content = 'width=device-width, initial-scale=0.6, maximum-scale=0.6, user-scalable=no'; var head = document.getElementsByTagName('head')[0]; head.appendChild(meta);"
let script = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
webView.configuration.userContentController.addUserScript(script)
We need to change the delegate call with following signatures in XCode 8:
override func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return nil
}
The application I work on needed the view to be zoomed by Javascript. The accepted answer blocked zooming by JavaScript from inside the page too.
I only needed to disable pinch gesture by the user of the appliction. The only solution I've found is to disable the gesture of the web view after the page has loaded:
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
/* disable pinch gesture recognizer to allow zooming the web view by code but prevent zooming it by the user */
for (UIGestureRecognizer *gr in self.webView.scrollView.gestureRecognizers) {
if ([gr isKindOfClass:[UIPinchGestureRecognizer class]]) {
gr.enabled = NO;
}
}
}
If it is better to disbaled the Pinch zoom when the webpage finish loading, and it can also specify which particular URL is going to be disbaled
public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
if let url = webView.url, url.absoluteString == "***" {
webView.scrollView.pinchGestureRecognizer?.isEnabled = false
}
print("finish")
}
For obj-c., add UIScrollViewDelegate protocol.
/* somewhere else, for example in viewDidLoad() */
_wkWeb.scrollView.delegate = self;
..
- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view {
scrollView.pinchGestureRecognizer.enabled = NO;
}
This worked for me. https://gist.github.com/paulofierro/5b642dcde5ee9e86a130
let source: String = "var meta = document.createElement('meta');" +
"meta.name = 'viewport';" +
"meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';" +
"var head = document.getElementsByTagName('head')[0];" + "head.appendChild(meta);";
let script: WKUserScript = WKUserScript(source: source, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
let userContentController: WKUserContentController = WKUserContentController()
let conf = WKWebViewConfiguration()
conf.userContentController = userContentController
userContentController.addUserScript(script)
let webView = WKWebView(frame: CGRect.zero, configuration: conf)
If the webview is user for read-only purposes, i.e, just for displaying the web content use
webView.isUserInteractionEnabled = false
By this the zoom gesture won't work on it.

Resources