Where best to call updateApplicationContext using Watch Connectivity? - ios

Several of the good blog posts detailing Watch Connectivity (http://www.kristinathai.com/watchos-2-tutorial-using-application-context-to-transfer-data-watch-connectivity-2/ and http://natashatherobot.com/watchconnectivity-application-context/) use simple app examples that send data to the watch when you tap on UI on the iPhone.
My app simply lists the data from my iPhone app, so I don't need to send data immediately, I just wanted to send it when the app loads or enters background...to this end I've made the updateApplicationContext in didFinishLaunching and didEnterBackground...however my dataSource delegate in my watch interface controllers are very spotting at getting triggered...particularly the glance only loads on the simulator and never on device. Is there a better time and place to push the info?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
WatchSessionManager.sharedManager.startSession()
do {
try WatchSessionManager.sharedManager.updateApplicationContext(["peopleDict" : peopleDict])
} catch {
print(error)
}
return true
}
func applicationDidEnterBackground(application: UIApplication) {
do {
try WatchSessionManager.sharedManager.updateApplicationContext(["peopleDict" : peopleDict])
} catch {
print(error)
}
}
below is my WatchSessionManager I used to call activiateSession in my extensionDelegate's appliciationDidFinishLaunching
import WatchConnectivity
protocol DataSourceChangedDelegate {
func dataSourceDidUpdate(dataSource: DataSource)
}
class WatchSessionManager: NSObject, WCSessionDelegate {
static let sharedManager = WatchSessionManager()
private override init() {
super.init()
}
private var dataSourceChangedDelegates = [DataSourceChangedDelegate]()
private let session: WCSession = WCSession.defaultSession()
func startSession() {
session.delegate = self
session.activateSession()
}
func addDataSourceChangedDelegate<T where T: DataSourceChangedDelegate, T: Equatable>(delegate: T) {
dataSourceChangedDelegates.append(delegate)
}
func removeDataSourceChangedDelegate<T where T: DataSourceChangedDelegate, T: Equatable>(delegate: T) {
for (index, indexDelegate) in dataSourceChangedDelegates.enumerate() {
if let indexDelegate = indexDelegate as? T where indexDelegate == delegate {
dataSourceChangedDelegates.removeAtIndex(index)
break
}
}
}
}
// MARK: Application Context
// use when your app needs only the latest information
// if the data was not sent, it will be replaced
extension WatchSessionManager {
// Receiver
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
dispatch_async(dispatch_get_main_queue()) { [weak self] in
self?.dataSourceChangedDelegates.forEach { $0.dataSourceDidUpdate(DataSource(data: applicationContext))}
}
}
}

As updateApplicationContext only stores the newest application context you can update it whenever you like. The watch will only get the newest data. There is no queue with old contexts.
On the watch side the most secure location to activate the session and set the WCSessionDelegate is in the ExtensionDelegate init method:
class ExtensionDelegate: NSObject, WKExtensionDelegate {
override init() {
super.init()
WatchSessionManager.sharedManager.startSession()
}
...
}
Your Glance does not update because when the Glance is shown, applicationDidFinishLaunching is not being called (because the watch app is not launched when only the Glance is launched)

Related

Add delegate to custom iOS Flutter Plugin

I'm working on integrating a custom iOS plugin into my Flutter app, problem is that I'm not getting delegate callbacks from the custom SDK Protocol.
I have to connect a bluetooth device to my app and I from the delegate calls I should receive the device's ID and pair it.
From the Flutter side, I can call the native functions from the customSdk: sdkInstance.scan() and there are even some internal (inside the sdk) prints with the scan results but my delegate calls are not in place.
I think I'm not correctly adding the delegate to the SDK, I can get this to work in a swift native app but not as a Flutter Plugin.
So here's more or less the code:
iOS Code
AppDelegate.swift
import UIKit
import Flutter
#UIApplicationMain
#objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}
SwiftIosPlugin.swift
import Flutter
import UIKit
import CustomSDK
public class SwiftIosPlugin: NSObject, FlutterPlugin {
let sdkInstance = CustomSDK.shared // This returns an instance of the SDK
let channel: FlutterMethodChannel
public static func register(with registrar: FlutterPluginRegistrar)
let channel = FlutterMethodChannel(name: "ios_plugin_channel", binaryMessenger: registrar.messenger())
let instance = SwiftIosPlugin(channel)
registrar.addMethodCallDelegate(instance, channel: channel)
registrar.addApplicationDelegate(instance)
}
init (_ channel: FlutterMethodChannel) {
self.channel = channel
super.init()
// In Swift, this is done in viewDidLoad()
// Is this the correct place to do this?
sdkInstance.addDelegate(self)
}
public func handle(_ call: FlutterMethodCall, result: #escaping FlutterResult) {
switch call.method {
case "startScan":
do {
// This is being called and results printed
try sdkInstance.scan()
} catch {
result(FlutterError(code: "400", message: "\(error)", details: nil))
}
case "connect":
sdkInstance.connect(call, result)
default:
result(FlutterMethodNotImplemented)
}
}
}
// These should be called but are not
extension SwiftIosPlugin: CustomSDKDelegate {
// Isn't called when scan() is executeed!
public func onScanDevice(didScan id:String) {
// do logic
}
public func onPairedDevice(didPair id:String) {
// do logic
}
}
Update:
Silly thing that I hope nobody else has this trouble...
Two things to consider:
The problem was some of the delegate's functions public func onScanDevice(didScan id:String) was missing a parameter (even though there weren't any errors pointed out by Xcode).
sdkInstance.addDelegate(self) was called too early in the class "lifecycle".
Be mindful of these things and you won't have any trouble!

issue while accessing global func in app delegate

I am new to swift and i created one swift file with name mySession and in that file i am storing login data like below
Store Login Data
func setLoginData (data:Data) {
let preferences = UserDefaults.standard
let Key_Login = "Login"
preferences.set(data, forKey: Key_Login)
preferences.synchronize()
}
and second func for use that stored data
Get Data
func getLoginData ()->Data {
let preferences = UserDefaults.standard
let Key_Login = "Login"
if preferences.object(forKey: Key_Login) == nil {
return data
} else {
return preferences.data(forKey: Key_Login)!
}
}
So now my question is that i want to use getLoginData func in my app delegate to check user is logged in or not so not able to under stand how to use that func in app delegate
You should encapsulate these methods inside a class and use an instance of that class inside AppDelegate
class MySession {
func setLogin(_ data: Data){...}
func getLoginData() -> Data {...}
}
At the call site, instantiate MySession and use your methods to do what's needed.
class AppDelegate {
//...
let sessionHandler = MySession()
sessionHandler.getLoginData()
}
Sidenote, make sure you're using proper Swift 4 naming conventions.
First Import the file name in AppDelegate
Import mySession
Then just call the method in your desired function.
setLoginData(data:<your Data>)
For example.If you want to use it in didFinishLaunchingWithOptions method, follow this
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
DefaultNetworkManager.appsConfigSetup()
window = UIWindow(frame: UIScreen.main.bounds)
setupApplication()
setLoginData(data: <Your Data>)
return true
}

Rx_Swift for Local and Push Notifications

How can I implement observable for Local notification and Push notifications when they are received.
In app delegate, We are notify on
didReceiveLocalNotification
and
didReceiveRemoteNotification
How can I listen these notification on other screen? I've used NotificationCenter for notify but now I want to use RX-Swift. I've tried with this way but not working.
extension UILocalNotification {
var notificationSignal : Observable<UILocalNotification> {
return Observable.create { observer in
observer.on(.Next(self))
observer.on(.Completed)
return NopDisposable.instance
}
}
}
Can anyone help me?
Updated:
Hi, I found a solution for that using same way as you are used but some changes.
class NotificationClass {
static let bus = PublishSubject<AnyObject>()
static func send(object : AnyObject) {
bus.onNext(object)
}
static func toObservable() -> Observable<AnyObject> {
return bus
}
}
Send notification from AppDelegate:
func application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification) {
NotificationClass.send(notification)
}
Then observe on any other class.
NotificationClass.bus.asObserver()
.subscribeNext { notification in
if let notification : UILocalNotification = (notification as! UILocalNotification) {
print(notification.alertBody)
}
}
.addDisposableTo(disposeBag)
Best thing of this class is we can emit and consume anyObject through it.
How about something like this?
// this would really be UILocalNotification
struct Notif {
let message: String
}
class MyAppDelegate {
let localNotification = PublishSubject<Notif>()
// this would really be `application(application: UIApplication, didReceiveLocalNotification notification: UILocalNotification)`
func didReceiveLocalNotification(notif: Notif) {
localNotification.on(.Next(notif))
}
}
let appDelegate = MyAppDelegate() // this singleton would normally come from `UIApplication.sharedApplication().delegate`
class SomeClassA {
let disposeBag = DisposeBag()
init() {
appDelegate.localNotification
.subscribe { notif in
print(notif)
}
.addDisposableTo(disposeBag)
}
}
let a = SomeClassA()
appDelegate.didReceiveLocalNotification(Notif(message: "notif 1"))
let b = SomeClassA()
appDelegate.didReceiveLocalNotification(Notif(message: "notif 2"))
I'm still learning RxSwift, so this might not be the best way to do it. However, it works.

Using `InterfaceController` logic for `ComplicationController'

I'm doing an Apple Watch App, with a Complication.
I've got the WatchKit App part working great with this Ev class...
class Ev {
var evTColor:String
var evMatch:String
init(dataDictionary:Dictionary<String,String>) {
evTColor = dataDictionary["TColor"]!
evMatch = dataDictionary["Match"]!
}
class func newEv(dataDictionary:Dictionary<String,String>) -> Ev {
return Ev(dataDictionary: dataDictionary)
}
}
... and this InterfaceController
func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
if let tColorValue = userInfo["TColor"] as? String, let matchValue = userInfo["Match"] as? String {
receivedData.append(["TColor" : tColorValue , "Match" : matchValue])
evs.append(Ev(dataDictionary: ["TColor" : tColorValue , "Match" : matchValue]))
doTable()
} else {
print("tColorValue and matchValue are not same as dictionary value")
}
}
func doTable() {
self.rowTable.setNumberOfRows(self.evs.count, withRowType: "rows")
for (index, evt) in evs.enumerate() {
if let row = rowTable.rowControllerAtIndex(index) as? TableRowController {
row.mLabel.setText(evt.evMatch)
row.cGroup.setBackgroundColor(colorWithHexString(evt.evTColor))
} else {
print("nope")
}
}
}
I'm having a hard time getting the same sort of thing to work in my Complication, any ideas?
I'm not sure if I can just use the same Ev code for my ExtensionDelegate, and then what exactly to put in my ComplicationController.
If I use the same Ev code in my ExtensionDelegate I'm getting a fatal error: use of unimplemented initializer init().
And in my ComplicationController I'm not sure how to go about best using the data I already have from InterfaceController to fill out the getCurrentTimelineEntryForComplication &getTimelineEntriesForComplication methods in ComplicationController.
Will post any extra code as needed, thanks!
EDIT:
Per a question, my data comes from CloudKit to the iPhone App (which I then pass to the Watch App via WCSession, so my problem is accessing that data in my Watch App for my Complication)
Instead of having your InterfaceController implement and receive the WCSession messages, I would set up a singleton class that receives those messages instead. That class can parse and organize your user info data from the WCSession. That singleton class can/will be accessible in your ComplicationController and your InterfaceController
Singletons are fairly easy to setup in swift:
class DataManager : WCSessionDelegate {
// This is how you create a singleton
static let sharedInstance = DataManager()
override init() {
super.init()
if WCSession.isSupported() {
self.watchConnectivitySession?.delegate = self
self.watchConnectivitySession?.activateSession()
}
}
// This is where you would store your `Ev`s once fetched
var dataObjects = [Ev]()
// This is the method that would fetch them for you
func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
//parse your userInfoDictionary
self.dataObjects = evs
}
}
Then in your InterfaceController you can reference it using DataManager.sharedInstance.dataObjects to build your InterfaceController or ComplicationsController
The idea with a singleton is that you have a one global reference. DataManager only gets instantiated once and only once.

RxSwift subscribe block not called

I'm playing around with RxSwift and I'm stuck with a simple toy programm. My program essentially contains a model class and a viewcontroller. The model contains an observable that gets updated on the main queue after an asynchronous network call, the viewcontroller subscribes in viewDidLoad(). The AppDelegate initializes the model and passes it to ViewController and triggers the network request.
class GalleryModel {
var galleryCount: BehaviorSubject<Int>
init() {
galleryCount = BehaviorSubject.init(value:0)
}
func refresh() {
doAsyncRequestToAmazonWithCompletion { (response) -> AnyObject! in
var counter = 0
//process response
counter = 12
dispatch_async(dispatch_get_main_queue()) {
self.galleryCount.on(.Next(counter))
}
return nil
}
}
class ViewController: UIViewController {
#IBOutlet weak var label: UILabel!
var galleryModel: GalleryModel?
override func viewDidLoad() {
super.viewDidLoad()
galleryModel?.galleryCount.subscribe { e in
if let gc = e.element {
self.label.text = String(gc)
}
}
}
}
class AppDelegate: UIResponder, UIApplicationDelegate {
var galleryModel: GalleryModel?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
//do amazon setup
galleryModel = GalleryModel()
if let viewController = window?.rootViewController as? ViewController {
viewController.galleryModel = GalleryModel()
}
return true
}
func applicationDidBecomeActive(application: UIApplication) {
galleryModel?.refresh()
}
The label gets updated only one, it shows "0". I expected the label to get updated twice, showing "0" after the first update and showing "12" after the second update after the processing of the network request. A breakpoint in the dispatch_async block gets hit, but it seems that galleryCount lost its observer. Anybody any idea what's happening or how to debug this?
Best
In case reads this anyone is interested. It was an refactoring error, after renaming variables I stopped passing the observable to the ViewController. Instead I created a new one... facepalm
Here are some useful snippets for subscribe in RxSwift (in Japanese)
For example to subscribe to different events:
let source: Observable<Int> = create { (observer: ObserverOf<Int>) in
sendNext(observer, 42)
sendCompleted(observer)
return AnonymousDisposable {
print("disposed")
}
}
let subscription = source.subscribe { (event: Event<Int>) -> Void in
switch event {
case .Next(let element):
print("Next: \(element)")
case .Completed:
print("Completed")
case .Error(let error):
print("Error: \(error)")
}
}
Clean and Build solved the problems for me

Resources