Integrating with SwiftUI
PingOne Advanced Identity Cloud PingAM iOS
If your application uses SwiftUI, you can seamlessly integrate the Journey module by using an ObservableObject to manage the UI state:
ViewModel
Using the Journey module with a SwiftUI ObservableObject
@MainActor
class JourneyViewModel: ObservableObject {
/// Published property that holds the current state node data.
@Published public var state: JourneyState = JourneyState()
/// Published property to track whether the view is currently loading.
@Published public var isLoading: Bool = false
var journey: Journey
/// Initializes the view model and starts the Journey orchestration process.
init() {
Task {
await startJourney()
}
}
public func initializeJourney() {
journey = Journey.createJourney { config in
let currentConfig = ConfigurationManager.shared.currentConfigurationViewModel
config.serverUrl = currentConfig?.serverUrl
config.realm = currentConfig?.realm ?? "root"
config.cookie = currentConfig?.cookieName ?? ""
config.module(PingJourney.OidcModule.config) { oidcValue in
oidcValue.clientId = currentConfig?.clientId ?? ""
oidcValue.scopes = Set<String>(currentConfig?.scopes ?? [])
oidcValue.redirectUri = currentConfig?.redirectUri ?? ""
oidcValue.discoveryEndpoint = currentConfig?.discoveryEndpoint ?? ""
}
}
}
/// Starts the Journey orchestration process.
/// - Sets the initial node and updates the `data` property with the starting node.
public func startJourney() async {
await MainActor.run {
isLoading = true
}
let next = await journey.start("Login") { options in
options.forceAuth = false
options.noSession = false
}
await MainActor.run {
self.state = JourneyState(node: next)
isLoading = false
}
}
/// Advances to the next node in the orchestration process.
/// - Parameter node: The current node to progress from.
public func next(node: Node) async {
await MainActor.run {
isLoading = true
}
if let current = node as? ContinueNode {
// Retrieves the next node in the flow.
let next = await current.next()
await MainActor.run {
self.state = JourneyState(node: next)
isLoading = false
}
}
}
public func refresh() {
state = JourneyState(node: state.node)
}
}
View (SwiftUI)
Using the Journey module with a SwiftUI view
import SwiftUI
struct JourneyView: View {
/// The view model that manages the Davinci flow logic.
@StateObject private var journeyViewModel = JourneyViewModel()
/// A binding to the navigation stack path.
@Binding var path: [String]
var body: some View {
ZStack {
ScrollView {
VStack {
Spacer()
// Handle different types of nodes in the Journey.
switch journeyViewModel.state.node {
case let continueNode as ContinueNode:
// Display the callback view for the next node.
CallbackView(journeyViewModel: journeyViewModel, node: continueNode)
case let errorNode as ErrorNode:
// Handle server-side errors (e.g., invalid credentials)
// Display error to the user
ErrorNodeView(node: errorNode)
if let nextNode = errorNode.continueNode {
CallbackView(journeyViewModel: journeyViewModel, node: nextNode)
}
case let failureNode as FailureNode:
ErrorView(message: failureNode.cause.localizedDescription)
case is SuccessNode:
// Authentication successful, retrieve the session
VStack{}.onAppear {
path.removeLast()
path.append("Token")
}
default:
EmptyView()
}
}
}
}
}
}