WKWebView委托不响应委托方法调用

时间:2019-01-06 18:49:06

标签: ios swift delegates

我有一个WKWebView,用于显示OAuth身份提供程序的登录屏幕。

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

我还有一个IdentityService,用于显示此视图并响应其成功/错误。

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

delegate?.didReceiveAuthorizationCode(code)正在工作。

但是,当从WebView调用delegate?.didRevokeSession()时,身份服务不会响应。

我添加了一些控制台日志,当我调用注销方法时可以看到我的IdentityService正在初始化。

我相信这会导致在委托方法触发时它什么也不做。

如何确保委托方法仍被调用?

1 个答案:

答案 0 :(得分:0)

当我在寻找问题的答案时就出现了-如果您使用的是10.2或10.2.1编译器-在Release(而不是Debug)中进行编译时对我们来说是一个问题-委托函数不存在被称为

我们的解决办法是在所有委托函数调用IE之前添加@objc

@objc func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void)

如果您处于相同的情况下-应该会有所帮助