In-App Purchase invalid identifier during review(works fine while in development) - swift

i am working on an app that has a non-consumable product. I have tested with multiple sandbox accounts and everything works fine during development. but during the app review, the productid are being returned as invalid and i can't get past the review. i make sure that the non-consumable product is in "waiting for review" state just before i submit the app for review. also i have the latest provisioning profile setup in xcode. i already got rejected twice, any ideas on what might be causing the issue will be greatly appreciated.

In appDelegate you set put below code in appDelegate and import svprogresshud pod for handle error
let PRODUCT_ID = "your itunes bundal identifire for IN app purrchase"
func fetchProductPrice(){
InAppPurchaseManager.sharedManager.fetchProductPrice(productId: PRODUCT_ID) { (productInfo, price) in
print("price : \(price)")
}
}
InAppPurchaseManager
import UIKit
import StoreKit
import SVProgressHUD
var productTitle:String = ""
var productPrice:String = ""
struct InAppMessages {
static let kUserNotAuthorizedMessage = "You are not authorize to make purchase."
static let kProductNotAvailableMessage = "Product not available."
static let kErrorConnectingToServer = "Error in connecting to iTunes server."
static let kErrorInFetchProductInfoMessage = "Failed to get product information."
static let kProductPurchaseFailed = "Failed to purchase product.";
}
var iTuneURL = "https://buy.itunes.apple.com/verifyReceipt"
class InAppPurchaseManager: NSObject, SKProductsRequestDelegate, SKPaymentTransactionObserver {
typealias PurchaseFailureHandler = (_ error : String) -> ()
typealias PurchaseSuccessHandler = (_ transcation : SKPaymentTransaction) -> ()
typealias ReceiptFailureHandler = (_ error : String) -> ()
typealias ReceiptSuccessHandler = (_ receiptData : AnyObject) -> ()
typealias RestoreSuccessHandler = (_ restoreProductID : [String]) -> ()
typealias RestoreFailureHandler = (_ error : String) -> ()
typealias ProductFetchHandler = (_ productInfo : String, _ price: String) -> ()
static let sharedManager = InAppPurchaseManager()
var purchaseFailureHandler : PurchaseFailureHandler?
var purchaseSuccessHandler : PurchaseSuccessHandler?
var receiptFailureHandler : ReceiptFailureHandler?
var receiptSuccessHandler : ReceiptSuccessHandler?
var restoreSuccessHandler : RestoreSuccessHandler?
var restoreFailureHandler : RestoreFailureHandler?
var productFetchHandler : ProductFetchHandler?
var productToBuy : SKProduct?
var productID : String?
// #if DEBUG
// let iTuneURL = "https://sandbox.itunes.apple.com/verifyReceipt"
// #else
// #endif
override init() {
super.init()
//Add Payment Queue Transaction Observer
SKPaymentQueue.default().add(self)
}
//==========================================
//MARK: - In App Purchase Handler Methods
//==========================================
func finishInterruptedTransactionsWithSuccess(_ successHandler :#escaping PurchaseSuccessHandler, _ failureHandler : #escaping PurchaseFailureHandler){
purchaseSuccessHandler = successHandler
purchaseFailureHandler = failureHandler
let currentQueue : SKPaymentQueue = SKPaymentQueue.default()
print("Transactions: \(currentQueue.transactions.count)")
for transaction in currentQueue.transactions {
if (transaction.transactionState == SKPaymentTransactionState.failed) {
//possibly handle the error
currentQueue.finishTransaction(transaction);
SVProgressHUD.dismiss()
purchaseFailureHandler?(InAppMessages.kProductPurchaseFailed)
purchaseFailureHandler = nil
} else if (transaction.transactionState == SKPaymentTransactionState.purchased) {
//deliver the content to the user
currentQueue.finishTransaction(transaction)
purchaseSuccessHandler?(transaction)
purchaseSuccessHandler = nil
} else {
//handle other transaction states
SVProgressHUD.dismiss()
purchaseFailureHandler?(InAppMessages.kProductPurchaseFailed)
purchaseFailureHandler = nil
}
}
}
func canPurchase() -> Bool{
return SKPaymentQueue.canMakePayments()
}
func buyProduct(){
// show progress
self.showMessageIndicator(message: "Please wait...")
//AppData.showProgressGIFImage()
let payment = SKPayment(product: self.productToBuy!)
SKPaymentQueue.default().add(payment);
}
func fetchProductPrice(productId: String, fetchHandler : #escaping ProductFetchHandler){
productFetchHandler = fetchHandler
productID = productId
if (SKPaymentQueue.canMakePayments())
{
let productID:NSSet = NSSet(object: productId);
let productsRequest:SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>);
productsRequest.delegate = self;
productsRequest.start();
}
}
func purchaseProductWithProductId(productId : String, fetchHandler : #escaping ProductFetchHandler ,successHandler : #escaping PurchaseSuccessHandler, failureHandler : #escaping PurchaseFailureHandler){
purchaseSuccessHandler = successHandler
purchaseFailureHandler = failureHandler
productFetchHandler = fetchHandler
productID = productId
if (SKPaymentQueue.canMakePayments())
{
// show indicator
self.showMessageIndicator(message:"Getting Product Information...")
let productID:NSSet = NSSet(object: productId);
let productsRequest:SKProductsRequest = SKProductsRequest(productIdentifiers: productID as! Set<String>);
productsRequest.delegate = self;
productsRequest.start();
}else{
SVProgressHUD.dismiss()
purchaseFailureHandler?(InAppMessages.kErrorConnectingToServer)
// show error
// self.showErrorAlert(error: InAppMessages.kErrorConnectingToServer)
}
}
func restoreProduct(successHandler : #escaping RestoreSuccessHandler, failureHandler : #escaping RestoreFailureHandler){
// show indicator
self.showMessageIndicator(message:"Restoring purchase...")
restoreSuccessHandler = successHandler
restoreFailureHandler = failureHandler
SKPaymentQueue.default().restoreCompletedTransactions()
}
//==========================================
//MARK: - SKPayment Request Delegate Methods
//==========================================
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
print("resposen invalid product \(response.invalidProductIdentifiers)")
let count : Int = response.products.count
if (count>0) {
let validProduct: SKProduct = response.products[0]
if (validProduct.productIdentifier == productID) {
let numberFormatter = NumberFormatter()
numberFormatter.formatterBehavior = NumberFormatter.Behavior.behavior10_4
numberFormatter.numberStyle = NumberFormatter.Style.currency
numberFormatter.locale = validProduct.priceLocale
let formattedPrice = numberFormatter.string(from: validProduct.price)
print("formattedPrice: \(formattedPrice!)")
productPrice = formattedPrice!
hideIndicator()
self.productToBuy = validProduct
productTitle = validProduct.localizedTitle
//let productMessage = "Do you want to buy \(validProduct.localizedTitle) at \(formattedPrice!) amount?"
let productMessage = " Want to access premium content for only \(formattedPrice!) a month?"
productFetchHandler?(productMessage, formattedPrice!)
// buy product
//buyProduct(product: validProduct);
}
else {
SVProgressHUD.dismiss()
print(validProduct.productIdentifier)
purchaseFailureHandler?(InAppMessages.kProductNotAvailableMessage)
//self.showErrorAlert(error: InAppMessages.kProductNotAvailableMessage)
}
} else {
SVProgressHUD.dismiss()
purchaseFailureHandler?(InAppMessages.kProductNotAvailableMessage)
// show error
// self.showErrorAlert(error: InAppMessages.kProductNotAvailableMessage)
}
}
func request(_ request: SKRequest, didFailWithError error: Error) {
print("Error Fetching product information : \(error.localizedDescription)");
SVProgressHUD.dismiss()
purchaseFailureHandler?(InAppMessages.kErrorInFetchProductInfoMessage)
// show error alert
// self.showErrorAlert(error: InAppMessages.kErrorInFetchProductInfoMessage)
}
//============================================
//MARK: - SKPaymentTransactionObserver Methods
//============================================
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]){
//print("Received Payment Transaction Response from Apple");
for transaction in transactions {
switch transaction.transactionState {
case .purchased:
// hide indicator
self.hideIndicator()
//print("Product Purchased: \(transaction.transactionIdentifier)")
SKPaymentQueue.default().finishTransaction(transaction);
self.purchaseSuccessHandler?(transaction)
self.purchaseSuccessHandler = nil
//To Get Receipt from Bundle
// let mainBundle = Bundle.main as Bundle;
// let receiptUrl = mainBundle.appStoreReceiptURL;
//
// let data = NSData(contentsOf: receiptUrl!);
//
// if(data != nil){
//
// let base64String = data!.base64EncodedString()
//
// // verify payment receipt
// verifyPaymentReceipt(base64EncodedString: base64String)
// }
break;
case .failed:
SVProgressHUD.dismiss()
purchaseFailureHandler?(InAppMessages.kProductPurchaseFailed)
purchaseFailureHandler = nil
SKPaymentQueue.default().finishTransaction(transaction);
SVProgressHUD.dismiss()
//self.showErrorAlert(error: InAppMessages.kProductPurchaseFailed)
break;
case .purchasing:
self.showMessageIndicator(message: "Please wait \ntransaction in progress...")
print("Transaction is being added to the server queue.");
case .restored:
SKPaymentQueue.default().finishTransaction(transaction);
print("Already Purchased");
case .deferred:
print("The transaction is in the queue, but its final status is pending external action.");
}
}
}
func paymentQueue(_ queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
}
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) {
print("Restore Purchase done")
var productIds = [String]()
for transcation in queue.transactions{
productIds.append(transcation.payment.productIdentifier)
}
hideIndicator()
self.restoreSuccessHandler?(productIds)
self.restoreSuccessHandler = nil
self.restoreFailureHandler = nil
}
func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error){
self.restoreFailureHandler?(error.localizedDescription)
self.restoreSuccessHandler = nil
self.restoreFailureHandler = nil
}
//============================================
//MARK: - Verify Receipt
//============================================
func verifyPaymentReceipt(base64EncodedString : String,ReceiptValidationPassword : String, receiptSuccess : ReceiptSuccessHandler?, receiptFailure : ReceiptFailureHandler?){
receiptSuccessHandler = receiptSuccess
receiptFailureHandler = receiptFailure
self.showMessageIndicator(message: "Verifying payment...")
// Create the JSON object that describes the request
let requestContents = NSMutableDictionary();
requestContents.setObject(base64EncodedString, forKey: "receipt-data" as NSCopying);
requestContents.setObject(ReceiptValidationPassword, forKey: "password" as NSCopying);
var requestData : NSData?
do{
requestData = try JSONSerialization.data(withJSONObject: requestContents, options: JSONSerialization.WritingOptions()) as NSData?;
}catch{
NSLog("Error in json data creation at verifyPaymentReceipt");
}
StoreDataCheck(requestData!)
}
func StoreDataCheck(_ requestData:NSData)
{
// Create a POST request with the receipt data.
let storeURL = NSURL(string: iTuneURL);
var storeRequest = URLRequest(url: storeURL! as URL);
storeRequest.httpMethod = "POST";
storeRequest.httpBody = requestData as Data?;
let session = URLSession(configuration: URLSessionConfiguration.default)
let dataTask = session.dataTask(with: storeRequest) { (responseData, response, error) in
// self.hideIndicator()
if (error == nil){
var jsonResponse: NSDictionary = NSDictionary()
do{
jsonResponse = try JSONSerialization.jsonObject(with: responseData!, options: JSONSerialization.ReadingOptions.allowFragments) as! NSDictionary
if jsonResponse.value(forKey: "status") as? Int == 21007
{
iTuneURL = "https://sandbox.itunes.apple.com/verifyReceipt"
self.StoreDataCheck(requestData)
}
else
{
self.receiptSuccessHandler?(jsonResponse)
}
}catch{
self.receiptFailureHandler?("Parsing issue : verifyPaymentReceipt")
}
}
else{
self.receiptFailureHandler?(error!.localizedDescription)
}
}
dataTask.resume()
}
//============================================
//MARK: - Alert / Activity Indicator
//============================================
func showIndicator() {
SVProgressHUD.show()
}
func showMessageIndicator(message: String) {
hideIndicator()
SVProgressHUD.show(withStatus: message)
}
func hideIndicator() {
SVProgressHUD.dismiss()
}
func showErrorAlert(error: String){
hideIndicator()
SVProgressHUD.showError(withStatus: error)
}
func showWithStatus(status: String){
hideIndicator()
SVProgressHUD.showInfo(withStatus: status)
}
}
And put this code in your button action
func InApp()
{
SVProgressHUD.show()
if InAppPurchaseManager.sharedManager.canPurchase(){
InAppPurchaseManager.sharedManager.purchaseProductWithProductId(productId: PRODUCT_ID, fetchHandler: { (productInfoMessage, price) in
SVProgressHUD.dismiss()
InAppPurchaseManager.sharedManager.buyProduct()
print(productInfoMessage)
}, successHandler: { (transaction) in
self.purchaseSuccessful(transaction)
transectionID = transaction.transactionIdentifier!
}, failureHandler: { (error) in
print(error)
InAppPurchaseManager.sharedManager.hideIndicator()
)
InAppPurchaseManager.sharedManager.hideIndicator()
})
}else{
print( "Device is not able to make purchase. \n Please enable it from Settings -> General -> Restrictions -> In-App Purchases")
}
}
func purchaseSuccessful(_ transaction : SKPaymentTransaction) {
do {
if let receiptURL = Bundle.main.appStoreReceiptURL {
let data = try Data(contentsOf: receiptURL)
if data != nil {
InAppPurchaseManager.sharedManager.verifyPaymentReceipt(base64EncodedString: data.base64EncodedString(),
ReceiptValidationPassword: inAppPassword,
receiptSuccess: { (response) in
print("response \(response)")
if let receipt = response as? NSDictionary {
if let receiptInfo = receipt.object(forKey: "receipt" as NSCopying) as? NSDictionary{
let strOriginalPurchaseTime = receiptInfo.object(forKey: "receipt_creation_date_ms" as NSCopying) as! String
let timeInMS = TimeInterval(Double(strOriginalPurchaseTime)!)
let date = Date(timeIntervalSince1970: timeInMS / 1000)
purchaseDate = "\(date)"
print("date of purchase \(date)")
}
}
}, receiptFailure: { (error) in
print("error \(error)")
})
}
}
}catch {
}
}

Related

Why does webSocketTask.receive never complete and how do I force completion in my Swift app?

I have a Swift app that uses a web socket to download stock price information from a public API. I send a message through the socket to subscribe to various stock price changes then wait for a continuous stream of messages to be received but when I turn off wifi the call to the message receive function, webSocketTask.receive, never returns. How can I force abort of the message receive function so that I alert the user that the network connection has been lost.
Here is my NetworkServices class;
import UIKit
import Network
protocol NetworkServicesDelegate: AnyObject {
func sendStockInfo(stocksInfo: [String: StockInfo])
}
final class NetworkServices: NSObject {
static let sharedInstance = NetworkServices()
var urlSession: URLSession?
var webSocketTask: URLSessionWebSocketTask?
var stocksInfo: [String: StockInfo] = [:]
var socketResults: [String: [StockInfo]] = [:]
weak var delegate: NetworkServicesDelegate?
var timer: Timer?
var stockSymbols: [String] = []
private let queue = DispatchQueue.global(qos: .background)
private let monitor = NWPathMonitor()
public private(set) var isConnected: Bool = false
public private(set) var connectionType: ConnectionType = .unknown
enum ConnectionType {
case wifi
case cellular
case wiredEthernet
case unknown
}
public func startMonitoring() {
monitor.start(queue: queue)
monitor.pathUpdateHandler = { path in
self.isConnected = path.status == .satisfied
self.getConnectionType(path)
print("DEBUG: isConnected = \(self.isConnected); connectionType = \(self.connectionType)")
if self.isConnected == false {
}
}
}
public func stopMonitoring() {
monitor.cancel()
}
private func getConnectionType(_ path: NWPath) {
if path.usesInterfaceType(.wifi) {
connectionType = .wifi
} else if path.usesInterfaceType(.cellular) {
connectionType = .cellular
} else if path.usesInterfaceType(.wiredEthernet) {
connectionType = .wiredEthernet
} else {
connectionType = .unknown
}
}
func fetchStockInfo(symbols: [String], delegate: CompanyPriceListVC) {
stockSymbols = symbols
self.delegate = delegate
let configuration = URLSessionConfiguration.default
configuration.waitsForConnectivity = false
urlSession = URLSession(configuration: configuration, delegate: self, delegateQueue: OperationQueue.main)
webSocketTask = urlSession?.webSocketTask(with: FINNHUB_SOCKET_STOCK_INFO_URL!)
webSocketTask?.delegate = self
webSocketTask!.resume()
for symbol in symbols {
let string = FINNHUB_SOCKET_MESSAGE_STRING + symbol + "\"}"
let message = URLSessionWebSocketTask.Message.string(string)
if isConnected {
webSocketTask!.send(message) { error in
if let error = error {
print("DEBUG: Error sending message: \(error)")
}
self.receiveMessage()
}
} else {
// Post notification to view controllers that connection has been lost
let name = Notification.Name(rawValue: isNotConnectedNotificationKey)
NotificationCenter.default.post(name: name, object: nil)
// try to re-connect
// successful re-connect?
// No, keep trying. Yes, call send message method again
}
}
}
private func receiveMessage() {
if isConnected {
self.webSocketTask?.receive { result in
print("DEBUG: Inside closure.")
switch result {
case .failure(let error):
print("DEBUG: Error receiving message: \(error.localizedDescription)")
case .success(.string(let jsonData)):
guard let stockData = jsonData.data(using: .utf8) else { return }
self.socketResults = [:]
self.stocksInfo = [:]
let decoder = JSONDecoder()
do {
let socketData = try decoder.decode(SocketData.self, from: stockData)
guard let stockInfoData = socketData.data else { return }
for stockInfo in stockInfoData {
let symbol = stockInfo.symbol
if self.socketResults[symbol] == nil {
self.socketResults[symbol] = [StockInfo]()
}
self.socketResults[symbol]?.append(stockInfo)
}
for (symbol, stocks) in self.socketResults {
for item in stocks {
if self.stocksInfo[symbol] == nil {
self.stocksInfo[symbol] = item
} else if item.timestamp > self.stocksInfo[symbol]!.timestamp {
self.stocksInfo[symbol] = item
}
}
}
self.delegate?.sendStockInfo(stocksInfo: self.stocksInfo)
self.receiveMessage()
} catch {
print("DEBUG: Error converting JSON: \(error)")
}
default:
print("DEBUG: default")
}
}
print("DEBUG: Got here 1")
} else {
print("DEBUG: Got here 2")
// Post notification to view controllers that connection has been lost
let name = Notification.Name(rawValue: isNotConnectedNotificationKey)
NotificationCenter.default.post(name: name, object: nil)
// try to re-connect.
// successful reconnect?
// No, keep trying. Yes, call receive message method again.
}
}
func closeWebSocketConnection() {
webSocketTask?.cancel(with: .goingAway, reason: nil)
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
print("DEBUG: didCompleteWithError called: error = \(error)")
}
}
func fetchCompanyInfo(symbol: String, completion: #escaping (CompanyInfo?, UIImage?)->()) {
let urlString = FINNHUB_HTTP_COMPANY_INFO_URL_STRING + symbol + "&token=" + FINNHUB_API_TOKEN
guard let url = URL(string: urlString) else { return }
let task = URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print("Error fetching company info: \(error)")
}
guard let data = data else { return }
let decoder = JSONDecoder()
do {
let companyInfo = try decoder.decode(CompanyInfo.self, from: data)
guard let logoURL = URL(string: companyInfo.logo) else { return }
let task = URLSession.shared.dataTask(with: logoURL) { data, response, error in
if let error = error {
print("Error fetching logo image: \(error)")
}
guard let data = data else { return }
guard let logoImage = UIImage(data: data) else { return }
completion(companyInfo, logoImage)
}
task.resume()
} catch {
print("Error decoding JSON: \(error)")
completion(nil, nil)
}
}
task.resume()
}
}
extension NetworkServices: URLSessionTaskDelegate, URLSessionWebSocketDelegate, URLSessionDelegate {
func urlSession(_ session: URLSession, taskIsWaitingForConnectivity task: URLSessionTask) {
print("DEBUG: inside taskIsWaitingForConnectivity")
}
func urlSession(_ session: URLSession, didBecomeInvalidWithError error: Error?) {
print("DEBUG: didBecomeInvalidWithError: error = \(String(describing: error?.localizedDescription))")
}
func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
let reasonString: String
if let reason = reason, let string = String(data: reason, encoding: .utf8) {
reasonString = string
} else {
reasonString = ""
}
print("DEBUG: didCloseWith called: close code is \(closeCode), reason is \(String(describing: reasonString))")
}
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
print("DEBUG: urlSessionDidFinishEvents called")
}
}
I have tried placing code in the pathUpdateHandler closure to cancel the task on the task object and finish current tasks and cancel on the URLSession object but neither works.
I needed to post a notification when path.status == .satisfied. The notification goes to the custom UIViewController class which then calls the FetchStockInfo(symbols:delegate) method.

Cannot convert Result<Bool, Error> to expected condition type 'Bool'. Swift

How to get the result from Result<Bool, Error> as a Boolean answer only in Swift?
I do the purchase from the AppStore from the ViewController:
Purchases.default.purchaseProduct(productId: "com.justdoit.buy_1week") { [weak self] res in
self?.hideSpinner()
if res == .success {
// Handle result
// IF OKAY - WE DO THE REQUEST AND CHANGE EVERYTHING FOR PREMIUM USER
} else {
self?.alert(alertMessage: "Error during the purchase") // my popUp view
}
}
Purchases.swift:
import StoreKit
typealias RequestProductsResult = Result<[SKProduct], Error>
typealias PurchaseProductResult = Result<Bool, Error>
typealias RequestProductsCompletion = (RequestProductsResult) -> Void
typealias PurchaseProductCompletion = (PurchaseProductResult) -> Void
// class
class Purchases: NSObject {
static let `default` = Purchases()
fileprivate var productPurchaseCallback: ((PurchaseProductResult) -> Void)?
private let productIdentifiers = Set<String>(
arrayLiteral:
"com.justdoit.buy_1week",
"com.justdoit.buy_1month",
"com.justdoit.buy_3months",
"com.justdoit.buy_1year"
)
private var products: [String: SKProduct]?
private var productRequest: SKProductsRequest?
func initialize(completion: #escaping RequestProductsCompletion) {
requestProducts(completion: completion)
}
private var productsRequestCallbacks = [RequestProductsCompletion]()
private func requestProducts(completion: #escaping RequestProductsCompletion) {
guard productsRequestCallbacks.isEmpty else {
productsRequestCallbacks.append(completion)
return
}
productsRequestCallbacks.append(completion)
let productRequest = SKProductsRequest(productIdentifiers: productIdentifiers)
productRequest.delegate = self
productRequest.start()
self.productRequest = productRequest
}
func purchaseProduct(productId: String, completion: #escaping (PurchaseProductResult) -> Void) {
guard productPurchaseCallback == nil else {
completion(.failure(PurchasesError.purchaseInProgress))
return
}
guard let product = products?[productId] else {
completion(.failure(PurchasesError.productNotFound))
return
}
productPurchaseCallback = completion
let payment = SKPayment(product: product)
SKPaymentQueue.default().add(payment)
}
public func restorePurchases(completion: #escaping (PurchaseProductResult) -> Void) {
guard productPurchaseCallback == nil else {
completion(.failure(PurchasesError.purchaseInProgress))
return
}
productPurchaseCallback = completion
// 4:
SKPaymentQueue.default().restoreCompletedTransactions()
}
}
// responses
extension Purchases: SKProductsRequestDelegate {
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
guard !response.products.isEmpty else {
print("Found 0 products")
productsRequestCallbacks.forEach { $0(.success(response.products)) }
productsRequestCallbacks.removeAll()
return
}
var products = [String: SKProduct]()
for skProduct in response.products {
print("Found product: \(skProduct.productIdentifier)")
products[skProduct.productIdentifier] = skProduct
}
self.products = products
productsRequestCallbacks.forEach { $0(.success(response.products)) }
productsRequestCallbacks.removeAll()
}
func request(_ request: SKRequest, didFailWithError error: Error) {
print("Failed to load products with error:\n \(error)")
productsRequestCallbacks.forEach { $0(.failure(error)) }
productsRequestCallbacks.removeAll()
}
}
extension Purchases: SKPaymentTransactionObserver {
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
// 1:
for transaction in transactions {
switch transaction.transactionState {
// 2:
case .purchased, .restored:
if finishTransaction(transaction) {
SKPaymentQueue.default().finishTransaction(transaction)
productPurchaseCallback?(.success(true))
} else {
productPurchaseCallback?(.failure(PurchasesError.unknown))
}
// 3:
case .failed:
productPurchaseCallback?(.failure(transaction.error ?? PurchasesError.unknown))
SKPaymentQueue.default().finishTransaction(transaction)
default:
break
}
}
productPurchaseCallback = nil
}
}
extension Purchases {
// 4:
func finishTransaction(_ transaction: SKPaymentTransaction) -> Bool {
let productId = transaction.payment.productIdentifier
print("Product \(productId) successfully purchased")
return true
}
}
enum PurchasesError: Error {
case purchaseInProgress
case productNotFound
case unknown
}
The first time I've been working with payments in Swift, I have no idea how to get the .success(true) result for successful payments only and use it later...
Is there any advice?
Use switch case. Here enum have Associated Values.
Purchases.default.purchaseProduct(productId: "com.justdoit.buy_1week") { [weak self] res in
switch res {
case .success(let value):
break
case .failure(let error):
print(error.lo)
}
}
You can also use if
if case Result.success(let value) = res {
print(value)
} else if case Result.failure(let error) = res {
print(error.localizedDescription)
}

Update data stored in Firebase using MVVM nested models

I built an MVVM architecture to support my app which is supposed to control the pipeline between the frontend and my firebase database. Initially, I successfully implemented the entire work by coding totally in the frontend, but there are lots of bugs when I encapsulated them into a function.
For example, the next sheet will be presented when the currently presented sheet gets dismissed. Sometimes I needed to wait for a long time until the app is unfrozen. Even worse, the app crashed down when I clicked the button.
I heard that nested models don't work yet if SwiftUI is in use (reference). However, I just cannot come up with a better solution if my classes are untested.
// This is Model
import Foundation
import SwiftUI
struct userModel {
var uid = UUID()
var name = ""
var bio = ""
var interest = ""
var level = 1
var xp = 0
var email = ""
var image: Data = Data(count: 0)
init() {
}
init(_ name:String, _ xp: Int) {
self.name = name
self.xp = xp
self.level = self.xp2Level(xp: xp)
}
func xp2Level(xp:Int) -> Int {
if xp < 9500 {
return xp / 500 + 1
}
else if xp < 29500 {
return (xp - 9500) / 1000 + 1
}
else {
return (xp - 29500) / 2000 + 1
}
}
}
// This is ViewModel
import Foundation
import SwiftUI
import Firebase
class userViewModel: ObservableObject {
#Published var user: userModel = userModel()
#Published var isLoading = false
#AppStorage("status") var status = false
private var ref = Firestore.firestore()
private let store = Storage.storage().reference()
var picker = false
func updateXP(completion: #escaping () -> Int) -> Int {
guard let uid = Auth.auth().currentUser?.uid else {
return 0
}
// catch the information of the current user
let db = ref.collection("Users")
db.addSnapshotListener { [self] (querySnapshot, error) in
guard (querySnapshot?.documents) != nil else {
print("Document is empty")
return
}
let docRef = db.document(uid)
docRef.getDocument { (snapshot, error) in
if let doc = snapshot,
let xp = doc.get("xp") as? Int {
self.user.xp = xp
}
}
}
return completion()
}
func updateLevel(completion: #escaping () -> Int) -> Int {
guard let uid = Auth.auth().currentUser?.uid else {
return 1
}
// catch the information of the current user
let db = ref.collection("Users")
db.addSnapshotListener { [self] (querySnapshot, error) in
guard (querySnapshot?.documents) != nil else {
print("Document is empty")
return
}
let docRef = db.document(uid)
docRef.getDocument { (snapshot, error) in
if let doc = snapshot,
let level = doc.get("level") as? Int {
self.user.level = level
}
}
}
return completion()
}
func updateName (completion: #escaping () -> String) -> String {
guard let uid = Auth.auth().currentUser?.uid else {
return ""
}
// catch the information of the current user
let db = ref.collection("Users")
db.addSnapshotListener { [self] (querySnapshot, error) in
guard (querySnapshot?.documents) != nil else {
print("Document is empty")
return
}
let docRef = db.document(uid)
docRef.getDocument { (snapshot, error) in
if let doc = snapshot,
let name = doc.get("username") as? String {
self.user.name = name
}
}
}
return completion()
}
func updateBio (completion: #escaping () -> String) -> String {
guard let uid = Auth.auth().currentUser?.uid else {
return ""
}
// catch the information of the current user
let db = ref.collection("Users")
db.addSnapshotListener { [self] (querySnapshot, error) in
guard (querySnapshot?.documents) != nil else {
print("Document is empty")
return
}
let docRef = db.document(uid)
docRef.getDocument { (snapshot, error) in
if let doc = snapshot,
let bio = doc.get("bio") as? String {
self.user.bio = bio
}
}
}
return completion()
}
func updateInterest (completion: #escaping () -> String) -> String {
guard let uid = Auth.auth().currentUser?.uid else {
return ""
}
// catch the information of the current user
let db = ref.collection("Users")
db.addSnapshotListener { [self] (querySnapshot, error) in
guard (querySnapshot?.documents) != nil else {
print("Document is empty")
return
}
let docRef = db.document(uid)
docRef.getDocument { (snapshot, error) in
if let doc = snapshot,
let interest = doc.get("interest") as? String {
self.user.interest = interest
}
}
}
return completion()
}
func updatePhoto (completion: #escaping () -> Data) -> Data {
guard let uid = Auth.auth().currentUser?.uid else {
return Data(count: 0)
}
// catch the information of the current user
let db = ref.collection("Users")
db.addSnapshotListener { [self] (querySnapshot, error) in
guard (querySnapshot?.documents) != nil else {
print("Document is empty")
return
}
let docRef = db.document(uid)
docRef.getDocument { (snapshot, error) in
if snapshot != nil {
let imageRef = store.child("profile_Photos").child(uid)
imageRef.getData(maxSize: 1000 * 64 * 64, completion: { (data, error) in
if let error = error {
print("Encountered error: \(error) when getting image")
self.user.image = Data(count: 0)
} else if let data = data,
!data.isEmpty{
// self.currentUser.image = Image(uiImage: UIImage(data: data)!).resizable()
self.user.image = data
} else {
// self.currentUser.image = Image(systemName: "person").resizable()
self.user.image = Data(count: 0)
}
})
} else if let error = error {
print(error)
}
}
}
return completion()
}
public func getXP() -> Int{
updateXP {
return (self.user.xp) as Int
}
}
public func getLevel() -> Int {
updateLevel(completion: {
return (self.user.level) as Int
})
}
public func getName() -> String {
updateName(completion: {
return (self.user.name) as String
})
}
public func getBio() -> String {
updateBio(completion: {
return (self.user.bio) as String
})
}
public func getInterest() -> String {
updateInterest(completion: {
return (self.user.interest) as String
})
}
public func getPhoto() -> Data {
updatePhoto(completion: {
return (self.user.image) as Data
})
}
func updatePersonalInfo() {
//sending user data to Firebase
let uid = Auth.auth().currentUser?.uid
isLoading = true
self.uploadImage(imageData: self.getPhoto(), path: "profile_Photos") { (url) in
self.ref.collection("Users").document(uid ?? "").setData([
"uid": uid ?? "",
"imageurl": url,
"username": self.user.name,
"bio": self.user.bio,
"interest" : self.user.interest
], merge: true) { (err) in
if err != nil{
self.isLoading = false
return
}
self.isLoading = false
// success means settings status as true...
self.status = true
}
}
}
func increaseXPnLV() {
//sending user data to Firebase
let uid = Auth.auth().currentUser!.uid
let docRef = ref.collection("Users").document(uid)
docRef.getDocument { (document, error) in
if let document = document, document.exists {
docRef.updateData(["xp": FieldValue.increment(Int64(50))])
// update level
let xp = document.data()!["xp"] as! Int
docRef.updateData(["level": self.user.xp2Level(xp: xp)])
} else {
print("Document does not exist")
}
}
}
func uploadImage(imageData: Data, path: String, completion: #escaping (String) -> ()){
let storage = Storage.storage().reference()
let uid = Auth.auth().currentUser?.uid
storage.child(path).child(uid ?? "").putData(imageData, metadata: nil) { (_, err) in
print("imageData: \(imageData)")
if err != nil{
completion("")
return
}
// Downloading Url And Sending Back...
storage.child(path).child(uid ?? "").downloadURL { (url, err) in
if err != nil{
completion("")
return
}
completion("\(url!)")
}
}
}
}
// This is View
import SwiftUI
import CoreData
import Firebase
import FirebaseFirestore
struct Goals: View {
let current_user_id = Auth.auth().currentUser?.uid
#State private var showingAlert = false
var ref = Firestore.firestore()
#StateObject var currentUser: userViewModel
#StateObject var homeData = HomeViewModel()
#State var txt = ""
#State var edge = UIApplication.shared.windows.first?.safeAreaInsets
#FetchRequest(entity: Goal.entity(), sortDescriptors: [NSSortDescriptor(key: "date",
ascending: true)], animation: .spring()) var results : FetchedResults<Goal>
// let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
#State private var greeting : String = "Hello"
#Environment(\.managedObjectContext) var context
var body: some View {
ForEach(results){goal in
Button(action: {
context.delete(goal)
try! context.save()
if current_user_id != nil {
currentUser.updateXPnLV()
self.showingAlert = true
}
}, label: Text("Something")
)
.alert(isPresented: $showingAlert) {
() -> Alert in
Alert(title: Text("Congratulations!"), message: Text("You completed a goal today, XP+50!"), dismissButton: .default(Text("OK")))
}
}
}
}
EDIT
Another error I saw is AttributeGraph precondition failure: attribute failed to set an initial value: 805912, ForEachChild<Array<userInfoModel>, ObjectIdentifier, HStack<VStack<HStack<TupleView<(Text, Divider, Text)>>>>>.
AppStorage is for use in a View while it may appear to be working all the SwiftUI wrappers with the exception of #Published inside an ObservableObject seem to be unreliable outside of a struct that is a View.
https://developer.apple.com/documentation/swiftui/appstorage
As a standard practice all your class and struct should be capitalized so change class CurrentUserViewModel andclass UserInfoModel
Also, change #StateObject var currentUser: currentUserViewModel to #StateObject var currentUser: CurrentUserViewModel = CurrentUserViewModel()
The initialization is the most important part that is missing.
Everything in the ForEach is just floating it isn't within a variable, function or inside the body. Where is your body?
This is probably what the error is talking about. wrap all of that code in a body
var body: some View {
//All the code for the ForEach
}
Your Button seems to be missing a title or label
Remove this line
() -> Alert in
I am sure there are other little things. I suggest you start from scratch in this View and you start putting in code line by line.
Here is a starting point. The Firebase part needs quite a but of work but you should be able to get started by focusing on the code that I commented out and removing the code to mimic a response from Firebase.
All of this is in the FirebaseManager class
Once this is working the rest will work.
The code as is works so you can see it in action with the fake responses
///Keep all the Firebase Code HERE
class FirebaseManager{
//private var ref = Firestore.firestore()
//private let store = Storage.storage().reference()
func retrieveFromDB(collectionName: String, variableName: String, completion: #escaping (Result<Any, Error>) -> Void) {
print(#function)
//This is sample code, likely has errors because I dont have Firebase setup but you can see the logic so you can touch up
// guard let uid = Auth.auth().currentUser?.uid else {
// completion(.failure(FirebaseError.notLoggedIn))
// return
// }
// catch the information of the current user
// let db = ref.collection(collectionName)
// db.addSnapshotListener { [self] (querySnapshot, error) in
//
// if let error = error{
// completion(.failure(error))
// return
// }
// guard (querySnapshot?.documents) != nil else {
// print("Document is empty")
// completion(.failure(FirebaseError.emptyDocument))
// return
// }
// let docRef = db.(document(uid)
//
// docRef.getDocument { (snapshot, error) in
// if let error = error{
// completion(.failure(error))
// return
// }
//
// completion(.success(snapshot.get(variableName)))
// }
// }
//For sample purposes I will mimic response remove this in your actual code
DispatchQueue.main.async {
if variableName == "xp" || variableName == "level"{
completion(.success(Int.random(in: 0...200)))
}else{
let strings = ["apple", "orange", "banana", "kiwi", "startfruit"]
completion(.success(strings.randomElement()!))
}
}
}
///For Int variables
func retrieveFromUsers(variableName: String, completion: #escaping (Result<Int, Error>) -> Void) {
print(#function)
retrieveFromDB(collectionName: "Users", variableName: variableName, completion: {result in
switch result {
case .success(let value):
let xp = value as? Int
if xp != nil{
completion(.success(xp!))
}else{
completion(.failure(FirebaseError.wrongType))
}
return
case .failure(let error):
print(error)
completion(.failure(error))
}
})
}
///For String variables
func retrieveUserProperty(variableName: String, completion: #escaping (Result<String, Error>) -> Void) {
print(#function)
retrieveFromDB(collectionName: "Users", variableName: variableName, completion: {result in
switch result {
case .success(let value):
let username = value as? String
if username != nil{
completion(.success(username!))
}else{
completion(.failure(FirebaseError.wrongType))
}
return
case .failure(let error):
print(error)
completion(.failure(error))
}
})
}
func retrieveXP(completion: #escaping (Result<Int, Error>) -> Void) {
print(#function)
retrieveFromUsers(variableName: "xp", completion: completion)
}
func retrieveLevel(completion: #escaping (Result<Int, Error>) -> Void) {
print(#function)
retrieveFromUsers(variableName: "level", completion: completion)
}
func retrieveName (completion: #escaping (Result<String, Error>) -> Void) {
print(#function)
retrieveUserProperty(variableName: "username", completion: completion)
}
func retrieveBio (completion: #escaping (Result<String, Error>) -> Void) {
print(#function)
retrieveUserProperty(variableName: "bio", completion: completion)
}
func retrieveInterest (completion: #escaping (Result<String, Error>) -> Void) {
print(#function)
retrieveUserProperty(variableName: "interest", completion: completion)
}
//Database code to retrieve Image needs to be added
func updateDB(collectionName: String, variableName: String, incrementBy: Int, completion: #escaping (Result<Int, Error>) -> Void) {
print(#function)
//sending user data to Firebase
// let uid = Auth.auth().currentUser!.uid
// let docRef = ref.collection(collectionName).document(uid)
// docRef.getDocument { (document, error) in
// if let document = document, document.exists {
// docRef.updateData([variableName: FieldValue.increment(incrementBy)])
//let newValue = document.data()![variableName] as! Int
// completion(.success(newValue))
// } else {
// completion(.failure(FirebaseError.documentDoesntExist))
// }
// }
//For sample purposes I will mimic response remove this in your actual code
DispatchQueue.main.async {
completion(.success(Int.random(in: 0...200) + incrementBy))
}
}
func updateDB(collectionName: String, variableName: String, value: String, completion: #escaping (Result<String, Error>) -> Void) {
print(#function)
//sending user data to Firebase
// let uid = Auth.auth().currentUser!.uid
// let docRef = ref.collection(collectionName).document(uid)
// docRef.getDocument { (document, error) in
// if let document = document, document.exists {
// docRef.updateData([variableName: value])
//let newValue = document.data()![variableName] as! Int
// completion(.success(newValue))
// } else {
// completion(.failure(FirebaseError.documentDoesntExist))
// }
// }
//For sample purposes I will mimic response remove this in your actual code
DispatchQueue.main.async {
let strings = ["apple", "orange", "banana", "kiwi", "startfruit"]
completion(.success(strings.randomElement()!))
}
}
func updateDB(collectionName: String, variableName: String, value: Int, completion: #escaping (Result<Int, Error>) -> Void) {
print(#function)
//sending user data to Firebase
// let uid = Auth.auth().currentUser!.uid
// let docRef = ref.collection(collectionName).document(uid)
// docRef.getDocument { (document, error) in
// if let document = document, document.exists {
// docRef.updateData([variableName: value])
//let newValue = document.data()![variableName] as! Int
// completion(.success(newValue))
// } else {
// completion(.failure(FirebaseError.documentDoesntExist))
// }
// }
//For sample purposes I will mimic response
DispatchQueue.main.async {
completion(.success(Int.random(in: 0...200)))
}
}
func updateUsers(variableName: String, value: String, completion: #escaping (Result<String, Error>) -> Void) {
print(#function)
updateDB(collectionName: "Users", variableName: variableName, value: value, completion: completion)
}
func updateUsers(variableName: String, value: Int, completion: #escaping (Result<Int, Error>) -> Void) {
print(#function)
updateDB(collectionName: "Users", variableName: variableName, value: value, completion: completion)
}
func updateUsers(variableName: String, incrementBy: Int, completion: #escaping (Result<Int, Error>) -> Void) {
print(#function)
updateDB(collectionName: "Users", variableName: variableName, incrementBy: incrementBy, completion: completion)
}
//Code to update Image will need to be added
}
Here is the rest
import SwiftUI
import CoreData
//Capitalized no other changes here
struct UserModel {
var uid = UUID()
var name = ""
var bio = ""
var interest = ""
var level = 1
var xp = 0
var email = ""
var image: Data = Data(count: 0)
init() {
print(#function)
}
init(_ name:String, _ xp: Int) {
print(#function)
self.name = name
self.xp = xp
self.level = self.xp2Level(xp: xp)
}
func xp2Level(xp:Int) -> Int {
print(#function)
if xp < 9500 {
return xp / 500 + 1
}
else if xp < 29500 {
return (xp - 9500) / 1000 + 1
}
else {
return (xp - 29500) / 2000 + 1
}
}
}
//This is to standardize what comes from your Firebase Code. Try to condense code that is duplicated
enum FirebaseError: Error {
case notLoggedIn
case emptyDocument
case wrongType
case documentDoesntExist
}
Capitalize
class UserViewModel: ObservableObject {
let alertVM = AlertViewModel.shared
#Published var user: UserModel = UserModel()
#Published var isLoading = false
//AppStorage wont work here
var status: Bool{
get{
UserDefaults.standard.bool(forKey: "status")
}
set{
UserDefaults.standard.set(newValue, forKey: "status")
}
}
//Separate all Firebase Code
let firebaseManager = FirebaseManager()
init() {
populateAllVariables()
}
func increaseXPnLV() {
print(#function)
//sending xp to Firebase
firebaseManager.updateUsers(variableName: "xp", incrementBy: 50, completion: {result in
switch result {
case .success(let newXp):
self.user.xp = newXp
//sending level to Firebase
self.firebaseManager.updateUsers(variableName: "level", value: self.user.xp2Level(xp: newXp), completion: {result in
switch result {
case .success(let newLevel):
print("newLevel = \(newLevel)")
self.user.level = newLevel
self.alertVM.scheduleAlert(title: "Congratulations!", message: "You completed a goal today, XP+50!")
return
case .failure(let error as NSError):
//Show alert here
self.alertVM.scheduleAlert(error: error)
print(error)
}
})
return
case .failure(let error):
//Show alert here
self.alertVM.scheduleAlert(error: error)
print(error)
}
})
}
func populateAllVariables() {
print(#function)
getXP()
getLevel()
getName()
getBio()
getInterest()
}
public func getXP() {
print(#function)
firebaseManager.retrieveXP(completion: {result in
switch result {
case .success(let xp):
self.user.xp = xp
case .failure(let error):
//Show alert here
self.alertVM.scheduleAlert(error: error)
print(error)
}
})
}
public func getLevel() {
print(#function)
firebaseManager.retrieveLevel(completion: {result in
switch result {
case .success(let level):
self.user.level = level
case .failure(let error):
//Show alert here
self.alertVM.scheduleAlert(error: error)
print(error)
}
})
}
public func getName() {
print(#function)
firebaseManager.retrieveName(completion: {result in
switch result {
case .success(let name):
self.user.name = name
case .failure(let error):
//Show alert here
self.alertVM.scheduleAlert(error: error)
print(error)
}
})
}
public func getBio() {
print(#function)
firebaseManager.retrieveBio(completion: {result in
switch result {
case .success(let bio):
self.user.bio = bio
case .failure(let error):
//Show alert here
self.alertVM.scheduleAlert(error: error)
print(error)
}
})
}
public func getInterest() {
print(#function)
firebaseManager.retrieveInterest(completion: {result in
switch result {
case .success(let interest):
self.user.interest = interest
case .failure(let error):
//Show alert here
self.alertVM.scheduleAlert(error: error)
print(error)
}
})
}
///This will need work
// public func getPhoto() -> Data {
// updatePhoto(completion: {
// return (self.user.image) as Data
// })
// }
//It is best to separate work from the View
func deleteGoal(moc: NSManagedObjectContext, goal: Goal) -> Bool{
print(#function)
var result = false
moc.performAndWait {
moc.delete(goal)
do{
try moc.save()
result = true
}catch{
self.alertVM.scheduleAlert(error: error)
result = false
}
}
return result
}
}
//This is to centralize alerts. When you are using the web there will be errors and therefore alerts that the user should be aware of
struct CustomAlert: Identifiable {
let id: UUID = UUID()
let title: String
let message: String
let dismissButtonTitle: String
}
//Again to centralize the alerts. I like putting this on the uppermost View so you can send alerts from anywhere
class AlertViewModel: ObservableObject {
//Singleton keeps everything connected
static let shared: AlertViewModel = AlertViewModel()
#Published var currentAlert: CustomAlert?
private init() {
//Required because you need to share the instance
}
//Use this for a custom message
func scheduleAlert(title: String = "ERROR", message: String, dismissButtonTitle: String = "OK") {
currentAlert = CustomAlert(title: title, message: message, dismissButtonTitle: dismissButtonTitle)
}
//Use this if you have a fully formed Error
func scheduleAlert(error: Error) {
let error = error as NSError
currentAlert = CustomAlert(title: "ERROR", message: (error.localizedFailureReason ?? "") + error.localizedDescription + (error.localizedRecoverySuggestion ?? ""), dismissButtonTitle: "OK")
}
}
struct Goals: View {
//The View should never be aware of where your data is stored. all Firebase code should be removed
#StateObject var currentUser: UserViewModel = UserViewModel()
//This observes the alerts that are sent from anywhere
#StateObject var alertVM: AlertViewModel = AlertViewModel.shared
//No code provided
//#StateObject var homeData = HomeViewModel()
#FetchRequest(entity: Goal.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Goal.date, ascending: true)], animation: .spring()) var results : FetchedResults<Goal>
#Environment(\.managedObjectContext) var context
var body: some View {
VStack{
Text("Name = " + currentUser.user.name.description)
Text("Level = " + currentUser.user.level.description)
Text("XP = " + currentUser.user.xp.description)
ForEach(results){goal in
Button(action: {
//There should be some kind of check here to make sure the goal got deleted
if currentUser.deleteGoal(moc: context, goal: goal){
//No code provided
//if current_user_id != nil {
currentUser.increaseXPnLV()
}
//}
}, label: {
//Missing Brackets
Text("Goal \(goal.name?.description ?? "") Completed")
})
//This gets presented from everywhere
.alert(item: $alertVM.currentAlert, content: {current in
Alert(title: Text(current.title), message: Text(current.message), dismissButton: .cancel(Text(current.dismissButtonTitle)))
})
}
}
}
}

How can I call a function from a Swift file and use it in a ViewController?

I have a Swift file that gets details about the user that is currently logged in/signed up named CognitoUserPoolController.swift
import Foundation
import AWSCognitoIdentityProvider
class CognitoUserPoolController {
let userPoolRegion: AWSRegionType = "Private Info"
let userPoolID = "Private Info"
let appClientID = "Private Info"
let appClientSecret = "Private Info"
var userPool:AWSCognitoIdentityUserPool?
var currentUser:AWSCognitoIdentityUser? {
get {
return userPool?.currentUser()
}
}
static let sharedInstance: CognitoUserPoolController = CognitoUserPoolController()
private init() {
let serviceConfiguration = AWSServiceConfiguration(region: userPoolRegion, credentialsProvider: nil)
let poolConfiguration = AWSCognitoIdentityUserPoolConfiguration(clientId: appClientID,
clientSecret: appClientSecret,
poolId: userPoolID)
AWSCognitoIdentityUserPool.register(with: serviceConfiguration,
userPoolConfiguration: poolConfiguration,
forKey:"AWSChat")
userPool = AWSCognitoIdentityUserPool(forKey: "AWSChat")
AWSDDLog.sharedInstance.logLevel = .verbose
}
func login(username: String, password:String, completion:#escaping (Error?)->Void) {
let user = self.userPool?.getUser(username)
let task = user?.getSession(username, password: password, validationData:nil)
task?.continueWith(block: { (task: AWSTask<AWSCognitoIdentityUserSession>) -> Any? in
if let error = task.error {
completion(error)
return nil
}
completion(nil)
return nil
})
}
func signup(username: String, password:String, emailAddress:String, completion:#escaping (Error?, AWSCognitoIdentityUser?)->Void) {
var attributes = [AWSCognitoIdentityUserAttributeType]()
let emailAttribute = AWSCognitoIdentityUserAttributeType(name: "email", value: emailAddress)
attributes.append(emailAttribute)
print(emailAttribute.value!)
let task = self.userPool?.signUp(username, password: password, userAttributes: attributes, validationData: nil)
task?.continueWith(block: {(task: AWSTask<AWSCognitoIdentityUserPoolSignUpResponse>) -> Any? in
if let error = task.error {
completion(error, nil)
return nil
}
guard let result = task.result else {
let error = NSError(domain: "Private Info",
code: 100,
userInfo: ["__type":"Unknown Error", "message":"Cognito user pool error."])
completion(error, nil)
return nil
}
completion(nil, result.user)
return nil
})
}
func confirmSignup(user: AWSCognitoIdentityUser, confirmationCode:String, completion:#escaping (Error?)->Void) {
let task = user.confirmSignUp(confirmationCode)
task.continueWith { (task: AWSTask<AWSCognitoIdentityUserConfirmSignUpResponse>) -> Any? in
if let error = task.error {
completion(error)
return nil
}
completion(nil)
return nil
}
}
func resendConfirmationCode(user: AWSCognitoIdentityUser, completion:#escaping (Error?)->Void) {
let task = user.resendConfirmationCode()
task.continueWith { (task: AWSTask<AWSCognitoIdentityUserResendConfirmationCodeResponse>) -> Any? in
if let error = task.error {
completion(error)
return nil
}
completion(nil)
return nil
}
}
func getUserDetails(user: AWSCognitoIdentityUser, completion:#escaping (Error?, AWSCognitoIdentityUserGetDetailsResponse?)->Void) {
let task = user.getDetails()
task.continueWith(block: { (task: AWSTask<AWSCognitoIdentityUserGetDetailsResponse>) -> Any? in
if let error = task.error {
completion(error, nil)
return nil
}
guard let result = task.result else {
let error = NSError(domain: "Private Info",
code: 100,
userInfo: ["__type":"Unknown Error", "message":"Cognito user pool error."])
completion(error, nil)
return nil
}
completion(nil, result)
return nil
})
}
}
After a user successfully signs up they are presented with HomeViewController. In HomeViewController I try to print an attribute email value like this but it does not work
import UIKit
import AWSCognitoIdentityProvider
class HomeViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let userpoolController = CognitoUserPoolController.sharedInstance
userpoolController.getUserDetails(user: userpoolController.currentUser!) { (error: Error?, details:AWSCognitoIdentityUserGetDetailsResponse?) in
view.backgroundColor = .green // This line of code works, but below this line it does not.
if let loggedInUserAttributes = details?.userAttributes {
self.view.backgroundColor = .systemPink
for attribute in loggedInUserAttributes {
if attribute.name?.compare("email") == .orderedSame {
print ("Email address of logged-in user is \(attribute.value!)")
}
}
}
}
}
}
The background color successfully changes to green but does not change to pink (That was to see if the code was working.) Inside of the if let statement is where the code is not working and there are not any errors. How can I properly fix this?

Update two fields at once with updateData

I am changing my online status with this code:
static func online(for uid: String, status: Bool, success: #escaping (Bool) -> Void) {
//True == Online, False == Offline
let db = Firestore.firestore()
let lastTime = Date().timeIntervalSince1970
let onlineStatus = ["onlineStatus" : status]
let lastTimeOnline = ["lastTimeOnline" : lastTime]
let ref = db.collection("users").document(uid)
ref.updateData(lastTimeOnline) {(error) in
if let error = error {
assertionFailure(error.localizedDescription)
success(false)
}
success(true)
}
ref.updateData(onlineStatus) {(error) in
if let error = error {
assertionFailure(error.localizedDescription)
success(false)
}
success(true)
}
}
I update the lastTimeOnline and the onlineStatus.
I listen to this updates via:
// Get the user online offline status
func getUserOnlineStatus(completion: #escaping (Dictionary<String, Any>) -> Void) {
let db = Firestore.firestore()
db.collection("users").addSnapshotListener { (querySnapshot, error) in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .modified) {
//GETS CALLED TWICE BUT I ONLY WANT ONCE
print("modified called..")
guard let onlineStatus = diff.document.get("onlineStatus") as? Bool else {return}
guard let userId = diff.document.get("uid") as? String else {return}
var userIsOnline = Dictionary<String, Any>()
userIsOnline[userId] = [onlineStatus, "huhu"]
completion(userIsOnline)
}
}
}
}
The problem is now, since I use ref.updateData twice, my SnapshotListener .modified returns the desired data twice.
How can I update two fields in a single call, so my .modified just return one snapshot?
You can try to combine them
let all:[String:Any] = ["onlineStatus" : status ,"lastTimeOnline" : lastTime]
let ref = db.collection("users").document(uid)
ref.updateData(all) {(error) in
if let error = error {
assertionFailure(error.localizedDescription)
success(false)
}
success(true)
}