@@ -18,168 +18,169 @@ import SwiftUI
1818
1919struct AuthenticateWithPKICertificateView : View {
2020 /// The model that backs this view.
21- @StateObject private var model = Model ( )
21+ @StateObject private var model = Model ( )
2222
23- var body : some View {
24- Form {
25- switch model. portalContent {
26- case . success( let success) :
27- ForEach ( success. results, id: \. id? . rawValue) { item in
28- Button ( item. title) {
29- model. selectedItem = . init( portalItem: item)
30- }
31- . buttonStyle ( . plain)
32- }
33- case . failure:
34- urlEntryView
35- ContentUnavailableView (
36- " Error " ,
37- systemImage: " exclamationmark.triangle " ,
38- description: Text ( " Error searching specified portal. " )
39- )
40- case nil :
41- if model. isConnecting {
42- ProgressView ( )
43- . frame ( maxWidth: . infinity)
44- } else {
45- urlEntryView
46- }
47- }
48- }
49- . onTeardown {
50- // Reset the challenge handlers and clear credentials
51- // when the view disappears so that user is prompted to enter
52- // credentials every time the sample is run, and to clean
53- // the environment for other samples.
54- await model. teardownAuthenticator ( )
55- }
56- . sheet ( item: $model. selectedItem) { selectedItem in
57- NavigationStack {
58- MapView ( map: Map ( item: selectedItem. portalItem) )
59- . navigationTitle ( selectedItem. portalItem. title)
60- . navigationBarTitleDisplayMode ( . inline)
61- . toolbar {
62- ToolbarItem ( placement: . topBarTrailing) {
63- Button ( " Done " ) {
64- model. selectedItem = nil
65- }
66- }
67- }
68- }
69- . interactiveDismissDisabled ( )
70- . highPriorityGesture ( DragGesture ( ) )
71- . pagePresentation ( )
72- }
73- . animation ( . default, value: model. isConnecting)
74- . authenticator ( model. authenticator)
75- }
76-
77- @ViewBuilder private var urlEntryView : some View {
78- Section {
79- HStack {
80- TextField ( " PKI Secured Portal URL " , text: $model. portalURLString)
81- . onSubmit { Task { await model. connectToPortal ( ) } }
82- . autocapitalization ( . none)
83- . keyboardType ( . URL)
84- Button ( " Connect " ) {
85- Task { await model. connectToPortal ( ) }
86- }
87- . disabled ( model. portalURL == nil )
88- }
89- }
90- }
91- }
92-
93- /// A value that represents an item selected by the user.
94- private struct SelectedItem : Identifiable {
95- /// The portal item that was selected.
96- let portalItem : PortalItem
97-
98- var id : ObjectIdentifier {
99- ObjectIdentifier ( portalItem)
100- }
101- }
102-
103- extension AuthenticateWithPKICertificateView {
104- @MainActor
105- class Model : ObservableObject {
106- /// The authenticator to handle authentication challenges.
107- let authenticator = Authenticator ( )
108-
109- /// The URL string entered by the user.
110- @Published var portalURLString = " "
111-
112- /// The fetched portal content.
113- @Published var portalContent : Result < PortalQueryResultSet < PortalItem > , Error > ?
114-
115- /// A Boolean value indicating if a portal connection is in progress.
116- @Published var isConnecting = false
117-
118- /// The selected item.
119- @Published fileprivate var selectedItem : SelectedItem ?
120-
121- /// The URL of the portal.
122- var portalURL : URL ? { URL ( string: portalURLString) }
123-
124- init ( ) {
125- setupAuthenticator ( )
126- }
127-
128- /// Connects to the portal and finds a batch of webmaps.
129- func connectToPortal( ) async {
130- precondition ( portalURL != nil )
131-
132- isConnecting = true
133- defer { isConnecting = false }
134-
135- do {
136- let portal = Portal ( url: portalURL!)
137- try await portal. load ( )
138- let results = try await portal. findItems ( queryParameters: . items( ofKinds: [ . webMap] ) )
139- portalContent = . success( results)
140- } catch {
141- portalContent = . failure( error)
142- }
143- }
144-
145- /// Sets up the authenticator to handle challenges.
146- private func setupAuthenticator( ) {
147- // Setting the challenge handlers here when the model is created so user is prompted to enter
148- // credentials every time trying the sample. In real world applications, set challenge
149- // handlers at the start of the application.
150-
151- // Sets authenticator as ArcGIS and Network challenge handlers to handle authentication
152- // challenges.
153- ArcGISEnvironment . authenticationManager. handleChallenges ( using: authenticator)
154-
155- // In your application you may want to uncomment this code to persist
156- // credentials in the keychain.
157- // setupPersistentCredentialStorage()
158- }
159-
160- /// Stops the authenticator from handling the challenges and clears credentials.
161- nonisolated func teardownAuthenticator( ) async {
162- // Resets challenge handlers.
163- ArcGISEnvironment . authenticationManager. handleChallenges ( using: nil )
23+ var body : some View {
24+ Form {
25+ switch model. portalContent {
26+ case . success( let success) :
27+ ForEach ( success. results, id: \. id? . rawValue) { item in
28+ Button ( item. title) {
29+ model. selectedItem = . init( portalItem: item)
30+ }
31+ . buttonStyle ( . plain)
32+ }
33+ case . failure:
34+ urlEntryView
35+ ContentUnavailableView (
36+ " Error " ,
37+ systemImage: " exclamationmark.triangle " ,
38+ description: Text ( " Error searching specified portal. " )
39+ )
40+ case nil :
41+ if model. isConnecting {
42+ ProgressView ( )
43+ . frame ( maxWidth: . infinity)
44+ } else {
45+ urlEntryView
46+ }
47+ }
48+ }
49+ . onTeardown {
50+ // Reset the challenge handlers and clear credentials
51+ // when the view disappears so that user is prompted to enter
52+ // credentials every time the sample is run, and to clean
53+ // the environment for other samples.
54+ await model. teardownAuthenticator ( )
55+ }
56+ . sheet ( item: $model. selectedItem) { selectedItem in
57+ NavigationStack {
58+ MapView ( map: Map ( item: selectedItem. portalItem) )
59+ . navigationTitle ( selectedItem. portalItem. title)
60+ . navigationBarTitleDisplayMode ( . inline)
61+ . toolbar {
62+ ToolbarItem ( placement: . topBarTrailing) {
63+ Button ( " Done " ) {
64+ model. selectedItem = nil
65+ }
66+ }
67+ }
68+ }
69+ . interactiveDismissDisabled ( )
70+ . highPriorityGesture ( DragGesture ( ) )
71+ . pagePresentation ( )
72+ }
73+ . animation ( . default, value: model. isConnecting)
74+ . authenticator ( model. authenticator)
75+ }
76+
77+ @ViewBuilder private var urlEntryView : some View {
78+ Section {
79+ HStack {
80+ TextField ( " PKI Secured Portal URL " , text: $model. portalURLString)
81+ . onSubmit { Task { await model. connectToPortal ( ) } }
82+ . autocapitalization ( . none)
83+ . keyboardType ( . URL)
84+ Button ( " Connect " ) {
85+ Task { await model. connectToPortal ( ) }
86+ }
87+ . disabled ( model. portalURL == nil )
88+ }
89+ }
90+ }
91+ }
16492
165- // In your application, code may need to run at a different
166- // point in time based on the workflow desired. For example, it
167- // might make sense to remove credentials when the user taps
168- // a "sign out" button.
169- await ArcGISEnvironment . authenticationManager. revokeOAuthTokens ( )
170- await ArcGISEnvironment . authenticationManager. clearCredentialStores ( )
171- }
93+ /// A value that represents an item selected by the user.
94+ private struct SelectedItem : Identifiable {
95+ /// The portal item that was selected.
96+ let portalItem : PortalItem
97+
98+ var id : ObjectIdentifier {
99+ ObjectIdentifier ( portalItem)
100+ }
101+ }
172102
173- /// Sets up new ArcGIS and Network credential stores that will be persisted in the keychain.
174- private func setupPersistentCredentialStorage( ) {
175- Task {
176- try await ArcGISEnvironment . authenticationManager. setupPersistentCredentialStorage (
177- access: . whenUnlockedThisDeviceOnly,
178- synchronizesWithiCloud: false
179- )
180- }
181- }
182- } }
103+ extension AuthenticateWithPKICertificateView {
104+ @MainActor
105+ class Model : ObservableObject {
106+ /// The authenticator to handle authentication challenges.
107+ let authenticator = Authenticator ( )
108+
109+ /// The URL string entered by the user.
110+ @Published var portalURLString = " "
111+
112+ /// The fetched portal content.
113+ @Published var portalContent : Result < PortalQueryResultSet < PortalItem > , Error > ?
114+
115+ /// A Boolean value indicating if a portal connection is in progress.
116+ @Published var isConnecting = false
117+
118+ /// The selected item.
119+ @Published fileprivate var selectedItem : SelectedItem ?
120+
121+ /// The URL of the portal.
122+ var portalURL : URL ? { URL ( string: portalURLString) }
123+
124+ init ( ) {
125+ setupAuthenticator ( )
126+ }
127+
128+ /// Connects to the portal and finds a batch of webmaps.
129+ func connectToPortal( ) async {
130+ precondition ( portalURL != nil )
131+
132+ isConnecting = true
133+ defer { isConnecting = false }
134+
135+ do {
136+ let portal = Portal ( url: portalURL!)
137+ try await portal. load ( )
138+ let results = try await portal. findItems ( queryParameters: . items( ofKinds: [ . webMap] ) )
139+ portalContent = . success( results)
140+ } catch {
141+ portalContent = . failure( error)
142+ }
143+ }
144+
145+ /// Sets up the authenticator to handle challenges.
146+ private func setupAuthenticator( ) {
147+ // Setting the challenge handlers here when the model is created so user is prompted to enter
148+ // credentials every time trying the sample. In real world applications, set challenge
149+ // handlers at the start of the application.
150+
151+ // Sets authenticator as ArcGIS and Network challenge handlers to handle authentication
152+ // challenges.
153+ ArcGISEnvironment . authenticationManager. handleChallenges ( using: authenticator)
154+
155+ // In your application you may want to uncomment this code to persist
156+ // credentials in the keychain.
157+ // setupPersistentCredentialStorage()
158+ }
159+
160+ /// Stops the authenticator from handling the challenges and clears credentials.
161+ nonisolated func teardownAuthenticator( ) async {
162+ // Resets challenge handlers.
163+ ArcGISEnvironment . authenticationManager. handleChallenges ( using: nil )
164+
165+ // In your application, code may need to run at a different
166+ // point in time based on the workflow desired. For example, it
167+ // might make sense to remove credentials when the user taps
168+ // a "sign out" button.
169+ await ArcGISEnvironment . authenticationManager. revokeOAuthTokens ( )
170+ await ArcGISEnvironment . authenticationManager. clearCredentialStores ( )
171+ }
172+
173+ /// Sets up new ArcGIS and Network credential stores that will be persisted in the keychain.
174+ private func setupPersistentCredentialStorage( ) {
175+ Task {
176+ try await ArcGISEnvironment . authenticationManager. setupPersistentCredentialStorage (
177+ access: . whenUnlockedThisDeviceOnly,
178+ synchronizesWithiCloud: false
179+ )
180+ }
181+ }
182+ }
183+ }
183184
184185#Preview {
185186 AuthenticateWithPKICertificateView ( )
0 commit comments