I have NSNotifications using Kugel that are working great on the watch simulator and both the iPhone and iPhone simulator to deliver messages to update the UI/state but these are failing to deliver on the watch side when testing on the devices.
The issue I believe is that the NSNotifications are triggered based on a WCSession message from the iPhone. Everything works fine on the simulator and iPhone side possibly because the connection and notifications are always delivered since the sim keep the watch app active all the time and the iPhone has full session support. On the watch there is the potential for failure of both the session and possibly the notification based on the state of the watch.
Debugging on the watch is painfully slow. It's taking 5-10 minutes just to start the debug process!
Can someone point me to some reading on how best to ensure a phone message is received on the watch and the watch app informed of the need to update based on a message? Or maybe some good debugging code that can log WCSession and NSNotification information that I can review later?
My code is fairly straightforward but still a work in progress ....
On both sides I create a singleton to manage the session, here is the phone side code:
import WatchConnectivity
import UIKit
// This class manages messaging between the Watch and iPhone
class PhoneSession: NSObject, WCSessionDelegate
{
static let manager = PhoneSession()
private var appDelegate: AppDelegate!
private let session: WCSession? = WCSession.isSupported() ? WCSession.defaultSession() : nil
private var validSession: WCSession?
{
if let session = session where session.reachable
{
return session
}
return nil
}
func startSession()
{
session?.delegate = self
session?.activateSession()
appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
}
func isEditing() -> Bool
{
if (UIApplication.sharedApplication().applicationState == .Active)
{
if (appDelegate.mainView.visible && appDelegate.mainView.currentDay.isToday())
{
return false
}
return true
}
return false
}
}
extension PhoneSession
{
func sendEditing()
{
if session!.reachable
{
sendMessage([Keys.UpdateType : PhoneUpdateType.Editing.rawValue])
}
}
func sendDoneEditing()
{
if session!.reachable
{
sendMessage([Keys.UpdateType : PhoneUpdateType.DoneEdit.rawValue])
}
}
func sendTable()
{
let tableInfo: WatchWorkout = PhoneData().buildWatchTableData()
let archivedTable: NSData = NSKeyedArchiver.archivedDataWithRootObject(tableInfo)
if session!.reachable
{
sendMessage([Keys.UpdateType : PhoneUpdateType.TableInfo.rawValue, Keys.Workout: archivedTable])
}
else
{
do
{
try updateApplicationContext([Keys.UpdateType : PhoneUpdateType.TableInfo.rawValue, Keys.Workout: archivedTable])
}
catch
{
print("error sending info: \(error)")
}
}
}
func sendRowDone(row: Int, done: Bool)
{
if session!.reachable
{
sendMessage([Keys.UpdateType : PhoneUpdateType.RowDone.rawValue,
Keys.RowIndex: row, Keys.Done: done])
}
else
{
let tableInfo: WatchWorkout = PhoneData().buildWatchTableData()
let archivedTable: NSData = NSKeyedArchiver.archivedDataWithRootObject(tableInfo)
do
{
try updateApplicationContext( [Keys.UpdateType : PhoneUpdateType.TableInfo.rawValue,
Keys.Workout: archivedTable])
}
catch
{
print("error sending info: \(error)")
}
}
}
func receivedRowDone(info: [String : AnyObject])
{
let row: Int = info[Keys.Row] as! Int
let done: Bool = info[Keys.Done] as! Bool
PhoneData.manager.updateInfoFromWatch(row, done: done)
}
func receivedRowInfo(info: [String : AnyObject])
{
let row: Int = info[Keys.Row] as! Int
let rest: Int = info[Keys.Rest] as! Int
let reps: Int = info[Keys.Reps] as! Int
let force: Double = info[Keys.Force] as! Double
PhoneData.manager.updateSetInfoFromWatch(row, rest: rest, reps: reps, force: force)
}
func receivedTableDone(info: [String : AnyObject])
{
let date: Int = info[Keys.Date] as! Int
let dones: [Bool] = info[Keys.TableDones] as! [Bool]
PhoneData.manager.updateDones(dones, forDate: date)
Kugel.publish(PhoneNotificationKeys.ReloadTable)
}
func receivedTableComplete()
{
Kugel.publish(PhoneNotificationKeys.ReloadTable)
}
func receivedStartRest()
{
Kugel.publish(PhoneNotificationKeys.StartRest)
}
func receivedInfo(info: [String : AnyObject]) -> NSData?
{
let messageString: String = info[Keys.UpdateType] as! String
let updateType: WatchUpdateType = WatchUpdateType.getType(messageString)
switch (updateType)
{
case .RowInfo:
receivedRowInfo(info)
case .TableDone:
receivedTableDone(info)
case .RowDone:
receivedRowDone(info)
case .TableComplete:
receivedTableComplete()
case .StartRest:
receivedStartRest()
case .RequestUpdate:
let tableInfo: WatchWorkout = PhoneData().buildWatchTableData()
let archivedTable: NSData = NSKeyedArchiver.archivedDataWithRootObject(tableInfo)
return archivedTable
case .Ignore:
print("Opps")
}
return nil
}
}
// MARK: Interactive Messaging
extension PhoneSession
{
// Sender
func sendMessage(message: [String : AnyObject], replyHandler: (([String : AnyObject]) -> Void)? = nil, errorHandler: ((NSError) -> Void)? = nil)
{
validSession!.sendMessage(message,
replyHandler:
{
(returnMessage: [String : AnyObject]) -> Void in
if let theMessage = returnMessage[Keys.MessageStatus]
{
print("Return Message from Watch: \(theMessage)")
}
},
errorHandler:
{
(error) -> Void in
print("Error Message during transfer to Watch: \(error)")
}
)
}
func session(session: WCSession, didReceiveMessage message: [String : AnyObject])
{
self.receivedInfo(message)
}
// Receiver
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void)
{
let returnMessage = self.receivedInfo(message)
if (returnMessage != nil)
{
if let archivedTable: NSData = returnMessage!
{
let replyValues = [Keys.UpdateType : PhoneUpdateType.TableInfo.rawValue, Keys.Workout: archivedTable] // Data to be returned
replyHandler(replyValues)
}
}
}
}
// MARK: Application Context
// use when your app needs only the latest information, if the data was not sent, it will be replaced
extension PhoneSession
{
// Sender
func updateApplicationContext(applicationContext: [String : AnyObject]) throws
{
if ((session) != nil)
{
do
{
try session!.updateApplicationContext(applicationContext)
}
catch let error
{
throw error
}
}
}
// Receiver
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject])
{
self.receivedInfo(applicationContext)
}
}
and this is the watch side:
import WatchConnectivity
class WatchSession: NSObject, WCSessionDelegate
{
static let manager = WatchSession()
private let session: WCSession? = WCSession.isSupported() ? WCSession.defaultSession() : nil
private var validSession: WCSession?
{
if let session = session where session.reachable
{
return session
}
return nil
}
func startSession()
{
session?.delegate = self
session?.activateSession()
}
}
extension WatchSession
{
func sendRowInfo(row:Int, rest: Int, reps: Int, force: Double)
{
if session!.reachable
{
let message: [String: AnyObject] = [Keys.UpdateType : WatchUpdateType.RowInfo.rawValue,
Keys.Row : row,
Keys.Rest : rest,
Keys.Reps : reps,
Keys.Force : force]
sendMessage(message)
print("sent row done to Phone: \(message)")
}
else
{
sendTableDone()
print("failed to connect to Phone, sent table done context to Phone")
}
}
func sendRowDone(row:Int, done: Bool)
{
if session!.reachable
{
let message: [String: AnyObject] = [Keys.UpdateType : WatchUpdateType.RowDone.rawValue,
Keys.Row : row,
Keys.Done : done]
sendMessage(message)
print("sent row done to Phone: \(message)")
}
else
{
sendTableDone()
print("failed to connect to Phone, sent table done context to Phone")
}
}
func sendTableDone()
{
let tableDones: [Bool] = WatchData.manager.watchTableDone()
let date: Int = WatchData.manager.date()
do
{
try updateApplicationContext( [Keys.UpdateType : WatchUpdateType.TableDone.rawValue,
Keys.Date : date, Keys.TableDones: tableDones])
}
catch _
{
print("error trying to send TableDones")
}
}
func sendTableComplete()
{
if session!.reachable
{
sendMessage([Keys.UpdateType : WatchUpdateType.TableComplete.rawValue])
}
else
{
let date: Int = WatchData.manager.date()
do
{
try updateApplicationContext( [Keys.UpdateType : WatchUpdateType.TableComplete.rawValue,
Keys.Date : date])
}
catch _
{
print("error trying to send TableComplete")
}
}
}
func sendRest() -> Bool
{
var sent: Bool = false
if session!.reachable
{
sendMessage([Keys.UpdateType : WatchUpdateType.StartRest.rawValue])
sent = true
}
return sent
}
func requestUpdate() -> Bool
{
var sent: Bool = false
if session!.reachable
{
print("requesting update reply")
sendMessage([Keys.UpdateType : WatchUpdateType.RequestUpdate.rawValue])
sent = true
}
return sent
}
func receivedUpdateReply(info: [String : AnyObject])
{
}
func receiveRowDone(info: [String : AnyObject])
{
let row: Int = info[Keys.RowIndex] as! Int
let done: Bool = info[Keys.Done] as! Bool
WatchData.manager.updateWatchTable(row, done: done)
Kugel.publish(WatchNotificationKeys.UpdateRow)
}
func receivedTable(archivedTable: NSData)
{
let workout: WatchWorkout = NSKeyedUnarchiver.unarchiveObjectWithData(archivedTable) as! WatchWorkout
WatchData.manager.updateWatchWorkout(workout)
Kugel.publish(WatchNotificationKeys.ReloadTable)
}
func receivedStartEditStatus()
{
Kugel.publish(WatchNotificationKeys.StartEdit)
}
func receivedDoneEditStatus()
{
WatchData.manager.retrieveWorkout()
Kugel.publish(WatchNotificationKeys.DoneEdit)
}
func receivedStopRest()
{
Kugel.publish(WatchNotificationKeys.StopRest)
}
func receivedInfo(info: [String : AnyObject])
{
let messageString: String = info[Keys.UpdateType] as! String
let updateType: PhoneUpdateType = PhoneUpdateType.getType(messageString)
switch (updateType)
{
case .TableInfo:
receivedTable(info[Keys.Workout] as! NSData)
case .Editing:
receivedStartEditStatus()
case .DoneEdit:
receivedDoneEditStatus()
case .RowDone:
receiveRowDone(info)
case .StopRest:
receivedStopRest()
case .Ignore:
print("Opps")
}
}
func receivedReply(info: [String : AnyObject])
{
if let replyString: String = info[Keys.ReplyType] as? String
{
let replyType: ReplyType = ReplyType.getType(replyString)
switch (replyType)
{
case .Table:
print("received Reply Table")
receivedTable(info[Keys.Workout] as! NSData)
case .NoData:
print("Opps ... nodata in reply")
case .Ignore:
print("Opps replyType message error")
}
}
}
}
// MARK: Interactive Messaging
extension WatchSession
{
// Sender
func sendMessage(message: [String : AnyObject], replyHandler: (([String : AnyObject]) -> Void)? = nil, errorHandler: ((NSError) -> Void)? = nil)
{
validSession!.sendMessage(message,
replyHandler:
{
(replyMessage: [String : AnyObject]) -> Void in
if let typeMessage: String = replyMessage[Keys.ReplyType] as? String
{
self.receivedReply(replyMessage)
print("Return Message from Phone: \(typeMessage)")
}
},
errorHandler:
{
(error) -> Void in
print("Error Message during transfer to Phone: \(error)")
}
)
}
// Receiver
func session(session: WCSession, didReceiveMessage message: [String : AnyObject])
{
self.receivedInfo(message)
}
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void)
{
self.receivedInfo(message)
let replyValues = [Keys.MessageStatus : "Watch received message"] // Data to be returned
replyHandler(replyValues)
}
}
// MARK: Application Context
extension WatchSession
{
// Sender
func updateApplicationContext(applicationContext: [String : AnyObject]) throws
{
if let session = validSession
{
do
{
try session.updateApplicationContext(applicationContext)
}
catch let error
{
throw error
}
}
}
// Receiver
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject])
{
// handle receiving application context
receivedInfo(applicationContext)
}
}
I create the singleton in my AppDelegate on the iPhone side and the ExtensionDelegate on the watch side, here is the phone side:
var phoneSession: PhoneSession!
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool
{
self.phoneSession = PhoneSession()
PhoneSession.manager.startSession()
The basic logic when sending a message is it looks if the other side is reachable, if it is sendMessage is used, if it is not reachable then sendApplicationContext is used.
When the message is received on the phone side there is some extra logic to see if the app is in the foreground or background, if in foreground it will push a notification onto the main thread, if in background just the info is updated. On the watch side it always pushes onto the main thread since my understanding is messages will not be received in the background.
Related
I have part of my Apple Watch app behind a paywall, but I am unable to extract information from the WCSession transferUserInfo session.
Also, how do I store that data on the Watch app as no NSUserDefaults are available on the Watch?
Here is my code, that does open the session, but does not pass data correctly:
ViewController
//This creates the dictionary that will be passed to the Watch
var WatchInAppPurchases = ["product1":Bool(), "product2":Bool(),]
func dictionaryCreator() {
if (UserDefaults.standard.value(forKey: "product1purchased") != nil) == true {
WatchInAppPurchases["product1"] = true
} else {
WatchInAppPurchases["product1"] = false
}
if (UserDefaults.standard.value(forKey: "product2purchased") != nil) == true {
WatchInAppPurchases["product2"] = true
} else {
WatchInAppPurchases["product2"] = false
}
}
//This runs both with the .purchased and .restored function.
func syncronizeWatch() {
if WCSession.isSupported() {
let session = WCSession.default
session.delegate = self
session.activate()
session.transferUserInfo(WatchInAppPurchases)
}
}
Interface controller:
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
NSLog("%#", "activationDidCompleteWith activationState:\(activationState) error:\(String(describing: error))")
}
var userinfo0 : Bool?
var userinfo1 : Bool?
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
let userinfodic = userInfo
userinfo0 = userinfodic["product"]!
userinfo1 = userinfodic["product2"]!
}
After this runs, how am I supped store this data correctly?
if userInfo0 {
//show paid content?
}
I'm using pod 'Socket.IO-Client-Swift', '~> 14.0.0'
here is my socket manager file
I don't know why socketGetLatestMsg() callback repeat multiple times when I send message once this call one but after second message callback repeat twice after 3 msg callback repeat thrice and so on...
and go back to user lists inbox and come back to same user all repeated message show as it is but in database repeated message not save.
class SocketIOManager: NSObject {
static let shared = SocketIOManager(chatID: nil, token: nil);
var socket: SocketIOClient?
var manager: SocketManager?
init(chatID: Int?, token: String?) {
super.init();
if chatID != nil && token != nil {
manager = SocketManager(socketURL: URL(string: SOCKET_URL)!, config: [.log(true), .compress, .connectParams(["auth_token": token!, "user_id": chatID!])])
self.socket = manager!.defaultSocket;
} else if UserDefaults.standard.value(forKey: "userID") != nil && UserDefaults.standard.value(forKey: "authToken") != nil {
manager = SocketManager(socketURL: URL(string: SOCKET_URL)!, config: [.log(true), .compress, .connectParams(["auth_token": UserDefaults.standard.value(forKey: "authToken") as! String, "user_id": UserDefaults.standard.value(forKey: "userID") as! Int])])
self.socket = manager!.defaultSocket;
}
self.socket?.connect();
}
//MARK:- All Emit
func socketInboxEmit(userID: Int?,dictData: [String: Any]?) {
if dictData != nil {
self.socket?.on(clientEvent: .connect, callback: { (data, ack) in
self.socket?.emit("chatroom.inbox", dictData!);
self.getInboxList(chatID: userID);
print("get Inbox socket List");
})
}
}
func socketGetUsrMsgEmit(chatListID: Int?, userID: Int?, dictData: [String: Any]?) {
if chatListID != nil && dictData != nil && userID != nil {
self.socket?.emit("chatroom.messages", dictData!);
self.socketGetAllMsg(userID: userID, chatListID: chatListID!);
}
}
func socketSaveMsg(chatListID: Int, dictData: [String: Any]?) {
//self.socket?.on(clientEvent: .connect, callback: { (data, ack) in
self.socket?.emit("save_message", dictData!);
self.socketGetLatestMsg(chatListID: chatListID);
print("socket connected");
//})
}
func contactListEmit(dictData: [String: Any]?) {
self.socket?.emit("chatroom.friends", dictData!);
}
//MARK:- Get All Data
func socketCallBack(chatID: Int?, token: String?, completionHandler: #escaping (String, [String: Any]?) -> Void) {
if chatID != nil {
completionHandler("Fail", nil)
}
else {
print("chat id not match or nil");
}
}
func socketGetLatestMsg(chatListID: Int?) {
socket?.on("chatroom.\(chatListID!)", callback: { (data, ack) in
for filterData in data {
if ((filterData as? [String: Any]) != nil) {
ChatVM.sharedInstance.appendNewMsg(responseDict: filterData as! JSONDictionary)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "tblReload"), object: nil, userInfo: ["table": "reload"])
} else {
print("Ok")
}
}
})
}
func socketGetAllMsg(userID: Int?, chatListID: Int) {
self.socket?.on("chat.\(userID!).room.\(chatListID)", callback: { (data, ack) in
for filterData in data {
if ((filterData as? [String: Any]) != nil) {
ChatVM.sharedInstance.parseChatListData(responseDict: filterData as! JSONDictionary)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "tblReload"), object: nil, userInfo: ["table": "reload"])
} else {
print("Ok")
}
}
})
}
func getInboxList(chatID: Int?) {
socket?.on("user.\(chatID!).chatinbox", callback: { (data, ack) in
for filterData in data {
if ((filterData as? [String: Any]) != nil) {
ChatVM.sharedInstance.parseInboxListData(responseDict: filterData as! JSONDictionary);
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "tblInboxReload"), object: nil);
//completionHandler("Success", filterData as? [String : Any])
} else {
print("Ok")
//completionHandler("Fail", nil)
}
}
})
}
func getContactList(userID: Int?) {
socket?.on("user.\(userID!).friends", callback: { (data, ack) in
for filterData in data {
if ((filterData as? [String: Any]) != nil) {
ChatVM.sharedInstance.parseFriendListData(responseDict: filterData as! JSONDictionary)
NotificationCenter.default.post(name: NSNotification.Name(rawValue: "tblPendingReload"), object: nil, userInfo: filterData as! [AnyHashable : Any])
//completionHandler("Success", filterData as? [String : Any])
} else {
print("Ok")
//completionHandler("Fail", nil)
}
}
})
}
func closeSocket() {
self.socket?.disconnect();
}
func activeSocket() {
self.socket?.connect();
}
}
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 {
}
}
Thread Sanitizer does not show all issues. for example
private func observeConversationUsers(_ isObserve: Bool, conversationID: String, updated: (() -> Void)?, fail: ((_ error: Error) -> Void)?) {
guard isObserveConversationUsers != isObserve else { return }
isObserveConversationUsers = isObserve
DispatchQueue.global(qos: .background).async {
let conversationUserRef = Database.database().reference().child(MainGateways.chat.description).child(MainGateways.conversationUsers.description).child(conversationID)
if !isObserve {
conversationUserRef.removeAllObservers()
return
}
if !self.references.contains(conversationUserRef) { // for example this issue
self.references.append(conversationUserRef)
}
conversationUserRef.observe(.childAdded, with: { (snap) in
if snap.value is NSNull {
return
}
guard let dict = snap.value as? [String : Any] else { return }
guard let chatUserActivityModel = Mapper<ChatUserActivityModel>().map(JSON: dict) else { return }
self.downloadImageProfile(chatUserActivityModel.userID, conversationID: conversationID)
}, withCancel: { (error) in
// TODO: - it
})
conversationUserRef.observe(.childRemoved, with: { (snap) in
}, withCancel: { (error) in
// TODO: - it
})
}
}
My diagnostics settings
How can I fix it?
Update: I made a very simple example but Xcode does not show an error here, although the isPushSettingsFormVC property is called in another thread
class MainTabBarController: UITabBarController {
var isPushSettingsFormVC = false
override func viewDidLoad() {
super.viewDidLoad()
// I think here is a data race
DispatchQueue.global(qos: .background).async { [weak self] in
self?.isPushSettingsFormVC = false
}
}
}
I am making a chat app following a tutorial and everything was looking good except for these two errors in the same line. It might have something to do with other ViewControllers or files so just let me know.
Here is the code.
import UIKit
class SocketIOManager: NSObject {
static let sharedInstance = SocketIOManager()
var socket: SocketIOClient = SocketIOClient(socketURL: NSURL(string: "http://192.168.1.197")!)
override init() {
super.init()
}
func establishConnection() {
socket.connect()
}
func closeConnection() {
socket.disconnect()
}
func connectToServerWithNickname(nickname: String, completionHandler: (userList: [[String: AnyObject]]!) -> Void) {
socket.emit("connectUser", nickname)
socket.on("userList") { ( dataArray, ack) -> Void in
completionHandler(userList: dataArray[0] as! [[String: AnyObject]])
}
listenForOtherMessages()
}
func exitChatWithNickname(nickname: String, completionHandler: () -> Void) {
socket.emit("exitUser", nickname)
completionHandler()
}
func sendMessage(message: String, withNickname nickname: String) {
socket.emit("chatMessage", nickname, message)
}
func getChatMessage(completionHandler: (messageInfo: [String: AnyObject]) -> Void) {
socket.on("newChatMessage") { (dataArray, socketAck) -> Void in
var messageDictionary = [String: AnyObject]()
messageDictionary["nickname"] = dataArray[0] as! String
messageDictionary["message"] = dataArray[1] as! String
messageDictionary["date"] = dataArray[2] as! String
completionHandler(messageInfo: messageDictionary)
}
}
private func listenForOtherMessages() {
socket.on("userConnectUpdate") { (dataArray, socketAck) -> Void in
NSNotificationCenter.defaultCenter().postNotificationName("userWasConnectedNotification", object: dataArray[0] as! [String: AnyObject])
}
socket.on("userExitUpdate") { (dataArray, socketAck) -> Void in
NSNotificationCenter.defaultCenter().postNotificationName("userWasDisconnectedNotification", object: dataArray[0] as! String)
}
socket.on("userTypingUpdate") { (dataArray, socketAck) -> Void in
NSNotificationCenter.defaultCenter().postNotificationName("userTypingNotification", object: dataArray[0] as? [String: AnyObject])
}
}
func sendStartTypingMessage(nickname: String) {
socket.emit("startType", nickname)
}
func sendStopTypingMessage(nickname: String) {
socket.emit("stopType", nickname)
}
}
The line with the errors is,
var socket: SocketIOClient = SocketIOClient(socketURL: NSURL(string: "http://192.168.1.197")!)
Thanks.
Check if Source folder of socket.io-client swift master contains all files or not, if not you can again download that from https://github.com/socketio/socket.io-client-swift and copy source folder in your app
Go to the SocketIOClient file, open the File inspector on the right part of Xcode and see if SocketIOClient is part of your app target. The error might be caused by that.
Add import SocketIO to fix "Use of Undeclared Type SocketIOClient" error