I have a web app, and I am not an IOS developer of any sort. I simply want to display my webapp in an IOS application for the appstore. Luckily I’m aware of WKWebView. So with that in mind I have attempted to load my webapp into the webview and display it. However I’m encountering some weird issues. It seems the webview asset has some native conditions in which it interperets CSS and HTML in its own special way, as it is completely failing to render navbars where I want them to be. I am trying to ignore the safe area of the IOS setup. And I can’t make this work. I have tried multiple times to no success. When I ignore the safe areas, the majority of the app ignores them, apart from some navbar styled elements…. I thought this was weird so I changed the app to w3schools.com and noticed the same glitch. safe area still blocks the navbar… How can I make it not do this!!
I have my
webView.topAnchor.constraint(equalTo: view.topAnchor)
and this allows the body to go all the way to the top of the physical screen as shown below.
But notice the red box I have added to highlight the way in which the navbar of any website cant also go up there….. it also seems to similarly do some funky stuff with any fixed elements at the bottom of the app. But that might be resolved by fixing this top issue.
I really need a fix for this. Any help at all would be appreciated. My full ViewController code is present below aswell for help.
Many Thanks.
My Entire ViewController Code
import UIKit
import WebKit
class ViewController: UIViewController, WKNavigationDelegate {
var webView: WKWebView!
var connectingLabel: UILabel!
var errorLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Create the WKWebView instance
webView = WKWebView()
webView.translatesAutoresizingMaskIntoConstraints = false
webView.navigationDelegate = self
view.addSubview(webView)
// Create the "Connecting" UILabel instance
connectingLabel = UILabel()
connectingLabel.text = "Connecting"
connectingLabel.textAlignment = .center
connectingLabel.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(connectingLabel)
// Create the "Service Offline" UILabel instance
errorLabel = UILabel()
errorLabel.text = "Service Offline"
errorLabel.textAlignment = .center
errorLabel.translatesAutoresizingMaskIntoConstraints = false
errorLabel.isHidden = true
view.addSubview(errorLabel)
// Set up auto layout constraints for WKWebView to fill the entire view, including areas under status bar and home indicator
NSLayoutConstraint.activate([
webView.topAnchor.constraint(equalTo: view.topAnchor),
webView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
webView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
webView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
])
// Set up auto layout constraints for "Connecting" UILabel
NSLayoutConstraint.activate([
connectingLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
connectingLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
// Set up auto layout constraints for "Service Offline" UILabel
NSLayoutConstraint.activate([
errorLabel.centerXAnchor.constraint(equalTo: view.centerXAnchor),
errorLabel.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
// Disable double-tap to zoom (requires setting viewport settings)
let viewportScript = """
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 userScript = WKUserScript(source: viewportScript, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
webView.configuration.userContentController.addUserScript(userScript)
// Load the URL
if let url = URL(string: "https://w3schools.com") {
let request = URLRequest(url: url)
loadCookies {
self.webView.load(request)
}
}
// Observe when the app is about to terminate or go into the background
NotificationCenter.default.addObserver(self, selector: #selector(saveCookies), name: UIApplication.willTerminateNotification, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(saveCookies), name: UIApplication.didEnterBackgroundNotification, object: nil)
}
deinit {
NotificationCenter.default.removeObserver(self, name: UIApplication.willTerminateNotification, object: nil)
NotificationCenter.default.removeObserver(self, name: UIApplication.didEnterBackgroundNotification, object: nil)
}
// WKNavigationDelegate method to show "Connecting" label when content starts to load
func webView(_ webView: WKWebView, didStartProvisionalNavigation navigation: WKNavigation!) {
connectingLabel.isHidden = false
errorLabel.isHidden = true
}
// WKNavigationDelegate method to hide "Connecting" label when content finishes loading
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
connectingLabel.isHidden = true
}
// WKNavigationDelegate method to handle HTTP errors
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
if let response = navigationResponse.response as? HTTPURLResponse, response.statusCode == 404 {
errorLabel.isHidden = false
connectingLabel.isHidden = true
webView.isHidden = true
}
decisionHandler(.allow)
}
// Handle failure in loading
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
connectingLabel.isHidden = true
errorLabel.isHidden = false
}
@objc func saveCookies() {
webView.configuration.websiteDataStore.httpCookieStore.getAllCookies { cookies in
let cookieDicts = cookies.map { cookie -> [String: Any] in
return [
"name": cookie.name,
"value": cookie.value,
"domain": cookie.domain,
"path": cookie.path,
"expiresDate": cookie.expiresDate ?? Date.distantFuture,
"isSecure": cookie.isSecure
]
}
let userDefaults = UserDefaults.standard
userDefaults.set(cookieDicts, forKey: "savedCookies")
userDefaults.synchronize()
}
}
func loadCookies(completion: @escaping () -> Void) {
let userDefaults = UserDefaults.standard
if let cookieDicts = userDefaults.array(forKey: "savedCookies") as? [[String: Any]] {
let cookies = cookieDicts.compactMap { dict -> HTTPCookie? in
return HTTPCookie(properties: [
.name: dict["name"]!,
.value: dict["value"]!,
.domain: dict["domain"]!,
.path: dict["path"]!,
.expires: dict["expiresDate"]!,
.secure: dict["isSecure"]!
])
}
let cookieStore = webView.configuration.websiteDataStore.httpCookieStore
let dispatchGroup = DispatchGroup()
for cookie in cookies {
dispatchGroup.enter()
cookieStore.setCookie(cookie) {
dispatchGroup.leave()
}
}
dispatchGroup.notify(queue: .main) {
completion()
}
} else {
completion()
}
}
}