Sign In With Apple Cannot Connect With Firebase Backend - swift

So I've used sign in with apple in my app to sign users in, an when they click on the button, the user's name, email and user's id all show up. Then when I open the terminal I see : Optional("The identity provider configuration is not found."). Again, in Firebase, I can see that the user is all set up.
Here is the Login View;
import SwiftUI
import AuthenticationServices
import FirebaseAuth
import CryptoKit
struct LoginView: View {
#EnvironmentObject var userAuth: UserAuth
#State var currentnonce: String?
private func randomNonceString(length: Int = 32) -> String {
precondition(length > 0)
let charset: [Character] =
Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
var result = ""
var remainingLength = length
while remainingLength > 0 {
let randoms: [UInt8] = (0 ..< 16).map { _ in
var random: UInt8 = 0
let errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random)
if errorCode != errSecSuccess {
fatalError(
"Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)"
)
}
return random
}
randoms.forEach { random in
if remainingLength == 0 {
return
}
if random < charset.count {
result.append(charset[Int(random)])
remainingLength -= 1
}
}
}
return result
}
private func sha256(_ input: String) -> String {
let inputData = Data(input.utf8)
let hashedData = SHA256.hash(data: inputData)
let hashString = hashedData.compactMap {
String(format: "%02x", $0)
}.joined()
return hashString
}
var body: some View {
NavigationView {
Group {
SignInWithAppleButton(
onRequest: { request in
let nonce = randomNonceString()
currentnonce = nonce
request.requestedScopes = [.fullName, .email]
request.nonce = sha256(nonce)
},
onCompletion: { result in
switch result {
case .success(let authResults):
switch authResults.credential {
case let appleIDCredential as ASAuthorizationAppleIDCredential:
guard let nonce = currentnonce else {
fatalError("Invalid state: A login callback was received, but no login request was sent.")
}
guard let appleIDToken = appleIDCredential.identityToken else {
fatalError("Invalid state: A login callback was received, but no login request was sent.")
}
guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
print("Unable to serialize token string from data: \(appleIDToken.debugDescription)")
return
}
let credential = OAuthProvider.credential(withProviderID: "apple.com",idToken: idTokenString,rawNonce: nonce)
Auth.auth().signIn(with: credential) { (authResult, error) in
if (error != nil) {
// Error. If error.code == .MissingOrInvalidNonce, make sure
// you're sending the SHA256-hashed nonce as a hex string with
// your request to Apple.
print(error?.localizedDescription as Any)
return
}
print("signed in")
self.userAuth.login()
}
print("\(String(describing: Auth.auth().currentUser?.uid))")
default:
break
}
default:
break
}
NavigationLink("Login", destination: HomeView())
}
)
.frame(width: 200, height: 45, alignment: .center)
}
Text(self.loginStatusMessage)
.foregroundColor(.red)
}
.padding()
}
UserAuth;
import Foundation
class UserAuth: ObservableObject {
#Published var isLoggedIn: Bool = false
func login() {
self.isLoggedIn = true
}
}
The user gets added, but I don't think that the app recognizes that the user got added. It isn't letting the user go to the home screen. I've tried to add a navigation link, but that was not working.

Related

Image foregroundColor bugs when view gets updated

My view contains like-buttons, but when the view gets updated, the like-buttons get reloaded, and for a split second, the like-buttons that are liked ("heart.fill") show the neutral image for one second ("heart").
This happens when I click on other buttons that update the view.
I can't figure out why this problem occurs.
struct CommentCell: View {
#ObservedObject private var commentRowViewModel: CommentRowViewModel
init(post: Post, comment: Comment) {
commentRowViewModel = CommentRowViewModel(comment: comment, post: post)
}
var body: some View {
VStack {
Button {
commentRowViewModel.comment.didLike ?? false ?
commentRowViewModel.unlikeComment() :
commentRowViewModel.likeComment()
} label: {
let didLike = commentRowViewModel.comment.didLike ?? false
ZStack {
Image(systemName: "heart.fill")
.opacity(didLike ? 1 : 0)
.foregroundColor(Color(r: 221, g: 102, b: 91))
Image(systemName: "heart")
.opacity(didLike ? 0 : 1)
.foregroundColor(Color.black)
}
}
Text("\(commentRowViewModel.comment.likes)")
.font(.system(size: 14))
}
}
class CommentRowViewModel: ObservableObject {
#Published var comment: Comment
let service = CommentService()
let post: Post
init(comment: Comment, post: Post) {
self.comment = comment
self.post = post
checkIfUserLikedComment()
}
func likeComment() {
service.likeComment(post: post, comment: comment) { likes in
self.comment.didLike = true
self.comment.likes = likes
}
}
func checkIfUserLikedComment() {
service.checkIfUserLikedComment(comment: comment) { didLike in
if didLike {
self.comment.didLike = true
}
}
}
func unlikeComment() {
service.unlikeComment(post: post, comment: comment) {likes in
self.comment.didLike = false
self.comment.likes = likes
}
}
}
struct CommentService {
func likeComment(post: Post, comment: Comment, completion: #escaping(Int) -> Void) {
let newValue = comment.likes + 1
guard let uid = FirebaseManager.shared.auth.currentUser?.uid else {return}
guard let postId = post.id else {return}
guard let commentId = comment.id else {return}
let userLikesRef = FirebaseManager.shared.firestore.collection("users").document(uid).collection("comment-likes")
FirebaseManager.shared.firestore.collection("posts").document(postId).collection("comments").document(commentId)
.updateData(["likes": newValue]) { error in
if error != nil {
print("Failed with finding userLikes")
return
}
userLikesRef.document(commentId).setData([:]) { err in
if err != nil {
print("Failed with err", err ?? "")
return
}
completion(newValue)
}
}
}
func unlikeComment(post: Post, comment: Comment, completion: #escaping(Int) -> Void) {
guard let uid = FirebaseManager.shared.auth.currentUser?.uid else {return}
guard let postId = post.id else {return}
guard let commentId = comment.id else {return}
guard comment.likes > 0 else {return}
let newValue = comment.likes - 1
let userLikesRef = FirebaseManager.shared.firestore.collection("users").document(uid).collection("comment-likes")
FirebaseManager.shared.firestore.collection("posts").document(postId).collection("comments").document(commentId)
.updateData(["likes": newValue]) { error in
if error != nil {
print("failed to unlike", error ?? "")
return
}
userLikesRef.document(commentId).delete { err in
if err != nil {
print(err ?? "")
return
}
completion(newValue)
}
}
}
func checkIfUserLikedComment(comment: Comment, completion: #escaping(Bool)->Void) {
guard let uid = FirebaseManager.shared.auth.currentUser?.uid else {return}
guard let commentId = comment.id else {return}
FirebaseManager.shared.firestore.collection("users")
.document(uid)
.collection("comment-likes")
.document(commentId)
.getDocument { snapshot, error in
if error != nil {
print("Failed to fetch if user have liked post", error ?? "")
return
}
guard let snap = snapshot else {return}
completion(snap.exists)
}
}
}

Problem trying to add Search bar for simple SwiftUI app retrieving web data

I have a small project which is an extension of a Swift UI exercise making a web call to Github from Greg Lim's book Beginning Swift UI:
https://github.com/ethamoos/GitProbe
I’ve been using this to practise basic skills and to try and add other features that could be useful in a realworld app.
My main change from the initial exercise was to add the option to choose which user to lookup (this was previously hardcoded) and allow the user to enter this. Because this can return a lot of data I would now like to make the resulting List .searchable so that the user can filter the results.
I’ve been following this tutorial here:
https://www.hackingwithswift.com/quick-start/swiftui/how-to-add-a-search-bar-to-filter-your-data
but I’ve realised that this is based upon the data being returned being Strings, and therefore the search is a string.
I am returning JSON decoded into a list of User data objects so a straight search does not work. I am assuming that I can adjust this to match a string search against my custom objects but I'm not sure how to do this.
To give you an idea of what I mean here is the code:
import SwiftUI
import URLImage
struct Result: Codable {
let totalCount: Int
let incompleteResults: Bool
let items: [User]
enum CodingKeys: String, CodingKey {
case totalCount = "total_count"
case incompleteResults = "incomplete_results"
case items
}
}
struct User: Codable, Hashable {
let login: String
let id: Int
let nodeID: String
let avatarURL: String
let gravatarID: String
enum CodingKeys: String, CodingKey {
case login, id
case nodeID = "node_id"
case avatarURL = "avatar_url"
case gravatarID = "gravatar_id"
}
}
class FetchUsers: ObservableObject {
#Published var users = [User]()
func search(for user:String) {
var urlComponents = URLComponents(string: "https://api.github.com/search/users")!
urlComponents.queryItems = [URLQueryItem(name: "q", value: user)]
guard let url = urlComponents.url else {
return
}
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let data = data {
let decodedData = try JSONDecoder().decode(Result.self, from: data)
DispatchQueue.main.async {
self.users = decodedData.items
}
} else {
print("No data")
}
} catch {
print("Error: \(error)")
}
}.resume()
}
}
struct ContentView: View {
#State var username: String = ""
var body: some View {
NavigationView {
Form {
Section {
Text("Enter user to search for")
TextField("Enter your username", text: $username).disableAutocorrection(true)
.autocapitalization(.none)
}
NavigationLink(destination: UserView(username: username)) {
Text("Show detail for \(username)")
}
}
}
}
}
struct UserView: View {
#State var username: String
#ObservedObject var fetchUsers = FetchUsers()
#State var searchText = ""
var body: some View {
List {
ForEach(fetchUsers.users, id:\.self) { user in
NavigationLink(user.login, destination: UserDetailView(user:user))
}
}.onAppear {
self.fetchUsers.search(for: username)
}
.searchable(text: $searchText)
.navigationTitle("Users")
}
/// With suggestion added
/// The search results
private var searchResults: [User] {
if searchText.isEmpty {
return fetchUsers.users // your entire list of users if no search input
} else {
return fetchUsers.search(for: searchText) // calls your search method passing your search text
}
}
}
struct UserDetailView: View {
var user: User
var body: some View {
Form {
Text(user.login).font(.headline)
Text("Git iD = \(user.id)")
URLImage(URL(string:user.avatarURL)!){ image in
image.resizable().frame(width: 50, height: 50)
}
}
}
}
Any help with this would be much appreciated.
Your UserListView is not properly constructed. I don't see why you would need a ScrollView with an empty text inside? I removed that.
So I removed searchText from the View to the FetchUsers class so we can delay the server requests thus avoiding unnecessary multiple calls. Please adjust it to your needs (check Apple's Debounce documentation. Everything should work as expected now.
import Combine
class FetchUsers: ObservableObject {
#Published var users = [User]()
#Published var searchText = ""
var subscription: Set<AnyCancellable> = []
init() {
$searchText
.debounce(for: .milliseconds(500), scheduler: RunLoop.main) // debounces the string publisher, delaying requests and avoiding unnecessary calls.
.removeDuplicates()
.map({ (string) -> String? in
if string.count < 1 {
self.users = [] // cleans the list results when empty search
return nil
}
return string
}) // prevents sending numerous requests and sends nil if the count of the characters is less than 1.
.compactMap{ $0 } // removes the nil values
.sink { (_) in
//
} receiveValue: { [self] text in
search(for: text)
}.store(in: &subscription)
}
func search(for user:String) {
var urlComponents = URLComponents(string: "https://api.github.com/search/users")!
urlComponents.queryItems = [URLQueryItem(name: "q", value: user.lowercased())]
guard let url = urlComponents.url else {
return
}
URLSession.shared.dataTask(with: url) {(data, response, error) in
guard error == nil else {
print("Error: \(error!.localizedDescription)")
return
}
guard let data = data else {
print("No data received")
return
}
do {
let decodedData = try JSONDecoder().decode(Result.self, from: data)
DispatchQueue.main.async {
self.users = decodedData.items
}
} catch {
print("Error: \(error)")
}
}.resume()
}
}
struct UserListView: View {
#State var username: String
#ObservedObject var fetchUsers = FetchUsers()
var body: some View {
NavigationView {
List {
ForEach(fetchUsers.users, id:\.self) { user in
NavigationLink(user.login, destination: UserDetailView(user:user))
}
}
.searchable(text: $fetchUsers.searchText) // we move the searchText to fetchUsers
.navigationTitle("Users")
}
}
}
I hope this helps! :)
In the end, I think I've figured this out - thanks to the suggestions from Andre.
I need to correctly filter my data and then return the remainder.
Here's the corrected (abridged) version:
import SwiftUI
import URLImage
struct Result: Codable {
let totalCount: Int
let incompleteResults: Bool
let items: [User]
enum CodingKeys: String, CodingKey {
case totalCount = "total_count"
case incompleteResults = "incomplete_results"
case items
}
}
struct User: Codable, Hashable {
let login: String
let id: Int
let nodeID: String
let avatarURL: String
let gravatarID: String
enum CodingKeys: String, CodingKey {
case login, id
case nodeID = "node_id"
case avatarURL = "avatar_url"
case gravatarID = "gravatar_id"
}
}
class FetchUsers: ObservableObject {
#Published var users = [User]()
func search(for user:String) {
var urlComponents = URLComponents(string: "https://api.github.com/search/users")!
urlComponents.queryItems = [URLQueryItem(name: "q", value: user)]
guard let url = urlComponents.url else {
return
// print("error")
}
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let data = data {
let decodedData = try JSONDecoder().decode(Result.self, from: data)
DispatchQueue.main.async {
self.users = decodedData.items
}
} else {
print("No data")
}
} catch {
print("Error: \(error)")
}
}.resume()
}
}
struct ContentView: View {
#State var username: String = ""
var body: some View {
NavigationView {
Form {
Section {
Text("Enter user to search for")
TextField("Enter your username", text: $username).disableAutocorrection(true)
.autocapitalization(.none)
}
NavigationLink(destination: UserView(username: username)) {
Text("Show detail for \(username)")
}
}
}
}
}
struct UserView: View {
#State var username: String
#ObservedObject var fetchUsers = FetchUsers()
#State var searchText = ""
var body: some View {
List {
ForEach(searchResults, id:\.self) { user in
NavigationLink(user.login, destination: UserDetailView(user:user))
}
}.onAppear {
self.fetchUsers.search(for: username)
}
.searchable(text: $searchText)
.navigationTitle("Users")
}
var searchResults: [User] {
if searchText.isEmpty {
print("Search is empty")
return fetchUsers.users
} else {
print("Search has a value - is filtering")
return fetchUsers.users.filter { $0.login.contains(searchText) }
}
}
}
struct UserDetailView: View {
var user: User
var body: some View {
Form {
Text(user.login).font(.headline)
Text("Git iD = \(user.id)")
URLImage(URL(string:user.avatarURL)!){ image in
image.resizable().frame(width: 50, height: 50)
}
}
}
}

SwiftUI - Firebase Apple Auth not authenticating

im fairly new to swiftUI... pardon the ignorance :) I have most of the code entered in my project from the instructions on the Firebase website. For some reason apple authentication will not successfully authenticate. No idea why, I suspect the nonce part of the code located in 'ConentView' is potentially not being linked to the service. If anyone has any thoughts on why this is occurring I would be greatly appreciative for any help?
Xcode simulator - will not load past this screen
ContentView:
import SwiftUI
import FirebaseAuthUI
import CryptoKit
import AuthenticationServices
struct ContentView: View {
private func sha256(_ input: String) -> String {
let inputData = Data(input.utf8)
let hashedData = SHA256.hash(data: inputData)
let hashString = hashedData.compactMap {
String(format: "%02x", $0)
}.joined()
return hashString
}
#ObservedObject private var authStateManager =
FirebaseAuthStateManager()
#State var isShowSheet = false
#State var currentNonce:String?
private func randomNonceString(length: Int = 32) ->
String {
precondition(length > 0)
let charset: [Character] =
***key***
var result = ""
var remainingLength = length
while remainingLength > 0 {
let randoms: [UInt8] = (0 ..< 16).map { _ in
var random: UInt8 = 0
let errorCode =
SecRandomCopyBytes(kSecRandomDefault, 1, &random)
if errorCode != errSecSuccess {
fatalError(
"Unable to generate nonce. SecRandomCopyBytes
failed with OSStatus \(errorCode)"
)
}
return random
}
randoms.forEach { random in
if remainingLength == 0 {
return
}
if random < charset.count {
result.append(charset[Int(random)])
remainingLength -= 1
}
}
}
return result
}
func authorizationController(controller:
ASAuthorizationController, didCompleteWithAuthorization
authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential
as? ASAuthorizationAppleIDCredential {
guard let nonce = currentNonce else {
fatalError("Invalid state: A login callback was
received, but no login request was sent.")
}
guard let appleIDToken =
appleIDCredential.identityToken else {
print("Unable to fetch identity token")
return
}
guard let idTokenString = String(data: appleIDToken,
encoding:
.utf8) else {
print("Unable to serialize token string from data: \
(appleIDToken.debugDescription)")
return
}
// Initialize a Firebase credential.
let credential =
OAuthProvider.credential(withProviderID:"apple.com",
idToken: idTokenString,rawNonce: nonce)
// Sign in with Firebase.
Auth.auth().signIn(with: credential) { (authResult,
error) in
if (error != nil) {
// Error. If error.code ==
.MissingOrInvalidNonce, make sure
// you're sending the SHA256-hashed nonce as a
hex string with
// your request to Apple.
print(error?.localizedDescription as Any)
return
}
// User is signed in to Firebase with Apple.
// ...
}
}
func authorizationController(controller:
ASAuthorizationController, didCompleteWithError error:
Error) {
// Handle error.
print("Sign in with Apple errored: \(error)")
}
}
var body: some View {
VStack {
if authStateManager.signInState == false {
// Sign-Out
Button(action: {
self.isShowSheet.toggle()
}) {
Text("Sign-In")
}
} else {
// Sign-In
Button(action: {
do {
try Auth.auth().signOut()
} catch {
print("Error")
}
}) {
Text("Sign-Out")
}
}
}
.sheet(isPresented: $isShowSheet) {
FirebaseUIView(isShowSheet: self.$isShowSheet)
}
}
}
FirebaseUIView:
import SwiftUI
import FirebaseAuthUI
import FirebaseGoogleAuthUI
import FirebaseOAuthUI
import CryptoKit
import AuthenticationServices
import FirebaseEmailAuthUI
struct FirebaseUIView: UIViewControllerRepresentable {
#Binding var isShowSheet: Bool
class Coordinator: NSObject,
FUIAuthDelegate {
// FirebaseUIView
let parent: FirebaseUIView
//
init(_ parent: FirebaseUIView) {
self.parent = parent
}
// MARK: - FUIAuthDelegate
func authUI(_ authUI: FUIAuth, didSignInWith user:
User?, error: Error?) {
// handle user and error as necessary
if let error = error {
//
print("Auth NG:\
(error.localizedDescription)")
}
if let _ = user {
//
}
// Sheet(ModalView)
parent.isShowSheet = false
}
}
func makeCoordinator() -> Coordinator {
// Coordinator
Coordinator(self)
}
func makeUIViewController(context: Context) ->
UINavigationController {
let authUI = FUIAuth.defaultAuthUI()!
// You need to adopt a FUIAuthDelegate protocol to
receive callback
authUI.delegate = context.coordinator
let providers: [FUIAuthProvider] = [
FUIGoogleAuth(authUI: authUI),
FUIOAuth.microsoftAuthProvider(),
// FUIFacebookAuth(authUI: authUI),
// FUIOAuth.twitterAuthProvider(),
FUIEmailAuth(),
// FUIPhoneAuth(authUI:authUI),
FUIOAuth.appleAuthProvider(),
]
authUI.providers = providers
// FirebaseUI
let authViewController = authUI.authViewController()
return authViewController
}
func updateUIViewController(_ uiViewController:
UINavigationController, context: Context) {
}
}
[ContentView[][2]2
Check out SignInWithApple from FirebaseService: https://github.com/rebeloper/FirebaseService
Turns out there was nothing wrong with my code. The moment I switched from simulator to the actual iPhone device the code worked perfectly. This is a known BUG with the simulator in Xcode.

Swift / Apple Sign In - Type HASH256 has no member hash

Issue: "Type HASH256 has no member hash"
Background: Trying to implement Apple sign in with Firebase on Swift
Tried to resolve the issue with the following:
-all pods update
-import CommonCrypto + import CryptoKit
-clean build folder / build
The error is still present
// Adapted from https://auth0.com/docs/api-auth/tutorials/nonce#generate-a-cryptographically-random-nonce
private func randomNonceString(length: Int = 32) -> String {
precondition(length > 0)
let charset: Array<Character> =
Array("0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._")
var result = ""
var remainingLength = length
while remainingLength > 0 {
let randoms: [UInt8] = (0 ..< 16).map { _ in
var random: UInt8 = 0
let errorCode = SecRandomCopyBytes(kSecRandomDefault, 1, &random)
if errorCode != errSecSuccess {
fatalError("Unable to generate nonce. SecRandomCopyBytes failed with OSStatus \(errorCode)")
}
return random
}
randoms.forEach { random in
if length == 0 {
return
}
if random < charset.count {
result.append(charset[Int(random)])
remainingLength -= 1
}
}
}
return result
}
//Start Apple's sign-in flow
// Unhashed nonce.
fileprivate var currentNonce: String?
#available(iOS 13, *)
func startSignInWithAppleFlow() {
let nonce = randomNonceString()
currentNonce = nonce
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
request.nonce = sha256(nonce)
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self as! ASAuthorizationControllerDelegate
authorizationController.presentationContextProvider = self as! ASAuthorizationControllerPresentationContextProviding
authorizationController.performRequests()
}
#available(iOS 13, *)
private func sha256(_ input: String) -> String {
let inputData = Data(input.utf8)
let hashedData = SHA256.hash(data: inputData)
let hashString = hashedData.compactMap {
return String(format: "%02x", $0)
}.joined()
return hashString
}
// func SHA256() -> String {
//
// let data = self.data(using: String.Encoding.utf8)
// let res = NSMutableData(length: Int(CC_SHA256_DIGEST_LENGTH))
// CC_SHA256(((data! as NSData)).bytes, CC_LONG(data!.count), res?.mutableBytes.assumingMemoryBound(to: UInt8.self))
// let hashedString = "\(res!)".replacingOccurrences(of: "", with: "").replacingOccurrences(of: " ", with: "")
// let badchar: CharacterSet = CharacterSet(charactersIn: "\"<\",\">\"")
// let cleanedstring: String = (hashedString.components(separatedBy: badchar) as NSArray).componentsJoined(by: "")
// return cleanedstring
//
// }
}
//Apple extension
#available(iOS 13.0, *)
extension AuthViewController: ASAuthorizationControllerDelegate {
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
guard let nonce = currentNonce else {
fatalError("Invalid state: A login callback was received, but no login request was sent.")
}
guard let appleIDToken = appleIDCredential.identityToken else {
print("Unable to fetch identity token")
return
}
guard let idTokenString = String(data: appleIDToken, encoding: .utf8) else {
print("Unable to serialize token string from data: \(appleIDToken.debugDescription)")
return
}
// Initialize a Firebase credential.
let credential = OAuthProvider.credential(withProviderID: "apple.com",
idToken: idTokenString,
accessToken: nonce)
// Sign in with Firebase.
Auth.auth().signIn(with: credential) { (authResult, error) in
if (error != nil) {
// Error. If error.code == .MissingOrInvalidNonce, make sure
// you're sending the SHA256-hashed nonce as a hex string with
// your request to Apple.
print(error?.localizedDescription)
return
}
// User is signed in to Firebase with Apple.
// ...
}
}
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
// Handle error.
print("Sign in with Apple errored: \(error)")
}
}
Image of error
I encountered the same problem, I spend two days figured it out!
The reason is we mistaken installed 'CryptoKit' in our Podfile. which apple also has a build-in 'CryptoKit' for iOS version 13+.
Solution :
1.deleted pod ''CryptoKit' in our pod file.
2. pod install
after that, we will use apple build in 'CryptoKit' which has the build-in method hash.
This should work: add this outside of your class and then instead of request.nonce = sha256(nonce), type request.nonce = nonce.sha256()
extension String {
func sha256() -> String{
if let stringData = self.data(using: String.Encoding.utf8) {
return hexStringFromData(input: digest(input: stringData as NSData))
}
return ""
}
private func digest(input : NSData) -> NSData {
let digestLength = Int(CC_SHA256_DIGEST_LENGTH)
var hash = [UInt8](repeating: 0, count: digestLength)
CC_SHA256(input.bytes, UInt32(input.length), &hash)
return NSData(bytes: hash, length: digestLength)
}
private func hexStringFromData(input: NSData) -> String {
var bytes = [UInt8](repeating: 0, count: input.length)
input.getBytes(&bytes, length: input.length)
var hexString = ""
for byte in bytes {
hexString += String(format:"%02x", UInt8(byte))
}
return hexString
}
}
credit

Swift Combine: subsequent Publisher that consumes other Publishers (using CombineLatest) doesn't "fire"

I am trying to replicate the "Wizard School Signup"-example which was given in the WWDC 2019 session "Combine in Practice" https://developer.apple.com/videos/play/wwdc2019/721/ starting at 22:50 using SwiftUI (as opposed to UIKit, which was used during the session).
I have created all the publishers from the example: validatedEMail, validatedPassword and validatedCredentials. While validatedEMail and validatedPassword work just fine, validatedCredentials, which consumes both publishers using CombineLatest, never fires
//
// RegistrationView.swift
//
// Created by Lars Sonchocky-Helldorf on 04.07.19.
// Copyright © 2019 Lars Sonchocky-Helldorf. All rights reserved.
//
import SwiftUI
import Combine
struct RegistrationView : View {
#ObjectBinding var registrationModel = RegistrationModel()
#State private var showAlert = false
#State private var alertTitle: String = ""
#State private var alertMessage: String = ""
#State private var registrationButtonDisabled = true
#State private var validatedEMail: String = ""
#State private var validatedPassword: String = ""
var body: some View {
Form {
Section {
TextField("Enter your EMail", text: $registrationModel.eMail)
SecureField("Enter a Password", text: $registrationModel.password)
SecureField("Enter the Password again", text: $registrationModel.passwordRepeat)
Button(action: registrationButtonAction) {
Text("Create Account")
}
.disabled($registrationButtonDisabled.value)
.presentation($showAlert) {
Alert(title: Text("\(alertTitle)"), message: Text("\(alertMessage)"))
}
.onReceive(self.registrationModel.validatedCredentials) { newValidatedCredentials in
self.registrationButtonDisabled = (newValidatedCredentials == nil)
}
}
Section {
Text("Validated EMail: \(validatedEMail)")
.onReceive(self.registrationModel.validatedEMail) { newValidatedEMail in
self.validatedEMail = newValidatedEMail != nil ? newValidatedEMail! : "EMail invalid"
}
Text("Validated Password: \(validatedPassword)")
.onReceive(self.registrationModel.validatedPassword) { newValidatedPassword in
self.validatedPassword = newValidatedPassword != nil ? newValidatedPassword! : "Passwords to short or don't matchst"
}
}
}
.navigationBarTitle(Text("Sign Up"))
}
func registrationButtonAction() {
let trimmedEMail: String = self.registrationModel.eMail.trimmingCharacters(in: .whitespaces)
if (trimmedEMail != "" && self.registrationModel.password != "") {
NetworkManager.sharedInstance.registerUser(NetworkManager.RegisterRequest(uid: trimmedEMail, password: self.registrationModel.password)) { (status) in
if status == 200 {
self.showAlert = true
self.alertTitle = NSLocalizedString("Registration successful", comment: "")
self.alertMessage = NSLocalizedString("please verify your email and login", comment: "")
} else if status == 400 {
self.showAlert = true
self.alertTitle = NSLocalizedString("Registration Error", comment: "")
self.alertMessage = NSLocalizedString("already registered", comment: "")
} else {
self.showAlert = true
self.alertTitle = NSLocalizedString("Registration Error", comment: "")
self.alertMessage = NSLocalizedString("network or app error", comment: "")
}
}
} else {
self.showAlert = true
self.alertTitle = NSLocalizedString("Registration Error", comment: "")
self.alertMessage = NSLocalizedString("username / password empty", comment: "")
}
}
}
class RegistrationModel : BindableObject {
#Published var eMail: String = ""
#Published var password: String = ""
#Published var passwordRepeat: String = ""
public var didChange = PassthroughSubject<Void, Never>()
var validatedEMail: AnyPublisher<String?, Never> {
return $eMail
.debounce(for: 0.5, scheduler: RunLoop.main)
.removeDuplicates()
.flatMap { username in
return Future { promise in
self.usernameAvailable(username) { available in
promise(.success(available ? username : nil))
}
}
}
.eraseToAnyPublisher()
}
var validatedPassword: AnyPublisher<String?, Never> {
return Publishers.CombineLatest($password, $passwordRepeat)
.debounce(for: 0.5, scheduler: RunLoop.main)
.map { password, passwordRepeat in
guard password == passwordRepeat, password.count > 5 else { return nil }
return password
}
.eraseToAnyPublisher()
}
var validatedCredentials: AnyPublisher<(String, String)?, Never> {
return Publishers.CombineLatest(validatedEMail, validatedPassword)
.map { validatedEMail, validatedPassword in
guard let eMail = validatedEMail, let password = validatedPassword else { return nil }
return (eMail, password)
}
.eraseToAnyPublisher()
}
func usernameAvailable(_ username: String, completion: (Bool) -> Void) {
let isValidEMailAddress: Bool = NSPredicate(format:"SELF MATCHES %#", "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}").evaluate(with: username)
completion(isValidEMailAddress)
}
}
#if DEBUG
struct RegistrationView_Previews : PreviewProvider {
static var previews: some View {
RegistrationView()
}
}
#endif
I expected the form button to get enabled when a valid username (valid E-Mail-address) and two matching passwords with the right length are provided. The two Publishers responsible for those two tasks work, I can see the validatedEMail and the validatedPassword in the user interface in the two Texts which I added for debugging purposes.
Just the third Publisher (also compare to the code shown in the Video from above at 32:20) never fires. I did set breakpoints in those Publishers, in the validatedPassword Publisher at line:
guard password == passwordRepeat, password.count > 5 else { return nil }
which stopped there just fine but a similar breakpoint in the validatedCredentials Publisher at line:
guard let eMail = validatedEMail, let password = validatedPassword else { return nil }
was never reached.
What did I do wrong?
Edit:
In order to make the above code run under Xcode-beta 11.0 beta 4 didChange needs to be replaced with willChange
I've got this question answered here: https://forums.swift.org/t/crash-in-swiftui-app-using-combine-was-using-published-in-conjunction-with-state-in-swiftui/26628/9 by the very friendly and helpful Nanu Jogi, who is not on stackoverflow.
It is rather straight forward:
add this line:
.receive(on: RunLoop.main) // run on main thread
in validatedCredentials so that it looks like this:
var validatedCredentials: AnyPublisher<(String, String)?, Never> {
return Publishers.CombineLatest(validatedEMail, validatedPassword)
.receive(on: RunLoop.main) // <<—— run on main thread
.map { validatedEMail, validatedPassword in
print("validatedEMail: \(validatedEMail ?? "not set"), validatedPassword: \(validatedPassword ?? "not set")")
guard let eMail = validatedEMail, let password = validatedPassword else { return nil }
return (eMail, password)
}
.eraseToAnyPublisher()
This is all what is needed.
And here one more time the whole code for reference (updated for Xcode 11.0 beta 5 (11M382q)):
//
// RegistrationView.swift
// Combine-Beta-Feedback
//
// Created by Lars Sonchocky-Helldorf on 09.07.19.
// Copyright © 2019 Lars Sonchocky-Helldorf. All rights reserved.
//
import SwiftUI
import Combine
struct RegistrationView : View {
#ObservedObject var registrationModel = RegistrationModel()
#State private var registrationButtonDisabled = true
#State private var validatedEMail: String = ""
#State private var validatedPassword: String = ""
var body: some View {
Form {
Section {
TextField("Enter your EMail", text: $registrationModel.eMail)
SecureField("Enter a Password", text: $registrationModel.password)
SecureField("Enter the Password again", text: $registrationModel.passwordRepeat)
Button(action: registrationButtonAction) {
Text("Create Account")
}
.disabled($registrationButtonDisabled.wrappedValue)
.onReceive(self.registrationModel.validatedCredentials) { newValidatedCredentials in
self.registrationButtonDisabled = (newValidatedCredentials == nil)
}
}
Section {
Text("Validated EMail: \(validatedEMail)")
.onReceive(self.registrationModel.validatedEMail) { newValidatedEMail in
self.validatedEMail = newValidatedEMail != nil ? newValidatedEMail! : "EMail invalid"
}
Text("Validated Password: \(validatedPassword)")
.onReceive(self.registrationModel.validatedPassword) { newValidatedPassword in
self.validatedPassword = newValidatedPassword != nil ? newValidatedPassword! : "Passwords to short or don't match"
}
}
}
.navigationBarTitle(Text("Sign Up"))
}
func registrationButtonAction() {
}
}
class RegistrationModel : ObservableObject {
#Published var eMail: String = ""
#Published var password: String = ""
#Published var passwordRepeat: String = ""
var validatedEMail: AnyPublisher<String?, Never> {
return $eMail
.debounce(for: 0.5, scheduler: RunLoop.main)
.removeDuplicates()
.map { username in
return Future { promise in
print("username: \(username)")
self.usernameAvailable(username) { available in
promise(.success(available ? username : nil))
}
}
}
.switchToLatest()
.eraseToAnyPublisher()
}
var validatedPassword: AnyPublisher<String?, Never> {
return Publishers.CombineLatest($password, $passwordRepeat)
.debounce(for: 0.5, scheduler: RunLoop.main)
.map { password, passwordRepeat in
print("password: \(password), passwordRepeat: \(passwordRepeat)")
guard password == passwordRepeat, password.count > 5 else { return nil }
return password
}
.eraseToAnyPublisher()
}
var validatedCredentials: AnyPublisher<(String, String)?, Never> {
return Publishers.CombineLatest(validatedEMail, validatedPassword)
.receive(on: RunLoop.main)
.map { validatedEMail, validatedPassword in
print("validatedEMail: \(validatedEMail ?? "not set"), validatedPassword: \(validatedPassword ?? "not set")")
guard let eMail = validatedEMail, let password = validatedPassword else { return nil }
return (eMail, password)
}
.eraseToAnyPublisher()
}
func usernameAvailable(_ username: String, completion: (Bool) -> Void) {
let isValidEMailAddress: Bool = NSPredicate(format:"SELF MATCHES %#", "[A-Z0-9a-z._%+-]+#[A-Za-z0-9.-]+\\.[A-Za-z]{2,64}").evaluate(with: username)
completion(isValidEMailAddress)
}
}
#if DEBUG
struct RegistrationView_Previews : PreviewProvider {
static var previews: some View {
RegistrationView()
}
}
#endif
Just replace
.debounce(for: 0.5, scheduler: RunLoop.main)
with
.throttle(for: 0.5, scheduler: RunLoop.main, latest: true)
Since there is no expensive code in the publishers subscription no deferred processing would be basically needed. Throttling the key events with latest: true will do the job almost in the same way.
I'm not such a Reactive programming expert that I can judge what is the reason behind, I assume a design choice.
You might need to group some of these publisher's validation into one consumer. There is a cool playground outlining the combine framework and this is how they do a similar use case. In the example they are validating the user name and password within the same subscriber. The subscriber does not execute until something has been published to the user name and password publishers.
If you wanted to keep them separate then you would need to add some more publishers that basically outline the state of whether the password is valid and the user name is valid. Then have subscribers listening to when both the user name and password publishers are valid.