AuthWebViewController.swift 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. // Copyright 2023 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #if os(iOS)
  15. import Foundation
  16. import UIKit
  17. import WebKit
  18. /** @protocol AuthWebViewControllerDelegate
  19. @brief Defines a delegate for AuthWebViewController
  20. */
  21. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  22. protocol AuthWebViewControllerDelegate: AnyObject {
  23. /** @fn webViewControllerDidCancel:
  24. @brief Notifies the delegate that the web view controller is being cancelled by the user.
  25. @param webViewController The web view controller in question.
  26. */
  27. func webViewControllerDidCancel(_ controller: AuthWebViewController)
  28. /** @fn webViewController:canHandleURL:
  29. @brief Determines if a URL should be handled by the delegate.
  30. @param URL The URL to handle.
  31. @return Whether the URL could be handled or not.
  32. */
  33. func webViewController(_ controller: AuthWebViewController, canHandle url: URL) -> Bool
  34. /** @fn webViewController:didFailWithError:
  35. @brief Notifies the delegate that the web view controller failed to load a page.
  36. @param webViewController The web view controller in question.
  37. @param error The error that has occurred.
  38. */
  39. func webViewController(_ controller: AuthWebViewController, didFailWithError error: Error)
  40. /** @fn presentURL:UIDelegate:callbackMatcher:completion:
  41. @brief Presents an URL to interact with user.
  42. @param url The URL to present.
  43. @param uiDelegate The UI delegate to present view controller.
  44. @param completion A block to be called either synchronously if the presentation fails to start,
  45. or asynchronously in future on an unspecified thread once the presentation finishes.
  46. */
  47. func present(_ url: URL,
  48. uiDelegate: AuthUIDelegate?,
  49. callbackMatcher: @escaping (URL?) -> Bool,
  50. completion: @escaping (URL?, Error?) -> Void)
  51. }
  52. @available(iOS 13, tvOS 13, macOS 10.15, macCatalyst 13, watchOS 7, *)
  53. class AuthWebViewController: UIViewController,
  54. WKNavigationDelegate {
  55. // MARK: - Properties
  56. private var url: URL
  57. weak var delegate: AuthWebViewControllerDelegate?
  58. private weak var webView: AuthWebView?
  59. // MARK: - Initialization
  60. init(url: URL, delegate: AuthWebViewControllerDelegate) {
  61. self.url = url
  62. self.delegate = delegate
  63. super.init(nibName: nil, bundle: nil)
  64. }
  65. @available(*, unavailable)
  66. required init?(coder aDecoder: NSCoder) {
  67. fatalError("init(coder:) has not been implemented")
  68. }
  69. // MARK: - View Lifecycle
  70. override func loadView() {
  71. let webView = AuthWebView(frame: UIScreen.main.bounds)
  72. webView.webView.navigationDelegate = self
  73. view = webView
  74. self.webView = webView
  75. navigationItem.leftBarButtonItem = UIBarButtonItem(
  76. barButtonSystemItem: .cancel,
  77. target: self,
  78. action: #selector(cancel)
  79. )
  80. }
  81. override func viewDidAppear(_ animated: Bool) {
  82. super.viewDidAppear(animated)
  83. webView?.webView.load(URLRequest(url: url))
  84. }
  85. // MARK: - Actions
  86. @objc private func cancel() {
  87. delegate?.webViewControllerDidCancel(self)
  88. }
  89. // MARK: - WKNavigationDelegate
  90. func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction,
  91. decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
  92. let canHandleURL = delegate?.webViewController(
  93. self,
  94. canHandle: navigationAction.request.url ?? url
  95. ) ?? false
  96. if canHandleURL {
  97. decisionHandler(.allow)
  98. } else {
  99. decisionHandler(.cancel)
  100. }
  101. }
  102. func webView(_ webView: WKWebView,
  103. didStartProvisionalNavigation navigation: WKNavigation!) {
  104. self.webView?.spinner.isHidden = false
  105. self.webView?.spinner.startAnimating()
  106. }
  107. func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
  108. self.webView?.spinner.isHidden = true
  109. self.webView?.spinner.stopAnimating()
  110. }
  111. func webView(_ webView: WKWebView, didFail navigation: WKNavigation!,
  112. withError error: Error) {
  113. if (error as NSError).domain == NSURLErrorDomain,
  114. (error as NSError).code == NSURLErrorCancelled {
  115. // It's okay for the page to be redirected before it is completely loaded. See b/32028062 .
  116. return
  117. }
  118. // Forward notification to our delegate.
  119. self.webView(webView, didFinish: navigation)
  120. delegate?.webViewController(self, didFailWithError: error)
  121. }
  122. }
  123. #endif