unknown status after calling localizedNetworkReachabilityStatusString - swift

I need to send the status of the network to some analytics server, so I need to send it once the app starts. I tried to use Alamofire, but I usually get Unknown status, if there is some sort of delay it shows the right status :
These code would run in my AppDelegate (didFinishLaunchingWithOptions):
AFNetworkReachabilityManager.shared().startMonitoring()
AFNetworkReachabilityManager.shared().localizedNetworkReachabilityStatusString()
What is the best way to get the right status right away?
UPDATE 1:
I updated my code and tried to use completion handler, but why when I use this method it will print multiple YES?
connectedCompletionBlock({ connected in
if connected {
print("YES")
} else {
print("NO")
}
})
class func connectedCompletionBlock(_ completion: #escaping (_ connected: Bool) -> Void) {
AFNetworkReachabilityManager.shared().startMonitoring()
AFNetworkReachabilityManager.shared().setReachabilityStatusChange({ status in
var isConnected = false
let wifi = AFNetworkReachabilityStatus.reachableViaWiFi
let wwan = AFNetworkReachabilityStatus.reachableViaWWAN
if ( status == wifi || status == wwan) {
con = true
}
AFNetworkReachabilityManager.shared().stopMonitoring()
completion(isConnected)
})
}

Ok since nobody didn't answer, I think it's good to share the solution with you. The issue was this: I was calling this method on didFinishLaunchingWithOptions, and since it takes sometime for the Alamofire to figure out the connection status it would return unknown! I called it on applicationDidBecomeActive and it works fine now.

Related

Is it possible to use the beginBackgroundTask() API within SwiftUI lifecycle?

I need to run some code when the app is closed to remove the client from a game. To do this I'm wanting to execute a Google Cloud Function for the server to do the cleanup - the function works, I guess similar to this question I just do not have enough time, and I'm running a completion handler so it's not like iOS thinks the function is finished straight away.
I have seen multiple questions on this, many of which are rather old and do not include answers for the SwiftUI Lifecycle. I have seen this exact issue and a potential answer here, however I'm not using the Realtime Database, I'm using Firestore so there is no equivalents for the onDisconnect methods.
I have seen that you can increase the time you need when the application finishes through beginBackgroundTask(expirationHandler:), I just can't find anywhere to state this can be done through SwiftUI Lifecycle, what I have so far:
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willTerminateNotification), perform: { output in
Backend().removeFromGame(gameCode: otp, playerName: "name", completion: { res, error in
if error != nil{
print(error)
}
})
})
The function called is as follows:
func removeFromGame(gameCode: String, playerName: String, completion: #escaping (Bool?, Error?) -> Void){
Functions.functions().httpsCallable("removeFromGame").call(["gameCode": gameCode, "playerName": playerName]){ result, error in
if let error = error as NSError? {
if error.domain == FunctionsErrorDomain{
_ = FunctionsErrorCode(rawValue: error.code)
let errorDesc = error.localizedDescription
_ = error.userInfo[FunctionsErrorDetailsKey]
print(errorDesc)
}
}else{
print("Removed successfully")
}
}
}
I have seen in this Apple doc how to use the API:
func sendDataToServer( data : NSData ) {
// Perform the task on a background queue.
DispatchQueue.global().async {
// Request the task assertion and save the ID.
self.backgroundTaskID = UIApplication.shared.
beginBackgroundTask (withName: "Finish Network Tasks") {
// End the task if time expires.
UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
self.backgroundTaskID = UIBackgroundTaskInvalid
}
// Send the data synchronously.
self.sendAppDataToServer( data: data)
// End the task assertion.
UIApplication.shared.endBackgroundTask(self.backgroundTaskID!)
self.backgroundTaskID = UIBackgroundTaskInvalid
}
}
Just cannot seem to implement it correctly within the new way of getting these system notifications?

Why is my Plaid Link Integration not opening the Link UI

I am attempting to integrate with Plaid, their documentation is a link confusing to follow and their support team informed me they are working on it. I also reached out to them to see if they could help me with my current integration but they informed me they are not able to look at my code because every integration is different.
Per the Plaid documentation I added my ngrok url to the redirect uri section in the dashboard (This works with the Plaid Link demo app: https://github.com/plaid/plaid-link-ios).
Also I added allow arbitrary load to the info plist.
I was able to get their Plaid Link-demo app up and running but when I try to place this code in my project I get the following error:
Thread 12: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
// MARK: Start Plaid Link using a Link token
// For details please see https://plaid.com/docs/#create-link-token
func presentPlaidLinkUsingLinkToken() {
#warning("Replace <#GENERATED_LINK_TOKEN#> below with your link_token")
// In your production application replace the hardcoded linkToken below with code that fetches an link_token
// from your backend server which in turn retrieves it securely from Plaid, for details please refer to
// https://plaid.com/docs/#create-link-token
let linkToken = "TOKEN HERE"
// <!-- SMARTDOWN_PRESENT_LINKTOKEN -->
// With custom configuration using a link_token
var linkConfiguration = LinkTokenConfiguration(token: linkToken) { success in
print("public-token: \(success.publicToken) metadata: \(success.metadata)")
}
linkConfiguration.onExit = { exit in
if let error = exit.error {
print("exit with \(error)\n\(exit.metadata)")
} else {
print("exit with \(exit.metadata)")
}
}
let result = Plaid.create(linkConfiguration)
switch result {
case .failure(let error):
print("Unable to create Plaid handler due to: \(error)")
case .success(let handler):
// UI Update code here
handler.open(presentUsing: .viewController(self))
self.linkHandler = handler
}
// <!-- SMARTDOWN_PRESENT_LINKTOKEN -->
}
#IBAction func refreshButtonAction(_ sender: Any) {
// UI Update code here
self.presentPlaidLinkUsingLinkToken()
}
I tried to place the self.presentPlaidLinkUsingLinkToken() into the following snippet:
DispatchQueue.main.async {
self.presentPlaidLinkUsingLinkToken()
}
Also per the Plaid documentation I added the following method within my App delegate swift file:
// MARK: Continue Plaid Link for iOS to complete an OAuth authentication flow
// <!-- SMARTDOWN_OAUTH_SUPPORT -->
func application(_ application: UIApplication,
continue userActivity: NSUserActivity,
restorationHandler: #escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
guard userActivity.activityType == NSUserActivityTypeBrowsingWeb, let webpageURL = userActivity.webpageURL else {
return false
}
// Check that the userActivity.webpageURL is the oauthRedirectUri
// configured in the Plaid dashboard.
guard let linkOAuthHandler = window?.rootViewController as? LinkOAuthHandling,
let handler = linkOAuthHandler.linkHandler,
webpageURL.host == linkOAuthHandler.oauthRedirectUri?.host &&
webpageURL.path == linkOAuthHandler.oauthRedirectUri?.path
else {
return false
}
// Continue the Link flow
if let error = handler.continueFrom(redirectUri: webpageURL) {
print("Unable to continue from redirect due to: \(error)")
}
return true
}
// <!-- SMARTDOWN_OAUTH_SUPPORT -->
But the same error occurs. Any suggestions will be appreciated.
In case anyone is having issues with this, I was able to figure it out. It is something incorrect in their cocoapod. Once, I downloaded and dragged the LinkKit Framework everything worked as expected. Also Plaid does not use their own cocoapod in their demo project, they do the manual install. Maybe they are working on this issue.
It's hard to tell without seeing the code in your application calling presentPlaidUsingLinkToken(), but your application is crashing on Thread 12. UIKit is not multi-threaded, so if you are presenting a view controller on a background thread you can expect crashes.
I see you tried a DispatchQueue.main.async, however I suspect you may have another threading issue going on.
Just putting this out there since this is one of few results for this issue: My issue was that I wasn't retaining the Handler object. As a result, the Plaid UI would never appear and no events would fire, leaving me with an endless spinner. Simply maintaining the reference in the class did the trick (as shown in the sample project and mentioned in passing in the documentation).
Kona Farry's solution above works. From Plaid docs:
Create a Handler - A Handler is a one-time use object used to open a Link session. The Handler must be retained for the duration of the Plaid SDK flow.

Apple Watch WCConnectionDelegate, sending message in activationDidComplete fails occasionally?

I am having an issue where, when sending a message through WCConnection, the session.sendMessage fails sometimes if called in the delegate method activationDidCompleteWith. The issue is not repeatable every time (in fact, it works most of the time).
But forcing a session.sendMessage by using a button in my UI (calling the identical loading code) has a successful session communication immediately, so I know the issue is not in the session itself or the master app.
Is it unsafe to assume the session is ready to accept communication in activationDidCompleteWith? Is there a better place to be calling my initial communication?
In my experience watch OS is pretty finicky, especially when using older model watches. That being said I think the answer to the question: "Is it unsafe to assume the session is ready to accept communication in activationDidCompleteWith?" is yes, it is unsafe to assume that.
In my own app I have a very similar case to yours and I solved it by sending a message until a response is received.
// false until a response is received from the phone
let receivedResponse: Bool = false
// function that sends the message
func requestResponse() {
guard WCSession.default.isReachable else {
print("Phone not reachable")
return
}
// callback that handles response
let responseHandler: ([String: Any]) -> () = { response in
receivedResponse = true
callback(response)
}
WCSession.default.sendMessage(["Request": "Response"],
replyHandler: responseHandler) { error in
print(error.localizedDescription)
}
}
// timer that calls the request function repeatedly
let retryTimer = Timer.scheduledTimer(withTimeInterval: 1,
repeats: true) { timer in
if receivedResponse {
// we know we got a response so clean up timer
timer.invalidate()
}
requestResponse()
}

GoogleCast iOS sender, v4, not sending messages

On version 2, the sender app was able to send messages.
func deviceManager(_ deviceManager: GCKDeviceManager!,
didConnectToCastApplication
applicationMetadata: GCKApplicationMetadata!,
sessionID: String!,
launchedApplication: Bool) {
deviceManager.add(self.textChannel)
}
However, the API says that we are now using GCKSessionManager instead of GCKDeviceManager.
The API says I must have a GCKSession add the textChannel, which I did here:
Once the session starts, I add the textChannel (because sessionManager.currentCastSession was nil before the session started).
func sessionManager(_ sessionManager: GCKSessionManager, didStart session: GCKSession) {
if session.device == connectionQueue {
connectionQueue = nil
}
self.sessionManager!.currentCastSession!.add(textChannel)
print("")
}
Meanwhile, I send the text message in another function:
let result = self.textChannel.sendTextMessage("\(self.textField.text)", error: &error)
But the result is always false, and the error is always "Channel is not connected or is not registered with a session".
In addition, when I do:
print("isConnected1 \(self.textChannel.isConnected)")
the result is false.
Do you know what other steps I am missing for it to be connected?
Just learned that it was an issue of my namespace. It connects now.
Problem was the namespace wasn't matching the namespace from my receiver code.
fileprivate lazy var textChannel:TextChannel = {
return TextChannel(namespace: NAMESPACE)
}()

Stopping Mac from sleeping while app is running

I want to stop a the user's Macbook from automatically sleeping while one the app is running. How would I do that? I have seen this
https://developer.apple.com/library/content/qa/qa1340/_index.html
and this:
Disable sleep mode in OS X with swift
and this:
Using Swift to disable sleep/screen saver for OSX
But there is no simple line of code that I can put or a delegate method that makes this simple like there is in iOS? I just want to have the entire app work properly and stop the computer from automatically going to sleep. If someone puts in it to sleep manually, then obviously that is fine. I don't want to prevent the user from putting the computer to sleep. But as long as my app is running I don't want the computer to sleep. Like when you are watching a movie, the app does not go to sleep. How do I do that?
I feel like this is the code that I need but do I just run this function in the viewdidload and it will work? Is it that simple?
var assertionID: IOPMAssertionID = 0
var success: IOReturn?
func disableScreenSleep(reason: String = "Unknown reason") -> Bool? {
guard success != nil else { return nil }
success = IOPMAssertionCreateWithName( kIOPMAssertionTypeNoDisplaySleep as CFString,
IOPMAssertionLevel(kIOPMAssertionLevelOn),
reason as CFString,
&assertionID )
return success == kIOReturnSuccess
}
As I already mentioned in comments you need to remove the guard otherwise it will simply return nil and will never disable the screen sleep. You can also simplify your method, return void and change your success property to a Bool property like disabled to monitor the screen sleep state:
var assertionID: IOPMAssertionID = 0
var sleepDisabled = false
func disableScreenSleep(reason: String = "Disabling Screen Sleep") {
if !sleepDisabled {
sleepDisabled = IOPMAssertionCreateWithName(kIOPMAssertionTypeNoDisplaySleep as CFString, IOPMAssertionLevel(kIOPMAssertionLevelOn), reason as CFString, &assertionID) == kIOReturnSuccess
}
}
func enableScreenSleep() {
if sleepDisabled {
IOPMAssertionRelease(assertionID)
sleepDisabled = false
}
}
To disable the screen simply just call the method inside viewDidLoad of your first view controller:
override func viewDidLoad() {
super.viewDidLoad()
disableScreenSleep()
print("sleep Disabled:", sleepDisabled)
}