Delivery failure while sending a message from WatchOS app to iOS app - watchos-3

In my InterfaceController I have the following code:
#IBAction func buttonClicked() {
if (WCSession.default.isReachable) {
let message = ["Message": "Hello"]
WCSession.default.sendMessage(message, replyHandler: nil)
print ("message sent")
}
}
In ViewController I have the following code:
override func viewDidLoad() {
super.viewDidLoad()
if (WCSession.isSupported()) {
let session = WCSession.default
session.delegate = self
session.activate()
}
}
func session(_ session: WCSession, didReceiveMessage message: [String : Any], replyHandler: #escaping ([String : Any]) -> Void) {
print ("message received")
print (message)
}
When I send a message from the Watch App, I get the following error
message sent
2017-12-03 19:33:03.903709+0530 Watch Extension[1761:74704] [WC] -[WCSession _onqueue_notifyOfMessageError:messageID:withErrorHandler:] 5BBE38F1-13C7-46E3-8E99-A874B43C6516 errorHandler: NO with WCErrorCodeDeliveryFailed
Debug window of iOS App gives me following info:
2017-12-03 19:35:28.706212+0530 WatchTest[1770:76097] [WC] -[WCSession onqueue_handleDictionaryMessageRequest:withPairingID:]_block_invoke delegate WatchTest.ViewController does not implement delegate method
2017-12-03 19:35:28.707992+0530 WatchTest[1770:76097] [WC] -[WCSession _onqueue_sendResponseError:identifier:dictionaryMessage:] identifier: 96B10064-F17B-4D4B-8F5C-1154984D5163 with WCErrorCodeDeliveryFailed
Am I missing any implementation in ViewController class? I am using Xcode 9.0

I changed the buttonClicked action method to following:
#IBAction func buttonClicked() {
if (WCSession.default.isReachable) {
let message = ["Message": "Hello"]
print ("message sent")
WCSession.default.sendMessage(message, replyHandler: { reply in
self.statusLabel.setText(reply["status"] as? String)
}, errorHandler: { error in
print("error: \(error)")
})
}
}
The sendMessage probably requires the reply and error handlers to be defined.

Related

NSApp: Hide main window until proper ViewController can be loaded

I have a MacOS app that is a registered custom URL handler.
I'm trying to make it show a specific NSViewController if the app is started via the url handler or the regular window and ViewController if no parameters are used.
In my AppDelegate.swift I have this:
func application(_ application: NSApplication, open urls: [URL]) {
AppDelegate.externalCaller = true;
NSApp.hide(self)
let url: URL = urls[0];
// Process the URL.
let components = NSURLComponents(url: url, resolvingAgainstBaseURL: true);
let method = components?.host;
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
if (method == "DO_STH") {
// do something with no window
NSApplication.shared.windows.last?.close();
} else if (method == "DO_STH_2") {
// do something with no window
NSApplication.shared.windows.last?.close();
} else if (method == "PROCESS_STUFF") {
// Show window
DispatchQueue.main.async {
let mainStoryboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil);
let restoringViewController = mainStoryboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("restoringData")) as! RestoringViewController;
if let window = NSApp.mainWindow {
window.contentViewController = restoringViewController;
}
NSApp.activate(ignoringOtherApps: true);
AppDelegate.restoreData();
}
}
}
}
}
The problem is that by the time NSApp.hide(self) runs, there's already a window visible with the default viewController, creating some flickering effect.
What's the best way to make the app start without any visible window by default and only show the window if it wasn't started with any URL parameter and later on demand if a specific URL parameter exists?
Unchecking "Is initial Controller" and adding this to AppDelegate solved my issue
func applicationDidFinishLaunching(_ notification: Notification) {
if (!AppDelegate.externalCaller) {
showWindow();
}
}
func showWindow() {
let mainStoryboard = NSStoryboard(name: NSStoryboard.Name("Main"), bundle: nil);
let windowController = mainStoryboard.instantiateController(withIdentifier: NSStoryboard.SceneIdentifier("MainWindowController")) as! NSWindowController;
windowController.showWindow(self);
}

Turbolinks 5 iOS app link not pushing VisitableViewController onto stack

I've been playing around with Turbolinks 5 and I can't seem to get it to visit a new page correctly after clicking a link within my application. The app loads the new view as if it was replaced inside the webview and doesn't push a new view controller on to the stack like I would expect. It's as if it doesn't perform a Turblonks.visit
I'm running a rails 5.1 application with the Turbolinks 5 enabled. My link looks like this:
<%= link_to 'View', test_path(test_id), class: 'btn btn-secondary btn-block marginTop_short' %>
As you can see there is nothing special about this link!
My iOS app code is very basic:
import UIKit
import Turbolinks
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
var navigationController = UINavigationController()
var session = Session()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
window?.rootViewController = navigationController
startApplication()
return true
}
func startApplication() {
session.delegate = self
visit(URL: NSURL(string: "http://localhost:3000")!)
}
func visit(URL: NSURL) {
print("visiting", URL)
let visitableViewController = VisitableViewController(url: URL as URL)
navigationController.pushViewController(visitableViewController, animated: true)
session.visit(visitableViewController)
}
}
extension AppDelegate: SessionDelegate {
func session(_ session: Session, didProposeVisitToURL URL: URL, withAction action: Action) {
print("trying to visit", URL)
print("action", action)
visit(URL: URL as NSURL)
}
func session(_ session: Session, didFailRequestForVisitable visitable: Visitable, withError error: NSError) {
let alert = UIAlertController(title: "Error", message: error.localizedDescription, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
navigationController.present(alert, animated: true, completion: nil)
}
}
When I click a link it doesn't fire the func session(_ session: Session, didProposeVisitToURL URL: URL, withAction action: Action) callback.
Maybe it's more accurate to say when a link is clicked the webview is not responding to or createing a visit proposal?
What am I missing? Any help would be appreciated.
Let me know if any more detail is require or clarification.

iOS10: tapping an action from local notification does not bring app to the foreground

I am trying to implement the action from notification. And so far I am able to trigger the right delegate functions, but after tapping the app is not brought to the foreground.
Relevant code:
#available(iOS 10.0, *)
func registerCategory() -> Void{
print("register category")
let callNow = UNNotificationAction(identifier: "call", title: "Call now", options: [])
let clear = UNNotificationAction(identifier: "clear", title: "Clear", options: [])
let category : UNNotificationCategory = UNNotificationCategory.init(identifier: "IDENT123", actions: [callNow, clear], intentIdentifiers: [], options: [])
let center = UNUserNotificationCenter.currentNotificationCenter()
center.setNotificationCategories([category])
}
#available(iOS 10.0, *)
func scheduleNotification(event : String, interval: NSTimeInterval) {
print("schedule ", event)
let content = UNMutableNotificationContent()
content.title = event
content.body = "body"
content.categoryIdentifier = "CALLINNOTIFICATION"
let trigger = UNTimeIntervalNotificationTrigger.init(timeInterval: interval, repeats: false)
let identifier = "id_"+event
let request = UNNotificationRequest.init(identifier: identifier, content: content, trigger: trigger)
let center = UNUserNotificationCenter.currentNotificationCenter()
center.addNotificationRequest(request) { (error) in
}
}
#available(iOS 10.0, *)
func userNotificationCenter(center: UNUserNotificationCenter, willPresentNotification notification: UNNotification, withCompletionHandler completionHandler: (UNNotificationPresentationOptions) -> Void) {
print("willPresent")
completionHandler([.Badge, .Alert, .Sound])
}
#available(iOS 10.0, *)
func userNotificationCenter(center: UNUserNotificationCenter, didReceiveNotificationResponse response: UNNotificationResponse, withCompletionHandler completionHandler: () -> Void) {
let notification: UNNotification = response.notification
let UUID = notification.request.content.userInfo["UUID"] as! String
switch (response.actionIdentifier) {
case "COMPLETE":
UNUserNotificationCenter.currentNotificationCenter().removeDeliveredNotificationsWithIdentifiers([UUID])
case "CALLIN":
let call = Call()
CalendarController.sharedInstance.fetchMeetingByUUID(UUID, completion: { (thisMeeting) -> Void in
if(!CallIn.Yield(thisMeeting).ConferenceCallNumber.containsString("None")){
call._call(thisMeeting)
}else{
//will open detail view, in case that no number were detected
NSNotificationCenter.defaultCenter().postNotificationName("OpenDetailViewOfMeeting", object: self, userInfo: ["UUID":UUID])
}
})
UNUserNotificationCenter.currentNotificationCenter().removeDeliveredNotificationsWithIdentifiers([UUID])
default: // switch statements must be exhaustive - this condition should never be met
log.error("Error: unexpected notification action identifier: \(UUID)")
}
completionHandler()
}
I am able to hit the delegate function didReceiveNotificationResponse() with a breakpoint, and it does some actions that I put there, but not in a way that is expected (It has to start a device-call, instead it just dismisses notifications list, and nothing happens, however when I manually open the app again, the call starts as if there is no permission to open the app from notification).
I found out the reason myself, so this might be helpful to someone in the future. The answer turned out to be quite simple. When creating an action of the notification, there is this parameter: options. When you register category, you need to put it either way .Foreground or .Destructive like this:
func reisterCategory () {
let callNow = UNNotificationAction(identifier: NotificationActions.callNow.rawValue, title: "Call now", options: UNNotificationActionOptions.Foreground)
let clear = UNNotificationAction(identifier: NotificationActions.clear.rawValue, title: "Clear", options: UNNotificationActionOptions.Destructive)
let category = UNNotificationCategory.init(identifier: "NOTIFICATION", actions: [callNow, clear], intentIdentifiers: [], options: [])
let center = UNUserNotificationCenter.currentNotificationCenter()
center.setNotificationCategories([category])
}

WKWatchConnectivityRefreshBackgroundTask example

I want to pass data from my iOS App to my watchOS 3 app using WKWatchConnectivityRefreshBackgroundTask
How do I set up code in my watchOS App to handle the data being transferred?
For example in the past I used this iOS code to send a message from the iOS App and if there was no connection send a context:
func sendTable()
{
let tableInfo: WatchWorkout = PhoneData().buildWatchTableData(Foundation.Date().refDays())
let archivedTable: Data = NSKeyedArchiver.archivedData(withRootObject: tableInfo)
if validSession
{
sendMessage([Keys.UpdateType : PhoneUpdateType.TableInfo.rawValue, Keys.Workout: archivedTable])
}
else
{
do
{
try updateApplicationContext([Keys.UpdateType : PhoneUpdateType.TableInfo.rawValue, Keys.Workout: archivedTable])
}
catch
{
print("Phone Session - error sending info: \(error)")
}
}
}
func sendMessage(_ message: [String : AnyObject], replyHandler: (([String : AnyObject]) -> Void)? = nil, errorHandler: ((NSError) -> Void)? = nil)
{
print("Phone Session - phone sent message")
session!.sendMessage(message,
replyHandler:
nil,
errorHandler:
{
(error) -> Void in
print("Phone Session - Error Message during transfer to Watch: \(error)")
}
)
}
func updateApplicationContext(_ applicationContext: [String : AnyObject]) throws
{
print("Phone Session - phone sent context")
if ((session) != nil)
{
do
{
try session!.updateApplicationContext(applicationContext)
}
catch let error
{
print("Phone Session - OPPS something wrong - context send failed")
throw error
}
}
}
I'm not sure how to code the receipt of this data as a background task on the watch.
Can someone provide some example code or post a link? The only Apple example code is not very helpful:
https://developer.apple.com/library/prerelease/content/samplecode/WatchBackgroundRefresh/Introduction/Intro.html
Thanks
Greg
The Quick Switch sample code was updated together with the release of watchOS 3 to include an example of handling the WatchConnectivity background refresh task.
#ccjensen The Quick Switch sample code doesn't work, is it?
It will crash on my iPhone6 iOS10.0 beta3. I sent feedback already last Friday.
In my Case, calling
updateApplicationContext(_:)
transferUserInfo(_:)
transferCurrentComplicationUserInfo(_:)
transferFile(_:metadata:)
on iPhone side never trigger handle(_:) listener.

Type UNUserNotificationCenter has no member current, swift 2.3

In iOS10 User notifications were reworked by apple.
Now I am trying to adjust my app to these changes, following this:
let center = UNUserNotificationCenter.current()
gives me an error:
Type UNUserNotificationCenter has no member current, swift 2.3
I know that that tutorial is maybe for swift3. But I need to make it work in swift 2.3. Is it even feasible and if yes, how to do it?
for Xcode 8 / ios9/10
simply use:
import UserNotifications
.
.
// swift 3.0:
if #available(iOS 10.0, *) {
let center = UNUserNotificationCenter.current()
center.requestAuthorization(options: [.alert, .sound]) { (granted, error) in
// Enable or disable features based on authorization.
}
} else {
// Fallback on earlier versions
}
// NOTE: if are in FOREGROUND, You will NEVER be called! :)
The documentation seems in conflict with itself. Although it describes the current() method and says to use it, the examples show let center = UNUserNotificationCenter.currentNotificationCenter().
As you say, this may be a Swift version dependency.
Import this:
import UserNotifications
implement this delegate with in your appDelegate file:
UNUserNotificationCenterDelegate
Which will give you access to the following methods:
func userNotificationCenter(_ center: UNUserNotificationCenter, openSettingsFor notification: UNNotification?) {}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: #escaping () -> Void) {}
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {}
and then add this to your app delegate's didFinishLaunchWithOptions method to tie it all up.
if #available(iOS 10.0, *) {
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: {_, _ in })
} else {
let settings: UIUserNotificationSettings =
UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
application.registerUserNotificationSettings(settings)
}
application.registerForRemoteNotifications()
swift 4.1
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(options: authOptions, completionHandler: {_, _ in
self.getNotificationSettings()
})
} else {
// Fallback on earlier versions
let settings: UIUserNotificationSettings = UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
UIApplication.shared.registerUserNotificationSettings(settings)
}
DispatchQueue.main.async {
UIApplication.shared.registerForRemoteNotifications()
}