WKWebView not resizing when content changing - ios

WKWebView is not resizing when content changing.
Here is a code snippet:
#IBOutlet weak var containingView: UIView!
var webView: WKWebView!
override func viewDidLoad()
{
super.viewDidLoad()
let contentController = WKUserContentController();
self.webView = WKWebView(frame: CGRectMake(0, 0, self.view.frame.width, self.view.frame.height), configuration: WKWebViewConfiguration())
self.webView.autoresizingMask = [.FlexibleHeight]
self.webView.loadHTMLString(__, baseURL: __))
self.webView.scrollView.bounces = false
self.webView.scrollView.scrollEnabled = false
self.containingView.addSubview(self.webView)
}
What func should be added so I could intercept content size being changed and change the containingView size accordingly?

func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("webview did enter did finish navigation")
webView.evaluateJavaScript("document.readyState", completionHandler: { (complete, error) in
if complete != nil {
webView.evaluateJavaScript("document.body.scrollHeight", completionHandler: { (result, error) in
if let heightOfContent = result as? CGFloat {
}
})
}
})
}

What halped me was to have small initial WKWebView height
webView = WKWebView(frame: CGRectMake(0, 0, self.view.frame.width, 150), configuration: configuration)
instead of
webView = WKWebView(frame: CGRectMake(0, 0, self.view.frame.width, self.view.frame.height), configuration: configuration)

Conceptually in objective c you should do like below.
Two things:
Make sure to have these properties:
self.webView.scalesPageToFit = YES;
self.webView.contentMode = UIViewContentModeScaleAspectFit;
And finally calculate webView content height as :-
webView.isLoading is one of the most important property. Actually in a single call to a web url it is called multiple times.
- (void)webViewDidFinishLoad:(UIWebView *)webView{
if (webView.isLoading == NO)// as it is called multiple times so wait untill last call
{
CGFloat height = [[webView stringByEvaluatingJavaScriptFromString:#"document.height"] floatValue];
}
}
This is how you should compute web view height.
you can have a look here as WKWebView don't have a delete which you can use to get content size instead use KVO:
KVO example
May be it will help you more!

Related

Change background color of status bar

I have a very simple web view project which just loads a website. There is a bug though, the website content is showing in the status bar if I scroll down.
I read that this is because the background color of the ViewController is set to be transparent, how can I change it to another color?
I tried it like this:
let color = UIColor.black;
self.view.backgroundColor = color
But nothing changes
Whole Code:
ViewController.swift
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
#IBOutlet var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
// 1 The webView loads the url using an URLRequest object.
let url = URL(string: "https://www.blizz-z.de/")!
webView.load(URLRequest(url: url))
// 2 A refresh item is added to the toolbar which will refresh the current webpage.
let refresh = UIBarButtonItem(
barButtonSystemItem: .refresh,
target: webView,
action: #selector(webView.reload)
)
toolbarItems = [refresh]
navigationController?.isToolbarHidden = true
navigationController?.isNavigationBarHidden = true
}
override func loadView() {
webView = WKWebView()
webView.navigationDelegate = self
view = webView
let color = UIColor.black;
self.view.backgroundColor = color
}
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
title = webView.title
}
}
I could just disable the status bar with the following code to fix the bug, but I try to keep it:
override var prefersStatusBarHidden: Bool {
return true
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let height = UIScreen.main.bounds.height
let width = UIScreen.main.bounds.width
let webView = WKWebView(frame: CGRect(x: 0, y: 0, width: width, height: height))
webView.load(URLRequest(url: (URL(string: "https://www.blizz-z.de/")!)))
view.addSubview(webView)
UIApplication.shared.statusBarView?.backgroundColor = #colorLiteral(red: 0.9529411765, green: 0.9529411765, blue: 0.9529411765, alpha: 1)
}
}
extension UIApplication {
var statusBarView: UIView? {
if responds(to: Selector("statusBar")) {
return value(forKey: "statusBar") as? UIView
}
return nil
}
}
Make the webView.top constraint relative to safeArea.top constraint, and not the superview.top constraint. This allows you to make your webView under the status bar.

iOS Swift - Multiple activity indicator (spinners) appear in webview

I'm working on a webView based screen and I want to have an activity indicator to spin while the web content is loading.
Since I have to use the same activity indicator in another screen of the app, I've put the code in static functions in a specific file.
The activity indicator seems to work fine for some web (simple) web pages but I have an issue when I load more complex pages. The activity indicators gets duplicate several times. (See screenshot below)
On the screenshot, the first activity indicator has the correct layout but the one below is darker which implies that several other activity indicators have been overlaid on top of each other. And then they never disappears.
When it comes to code:
I have a webView and two delegate methods controlling the activity indicators.
func webViewDidStartLoad(_ webView: UIWebView) {
DispatchQueue.main.async {
EBUtil.startActivityIndicator(self)
}
}
func webViewDidFinishLoad(_ webView: UIWebView) {
DispatchQueue.main.async {
EBUtil.stopActivityIndicator(self)
}
}
I guess it comes from the fact that webViewDidStartLoad gets called several times. Any idea how I can prevent this behaviour to happen?
Thanks in advance.
Edouard
EDIT:
Here is the full code for my VC.
class NewsDetailsViewController: UIViewController, UIWebViewDelegate {
//MARK: - #IBOUTLETS
#IBOutlet weak var webView: UIWebView!
//MARK: - VARIABLES
var url: String!
//MARK: - APP LIFE CYCLE
override func viewDidLoad() {
super.viewDidLoad()
if url != nil {
let urlToLoad = NSURL(string: url)
webView.loadRequest(NSURLRequest(url: urlToLoad as! URL) as URLRequest)
}
}
func webViewDidStartLoad(_ webView: UIWebView) {
DispatchQueue.main.async {
EBUtil.startActivityIndicator(self)
}
}
func webViewDidFinishLoad(_ webView: UIWebView) {
DispatchQueue.main.async {
EBUtil.stopActivityIndicator(self)
}
}
}
And code for activity indicator start and stop:
static func startActivityIndicator(_ sender: UIViewController) {
print("START ANIMATING")
if let view = sender.view {
activityIndicatorBackground = UIView(frame: CGRect(x: view.center.x - 25, y: view.center.y - 25, width: 50, height: 50))
activityIndicatorBackground.backgroundColor = UIColor.black
activityIndicatorBackground.layer.zPosition = CGFloat(MAXFLOAT-1)
activityIndicatorBackground.alpha = 0.6
activityIndicatorBackground.layer.cornerRadius = 5
view.addSubview(activityIndicatorBackground)
activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
activityIndicator.center = view.center
activityIndicator.hidesWhenStopped = true
activityIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.white
activityIndicator.layer.zPosition = CGFloat(MAXFLOAT)
view.addSubview(activityIndicator)
activityIndicator.startAnimating()
UIApplication.shared.beginIgnoringInteractionEvents()
}
}
static func stopActivityIndicator(_ sender: UIViewController) {
print("STOP ANIMATING")
activityIndicatorBackground.removeFromSuperview()
activityIndicator.stopAnimating()
UIApplication.shared.endIgnoringInteractionEvents()
}
try this -
add below line in your viewController class -
var activityIndicator: UIActivityIndicatorView?
And update your startActivityIndicator stopActivityIndicator method like below-
func startActivityIndicator(_ sender: UIViewController) {
print("START ANIMATING")
if let view = sender.view {
if activityIndicator != nil {
activityIndicator?.removeFromSuperview()
}
activityIndicator = UIActivityIndicatorView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
activityIndicator?.center = view.center
activityIndicator?.layer.cornerRadius = 10
activityIndicator?.backgroundColor = UIColor.gray
activityIndicator?.hidesWhenStopped = true
activityIndicator?.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.white
activityIndicator?.layer.zPosition = CGFloat(MAXFLOAT)
view.addSubview(activityIndicator!)
activityIndicator?.startAnimating()
UIApplication.shared.beginIgnoringInteractionEvents()
}
}
func stopActivityIndicator(_ sender: UIViewController) {
print("STOP ANIMATING")
activityIndicator?.stopAnimating()
activityIndicator?.removeFromSuperview()
UIApplication.shared.endIgnoringInteractionEvents()
}
Hope it will work for you :)

Swift onfinish load event of a UIWebView

I'm loading a pdf in WebView from my server with this code :
webView = UIWebView(frame: CGRectMake(0, y, screenSize.width, screenSize.height-y))
let url : NSURL! = NSURL(string: urlFile)
webView?.loadRequest(NSURLRequest(URL: url))
webView?.opaque = false;
webView?.backgroundColor = UIColor.clearColor()
webView?.scalesPageToFit = true;
self.view.addSubview(webView!)
This code works but how can i receive an event "onPageLoad"?
sorry for bad english, i'm italian(:
You need to implement UIWebViewDelegate like this and use webViewDidFinishLoad to know the page is loaded successfully, for that set the webView delegate with your viewController like this, and implement the webViewDidFinishLoad method inside your viewController like below example.
import UIKit
class ViewController: UIViewController, UIWebViewDelegate {
override func viewDidLoad() {
super.viewDidLoad()
webView = UIWebView(frame: CGRectMake(0, y, screenSize.width, screenSize.height-y))
let url : NSURL! = NSURL(string: urlFile)
webView?.loadRequest(NSURLRequest(URL: url))
webView?.opaque = false;
webView?.backgroundColor = UIColor.clearColor()
webView?.scalesPageToFit = true;
webView?.delegate = self// Add this line to set the delegate of webView
self.view.addSubview(webView!)
}
func webViewDidFinishLoad(webView : UIWebView) {
//Page is loaded do what you want
}
}
In case someone is looking solution for WKWebView, implement WKNavigationDelegate:
add webView.navigationDelegate = self
and then use:
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
}
After webView?.loadRequest(NSURLRequest(URL: url)) this, set the webView to self (Write the below line).
webView.delegate = self
Implement the webview delegate methods.
You will get callback in func webViewDidFinishLoad(_ webView: UIWebView) on completing the page load.
func webViewDidFinishLoad(webView: UIWebView) {
//handle the callback here when page load completes
}
Just use effective with ;
func webViewDidFinishLoad(webView : UIWebView) {
// Here your loaded codes.
}

Trying to create an iOS WKWebView with a size smaller than the screen programmatically

Im starting with a simple app that just shows a web view using WKWebView.
Everything builds, and works okay, except that it is always fullscreen. Ideally it would not extend under the iOS status bar at the top. It needs to be lowered by 20px to be below it. I have searched extensively for a solution but nothing changes the size. Nothing is setup in InterfaceBuilder, I'm doing everything programmatically. Using Swift.
This seems like something basic that many apps with a WKWebView would do. It should be simple. I'm probably missing something obvious. Any help is appreciated. Thanks in advance.
This is what I have so far:
import UIKit
import WebKit
class ViewController: UIViewController, UIGestureRecognizerDelegate , UIWebViewDelegate, WKNavigationDelegate {
let url = NSURL(string:"http://stackoverflow.com")
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView = WKWebView(frame: CGRect( x: 0, y: 20, width: 380, height: 150 ), configuration: WKWebViewConfiguration() )
self.view = webView
self.view.frame = webView.frame
let req = NSURLRequest(URL:url!)
webView.loadRequest(req)
self.webView.allowsBackForwardNavigationGestures = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Many thanks Emilio!! The adjusted code works perfectly!
import UIKit
import WebKit
class ViewController: UIViewController, UIGestureRecognizerDelegate , UIWebViewDelegate, WKNavigationDelegate {
let url = NSURL(string:"http://stackoverflow.com")
var webView: WKWebView!
override func viewDidLoad() {
super.viewDidLoad()
webView = WKWebView(frame: CGRect( x: 0, y: 20, width: self.view.frame.width, height: self.view.frame.height - 20 ), configuration: WKWebViewConfiguration() )
self.view.addSubview(webView)
let req = NSURLRequest(URL:url!)
webView.loadRequest(req)
self.webView.allowsBackForwardNavigationGestures = true
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
Your problem is that UIViewControllers presented in certain ways will manage the frame of their view. If you present it modally, as the root controller, or in a navigation bar, for example, setting the fame manually won't have an effect. (Those are only a few examples).
If you want to manage the frame of the view, you can add the webView as a subview to your view controller's view. Modifying it's frame will work then.
One thing I'd like to point out is that calling self.view.frame = webView.frame after calling self.view = webView is redundant, because they are both the same object, so what you are effectively doing is:
self.view.frame = self.view.frame
I hat make room for some icon's and spend a lot of time fixing this. the solution was placing the adjustment in the viewDidAppear function.
import UIKit
import WebKit
class ViewController: UIViewController {
let webView = WKWebView()
let url_base = "http://www.ztatz.nl"
override func loadView() {
self.view = webView
webView.addObserver(self, forKeyPath: #keyPath(WKWebView.estimatedProgress),
options: .new, context: nil)
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?,
change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if keyPath == "estimatedProgress" {
print(Float(webView.estimatedProgress))
}
}
override func viewDidLoad() {
super.viewDidLoad()
webView.load( url_base )
}
override func viewDidAppear(_ animated: Bool) {
self.webView.frame = CGRect(x: 0, y: 0, width: self.view.frame.width,
height: self.view.frame.height - 100)
}
}
extension WKWebView {
func load(_ urlString: String) {
if let url = URL(string: urlString) {
let request = URLRequest(url: url)
load(request)
}
}
}
The above solutions Didn't Work for me.
So I tried the following solution and it worked.
import SwiftUI
import WebKit
struct WebView: UIViewRepresentable {
var url: URL
func makeUIView(context: Context) -> WKWebView {
return WKWebView(frame: CGRect(x: 0,y: 0 , width: UIScreen.main.bounds.size.width, height:200))
}
func updateUIView(_ webView: WKWebView, context: Context) {
let request = URLRequest(url: url)
webView.load(request)
}
}
And Call from your struct.
struct TermsOfService: View {
var body: some View {
VStack {
WebView(url: URL(string: "https://in.search.yahoo.com")!)
}
}
}

Custom 3D touch and WkWebView issue

I am trying to implement a custom peek/pop behaviour in WkWebView.
I see the delegate being called and the controller being returned, but peek controller does not showup.
I can only get the peek controller to showup by setting
self.webView?.userInteractionEnabled = false
What could be going on here?
Code:
class ViewController: UIViewController, WKNavigationDelegate, UIViewControllerPreviewingDelegate {
var webView: WKWebView?
func webView(webView: WKWebView,
decidePolicyForNavigationResponse navigationResponse: WKNavigationResponse,
decisionHandler: ((WKNavigationResponsePolicy) -> Void)){
decisionHandler(.Allow)
}
override func viewDidLoad() {
super.viewDidLoad()
/* Create our preferences on how the web page should be loaded */
let preferences = WKPreferences()
preferences.javaScriptEnabled = true
/* Create a configuration for our preferences */
let configuration = WKWebViewConfiguration()
configuration.preferences = preferences
/* Now instantiate the web view */
webView = WKWebView(frame: view.bounds, configuration: configuration)
if let theWebView = webView{
/* Load a web page into our web view */
let url = NSURL(string: "http://www.apple.com")
let urlRequest = NSURLRequest(URL: url!)
theWebView.loadRequest(urlRequest)
theWebView.navigationDelegate = self
view.addSubview(theWebView)
}
self.registerForPreviewingWithDelegate(self, sourceView: self.view)
}
func previewingContext(previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? {
self.webView?.userInteractionEnabled = false
let peekViewController = storyboard!.instantiateViewControllerWithIdentifier("peekaboo") as UIViewController
/*
Set the height of the preview by setting the preferred content size of the detail view controller.
Width should be zero, because it's not used in portrait.
*/
peekViewController.preferredContentSize = CGSize(width: 0.0, height: 0.0)
// Set the source rect to the cell frame, so surrounding elements are blurred.
//previewingContext.sourceRect = (self.webkitWebView?.frame)!
return peekViewController
}
/// Present the view controller for the "Pop" action.
func previewingContext(previewingContext: UIViewControllerPreviewing, commitViewController viewControllerToCommit: UIViewController) {
// Reuse the "Peek" view controller for presentation.
}
}
Set a prefferedContentSize height
peekViewController.preferredContentSize = CGSize(width: 0.0, height: 150)

Resources