Complete Challenge 3
> Our app silently fails when errors occur during biometric authentication. Add code to show those errors in an alert. But be careful: you can only add one alert() modifier to each view.
This commit is contained in:
parent
53dc10ab4a
commit
74ac5d349b
|
@ -14,7 +14,7 @@ import LocalAuthentication
|
|||
|
||||
|
||||
struct AppState {
|
||||
var authenticationService: AuthenticatingService
|
||||
var authenticationService: AuthenticationService
|
||||
var locationCollectionsState: LocationCollectionsState
|
||||
var wikiPagesState: WikiPagesState
|
||||
|
||||
|
@ -24,7 +24,7 @@ struct AppState {
|
|||
|
||||
|
||||
init(
|
||||
authenticationService: AuthenticatingService = AuthenticationService(laContextType: LAContext.self),
|
||||
authenticationService: AuthenticationService = AuthenticationService(laContextType: LAContext.self),
|
||||
locationCollectionsState: LocationCollectionsState = .init(),
|
||||
wikiPagesState: WikiPagesState = .init()
|
||||
) {
|
||||
|
|
|
@ -24,19 +24,12 @@ extension LAContext: LAContextType {}
|
|||
|
||||
|
||||
|
||||
protocol AuthenticatingService {
|
||||
|
||||
/// - Parameter reason: The app-provided reason for requesting authentication,
|
||||
/// which displays in the authentication dialog presented to the user.
|
||||
func authenticate(reason: String) -> AnyPublisher<Void, Error>
|
||||
}
|
||||
|
||||
|
||||
|
||||
final class AuthenticationService: AuthenticatingService {
|
||||
class AuthenticationService {
|
||||
static let authReason = "Please authenticate to unlock this app and access saved locations."
|
||||
|
||||
enum Error: Swift.Error {
|
||||
enum Error: Swift.Error, Identifiable {
|
||||
var id: String { self.localizedDescription }
|
||||
|
||||
case noBiometricsEnabled(Swift.Error?)
|
||||
case evaluationFailed(Swift.Error?)
|
||||
}
|
||||
|
@ -46,12 +39,14 @@ final class AuthenticationService: AuthenticatingService {
|
|||
private var context: LAContextType?
|
||||
|
||||
|
||||
init(laContextType: LAContextType.Type) {
|
||||
init(laContextType: LAContextType.Type = LAContext.self) {
|
||||
self.laContextType = laContextType
|
||||
}
|
||||
|
||||
|
||||
func authenticate(reason: String) -> AnyPublisher<Void, Swift.Error> {
|
||||
/// - Parameter reason: The app-provided reason for requesting authentication,
|
||||
/// which displays in the authentication dialog presented to the user.
|
||||
func authenticate(reason: String) -> AnyPublisher<Void, Error> {
|
||||
let context = laContextType.init()
|
||||
self.context = context
|
||||
|
||||
|
@ -94,8 +89,8 @@ final class AuthenticationService: AuthenticatingService {
|
|||
|
||||
extension SampleData {
|
||||
|
||||
class AuthService: AuthenticatingService {
|
||||
func authenticate(reason: String) -> AnyPublisher<Void, Error> {
|
||||
class AuthService: AuthenticationService {
|
||||
override func authenticate(reason: String) -> AnyPublisher<Void, Error> {
|
||||
Empty().eraseToAnyPublisher()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ extension LocationCollectionsContainerView {
|
|||
}
|
||||
}
|
||||
.navigationBarTitle("PlaceCase")
|
||||
.alert(item: $viewModel.authenticationError) { _ in self.authenticationErrorAlert }
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -70,6 +71,15 @@ extension LocationCollectionsContainerView {
|
|||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
|
||||
private var authenticationErrorAlert: Alert {
|
||||
.init(
|
||||
title: Text(viewModel.authenticationErrorAlertTitle),
|
||||
message: Text(viewModel.authenticationErrorAlertBody),
|
||||
dismissButton: .default(Text("OK"))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -14,20 +14,19 @@ import Combine
|
|||
final class LocationCollectionsContainerViewModel: ObservableObject {
|
||||
private var subscriptions = Set<AnyCancellable>()
|
||||
|
||||
private let authService: AuthenticatingService
|
||||
private let authService: AuthenticationService
|
||||
|
||||
|
||||
// MARK: - Published Properties
|
||||
@Published var isAuthenticated: Bool = false
|
||||
@Published var authenticationError: AuthenticationService.Error? = nil
|
||||
|
||||
|
||||
// MARK: - Init
|
||||
init(
|
||||
authService: AuthenticatingService
|
||||
authService: AuthenticationService
|
||||
) {
|
||||
self.authService = authService
|
||||
|
||||
setupSubscribers()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,7 +48,7 @@ extension LocationCollectionsContainerViewModel {
|
|||
switch completion {
|
||||
case .failure(let error):
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
print("Authentication failed: \(error)")
|
||||
self?.authenticationError = error
|
||||
self?.isAuthenticated = false
|
||||
}
|
||||
default:
|
||||
|
@ -67,11 +66,25 @@ extension LocationCollectionsContainerViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
// MARK: - Computed
|
||||
extension LocationCollectionsContainerViewModel {
|
||||
|
||||
var authenticationErrorAlertTitle: String { "Authentication Failed" }
|
||||
|
||||
var authenticationErrorAlertBody: String {
|
||||
guard let error = authenticationError else { return "" }
|
||||
|
||||
switch error {
|
||||
case .noBiometricsEnabled:
|
||||
return "No Biomentric authentication is enabled on this device."
|
||||
case .evaluationFailed(_):
|
||||
return "An error occured while attempting evaluation."
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: - Private Helpers
|
||||
private extension LocationCollectionsContainerViewModel {
|
||||
|
||||
func setupSubscribers() {
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue