From 8998824362d70ab6c27d63afd9a4348e0f5d4bfc Mon Sep 17 00:00:00 2001 From: Alex Babrykovich Date: Sat, 24 Feb 2024 19:32:05 +0100 Subject: [PATCH] feat: HCaptcha.redrawView (#140) * feat: HCaptcha.redrawView * test: HCaptcha.redrawView * Apply suggestions from code review * chore: add comment to moveHCaptchaUp * Update Example/HCaptcha/ViewController.swift * Update README.md --------- Co-authored-by: e271828- --- Example/HCaptcha/ViewController.swift | 29 ++++++++++++++ .../HCaptcha_Tests/Core/HCaptcha__Tests.swift | 24 +++++++++++ HCaptcha/Classes/HCaptcha.swift | 10 +++++ README.md | 40 +++++++++++++++++++ 4 files changed, 103 insertions(+) diff --git a/Example/HCaptcha/ViewController.swift b/Example/HCaptcha/ViewController.swift index 399cc79..679bf01 100644 --- a/Example/HCaptcha/ViewController.swift +++ b/Example/HCaptcha/ViewController.swift @@ -10,6 +10,7 @@ import HCaptcha import RxCocoa import RxSwift import UIKit +import WebKit class ViewController: UIViewController { private struct Constants { @@ -21,6 +22,7 @@ class ViewController: UIViewController { private var disposeBag = DisposeBag() private var locale: Locale? + private var challengeShown: Bool = false /// Don't init SDK to avoid unnecessary API calls and simplify debugging if the application used as a host for tests private var unitTesting: Bool { @@ -90,6 +92,12 @@ class ViewController: UIViewController { print("onEvent error: \(String(describing: error))") } + if event == .open { + self?.challengeShown = true + /// redrawView call is not required, included for demo purposes + self?.hcaptcha.redrawView() + } + let alertController = UIAlertController(title: "On Event", message: event.rawValue, preferredStyle: .alert) @@ -131,6 +139,7 @@ class ViewController: UIViewController { self?.view.viewWithTag(Constants.webViewTag) } .subscribe(onNext: { subview in + self.challengeShown = false subview?.removeFromSuperview() }) .disposed(by: disposeBag) @@ -169,6 +178,10 @@ class ViewController: UIViewController { hcaptcha = try! HCaptcha(locale: locale) hcaptcha.configureWebView { [weak self] webview in + if self?.challengeShown == true { + self?.moveHCaptchaUp(webview) + return + } webview.frame = self?.view.bounds ?? CGRect.zero webview.tag = Constants.webViewTag @@ -187,4 +200,20 @@ class ViewController: UIViewController { self?.view.addSubview(label) } } + + /** + Move hCaptcha WebView frame to the top part of the screen + */ + private func moveHCaptchaUp(_ webview: WKWebView) { + let padding: CGFloat = 0 + let windowHeight = view.frame.size.height + let targetHeight = (2.0 / 3.0) * windowHeight + + webview.frame = CGRect( + x: padding, + y: padding, + width: view.frame.size.width - 2 * padding, + height: targetHeight - 2 * padding + ) + } } diff --git a/Example/HCaptcha_Tests/Core/HCaptcha__Tests.swift b/Example/HCaptcha_Tests/Core/HCaptcha__Tests.swift index e14a1ac..95bcea9 100644 --- a/Example/HCaptcha_Tests/Core/HCaptcha__Tests.swift +++ b/Example/HCaptcha_Tests/Core/HCaptcha__Tests.swift @@ -108,6 +108,30 @@ class HCaptcha__Tests: XCTestCase { } wait(for: [exp], timeout: 10) } + + func test__reconfigure() { + let exp = expectation(description: "configureWebView called twice") + var configureCounter = 0 + let hcaptcha = HCaptcha(manager: HCaptchaWebViewManager(messageBody: "{action: \"showHCaptcha\"}")) + hcaptcha.configureWebView { _ in + configureCounter += 1 + if configureCounter == 2 { + exp.fulfill() + } + } + hcaptcha.didFinishLoading { + let view = UIApplication.shared.windows.first?.rootViewController?.view + hcaptcha.onEvent { e, _ in + if e == .open { + hcaptcha.redrawView() + } + } + hcaptcha.validate(on: view!) { _ in + XCTFail("Should not be called") + } + } + wait(for: [exp], timeout: 10) + } } diff --git a/HCaptcha/Classes/HCaptcha.swift b/HCaptcha/Classes/HCaptcha.swift index 8d4b106..7921524 100644 --- a/HCaptcha/Classes/HCaptcha.swift +++ b/HCaptcha/Classes/HCaptcha.swift @@ -206,6 +206,16 @@ public class HCaptcha: NSObject { manager.onDidFinishLoading = closure } + /** + Request for a call to the `configureWebView` closure. + + This may be useful if you need to modify the layout of hCaptcha. + */ + @objc + public func redrawView() { + manager.configureWebView?(manager.webView) + } + // MARK: - Development #if DEBUG diff --git a/README.md b/README.md index cebec1e..3e2a42f 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ + [Using landscape instead of portrait orientation](#using-landscape-instead-of-portrait-orientation) + [SDK Events](#sdk-events) + [Disable new token fetch on expiry](#disable-new-token-fetch-on-expiry) + + [Change hCaptcha frame](#change-hcaptcha-frame) * [Known issues](#known-issues) * [License](#license) * [Troubleshooting](#troubleshooting) @@ -311,6 +312,45 @@ hcaptcha.validate(on: view, resetOnError: false) { result in } ``` +### Change hCaptcha frame + +If you are customizing display beyond the defaults and need to resize or change the hCaptcha layout, for example after a visual challenge appears, you can use the following approach to trigger a redraw of the view: + +``` swift +let hcaptcha = try? HCaptcha(...) +var visualChallengeShown = false +... +hcaptcha?.configureWebView { [weak self] webview in + webview.tag = "hCaptchaViewTag" + if visualChallengeShown { + let padding = 10 + webview.frame = CGRect( + x: padding, + y: padding, + width: view.frame.size.width - 2 * padding, + height: targetHeight - 2 * padding + ) + } else { + webview.frame = self?.view.bounds ?? CGRect.zero + } +} +... +hcaptcha.onEvent { (event, data) in + if event == .open { + visualChallengeShown = true + hcaptcha.redrawView*() + } else if event == .error { + let error = data as? HCaptchaError + print("onEvent error: \(String(describing: error))") + ... + } +} +... +hcaptcha.validate(on: view, resetOnError: false) { result in + visualChallengeShown = false +} +``` + ### SwiftUI Example `HCaptcha` was originally designed to be used with UIKit. But you can easily use it with `SwiftUI` as well. Check out the [SwiftUI Example](./Example/SwiftUI)