Manos Chorianopoulos

Add webview Location Permissions

......@@ -7,7 +7,7 @@
<key>Pods-SwiftWarplyFramework.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
<integer>1</integer>
</dict>
</dict>
</dict>
......
......@@ -7,7 +7,7 @@
<key>SwiftWarplyFramework.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>1</integer>
<integer>0</integer>
</dict>
</dict>
</dict>
......
......@@ -10,15 +10,73 @@ import UIKit
import WebKit
import SwiftEventBus
// Location Permissions
import CoreLocation
var timer2: DispatchSourceTimer?
@objc public class CampaignViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler {
//@objc public class CampaignViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler {
@objc public class CampaignViewController: UIViewController, WKNavigationDelegate, WKScriptMessageHandler, CLLocationManagerDelegate, WKUIDelegate, UIScrollViewDelegate {
@IBOutlet weak var webview: WKWebView!
public var campaignUrl: String = ""
public var params: String = ""
public var showHeader: Bool = false
// Location Permissions
var webView: WKWebView!
var locationManager: CLLocationManager!
var listenersCount = 0;
public override func loadView() {
super.loadView();
locationManager = CLLocationManager()
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
let contentController = WKUserContentController()
contentController.add(self, name: "Cosmote")
contentController.add(self, name: "listenerAdded");
contentController.add(self, name: "listenerRemoved");
let config = WKWebViewConfiguration()
config.userContentController = contentController
let script = WKUserScript(source: getJavaScripToEvaluate(), injectionTime: .atDocumentEnd, forMainFrameOnly: true)
contentController.addUserScript(script)
self.webView = WKWebView(frame: self.view.bounds, configuration: config)
webView?.uiDelegate = self
webView?.navigationDelegate = self
webView?.scrollView.delegate = self
webView?.scrollView.bounces = false
webView?.scrollView.bouncesZoom = false
view.addSubview(webView!)
}
public override func viewDidLoad() {
super.viewDidLoad()
let campaignUrl = campaignUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
print("Webview url: " + (campaignUrl ?? ""))
if let url = URL(string: campaignUrl ?? "") {
webView.load(URLRequest(url: url))
webView.allowsBackForwardNavigationGestures = true
}
self.hidesBottomBarWhenPushed = true
if (showHeader) {
setBackButton()
}
}
public override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if (!showHeader) {
......@@ -61,26 +119,27 @@ var timer2: DispatchSourceTimer?
}
}
public override func viewDidLoad() {
super.viewDidLoad()
var campaignUrl = campaignUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
print("Webview url: " + (campaignUrl ?? ""))
self.hidesBottomBarWhenPushed = true
if (showHeader) {
setBackButton()
}
webview.navigationDelegate = self
if let url = URL(string: campaignUrl ?? "") {
webview.load(URLRequest(url: url))
webview.allowsBackForwardNavigationGestures = true
}
webview.configuration.userContentController.add(self, name: "Cosmote")
}
// public override func viewDidLoad() {
// super.viewDidLoad()
//
// var campaignUrl = campaignUrl.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
// print("Webview url: " + (campaignUrl ?? ""))
//
// self.hidesBottomBarWhenPushed = true
//
// if (showHeader) {
// setBackButton()
// }
//
// webview.navigationDelegate = self
//
// if let url = URL(string: campaignUrl ?? "") {
// webview.load(URLRequest(url: url))
// webview.allowsBackForwardNavigationGestures = true
// }
//
// webview.configuration.userContentController.add(self, name: "Cosmote")
// }
// MARK: - Functions
func startTimer() {
......@@ -122,6 +181,149 @@ var timer2: DispatchSourceTimer?
timer2 = nil
}
// Location Permissions
func locationServicesIsEnabled() -> Bool {
return (CLLocationManager.locationServicesEnabled()) ? true : false;
}
func authorizationStatusNeedRequest(status: CLAuthorizationStatus) -> Bool {
return (status == .notDetermined) ? true : false;
}
func authorizationStatusIsGranted(status: CLAuthorizationStatus) -> Bool {
return (status == .authorizedAlways || status == .authorizedWhenInUse) ? true : false;
}
func authorizationStatusIsDenied(status: CLAuthorizationStatus) -> Bool {
return (status == .restricted || status == .denied) ? true : false;
}
func onLocationServicesIsDisabled() {
// webView.evaluateJavaScript("navigator.geolocation.helper.error(2, 'Location services disabled');");
locationManager.requestWhenInUseAuthorization();
}
func onAuthorizationStatusNeedRequest() {
locationManager.requestWhenInUseAuthorization();
}
func onAuthorizationStatusIsGranted() {
locationManager.startUpdatingLocation();
}
func onAuthorizationStatusIsDenied() {
webView.evaluateJavaScript("navigator.geolocation.helper.error(1, 'App does not have location permission');");
}
public func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
print("=== locationManager didChangeAuthorization: ",status)
// didChangeAuthorization is also called at app startup, so this condition checks listeners
// count before doing anything otherwise app will start location service without reason
if (listenersCount > 0) {
if (authorizationStatusIsDenied(status: status)) {
onAuthorizationStatusIsDenied();
}
else if (authorizationStatusIsGranted(status: status)) {
onAuthorizationStatusIsGranted();
}
}
}
public func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if let location = locations.last {
print("=== locationManager didUpdateLocations latitude: ",location.coordinate.latitude)
print("=== locationManager didUpdateLocations longitude: ",location.coordinate.longitude)
webView.evaluateJavaScript("navigator.geolocation.helper.success('\(location.timestamp)', \(location.coordinate.latitude), \(location.coordinate.longitude), \(location.altitude), \(location.horizontalAccuracy), \(location.verticalAccuracy), \(location.course), \(location.speed));");
}
}
public func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
print("=== locationManager didFailWithError: ", error)
webView.evaluateJavaScript("navigator.geolocation.helper.error(2, 'Failed to get position (\(error.localizedDescription))');");
}
func getJavaScripToEvaluate() -> String {
let javaScripToEvaluate = """
// management for success and error listeners and its calling
navigator.geolocation.helper = {
listeners: {},
noop: function() {},
id: function() {
var min = 1, max = 1000;
return Math.floor(Math.random() * (max - min + 1)) + min;
},
clear: function(isError) {
for (var id in this.listeners) {
if (isError || this.listeners[id].onetime) {
navigator.geolocation.clearWatch(id);
}
}
},
success: function(timestamp, latitude, longitude, altitude, accuracy, altitudeAccuracy, heading, speed) {
var position = {
timestamp: new Date(timestamp).getTime() || new Date().getTime(), // safari can not parse date format returned by swift e.g. 2019-12-27 15:46:59 +0000 (fallback used because we trust that safari will learn it in future because chrome knows that format)
coords: {
latitude: latitude,
longitude: longitude,
altitude: altitude,
accuracy: accuracy,
altitudeAccuracy: altitudeAccuracy,
heading: (heading > 0) ? heading : null,
speed: (speed > 0) ? speed : null
}
};
for (var id in this.listeners) {
this.listeners[id].success(position);
}
this.clear(false);
},
error: function(code, message) {
var error = {
PERMISSION_DENIED: 1,
POSITION_UNAVAILABLE: 2,
TIMEOUT: 3,
code: code,
message: message
};
for (var id in this.listeners) {
this.listeners[id].error(error);
}
this.clear(true);
}
};
// @override getCurrentPosition()
navigator.geolocation.getCurrentPosition = function(success, error, options) {
var id = this.helper.id();
this.helper.listeners[id] = { onetime: true, success: success || this.noop, error: error || this.noop };
window.webkit.messageHandlers.listenerAdded.postMessage("");
};
// @override watchPosition()
navigator.geolocation.watchPosition = function(success, error, options) {
var id = this.helper.id();
this.helper.listeners[id] = { onetime: false, success: success || this.noop, error: error || this.noop };
window.webkit.messageHandlers.listenerAdded.postMessage("");
return id;
};
// @override clearWatch()
navigator.geolocation.clearWatch = function(id) {
var idExists = (this.helper.listeners[id]) ? true : false;
if (idExists) {
this.helper.listeners[id] = null;
delete this.helper.listeners[id];
window.webkit.messageHandlers.listenerRemoved.postMessage("");
}
};
""";
return javaScripToEvaluate;
}
// <===
// MARK: - API Calls
func startTrackingSteps() {
swiftApi().startTrackingSteps(startTrackingStepsCallback)
......@@ -147,11 +349,39 @@ var timer2: DispatchSourceTimer?
self.startTimer()
}
})
}
// MARK: - WKScriptMessageHandler
public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
// Location Permissions
if (message.name == "listenerAdded") {
listenersCount += 1;
if (!locationServicesIsEnabled()) {
onLocationServicesIsDisabled();
}
else if (authorizationStatusIsDenied(status: CLLocationManager.authorizationStatus())) {
onAuthorizationStatusIsDenied();
}
else if (authorizationStatusNeedRequest(status: CLLocationManager.authorizationStatus())) {
onAuthorizationStatusNeedRequest();
}
else if (authorizationStatusIsGranted(status: CLLocationManager.authorizationStatus())) {
onAuthorizationStatusIsGranted();
}
}
else if (message.name == "listenerRemoved") {
listenersCount -= 1;
// no listener left in web view to wait for position
if (listenersCount == 0) {
locationManager.stopUpdatingLocation();
}
}
// <==
else if (message.name == "Cosmote") {
if let event = message.body as? String {
let eventArray = event.split(separator: ":")
......@@ -383,5 +613,6 @@ var timer2: DispatchSourceTimer?
}
}
}
}
}
......
......@@ -403,6 +403,11 @@
<constraint firstItem="bHn-Kz-pbS" firstAttribute="leading" secondItem="iPT-gj-hEL" secondAttribute="leading" id="UTz-nY-JvS"/>
<constraint firstItem="bHn-Kz-pbS" firstAttribute="top" secondItem="iPT-gj-hEL" secondAttribute="top" id="WKD-3C-3kF"/>
</constraints>
<variation key="default">
<mask key="subviews">
<exclude reference="bHn-Kz-pbS"/>
</mask>
</variation>
</view>
</subviews>
<viewLayoutGuide key="safeArea" id="xUc-yV-Y8f"/>
......