I have an asynchronous call to refresh my cookies in a new wkwebview.
public override func viewDidLoad() {
super.viewDidLoad()
let cookies = cookieService.getCookies(forDomain: urlService.getTopLevelDomain())
authenticationService.authenticateIfNeeded { [weak self] error in
if let error = error {
print(failed)
} else {
self?.identityService.storeCookies(cookies) {
DispatchQueue.main.async {
self?.loadRequest()
}
}
}
}
public func authenticateIfNeeded(completion: #escaping (Error?) -> Void) {
let domain = urlService.getTopLevelDomain()
identityService.refreshCookies(for: domain, force: true, completion: completion)
}
I have put my network in a 100% packet loss preset.
The logic which has setcookies in identity services has retry options and it takes 60 seconds in total to complete this retry calls.
func storeCookies(_ cookies: [AnyObject], completion: (() -> Void)? = nil) {
let group = DispatchGroup()
let httpCookies = cookies.compactMap { $0 as? HTTPCookie }
for httpCookie in httpCookies {
self.cookieStorageService.setCookie(httpCookie)
group.enter()
wkCookieStorage.setCookie(httpCookie) {
group.leave()
}
}
group.notify(queue: .main) {
completion?()
}
}
func refreshCookies(for domain: String, force: Bool, completion: #escaping VoidResultHandler) {
Retry<Void>.call(
shouldRetry: self.shouldRetryIdentityOperation,
handler: completion,
method: { completion in
self.identityOperations.refreshCookies(force: force, domain: domain, handler: { result in
switch result {
case .value(let cookies):
self.storeCookies(cookies) {
completion(.value(()))
}
case .error(let error):
completion(.error(error))
}
})
})
}
Till then I see a blank screen and then I get a retry option. How to reduce this delay to have a better user experience.
import UIKit
import WebKit
class ViewController: UIViewController {
private weak var webView: WKWebView!
public override func viewDidLoad() {
super.viewDidLoad()
let webView = WKWebView(frame: .zero, configuration: WKWebViewConfiguration())
view.addSubview(webView)
webView.translatesAutoresizingMaskIntoConstraints = false
webView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
webView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
view.rightAnchor.constraint(equalTo: webView.rightAnchor).isActive = true
view.bottomAnchor.constraint(equalTo: webView.bottomAnchor).isActive = true
self.webView = webView
checkNetworkSpeed(timeout: 5) { [weak self] error in
guard let self = self else { return }
if let error = error { print("!!!! ERROR: \(error)"); return }
self.webView.load(URLRequest(url: URL(string: "http://google.com")!))
}
}
enum CheckNetworkErrors: Error {
case noResponse
case wrongStatusCode(Int)
}
public func checkNetworkSpeed(timeout: TimeInterval = 30, completion: ((Error?) -> Void)?) {
DispatchQueue.global(qos: .default).async {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = timeout
configuration.timeoutIntervalForResource = timeout
let session = URLSession(configuration: configuration)
let url = URL(string: "https://apple.com")
session.dataTask(with: url!, completionHandler: { data, response, error in
var responseError: Error?
defer { DispatchQueue.main.async { completion?(error) } }
if let error = error { responseError = error; return }
guard let httpResponse = response as? HTTPURLResponse else {
responseError = CheckNetworkErrors.noResponse
return
}
guard (200...299).contains(httpResponse.statusCode) else {
responseError = CheckNetworkErrors.wrongStatusCode(httpResponse.statusCode)
return
}
}).resume()
}
}
}
Related
I am trying to connect VPN using OpenVPNAdapter but the PacketTunnelProvider isn't called from the controller. What am i missing here?
Controller.swift
import NetworkExtension
var providerManager: NETunnelProviderManager!
var provider = PacketTunnelProvider()
override func viewDidLoad() {
super.viewDidLoad()
self.loadProviderManager {
self.configureVPN(response: self.arrResponse[0], serverAddress: self.arrResponse[0].iP, username: "vpn", password: "vpn")
}
}
func loadProviderManager(completion:#escaping () -> Void) {
NETunnelProviderManager.loadAllFromPreferences { (managers, error) in
if error == nil {
self.providerManager = managers?.first ?? NETunnelProviderManager()
completion()
}
}
}
func configureVPN(response:Response,serverAddress: String, username: String, password: String) {
let data = Data(base64Encoded:response.openVPNConfigDataBase64, options: .ignoreUnknownCharacters)
print(data!)
let decodedString = String(data: data!, encoding: .utf8)!
print(decodedString)
self.providerManager?.loadFromPreferences { error in
if error == nil {
let tunnelProtocol = NETunnelProviderProtocol()
tunnelProtocol.username = username
tunnelProtocol.serverAddress = serverAddress
tunnelProtocol.providerBundleIdentifier = "***.*****.********.***********.********"
tunnelProtocol.providerConfiguration = ["ovpn": data!, "username": username, "password": password]
tunnelProtocol.disconnectOnSleep = false
self.providerManager.protocolConfiguration = tunnelProtocol
self.providerManager.localizedDescription = "SMVPN"
self.providerManager.isEnabled = true
self.providerManager.saveToPreferences(completionHandler: { (error) in
if error == nil {
self.providerManager.loadFromPreferences(completionHandler: { (error) in
if error == nil {
self.provider.startTunnel(options: nil) { error in //this called here not in network extension
if error != nil {
print(error!)
}else {
}
}
}else {
print(error!.localizedDescription)
}
})
}
})
}
}
}
Project Entitlement
PacketTunnelProvider.swift
import NetworkExtension
import OpenVPNAdapter
class PacketTunnelProvider: NEPacketTunnelProvider {
var startHandler: ((Error?) -> Void)?
var stopHandler: (() -> Void)?
var vpnReachability = OpenVPNReachability()
var configuration: OpenVPNConfiguration!
var properties: OpenVPNConfigurationEvaluation!
var UDPSession: NWUDPSession!
var TCPConnection: NWTCPConnection!
lazy var vpnAdapter: OpenVPNAdapter = {
let adapter = OpenVPNAdapter()
adapter.delegate = self
return adapter
}()
override func startTunnel(options: [String : NSObject]?, completionHandler: #escaping (Error?) -> Void) {
// Add code here to start the process of connecting the tunnel.
guard
let protocolConfiguration = protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration
else {
fatalError()
}
guard let ovpnFileContent: Data = providerConfiguration["ovpn"] as? Data else { return }
let configuration = OpenVPNConfiguration()
configuration.fileContent = ovpnFileContent
do {
properties = try vpnAdapter.apply(configuration: configuration)
} catch {
completionHandler(error)
return
}
configuration.tunPersist = true
if !properties.autologin {
if let username: String = providerConfiguration["username"] as? String, let password: String = providerConfiguration["password"] as? String {
let credentials = OpenVPNCredentials()
credentials.username = username
credentials.password = password
do {
try vpnAdapter.provide(credentials: credentials)
} catch {
completionHandler(error)
return
}
}
}
vpnReachability.startTracking { [weak self] status in
guard status != .notReachable else { return }
self?.vpnAdapter.reconnect(afterTimeInterval: 5)
}
startHandler = completionHandler
vpnAdapter.connect(using: self)
}
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: #escaping () -> Void) {
// Add code here to start the process of stopping the tunnel.
stopHandler = completionHandler
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
vpnAdapter.disconnect()
completionHandler()
}
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
// Add code here to handle the message.
if let handler = completionHandler {
handler(messageData)
}
}
override func sleep(completionHandler: #escaping () -> Void) {
// Add code here to get ready to sleep.
completionHandler()
}
override func wake() {
// Add code here to wake up.
}
}
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?, completionHandler: #escaping (Error?) -> Void) {
setTunnelNetworkSettings(networkSettings) { (error) in
completionHandler(error == nil ? self.packetFlow as? Error : nil)
}
}
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?, completionHandler: #escaping (OpenVPNAdapterPacketFlow?) -> Void) {
setTunnelNetworkSettings(networkSettings) { (error) in
completionHandler(error == nil ? self.packetFlow : nil)
}
}
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleEvent event: OpenVPNAdapterEvent, message: String?) {
switch event {
case .connected:
if reasserting {
reasserting = false
}
guard let startHandler = startHandler else { return }
startHandler(nil)
self.startHandler = nil
case .disconnected:
guard let stopHandler = stopHandler else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
stopHandler()
self.stopHandler = nil
case .reconnecting:
reasserting = true
default:
break
}
}
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool, fatal == true else {
return
}
NSLog("Error: \(error.localizedDescription)")
NSLog("Connection Info: \(vpnAdapter.connectionInformation.debugDescription)")
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
if let startHandler = startHandler {
startHandler(error)
self.startHandler = nil
} else {
cancelTunnelWithError(error)
}
}
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
NSLog("Log: \(logMessage)")
}
}
extension PacketTunnelProvider: OpenVPNAdapterPacketFlow {
func readPackets(completionHandler: #escaping ([Data], [NSNumber]) -> Void) {
packetFlow.readPackets(completionHandler: completionHandler)
}
func writePackets(_ packets: [Data], withProtocols protocols: [NSNumber]) -> Bool {
return packetFlow.writePackets(packets, withProtocols: protocols)
}
}
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
Extension Entitlement
self.provider.startTunnel(options: nil) { error in // This is called here, not in network extension
This method is called from controller but didn't get called in network extension.
I posted all my code, so if I missed something, then please let me know. Any help is appreciated.
I found this question but I haven't figured it out yet.
PacketTunnelProvider network extension not called Swift 3
You haven't mentioned anything about how you are testing network extension. You should attach a Network extension process before running your project for debugging. Then only network extension methods will trigger for debug.
Finally i found my mistake it's i tried to called network extension but it will never called. so i called NETunnelProviderManager directly and it will work.
self.providerManager.loadFromPreferences(completionHandler: { (error) in
if error == nil {
do {
try self.providerManager.connection.startVPNTunnel()
} catch let error {
print(error.localizedDescription)
}
}else {
print(error!.localizedDescription)
}
})
I am working on a personal project where I will be connecting the user to a VPN. I have followed two blog posts on doing this which are https://medium.com/better-programming/how-to-build-an-openvpn-client-on-ios-c8f927c11e80 & https://kean.blog/post/vpn-configuration-manager. I was able to configure the VPN settings but still, it does not connect. Here are the images for my project Signing & Capabilities one for the App and the other is for the network extension.
my ViewController looks like this. There is a folder in the user app document directory that is named user which gets downloaded from the internet and these are the files that are in the folder. I am not sure if I am supposed to use it other than the user.ovpn file
import UIKit
import Zip
import Alamofire
import NetworkExtension
class ViewController: UIViewController {
var selectedServer: Server?
var providerManager: NETunnelProviderManager!
override func viewDidLoad() {
super.viewDidLoad()
self.loadProviderManager {
self.configureVPN(serverAddress: "openvpn://\(self.selectedServer!.server.address):\(self.selectedServer!.server.port)", username: "\(self.username)", password: "\(self.password)")
}
}
func loadProviderManager(completion:#escaping () -> Void) {
NETunnelProviderManager.loadAllFromPreferences { (managers, error) in
if error == nil {
self.providerManager = managers?.first ?? NETunnelProviderManager()
completion()
}
}
}
func configureVPN(serverAddress: String, username: String, password: String) {
guard let configData = self.readFile(path: "user/user.ovpn") else { return }
self.providerManager?.loadFromPreferences { error in
if error == nil {
let tunnelProtocol = NETunnelProviderProtocol()
tunnelProtocol.username = username
tunnelProtocol.serverAddress = serverAddress
tunnelProtocol.providerBundleIdentifier = "com.example.Networking.tunnel"
tunnelProtocol.providerConfiguration = ["ovpn": configData, "username": username, "password": password]
tunnelProtocol.disconnectOnSleep = false
self.providerManager.protocolConfiguration = tunnelProtocol
self.providerManager.localizedDescription = "OpenVPN"
self.providerManager.isEnabled = true
self.providerManager.saveToPreferences(completionHandler: { (error) in
if error == nil {
self.providerManager.loadFromPreferences(completionHandler: { (error) in
do {
try self.providerManager.connection.startVPNTunnel()
} catch let error {
print(error.localizedDescription)
}
})
}
})
}
}
}
func readFile(path: String) -> Data? {
let fileManager = FileManager.default
do {
let documentDirectory = try fileManager.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: false)
let fileURL = documentDirectory.appendingPathComponent(path)
return try Data(contentsOf: fileURL, options: .uncached)
}
catch let error {
print(error.localizedDescription)
}
return nil
}
}
And my PacketTunnelProvider looks like this
import NetworkExtension
import OpenVPNAdapter
class PacketTunnelProvider: NEPacketTunnelProvider {
var startHandler: ((Error?) -> Void)?
var stopHandler: (() -> Void)?
var vpnReachability = OpenVPNReachability()
var configuration: OpenVPNConfiguration!
var properties: OpenVPNProperties!
var UDPSession: NWUDPSession!
var TCPConnection: NWTCPConnection!
lazy var vpnAdapter: OpenVPNAdapter = {
let adapter = OpenVPNAdapter()
adapter.delegate = self
return adapter
}()
override func startTunnel(options: [String : NSObject]?, completionHandler: #escaping (Error?) -> Void) {
guard
let protocolConfiguration = protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration
else {
fatalError()
}
guard let ovpnFileContent: Data = providerConfiguration["ovpn"] as? Data else { return }
let configuration = OpenVPNConfiguration()
configuration.fileContent = ovpnFileContent
do {
properties = try vpnAdapter.apply(configuration: configuration)
} catch {
completionHandler(error)
return
}
configuration.tunPersist = true
if !properties.autologin {
if let username: String = providerConfiguration["username"] as? String, let password: String = providerConfiguration["password"] as? String {
let credentials = OpenVPNCredentials()
credentials.username = username
credentials.password = password
do {
try vpnAdapter.provide(credentials: credentials)
} catch {
completionHandler(error)
return
}
}
}
vpnReachability.startTracking { [weak self] status in
guard status != .notReachable else { return }
self?.vpnAdapter.reconnect(afterTimeInterval: 5)
}
startHandler = completionHandler
vpnAdapter.connect()
}
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: #escaping () -> Void) {
stopHandler = completionHandler
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
vpnAdapter.disconnect()
}
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
if let handler = completionHandler {
handler(messageData)
}
}
override func sleep(completionHandler: #escaping () -> Void) {
completionHandler()
}
override func wake() {
}
}
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?, completionHandler: #escaping (OpenVPNAdapterPacketFlow?) -> Void) {
setTunnelNetworkSettings(networkSettings) { (error) in
completionHandler(error == nil ? self.packetFlow : nil)
}
}
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleEvent event: OpenVPNAdapterEvent, message: String?) {
switch event {
case .connected:
if reasserting {
reasserting = false
}
guard let startHandler = startHandler else { return }
startHandler(nil)
self.startHandler = nil
case .disconnected:
guard let stopHandler = stopHandler else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
stopHandler()
self.stopHandler = nil
case .reconnecting:
reasserting = true
default:
break
}
}
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool, fatal == true else {
return
}
NSLog("Error: \(error.localizedDescription)")
NSLog("Connection Info: \(vpnAdapter.connectionInformation.debugDescription)")
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
if let startHandler = startHandler {
startHandler(error)
self.startHandler = nil
} else {
cancelTunnelWithError(error)
}
}
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleLogMessage logMessage: String) {
NSLog("Log: \(logMessage)")
}
}
extension PacketTunnelProvider: OpenVPNAdapterPacketFlow {
func readPackets(completionHandler: #escaping ([Data], [NSNumber]) -> Void) {
packetFlow.readPackets(completionHandler: completionHandler)
}
func writePackets(_ packets: [Data], withProtocols protocols: [NSNumber]) -> Bool {
return packetFlow.writePackets(packets, withProtocols: protocols)
}
}
extension NEPacketTunnelFlow: OpenVPNAdapterPacketFlow {}
I have a WKWebView that is used to present the login screen of my OAuth Identity provider.
import UIKit
import WebKit
protocol OAuth2WKWebViewDelegate: class {
func didReceiveAuthorizationCode(_ code: String) -> Void
func didRevokeSession() -> Void
}
class OAuth2WKWebViewController: UIViewController {
let targetUrl: URLComponents
let webView = WKWebView()
weak var delegate: OAuth2WKWebViewDelegate?
init(targetUrl: URLComponents) {
self.targetUrl = targetUrl
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
loadUrl()
}
}
extension OAuth2WKWebViewController: WKNavigationDelegate {
func loadUrl() {
guard let url = targetUrl.url else { return }
view = webView
webView.load(URLRequest(url: url))
webView.allowsBackForwardNavigationGestures = true
webView.navigationDelegate = self
}
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void) {
if let url = navigationAction.request.url {
if url.scheme == "appdev", url.absoluteString.range(of: "code") != nil {
let urlParts = url.absoluteString.components(separatedBy: "?")
let code = urlParts[1].components(separatedBy: "code=")[1]
delegate?.didReceiveAuthorizationCode(code)
}
if url.absoluteString == "appdev://oauth-callback-after-sign-out" {
delegate?.didRevokeSession()
}
}
decisionHandler(.allow)
}
}
I also have an IdentityService I use to present this view and respond to it's success / error.
protocol IdentityServiceProtocol {
var hasValidToken: Bool { get }
func initAuthCodeFlow() -> Void
func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) -> Void
func storeOAuthTokens(accessToken: String, refreshToken: String, completion: #escaping () -> Void) -> Void
func renderAuthView() -> Void
}
class IdentityService: IdentityServiceProtocol {
fileprivate var apiClient: APIClient
fileprivate var keyChainService: KeyChainService
init(apiClient: APIClient = APIClient(), keyChainService: KeyChainService = KeyChainService()) {
self.apiClient = apiClient
self.keyChainService = keyChainService
}
var hasValidToken: Bool {
return keyChainService.fetchSingleObject(withKey: "AccessToken") != nil
}
func initAuthCodeFlow() -> Void {
let queryItems = ["response_type": "code", "client_id": clientId, "redirect_uri": redirectUri, "state": state, "scope": scope]
renderOAuthWebView(forService: .auth(company: "benefex"), queryitems: queryItems)
}
func initRevokeSession() -> Void {
guard let refreshToken = keyChainService.fetchSingleObject(withKey: "RefreshToken") else { return }
let queryItems = ["refresh_token": refreshToken]
renderOAuthWebView(forService: .revokeSession(company: "benefex"), queryitems: queryItems)
}
func renderOAuthWebView(forService service: IdentityEndpoint, queryitems: [String: String]) -> Void {
guard let targetUrl = constructURLComponents(endPoint: service, queryItems: queryitems) else { return }
let webView = OAuth2WKWebViewController(targetUrl: targetUrl)
webView.delegate = self
UIApplication.shared.windows.first?.rootViewController = webView
}
func storeOAuthTokens(accessToken: String, refreshToken: String, completion: #escaping ()-> Void) -> Void {
let success = keyChainService.storeManyObjects(["AccessToken": accessToken, "RefreshToken": refreshToken])
guard success == true else { return }
completion()
}
func renderAuthView() -> Void {
UIApplication.shared.windows.first?.rootViewController = UINavigationController.init(rootViewController: AuthenticatedViewController())
}
}
extension IdentityService: OAuth2WKWebViewDelegate {
func didReceiveAuthorizationCode(_ code: String) {
apiClient.call(endpoint: IdentityEndpoint.accessToken(company: "benefex", code: code)) { [weak self] (response: OAuthTokenResponse) in
switch response {
case .success(let payload):
guard let accessToken = payload.accessToken, let refreshToken = payload.refreshToken else { return }
self?.storeOAuthTokens(accessToken: accessToken, refreshToken: refreshToken) { self?.renderAuthView() }
case .error:
// login failed for some reason
print("could not complete request for access token")
}
}
}
func didRevokeSession() {
print("This was called")
}
}
extension IdentityService {
fileprivate var state: String {
return generateState(withLength: 20)
}
fileprivate func constructURLComponents(endPoint: IdentityEndpoint, queryItems: [String: String]) -> URLComponents? {
var url = URLComponents(url: endPoint.baseUrl, resolvingAgainstBaseURL: false)
url?.path = endPoint.path
url?.queryItems = queryItems.map { URLQueryItem(name: $0.key, value: $0.value) }
return url
}
fileprivate func generateState(withLength len: Int) -> String {
let letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
let length = UInt32(letters.count)
var randomString = ""
for _ in 0..<len {
let rand = arc4random_uniform(length)
let idx = letters.index(letters.startIndex, offsetBy: Int(rand))
let letter = letters[idx]
randomString += String(letter)
}
return randomString
}
}
extension IdentityService {
var clientId: String {
let envVar = ProcessInfo.processInfo.environment
guard let value = envVar["APP_CLIENT_ID"] else { fatalError("Missing APP_CLIENT_ID enviroment variable") }
return value
}
var redirectUri: String {
let envVar = ProcessInfo.processInfo.environment
guard let value = envVar["APP_REDIRECT_URI"] else { fatalError("Missing APP_REDIRECT_URI enviroment variable") }
return value
}
var scope: String {
let envVar = ProcessInfo.processInfo.environment
guard let value = envVar["APP_SCOPES"] else { fatalError("Missing APP_SCOPES enviroment variable") }
return value
}
}
The delegate?.didReceiveAuthorizationCode(code) is working.
However when delegate?.didRevokeSession() is called from the WebView, the identity service does not respond.
I added some console logs and can see my IdentityService is being de - init when I invoke the logout method.
I believe this is causing it to do nothing when the delegate method fires.
How can I ensure the delegate method is called still?
This came up when I was searching for answers to my issue - if you are using the 10.2 or 10.2.1 compiler - an issue was occurring for us when compiling in Release (instead of Debug) - where the delegate functions are not being called
The fix for us was to include #objc before all delegate function calls, IE
#objc func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: #escaping (WKNavigationActionPolicy) -> Void)
If you are in the same scenario - this should help
I have created an overlay to run while I run an async data grab to the server so that users won't continue pressing buttons in the UI until the data grab is done. I have put the function into a global singleton class and I call it while passing in a bool to say whether or not I want to show or hide. I can get it to show but I cannot get it to hide. Here is the code:
class DataModel {
static let sharedInstance = DataModel()
func accessNetworkData(vc: UIViewController, params: [String:Any], wsURLPath: String, completion: #escaping (_ response: AnyObject) -> ()) {
DataModel.sharedInstance.toggleModalProgess(show: true)
// SHOW THE MODAL HERE ONCE THE DATA IS REQUESTED.
let url = URL(string: wsURLPath)!
let session = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "POST"
do { request.httpBody = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted) } catch let error { print(error.localizedDescription) }
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
DataModel.sharedInstance.toggleModalProgess(show: false)
// NOW SINCE THE NETWORK ACTIVITY IS DONE, HIDE THE UIALERTCONTROLLER
guard error == nil else {
print("WEB SERVICE ERROR <----------------------------<<<<<< " + wsURLPath)
print(error!)
let resp: [String: String] = [ "conn": "failed" ]
DispatchQueue.main.async { completion(resp as NSDictionary) }
return
}
guard let data = data else {
print("WEB SERVICE ERROR <----------------------------<<<<<< " + wsURLPath)
return
}
do {
if let parsedJSON = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
print("WEB SERVICE SUCCESS <----------------------------<<<<<< "+wsURLPath+" "+String(describing:params))
if let parsedResponseDict = parsedJSON["d"] as? NSDictionary {
DispatchQueue.main.async {
completion(parsedResponseDict)
}
}else if let parsedResponseArr = parsedJSON["d"] as? NSArray {
DispatchQueue.main.async {
completion(parsedResponseArr)
}
}else {
print("INVALID KEY <----------------------------<<<<<< " + wsURLPath)
DispatchQueue.main.async {
completion(parsedJSON as AnyObject)
}
}
}
} catch let error {
print("Error with JSON Serialization")
print(error.localizedDescription)
}
})
task.resume()
}
HERE IS WHERE I SET UP THE UIALERTCONTROLLER
let modalAlert = UIAlertController(title: "Please Wait...", message: "Loading Data...", preferredStyle: UIAlertControllerStyle.alert)
let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
func toggleModalProgess(show: Bool) -> Void {
print("toggleModalProgess: show = " + String(describing: show))
if (show) {
print("let's turn it on")
loadingIndicator.hidesWhenStopped = true
loadingIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
loadingIndicator.startAnimating()
modalAlert.view.addSubview(loadingIndicator)
modalAlert.show()
}else {
print("let's turn it off")
modalAlert.hide()
}
}
private init() { }
}
NOW THE EXTENSION WHERE THE MAGIC HAPPENS
public extension UIAlertController {
func show() {
let win = UIWindow(frame: UIScreen.main.bounds)
let vc = UIViewController()
vc.view.backgroundColor = .clear
win.rootViewController = vc
win.windowLevel = UIWindowLevelAlert + 1
win.makeKeyAndVisible()
vc.present(self, animated: true, completion: nil)
}
func hide() {
// HERE IS WHERE I NEED TO HIDE IT BUT I AM HAVING ISSUES
}
}
In order to dismiss the UIAlertController (which is a subclass of UIViewController), it should be sufficient to call the dismiss method:
func hide() {
dismiss(animated: true, completion: nil)
}
It works fine in my sample project.
You should do...
self.presentingViewController?.dismiss(animated: true, completion: nil)
Hope it helps
In my current project i have CAS auth. I made it in modal uiwebview. My func look like:
func models(callback: ([ModelType])->()) {
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
MLMPProvider.request(MLMP.Models, completion: {
(data, status, response, error) -> () in
func checkResponse(response:NSURLResponse){
if toString(response.URL!).rangeOfString("login") != nil{
NSNotificationCenter.defaultCenter().postNotificationName("LOGIN", object: response.URL!)
}
}
checkResponse(response!)
var result: [ModelType] = []
if let data = data, let models = JSON(data: data, options: NSJSONReadingOptions.allZeros, error: nil).array {
for model in models {
if let modelId = model["uuid"].string {
result += [.Custom(modelId)]
}
}
}
callback(result)
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
})
}
My question is: What is the best way to recall models with callback after auth? Maybe i can do some magic with selectors?
I solve my problem
i add vars to my network layer
var closure:Any!
var methodName:String!
and add notification callback when i get token from server
#objc private func authTokenGetted(notification: NSNotification){
recallMethodWith(methodName, andcallback: closure)
}
func recallMethodWith(name:String, andcallback: Any){
switch name{
case MethodsName.models:
models(andcallback as! ([ModelType])->())
case MethodsName.model:
println("modelcall")
default:
println(name)
}
}
init(){
NSNotificationCenter.defaultCenter().addObserver(self, selector: "authTokenGetted:", name: "AuthDone", object: nil)
}
and my models func looks like:
func checkResponse(response:NSURLResponse) -> Bool{
if toString(response.URL!).rangeOfString("login") != nil{
NSNotificationCenter.defaultCenter().postNotificationName("LOGIN", object: response.URL!)
return false
}
return true
}
func models(callback: ([ModelType])->()) {
UIApplication.sharedApplication().networkActivityIndicatorVisible = true
self.methodName = __FUNCTION__
self.closure = callback
MLMPProvider.request(MLMP.Models, completion: {
(data, status, response, error) -> () in
if self.checkResponse(response!){
var result: [ModelType] = []
if let data = data, let models = JSON(data: data, options: NSJSONReadingOptions.allZeros, error: nil).array {
for model in models {
if let modelId = model["uuid"].string {
result += [.Custom(modelId)]
}
}
}
callback(result)
UIApplication.sharedApplication().networkActivityIndicatorVisible = false
}
})
}