Problem with WatchConnectivity transferFile (watch -> iphone) - watchkit

I want to send a file created on the watch to the iOS companion-app, using WatchConnectivity and a setup WCSession, but it does not get through to the iPhone. When I use send a message containing a Dictionary in stead, the data does get to the iPhone.
Both AppDelegate and ExtensionDelegate uses NotificationCenter to communicate with ViewController and ExtensionController. They are left out for simplicity, but the notifications work fine.
iOS' AppDelegate.swift
extension AppDelegate: WCSessionDelegate {
// 1
func sessionDidBecomeInactive(_ session: WCSession) {
print("WC Session did become inactive")
}
// 2
func sessionDidDeactivate(_ session: WCSession) {
print("WC Session did deactivate")
WCSession.default.activate()
}
// 3
func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
if let error = error {
print("WC Session activation failed with error: \(error.localizedDescription)")
return
}
print("WC Session activated with state: \(activationState.rawValue)")
}
func setupWatchConnectivity() {
// 1
if WCSession.isSupported() {
// 2
let session = WCSession.default
// 3
session.delegate = self
// 4
session.activate()
}
}
func session(_ session: WCSession, didFinish fileTransfer: WCSessionFileTransfer, error: Error?) {
print("didFinish fileTransfer")
}
func session(_ session: WCSession, didReceive file: WCSessionFile) {
print("didReceive file")
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void) { // 2
print("didReceiveMessage reached")
}
}
On the watch I have:
ExtensionDelegate.swift
extension ExtensionDelegate: WCSessionDelegate {
func setupWatchConnectivity() {
if WCSession.isSupported() {
let session = WCSession.default
session.delegate = self
session.activate()
}
}
func session(_ session: WCSession, activationDidCompleteWith
activationState: WCSessionActivationState, error: Error?) {
if let error = error {
print("WC Session activation failed with error: " +
"\(error.localizedDescription)")
return
}
print("WC Session activated with state: " +
"\(activationState.rawValue)")
}
func sendFileToPhone(_ notification:Notification) {
// 1
if WCSession.isSupported() && WCSession.default.isReachable {
// 2
if let fileURL = notification.object as? URL {
print("Sending file for URL: \(fileURL.absoluteURL.absoluteString)")
WCSession.default.transferFile(fileURL, metadata: nil) // <- if I use sendMessage here stuff works...
}
}
}
func session(_ session: WCSession, didFinish fileTransfer: WCSessionFileTransfer, error: Error?) {
// handle filed transfer completion
print("File transfer complete")
print("Outstanding file transfers: \(WCSession.default.outstandingFileTransfers)")
print("Has content pending: \(WCSession.default.hasContentPending)")
}
func session(_session: WCSession, didFinishFileTransfer fileTransfer: WCSessionFileTransfer, error: NSError?) {
print("error: ", error as Any)
}
}
After the file is sent from the Watch I inspect, as you see, outstandingFileTransfers and hasContentPending properties of the WCSession, and they indicate that the file should have been transferred, but my :didReceive: method of my AppDelegate extension doesn't get called. Again, when I use sendMessage, the AppDelegate's :didReceiveMessage: is invoked as expected.
What am I missing?
Note: I've tried to run this Apple's demo project that features the different communication methods, and I experience the same thing: transferFile does not trigger anything on counterpart.

It seems to be a bug of iOS 13/watchOS 6 simulator. I've tried to change Xcode simulators to iOS 12 and watchOS 5, how it's suggested in this thread and the issue disappeared.

Related

Cannot retrieve data back to my app from Firebase

I hope the problem is explained well on the title but what I want to do is to write the message on my message app and want to retrieve it and write it on the app's message cell but nothing is seen. When I go to my firebase console I can see the messages there but not on app. I didn't attach anything about project but I don't know what to attach. but let me add my chatViewController at least .`import UIKit
import Firebase
class ChatViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
#IBOutlet weak var messageTextfield: UITextField!
let db = Firestore.firestore()
var messages: [Message] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
navigationItem.hidesBackButton = true
title = K.appName
tableView.register(UINib(nibName: K.cellNibName, bundle: nil), forCellReuseIdentifier: K.cellIdentifier)
loadMessages()
}
func loadMessages() {
db.collection(K.FStore.collectionName)
.order(by: K.FStore.dateField)
.addSnapshotListener { (querySnapshot, error) in
self.messages = []
if let e = error {
print("There was an issue retrieving data from Firestore \(e)")
}else {
if let snapshotDocuments = querySnapshot?.documents {
for doc in snapshotDocuments {
let data = doc.data()
if let messageSender = data[K.FStore.senderField] as? String, let messageBody = data[K.FStore.bodyField] as? String {
let newMessage = Message(sender: messageSender, body: messageBody)
self.messages.append(newMessage)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
}
}
}
#IBAction func sendPressed(_ sender: UIButton) {
if let messageBody = messageTextfield.text, let messageSender = Auth.auth().currentUser?.email {
db.collection(K.FStore.collectionName).addDocument(data: [K.FStore.senderField: messageSender, K.FStore.bodyField: messageBody]) { (error) in
if let e = error {
print("There was an issue saving data to firestore \(e)")
} else {
print("successfully saved data.")
}
}
}
}
#IBAction func logOutPressed(_ sender: UIBarButtonItem) {
do {
try Auth.auth().signOut()
navigationController?.popToRootViewController(animated: true)
} catch let signOutError as NSError {
print ("Error signing out: %#", signOutError)
}
}
}
extension ChatViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: K.cellIdentifier, for: indexPath) as! MessageCell
cell.label.text = messages[indexPath.row].body
return cell
}
}
`
And below is console output
'<FIRFirestore: 0x6000016d7040>
2022-10-17 17:55:19.817200+0300 Flash Chat iOS13[5876:41767] 10.0.0 - [FirebaseAnalytics][I-ACS023007] Analytics v.10.0.0 started
2022-10-17 17:55:19.817551+0300 Flash Chat iOS13[5876:41767] 10.0.0 - [FirebaseAnalytics][I-ACS023008] To enable debug logging set the following application argument: -FIRAnalyticsDebugEnabled (see xxx
2022-10-17 17:55:19.824914+0300 Flash Chat iOS13[5876:41677] [Assert] UINavigationBar decoded as unlocked for UINavigationController, or navigationBar delegate set up incorrectly. Inconsistent configuration may cause problems. navigationController=<UINavigationController: 0x13984a200>, navigationBar=<UINavigationBar: 0x13950f870; frame = (0 48; 0 50); opaque = NO; autoresize = W; layer = <CALayer: 0x600000390980>> delegate=0x13984a200
2022-10-17 17:55:19.873401+0300 Flash Chat iOS13[5876:41763] 10.0.0 - [FirebaseAnalytics][I-ACS800023] No pending snapshot to activate. SDK name: app_measurement
2022-10-17 17:55:19.890442+0300 Flash Chat iOS13[5876:41765] 10.0.0 - [FirebaseAnalytics][I-ACS023012] Analytics collection enabled
2022-10-17 17:55:19.890731+0300 Flash Chat iOS13[5876:41765] 10.0.0 - [FirebaseAnalytics][I-ACS023220] Analytics screen reporting is enabled. Call Analytics.logEvent(AnalyticsEventScreenView, parameters: [...]) to log a screen view event. To disable automatic screen reporting, set the flag FirebaseAutomaticScreenReportingEnabled to NO (boolean) in the Info.plist
successfully saved data.'

UpdateApplicationContext Not Being Called

It's my first time trying WatchConnectivity kit. When I attempt to send ApplicationContext from phone, my debug print indicates that updateApplicationContext is working correctly (with isPaired/reachable/isActivated/isWatchAppInstalled checked before sending the update). However, I'm having trouble receiving it form the paired watch simulator as nothing is shown on that end. Here are the code for the Phone Extension:
func sendDataToWatch(data: String) {
state.append(data)
do {
NSLog("sent by phone")
try WCSession.default.updateApplicationContext(["currentstate": state])
}
catch {
print(error)
}
}
Here are the code for watch:
func session(_ session: WCSession, didReceiveApplicationContext applicationContext: [String: Any]) {
print("Hello")
if let getState = applicationContext["currentstate"] as? [String]{
print("\(getState)")
self.state = getState[0]
}
}
Any suggestion would be appreciated!

Combine: Wrapped async call with Future, but Future.sink doesn't appear to complete

First-time poster, long-time reader ... I've wrapped an async call to the Firebase Authorization API. I'm calling it from inside a SwiftUI View function.
func authenticateFirebaseEmail(email: String, password: String) ->
Future<String, Error> {
return Future<String,Error> { promise in
Auth.auth().signIn(withEmail: email, password: password) { result, error in
if let error=error {
print("failure detected")
promise(.failure(error))
}
if let result=result {
print("result detected - returning success promise")
promise(.success(result.user.email!))
}
}
}
}
...
func logMeInFuncInView() {
var cancellable : AnyCancellable?
cancellable = authenticateFirebaseEmail(email: self.userEmail, password: self.password).map( {
value in return value
})
.sink(receiveCompletion: { (completion) in
print("completion received")
}, receiveValue: { user in
print("value received")
self.errorMessage = user
})
}
The console output is as follows, but never reaches the "completion received" or "value received" calls:
result detected - returning successful promise
Is the issue with the wrapped callback, the future, the way I'm using the future, or something that I'm not seeing entirely?
Your cancellable is local variable, so destroyed once went off context. As soon as subscriber is destroyed it cancels subscription and, as it is only one, publisher cancelled as well.
Your solution is to make your cancellable as property, ie
var cancellable : AnyCancellable? // << here !!
func logMeInFuncInView() {
cancellable = authenticateFirebaseEmail(email: self.userEmail, password: self.password).map( {
value in return value
})
// .. other code
}

Background data task in watchOS

I'm trying to build a basic proof-of-concept watchOS app and complication that pulls JSON data from an API and displays a gauge based on that.
I've watched Apple's 'Keping your watch app up to date' and found several other questions on the subject but the sample code has been taken down.
The API provides forecasts for the next ~48 hours and can be used to populate the timeline entries for the complication. When the Complication Controller requests the timeline entries I pull the data from the Extension Delegate and therefore it must be kept up to date. However the process of scheduling background data tasks has got me stumped. When I call backgroundSession.dataTask(with: URL(string: "https://...... I expect my URLSessionDataDelegate functions to be called but they never are and I never get a the handle(_ backgroundTasks) called with WKURLSessionRefreshBackgroundTask
Question:
When a WKApplicationRefreshBackgroundTask is sent to my ExtensionDelegate how should I request/schedule data from the API and then receive it?
Code
//ExtensionDelegate
class ExtensionDelegate: NSObject, WKExtensionDelegate, URLSessionDelegate, URLSessionDataDelegate {
...
func handle(_ backgroundTasks: Set<WKRefreshBackgroundTask>) {
for task in backgroundTasks {
switch task {
case let backgroundTask as WKApplicationRefreshBackgroundTask:
print("background task as WKApplicationRefreshBackgroundTask")
self.scheduleURLSession()
backgroundTask.setTaskCompletedWithSnapshot(false)
return
case let urlSessionTask as WKURLSessionRefreshBackgroundTask:
print("background task as WKURLSessionRefreshBackgroundTask")
let backgroundConfigObject =
URLSessionConfiguration.background(withIdentifier: urlSessionTask.sessionIdentifier)
let backgroundSession = URLSession(configuration: backgroundConfigObject, delegate: self, delegateQueue: nil)
print("Rejoining session ", backgroundSession)
self.savedTask = urlSessionTask
return
default:
// make sure to complete unhandled task types
task.setTaskCompletedWithSnapshot(false)
}
}
}
func scheduleURLSession() {
let backgroundConfigObject = URLSessionConfiguration.background(withIdentifier: "nz.co.craigstanton")
backgroundConfigObject.sessionSendsLaunchEvents = true
let backgroundSession = URLSession(configuration: backgroundConfigObject, delegate: self, delegateQueue: nil)
let dataTask = backgroundSession.dataTask(with: URL(string: "https://craigstanton.co.nz/uvi-test?latitude=-36&longitude=174")!)
print("scheduleURLSession about to 'resume' ")
dataTask.resume()
}
//Delegate callbacks
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
print("Data task error", error)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse,
completionHandler: #escaping (URLSession.ResponseDisposition) -> Void) {
print("urlSession Delegate did receive everything ")
}
func urlSession(_: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
print("urlSession Delegate did receive something ")
}

Trigger an alert when using Firebase observer and events

Working on an app that has two parts - Rider & Driver. When the driver accepts the request, an alert is then sent to the rider that the request was accepted and driver is on the way.
Unable to trigger the alert to the rider.
RiderVC:
func driverAcceptedRequest(requestAccepted: Bool, driverName: String) {
if !riderCancelledRequest {
if requestAccepted {
self.alertTheUser(title: "Ryde Accepted", message: "\(driverName) Has Accepted Your Ryde Request and will message you with details")
} else {
RydeHandler.Instance.cancelRyde()
alertTheUser(title: "Ryde Cancelled", message: "\(driverName) Has Cancelled the Ryde Request")
}
}
riderCancelledRequest = false
}
RydeHandler.swift:
// DRIVER ACCEPTED RYDE
DataService.Instance.requestAcceptedRef.observe(FIRDataEventType.childAdded) { (snapshot: FIRDataSnapshot) in
if let data = snapshot.value as? NSDictionary {
if let name = data[Constants.NAME] as? String {
if self.driver == "" {
self.driver = name
self.delegate?.driverAcceptedRequest(requestAccepted: true, driverName: self.driver)
}
}
}
}
Firebase database structure:
Edit
ViewDidLoad in tableviewcontroller - list of requests:
ref.child("drivers").child("RideRequests").observe(FIRDataEventType.value, with: { snapshot in
self.rideRequests.removeAll()
for item in snapshot.children{
self.rideRequests.append(item as! FIRDataSnapshot)
}
self.rideRequests.reverse()
self.tableView.reloadData()
})

Resources