SwiftUI Integration with ArkoseChallengeView (Deprecated)
Deprecated in v3.0.0
ArkoseChallengeView(iOS) andChallengeView(macOS/visionOS) are deprecated as of Apple SDK v3.0.0. They remain fully supported and available for use, so you can migrate at your own pace - but new SwiftUI integrations should useArkoseViewinstead. See the Mobile SDK for Apple page for theArkoseViewintegration, and the migration guide to move an existing screen toArkoseView.
This page documents SwiftUI integration using the deprecated ArkoseChallengeView component. For SDK installation and initialization, see the main page - those steps are the same for both components.
Integrating ArkoseChallengeView
ArkoseChallengeView is a SwiftUI View component of the SDK to integrate into the contents of the application View. Integrate it into your custom view, control its visibility with the isPresented binding, and receive events by implementing the ArkoseChallengeDelegate protocol on the SwiftUI View and passing the instance as the delegate parameter.
ArkoseChallengeView( isPresented: $isPresented,
delegate: self,
config: ArkoseConfig.Builder(withAPIKey: <YOUR_PUBLIC_KEY>)
.with(language: "en") //optional
.with(clientAPIRetryCount: 0) //optional
.with(styleTheme: "") //optional
.build())To simplify the implementation, ArkoseChallengeDelegate has a default implementation, so implement only the necessary methods for your desired functionality. The most commonly implemented protocol methods are onCompleted, onError, and onFailed. See the ArkoseChallengeDelegate reference for the full list of callbacks.
See the Appendix: SwiftUI Content View below for a complete example.
Preloading Challenges to onReady with On-Demand Presentation
Overview:
The inlineRunOnTrigger configuration enables preloading of enforcement challenge to onReady state, giving your application full control over when a session token is generated or the challenge is presented to the user. This greatly improves user-perceived latency.
With
ArkoseView(v3.0.0 and above) andwithPrewarm: true, preload-then-run is the default behaviour - noinlineRunOnTriggerconfiguration is needed. See Integrate usingArkoseView.
Availability:
Available from iOS SDK v2.20.0 (only in SwiftUI iOS Based SDK only).
Configuration:
- Set via .with(inlineRunOnTrigger: true) (default: false)
- Use with isPresented binding to preload the challenge
- Resume the challenge flow by setting runEnforcement binding
Note: Do NOT enable withActivity=true when using this feature, as it will cause the loader to display indefinitely
Implementation Example
struct PreloadedChallengeView: View {
@State private var isPresented = false
@State private var runEnforcement = false
var body: some View {
VStack {
Button("Run Enforcement") {
runEnforcement = true
}
ArkoseChallengeView(isPresented: $isPresented,
runEnforcement: $runEnforcement,
delegate: self,
withActivity: false,
config: ArkoseConfig
.Builder(withAPIKey: YOUR_API_KEY)
.with(inlineRunOnTrigger: true)
.build())
}.onAppear{
isPresented = true
}
}
}Programmatic Dismissal of Enforcement Challenge
Programmatic dismissal allows you to programmatically close an active Enforcement Challenge without user interaction.
This is useful in scenarios such as:
- Handling app lifecycle events (e.g., app going to background)
- Implementing timeout mechanisms
- Responding to external events that require immediate challenge dismissal
Availability
This API is available from API version 2.20.0 and above.
Configuration
Programmatic dismissal is handled through the isPresented binding that controls the ArkoseChallengeView. Simply set isPresented to false to trigger automatic dismissal of the challenge view. After dismissal completes, the SDK notifies the application via the onForceDismissCompleted() callback on the delegate.
Handling App Lifecycle Events
struct LoginView: View, ArkoseChallengeDelegate {
@State private var isPresented = false
var body: some View {
ZStack {
VStack {
Button("Login") {
isPresented = true
}
.padding()
}
ArkoseChallengeView(
isPresented: $isPresented,
delegate: self,
.. // other parameters
)
}
.onAppear {
// Set up background state observer
NotificationCenter.default.addObserver(
forName: UIApplication.willResignActiveNotification,
object: nil,
queue: .main
) { _ in
// Dismiss the Enforcement Challenge by setting isPresented to false
isPresented = false
}
}
}
func onForceDismissCompleted() {
print("Challenge dismissed due to app lifecycle event")
}
// ... other implementation ...
}Best Practices
Always implement onForceDismissCompleted()
Even if you don't need to perform specific actions, implementing this callback helps you track dismissal state and handle edge cases.
Wait for dismissal completion before presenting new screens
Do not present new screens or trigger navigation while programmatic dismissal is in progress. Instead, wait for the onForceDismissCompleted() callback to be triggered before proceeding with navigation or presenting new screens. See the Appendix: SwiftUI Content View below for a complete example.
API Reference - ArkoseChallengeView
public init(isPresented: Binding<Bool>,
delegate: ArkoseChallengeDelegate,
cancelActionConfig: ArkoseActionConfig? = nil,
resetActionConfig: ArkoseActionConfig? = nil,
withActivity: Bool? = nil,
withActivityBackgroundAlpha: CGFloat = 0.2,
config: ArkoseConfig? = nil)- isPresented: A
Boolvalue that controls the visibility of the Enforcement Challenge View. - delegate: An instance of
ArkoseChallengeDelegateto receive event notifications. - cancelButtonTitle: A localized
Stringfor the title of the Cancel button, with a default value ofnil. If set tonil, the Cancel button will not be displayed in the view. Deprecated since Arkose iOS SDK v2.18.0. - cancelActionConfig: A structure containing localized
Stringfor the title of the Cancel button and localizedStringfor the accessibilityHint of the Cancel Button. If set tonil, the Cancel button will not be displayed in the view. Refer to theArkoseActionConfigsection for implementation details. - resetButtonTitle: A localized String for the title of the Reset button, with a default value of
nil. If set tonil, the Reset button will not be displayed in the view. Deprecated since Arkose iOS SDK v2.18.0. - resetActionConfig: A structure containing localized
Stringfor the title of the Reset button and localizedStringfor the accessibilityHint of the Reset Button. If set tonil, the Reset button will not be displayed in the view. Refer to theArkoseActionConfigsection for implementation details. - withActivity: A
Boolthat controls the enablement of the loading spinner animation, with a default value oftrue. If set tonil, the loading spinner animation will be displayed in the view. - withActivityBackgroundAlpha: A
CGFloatvalue that sets the background alpha of the activity indicator or loader, ranging from0.0(fully transparent) to1.0(fully opaque), with a default value of0.2. - config: An
ArkoseConfigcreated using its builder, with a default value ofnil. If set tonil, the configuration is not updated with the latest configuration. - inlineRunOnTrigger: A boolean to allow preloading of enforcement challenge to
onReadystate, giving your application full control over when a session token is generated or the challenge is presented to the user. This greatly improves user-perceived latency. The default value isfalse. It is optional. Available from iOS SDK v2.20.0 (only in SwiftUI iOS Based SDK only)
Shared types
ArkoseConfig.Builder, ArkoseConfig, ArkoseActionConfig, ArkoseChallengeDelegate, and LogLevel are shared with the current API and documented in the Apple SDK Common API Reference.
Appendix: SwiftUI Content View
import SwiftUI
import ArkoseLabsKit
struct LoginView: View, ArkoseChallengeDelegate {
// MARK: - State Variables
@State private var isPresented = false
@State private var shouldNavigateToNext = false
@State private var isDismissingChallenge = false
@State private var username: String = ""
@State private var password: String = ""
// MARK: - ArkoseChallengeDelegate Methods
func onCompleted(response: [String: Any?]) {
print("onComplete received: \(response)")
isPresented = false
// Handle successful challenge completion
}
func onError(response: [String: Any?]) {
print("onError received: \(response)")
isPresented = false
// Handle error during challenge
}
func onFailed(response: [String: Any?]) {
print("onFailed received: \(response)")
let isRecoverable: Bool = (response["recoverable"] as? Bool) ?? false
if !isRecoverable {
// Safe to dismiss - non-recoverable failure
isPresented = false
}
// If recoverable, challenge will continue
}
func onForceDismissCompleted() {
isDismissingChallenge = false
// Now safe to trigger navigation
// The shouldNavigateToNext binding will trigger the sheet presentation
// after dismissal is complete
if shouldNavigateToNext {
// Navigation will be triggered by the sheet modifier
}
}
// MARK: - Body
var body: some View {
ZStack {
VStack {
Text("SwiftUI App")
.padding()
.font(.largeTitle)
.foregroundColor(Color.black)
TextField("Username", text: $username)
.font(.title3)
.disableAutocorrection(true)
.autocapitalization(.none)
.padding()
SecureField("Password", text: $password)
.font(.title3)
.disableAutocorrection(true)
.autocapitalization(.none)
.padding()
Button("Login") {
self.isPresented = true
}
.padding()
Button("Navigate") {
// ❌ Bad: Setting navigation state immediately
// isPresented = false
// shouldNavigateToNext = true // May cause conflicts
// ✅ Good: Wait for dismissal completion
if isDismissingChallenge {
// Already dismissing, wait for callback
return
}
isDismissingChallenge = true
shouldNavigateToNext = true
isPresented = false
// Navigation will be triggered in onForceDismissCompleted()
}
.padding()
}
ArkoseChallengeView(
isPresented: $isPresented,
delegate: self,
cancelActionConfig: ArkoseActionConfig(
title: "Cancel",
accessibilityHint: "Cancel the challenge"
),
resetActionConfig: ArkoseActionConfig(
title: "Reset",
accessibilityHint: "Reset the challenge"
),
config: ArkoseConfig.Builder(
withAPIKey: "<YOUR_PUBLIC_KEY>" // Replace <YOUR_PUBLIC_KEY> with the actual API key assigned to your account
)
.with(language: "fr")
.build()
)
}
.sheet(isPresented: $shouldNavigateToNext) {
NextView()
}
.onAppear {
// Set up background state observer
NotificationCenter.default.addObserver(
forName: UIApplication.willResignActiveNotification,
object: nil,
queue: .main
) { _ in
// Dismiss the Enforcement Challenge when app goes to background
if isPresented && !isDismissingChallenge {
isDismissingChallenge = true
isPresented = false
}
}
}
}
}
// MARK: - Preview
struct LoginView_Previews: PreviewProvider {
static var previews: some View {
LoginView()
}
}Updated about 6 hours ago