62

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.

Kevin
  • 3,021
  • 1
  • 15
  • 26

20 Answers20

80

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
    }
}
Sandy Chapman
  • 10,385
  • 3
  • 55
  • 63
Landschaft
  • 1,097
  • 1
  • 10
  • 11
  • 14
    Don't forget to add conformance to `UIScrollViewDelegate`, set `webView.scrollView.delegate = self;` and nullify the delegate pointer in `deinit`. – nikolovski Oct 20 '15 at 14:17
  • 2
    This should be noted as the correct answer. A native iOS solution rather than trying to inject JS. – C6Silver Dec 03 '15 at 19:18
  • 6
    I disagree. While a native solution would be ideal, I think this solution is just as hacky as the JS one and is, potentially, more fragile. While WKWebView vends a UIScrollView, internally is uses a WKScrollView. While it is a subclass of UIScrollView, it ignores/overrides a lot of standard behavior (contentInset for one). Zooming an HTML document is very different then zooming a UIView, so I could see a future where this changes/no longer works. Since the JS solution uses W3C standards, it should always be supported. – Kevin Jan 28 '16 at 18:52
  • That being said, if you are loading 3rd-party content, this is probably the safer solution. That being said, if the creator of the 3rd-party content wanted to allow zooming, it is probably wise to allow their intent. 1st-party/locally loaded content where you can have a reasonable idea of what is loaded in it, I'd still go Javascript. – Kevin Jan 28 '16 at 18:54
  • I've been able to repro a scenario where even though meta viewport setting was evident in the html, device rotations were still _sometimes_ causing the content to unexpectedly zoom out. This solution, (and then @quemeful's) was the only way to guarantee no zooming. – Stafford Williams Mar 30 '16 at 07:31
  • @MarkoNikolovski is it really necessary to nullify delegate pointer? – Prcela May 20 '16 at 11:10
  • 2
    @Prcela Nullifying the delegate is not necessary. It's a weak variable and thus doesn't create retain cycles. – Smongo Aug 25 '17 at 17:08
  • 1
    Update function name to "viewForZooming" (in iOS10, iOS11), per some more recent answers to this question (on this page) – Matt Gardner Sep 15 '17 at 20:55
  • 1
    This is a dangerous answer — setting a WKWebView's scrollViewDelegate has adverse effects. It will interfere with pan gesture handling when the WKWebView is inside another UIScrollView. Don't do this. – thefaj Sep 29 '17 at 00:36
  • 1
    Not recommended. Setting the scrollView delegate breaks auto scrolling to text field when keyboard appears. – Mikkel Selsøe Oct 26 '17 at 11:00
  • This worked for me in iOS 13, Swift 4 https://stackoverflow.com/a/49651534/385708 – Shyam Bhat Jul 16 '20 at 16:39
47

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.

Miguel Ruivo
  • 9,089
  • 3
  • 32
  • 62
Kevin
  • 3,021
  • 1
  • 15
  • 26
  • 11
    This still allows double tap zooming. `minimum-scale=1.0` needs to be set to avoid this. – fabb Mar 01 '15 at 12:12
  • 1
    This answer helped, but I still seem to be able to create situations where it's possible to zoom. Not sure what or how yet. – gravitron Jun 03 '16 at 02:49
  • 1
    This is the correct answer — do not mess with the WKWebView's UIScrollView delegate. – thefaj Sep 29 '17 at 00:38
  • Thanks! Used this with Turbolinks and seems to work perfectly (on iOS 11). Can neither pinch nor double-tap to zoom. – Henrik N Oct 11 '17 at 12:09
  • 1
    Might be easier to do this with userScripts: https://stackoverflow.com/a/41741125/1683141 – Mdlc Mar 30 '18 at 20:47
  • This should be the accepted answer. Thanks for that ! – Lapinou Jul 23 '18 at 08:27
  • I got error when apply javascript. Please help me. Error Domain=WKErrorDomain Code=4 "A JavaScript exception occurred" UserInfo={WKJavaScriptExceptionLineNumber=1, WKJavaScriptExceptionMessage=TypeError: undefined is not an object (evaluating 'document.getElementsByTagName('head')[0].appendChild'), WKJavaScriptExceptionColumnNumber=228, WKJavaScriptExceptionSourceURL=https://....., NSLocalizedDescription=A JavaScript exception occurred} – Yogendra Patel Jul 14 '20 at 09:25
47

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
   }
}
yohannes
  • 972
  • 9
  • 13
  • 1
    Thanks! This is exactly what I want. The accepted answer cannot work for my case because after return `nil` in delegate, the scale of my webpage is wrong. – Desmond DAI Sep 11 '17 at 06:06
  • 1
    Perfect ! Use this , you can change the images frame through JS without any bad effect. – guozqzzu Oct 13 '17 at 08:09
  • 1
    I tried all options like you. Your answer was perfect for me. Thanks :) – Ronaldo Albertini Dec 12 '18 at 17:53
  • NOTE: Avoid zoom issues when the user interface orientation changes by implementing viewWillTransition like so: override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) // Before rotation coordinator.animate(alongsideTransition: { context in // During rotation }, completion: { context in // After rotation webView.scrollView.setZoomScale(webView.scrollView.minimumScale, animated: false) }) } – Tyler Wood Oct 07 '20 at 11:14
22

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)
    }
}
rmaddy
  • 298,130
  • 40
  • 468
  • 517
Gulshan Kumar
  • 389
  • 2
  • 9
  • 1
    the only solution that worked for me iOS 11.4. I have no idea why the other solutions, especially native ones did not work but javascript trick did... Crazy how easy is to develop on Android and how difficult on iOS. Programming should be simple, no? – undefinedman Jun 22 '18 at 23:53
  • Your solution not work for given url pdf995.com/samples/pdf.pdf . Please help me if you have any solution. – Yogendra Patel Jul 28 '20 at 13:55
22

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
    }
}
pulse4life
  • 989
  • 9
  • 13
  • 2
    This is an exact duplicate of https://stackoverflow.com/a/43205413/1226963 posted over a year earlier than this one. Why? – rmaddy Jul 17 '19 at 23:10
15

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
    }
}
Mazen Kasser
  • 3,231
  • 2
  • 22
  • 33
Simon Epskamp
  • 6,866
  • 2
  • 45
  • 49
10

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
  }
}
Mazen Kasser
  • 3,231
  • 2
  • 22
  • 33
Yakup Ad
  • 1,364
  • 12
  • 13
  • This one worked for me on iOS 12. Other solutions like viewport or return nil for viewForZooming didn't – Nemanja Nov 03 '18 at 06:10
  • 2
    This solution is a good starting point for iOS13! What was only missing for me, is to set `` in HTML because when a phone's rotation has been changed, the view in-app was zooming. – Egel Nov 11 '19 at 09:28
8

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>
Leszek Szary
  • 8,558
  • 1
  • 49
  • 46
3

Swift 2.0

extension WKWebView {
    func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
        return nil
    }
}
quemeful
  • 8,148
  • 4
  • 51
  • 64
  • Note that would do it to all WKWebViews – Luke Mar 15 '17 at 05:35
  • 1
    Not if you subclass it first and apply the extension to the subclass. Works for me. – BaseZen Mar 31 '17 at 21:38
  • @Luke and to have a local side-effect, you could place it within the view controller file scope and mark it private ` private extension WKWebView { func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? { return nil } } ` – Mazen Kasser Feb 28 '20 at 01:23
3

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)
}
Waqar Ahmed
  • 249
  • 5
  • 16
2

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

lvp
  • 2,058
  • 17
  • 22
2

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;
    }    

}
godblessstrawberry
  • 2,924
  • 23
  • 36
  • Finally! Man it's tough tracking down the constant Swift changes (nearly everything else on the web has the delegate func being "viewForZoomingInScrollView". Thanks for this update to "viewForZooming" – Matt Gardner Sep 15 '17 at 20:53
1

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

Community
  • 1
  • 1
mark
  • 157
  • 2
  • 9
1

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())
    }
1

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)
Scott Gardner
  • 7,855
  • 1
  • 39
  • 34
0

We need to change the delegate call with following signatures in XCode 8:

override func viewForZooming(in scrollView: UIScrollView) -> UIView? { return nil }

Brianster
  • 9
  • 1
0

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;
        }
    }
}
UrK
  • 2,038
  • 2
  • 25
  • 36
0

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")
} 
Jin
  • 459
  • 5
  • 14
-1

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)
Jonny
  • 14,972
  • 14
  • 101
  • 217
-2

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.

Sameer Bhide
  • 163
  • 7
  • This will also disable scrolling. Just because the content is read-only does not mean you cannot scroll through it. – Bassem Sameh May 18 '18 at 11:04