Alternative of VpnService in iOS (Swift) - ios

We have an android app that has a custom VPN built into it. We have used VpnService class to implement this feature. This is the code in Android used to build a virtual interface.
We create the tunnel first and then create multiple udp sessions in multiple threads.
try {
if (vpnInterface == null) {
Builder builder = new Builder();
builder.setMtu(1024);
builder.addAddress("10.10.10.9", 32);
builder.addRoute("10.10.10.0", 24);
vpnInterface = builder
.setSession(getString(R.string.app_name))
.setConfigureIntent(pendingIntent)
.setBlocking(true)
.establish();
FileDescriptor vpnFileDescriptor = vpnInterface.getFileDescriptor();
writePacket(vpnFileDescriptor);
readPacket(vpnFileDescriptor);
}
} catch (Exception e) {
e.printStackTrace();
}
We want to do the exact same thing is ios as well. I'm a swift newbie and I tried to work with Network extensions and use the NEPacketTunnelNetworkSettings to replicate this feature but I could not make it work. How would I create a layer 3 virtual network interface in iOS? This is what I have so far.
import NetworkExtension
class PacketTunnelProvider: NEPacketTunnelProvider {
override func startTunnel(options: [String : NSObject]?, completionHandler: #escaping (Error?) -> Void) {
let serverAddress = "10.10.10.10"
let clientAddress = "10.10.10.9"
let mtu = 1024 as NSNumber?
let netMask = "255.255.255.255"
let settings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: serverAddress)
settings.ipv4Settings = NEIPv4Settings(addresses: [clientAddress], subnetMasks: [netMask])
settings.ipv4Settings?.includedRoutes = [NEIPv4Route.default()]
settings.ipv4Settings?.excludedRoutes = []
settings.mtu = mtu
setTunnelNetworkSettings(settings){ error in
if let e = error {
print("ERROR:",e)
NSLog("Settings error %#", e.localizedDescription)
} else {
NSLog("Settings %#", "Settings have been created successfully and tunnel has started")
}
}
}
override func stopTunnel(with reason: NEProviderStopReason, completionHandler: #escaping () -> Void) {
// Add code here to start the process of stopping the tunnel.
completionHandler()
}
override func handleAppMessage(_ messageData: Data, completionHandler: ((Data?) -> Void)?) {
// Add code here to handle the message.
if let handler = completionHandler {
handler(messageData)
}
}
override func sleep(completionHandler: #escaping () -> Void) {
// Add code here to get ready to sleep.
completionHandler()
}
override func wake() {
// Add code here to wake up.
}
}

Related

Flutter Plugin IOS Error - [Security] This method should not be called on the main thread as it may lead to UI unresponsiveness

I am trying to implement flutter version of this IOS Code - https://developer.mastercard.com/open-banking-us/documentation/connect/mobile-sdks/#ios
I am facing this issue when I am trying to connect the Connect SDK
2022-12-21 23:52:39.678730+0530 Runner[36839:1696429] [Security] This method should not be called on the main thread as it may lead to UI unresponsiveness.
This is the Code I have written so far:
import Flutter
import UIKit
import Connect
public class SwiftFinicityPlugin: NSObject, FlutterPlugin {
var flutterResult: FlutterResult!
var connectViewController: ConnectViewController!
var connectNavController: UINavigationController!
public static func register(with registrar: FlutterPluginRegistrar) {
let channel = FlutterMethodChannel(name: "finicity", binaryMessenger: registrar.messenger())
let instance = SwiftFinicityPlugin()
registrar.addMethodCallDelegate(instance, channel: channel)
}
public func handle(_ call: FlutterMethodCall, result: #escaping FlutterResult) {
flutterResult = result;
guard let args = call.arguments as? [String : Any] else {return}
let connectUrl = args["connectUrl"] as! String
let config = ConnectViewConfig(connectUrl: connectUrl, loaded: self.connectLoaded, done: self.connectDone, cancel: self.connectCancelled, error: self.connectError, route: self.connectRoute, userEvent: self.connectUserEvent)
flutterResult("iOS " + UIDevice.current.systemVersion)
self.connectViewController = ConnectViewController()
self.connectViewController.load(config: config)
}
func connectLoaded() {
self.connectNavController = UINavigationController(rootViewController: self.connectViewController)
self.connectNavController.modalPresentationStyle = .fullScreen
self.present(self.connectNavController, animated: false)
}
func connectDone(_ data: NSDictionary?) {
flutterResult("iOS " + UIDevice.current.systemVersion)
}
func connectCancelled() {
// Connect flow exited prematurely
}
func connectError(_ data: NSDictionary?) {
// Error encountered in Connect flow
}
func connectRoute(_data: NSDictionary?) {
// Connect route changed
}
func connectUserEvent(_ data: NSDictionary?) {
// Connect user event fired in response to user action
}
}

DNSProxyProvider is not getting installed

I've created a MacOS app project which contains System Network Extension (DNS proxy). With help of https://developer.apple.com/forums/thread/81103?answerId=246229022 and How to use NEDNSProxyProvider in iOS 11. I enabled DNSProxyProvider. When I built the app for the first time a prompt asked to allow the system extension. I allowed it and Under network in system preference, I can see DNS is not running (yellow dot).
Not running
I added OSSystemextensionRequest from the container App. This is my code.
import Cocoa
import NetworkExtension
import SystemExtensions
import OSLog
class ViewController: NSViewController {
let log=OSLog(subsystem:"com.example.applesamplecode.DNSTestBed", category: "app")
let manager = NEDNSProxyManager.shared()
private func installSystemExtension() {
os_log("DNSFProxy: installing system extension")
let request = OSSystemExtensionRequest.activationRequest(
forExtensionWithIdentifier: "my system extension's bundle indentifier",
queue: .main
)
request.delegate = self
OSSystemExtensionManager.shared.submitRequest(request)
}
private func enable() {
NSLog("enabled already ",self.manager.isEnabled )
NSLog(self.manager.isEnabled ? "Yes" : "No")
self.update {
self.manager.localizedDescription = "DNS"
let proto = NEDNSProxyProviderProtocol()
proto.serverAddress = "localhost"
proto.providerBundleIdentifier = "my system extension's bundle indentifier"
self.manager.providerProtocol = proto
self.manager.isEnabled = true
}
NSLog(self.manager.isEnabled ? "Yes" : "No")
}
private func disable() {
self.update {
self.manager.isEnabled = false
}
}
private func update(_ body: #escaping () -> Void) {
self.manager.loadFromPreferences { (error) in
guard error == nil else {
NSLog("DNS Test App: load error")
return
}
body()
self.manager.saveToPreferences { (error) in
guard error == nil else {
NSLog("DNS Test App: save error")
return
}
NSLog("DNS Test App: saved")
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.enable()
self.installSystemExtension()
}
}
extension ViewController: OSSystemExtensionRequestDelegate {
func request(_ request: OSSystemExtensionRequest, actionForReplacingExtension existing: OSSystemExtensionProperties, withExtension ext: OSSystemExtensionProperties) -> OSSystemExtensionRequest.ReplacementAction {
os_log("DNSProxy: Replacing extension %# version %# with version %#", request.identifier, existing.bundleShortVersion, ext.bundleShortVersion)
return .replace
}
func requestNeedsUserApproval(_ request: OSSystemExtensionRequest) {
os_log("DNSProxy: Extension %# requires user approval", request.identifier)
}
func request(_ request: OSSystemExtensionRequest, didFailWithError error: Error) {
os_log("DNSProxy: System extension request failed: %#", error.localizedDescription)
}
/* Other delegate methods here */
func request(_ request: OSSystemExtensionRequest, didFinishWithResult result: OSSystemExtensionRequest.Result) {
switch result {
case .completed:
var manager = NEDNSProxyManager.shared()
case .willCompleteAfterReboot:
os_log("DNSProxy: willCompleteAfterReboot")
#unknown default:
os_log("DNSProxy: default")
}
}
}
Info.plist of system extension:
info.plist
I'm getting an error when the activation request happens: The operation couldn’t be completed. (OSSystemExtensionErrorDomain error 1.). Any help would be great. Thanks.
P.S: I also posted this in apple forums https://developer.apple.com/forums/thread/717647

Unable to attach debugger to Network Extension iOS ("Waiting to Attach")

I'm new to iOS development, and am writing a VPN app, using the OpenVPN framework. I'm making use of the Network Extension target to tunnel network traffic to our OpenVPN server. However, when trying to debug, I can't attach the debugger to the Network Extension - it's stuck at "Waiting to Attach" in the Debug Navigator view.
In this blog post by Alex Grebenyuk he gives some recommendations for troubleshooting when you fail to attach the debugger to the extension. I've meticulously checked all of them, and my app correctly uses everything there: Both the app and the network extension have the Packet Tunnel Network Extension capability and bundle names all work out.
Some more info on the bundle names in case it might be relevant:
Bundle name for the app: com.example.Example-App
Bundle name for the Network Extension: com.example.Example-App.Example-VPN
Group name: group.com.example.Example-App
I'm out of ideas here. Could the problem be in the Network Extension never being started? It doesn't seem to be the case, because the startTunnel() function is called. The code for starting the VPN through the extension:
func configureVPN(serverAddress: String) {
guard let configData = readTestFile() else { return }
// Test file with ovpn configuration seems is read in correctly - contents are shown if printed to console.
self.providerManager?.loadFromPreferences { error in
if error == nil {
let tunnelProtocol = NETunnelProviderProtocol()
tunnelProtocol.serverAddress = serverAddress
tunnelProtocol.providerBundleIdentifier = "com.example.Example-App.Example-VPN" // bundle id of the network extension target
tunnelProtocol.providerConfiguration = ["ovpn": configData]
self.providerManager.protocolConfiguration = tunnelProtocol
self.providerManager.localizedDescription = "Example VPN"
self.providerManager.isEnabled = true
self.providerManager.saveToPreferences(completionHandler: { (error) in
if error == nil {
self.providerManager.loadFromPreferences(completionHandler: { (error) in
do {
print("Trying to start connection now") // This line is printed to the Console
try self.providerManager.connection.startVPNTunnel() // start the VPN tunnel.
} catch let error {
print(error.localizedDescription)
}
})
}
})
}
}
}
Not sure if this is relevant information, but I am using the OpenVPNAdapter in the PacketTunnelProvider, following this tutorial:
import NetworkExtension
import OpenVPNAdapter
class PacketTunnelProvider: NEPacketTunnelProvider {
lazy var vpnAdapter: OpenVPNAdapter = {
let adapter = OpenVPNAdapter()
adapter.delegate = self
return adapter
}()
let vpnReachability = OpenVPNReachability()
var startHandler: ((Error?) -> Void)?
var stopHandler: (() -> Void)?
override func startTunnel(
options: [String : NSObject]?,
completionHandler: #escaping (Error?) -> Void
) {
// There are many ways to provide OpenVPN settings to the tunnel provider. For instance,
// you can use `options` argument of `startTunnel(options:completionHandler:)` method or get
// settings from `protocolConfiguration.providerConfiguration` property of `NEPacketTunnelProvider`
// class. Also you may provide just content of a ovpn file or use key:value pairs
// that may be provided exclusively or in addition to file content.
// In our case we need providerConfiguration dictionary to retrieve content
// of the OpenVPN configuration file. Other options related to the tunnel
// provider also can be stored there.
guard
let protocolConfiguration = protocolConfiguration as? NETunnelProviderProtocol,
let providerConfiguration = protocolConfiguration.providerConfiguration
else {
fatalError()
}
guard let ovpnFileContent: Data = providerConfiguration["ovpn"] as? Data else {
fatalError()
}
let configuration = OpenVPNConfiguration()
configuration.fileContent = ovpnFileContent
configuration.settings = [ :
// Additional parameters as key:value pairs may be provided here
]
// Uncomment this line if you want to keep TUN interface active during pauses or reconnections
// configuration.tunPersist = true
// Apply OpenVPN configuration
let evaluation: OpenVPNConfigurationEvaluation
do {
evaluation = try vpnAdapter.apply(configuration: configuration)
} catch {
completionHandler(error)
return
}
// Provide credentials if needed
if !evaluation.autologin {
// If your VPN configuration requires user credentials you can provide them by
// `protocolConfiguration.username` and `protocolConfiguration.passwordReference`
// properties. It is recommended to use persistent keychain reference to a keychain
// item containing the password.
guard let username: String = protocolConfiguration.username else {
fatalError()
}
// Retrieve a password from the keychain
let password: String = "Test"
let credentials = OpenVPNCredentials()
credentials.username = username
credentials.password = password
do {
try vpnAdapter.provide(credentials: credentials)
} catch {
completionHandler(error)
return
}
}
// Checking reachability. In some cases after switching from cellular to
// WiFi the adapter still uses cellular data. Changing reachability forces
// reconnection so the adapter will use actual connection.
vpnReachability.startTracking { [weak self] status in
guard status == .reachableViaWiFi else { return }
self?.vpnAdapter.reconnect(afterTimeInterval: 5)
}
// Establish connection and wait for .connected event
startHandler = completionHandler
vpnAdapter.connect(using: packetFlow as! OpenVPNAdapterPacketFlow)
}
override func stopTunnel(
with reason: NEProviderStopReason,
completionHandler: #escaping () -> Void
) {
stopHandler = completionHandler
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
vpnAdapter.disconnect()
}
}
extension PacketTunnelProvider: OpenVPNAdapterDelegate {
// OpenVPNAdapter calls this delegate method to configure a VPN tunnel.
// `completionHandler` callback requires an object conforming to `OpenVPNAdapterPacketFlow`
// protocol if the tunnel is configured without errors. Otherwise send nil.
// `OpenVPNAdapterPacketFlow` method signatures are similar to `NEPacketTunnelFlow` so
// you can just extend that class to adopt `OpenVPNAdapterPacketFlow` protocol and
// send `self.packetFlow` to `completionHandler` callback.
func openVPNAdapter(
_ openVPNAdapter: OpenVPNAdapter,
configureTunnelWithNetworkSettings networkSettings: NEPacketTunnelNetworkSettings?,
completionHandler: #escaping (Error?) -> Void
) {
// In order to direct all DNS queries first to the VPN DNS servers before the primary DNS servers
// send empty string to NEDNSSettings.matchDomains
networkSettings?.dnsSettings?.matchDomains = [""]
// Set the network settings for the current tunneling session.
setTunnelNetworkSettings(networkSettings, completionHandler: completionHandler)
}
// Process events returned by the OpenVPN library
func openVPNAdapter(
_ openVPNAdapter: OpenVPNAdapter,
handleEvent event:
OpenVPNAdapterEvent, message: String?
) {
switch event {
case .connected:
if reasserting {
reasserting = false
}
guard let startHandler = startHandler else { return }
startHandler(nil)
self.startHandler = nil
case .disconnected:
guard let stopHandler = stopHandler else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
stopHandler()
self.stopHandler = nil
case .reconnecting:
reasserting = true
default:
break
}
}
// Handle errors thrown by the OpenVPN library
func openVPNAdapter(_ openVPNAdapter: OpenVPNAdapter, handleError error: Error) {
// Handle only fatal errors
guard let fatal = (error as NSError).userInfo[OpenVPNAdapterErrorFatalKey] as? Bool,
fatal == true else { return }
if vpnReachability.isTracking {
vpnReachability.stopTracking()
}
if let startHandler = startHandler {
startHandler(error)
self.startHandler = nil
} else {
cancelTunnelWithError(error)
}
}
}
Any help with this would be hugely appreciated! Thanks.
You need to attache the bundle name and not the bundle identifier and the bundle name should not be an identifier. The PRODUCT_BUNDLE_IDENTIFIER of your network extension would be something like com.example.my-network-extension-id (it cannot be com.example.Example-App.Example-VPN, as a third period is not allowed for network extension IDs) and the PRODUCT_NAME would be something like My Network Extension, and you would then attach to My Network Extension (yes, with spaces and no extension).

How can I update a view's progress bar using the AppSync S3ObjectManager?

I'm using AWSAppSyncClient to upload files but I'm struggling to connect the upload progress hook with the view.
AWSAppSyncClient is a property of the the application delegate initialized with an S3ObjectManager. The object manager method upload has access to the upload progress via the AWSTransferUtilityUplaodExpression:
expression.progressBlock = {(task, progress) in
DispatchQueue.main.async(execute: {
// Can we update the controller's progress bar here?
print("Progress: \(Float(progress.fractionCompleted))")
})
}
My controller invokes the upload by calling perform:
var appSyncClient: AWSAppSyncClient? // retrieved from the app delegate singleton
appSyncClient?.perform(mutation: CreatePostMutation(input: input)) { (result, error) in ...
What I am struggling with: how do I provide the S3ObjectManager a reference to the controller? I thought of instantiating the AWSAppSyncClient in each controller, and maybe using some sort of delegate pattern?
It's probably overkill to instantiate a new client on each view controller. Setup & teardown take a bit of time & system resources to perform, and you'd probably prefer to keep those activities separate from the view controller in any case, just for separation of responsibilities.
There isn't really a good way of registering a per-object listener, since mutations are queued for eventual, asynchronous delivery. Your delegate idea seems like the best approach at this point.
NOTE: Code below is untested, and not thread-safe.
For example, you could declare a singleton delegate that manages watchers for individual views that need to report progress:
class AppSyncS3ObjectManagerProgressWatcher {
typealias ProgressSubscription = UUID
static let shared = AppSyncS3ObjectManagerProgressWatcher()
private var watchers = [UUID: AppSyncS3ObjectManagerProgressDelegate?]()
func add(_ watcher: AppSyncS3ObjectManagerProgressDelegate) -> ProgressSubscription {
let subscription = UUID()
weak var weakWatcher = watcher
watchers[subscription] = weakWatcher
return subscription
}
func remove(_ subscription: ProgressSubscription?) {
guard let subscription = subscription else {
return
}
watchers[subscription] = nil
}
}
extension AppSyncS3ObjectManagerProgressWatcher: AppSyncS3ObjectManagerProgressDelegate {
func progressReportingExpression(forDownloadingObject object: AWSS3ObjectProtocol) -> AWSS3TransferUtilityDownloadExpression {
let expression = AWSS3TransferUtilityDownloadExpression()
expression.progressBlock = { _, progress in
self.didReportProgress(forDownloadingObject: object, progress: progress)
}
return expression
}
func progressReportingExpression(forUploadingObject object: AWSS3ObjectProtocol & AWSS3InputObjectProtocol) -> AWSS3TransferUtilityUploadExpression {
let expression = AWSS3TransferUtilityUploadExpression()
expression.progressBlock = { _, progress in
self.didReportProgress(forUploadingObject: object, progress: progress)
}
return expression
}
func didReportProgress(forDownloadingObject object: AWSS3ObjectProtocol, progress: Progress) {
for watcher in watchers.values {
watcher?.didReportProgress(forDownloadingObject: object, progress: progress)
}
}
func didReportProgress(forUploadingObject object: AWSS3ObjectProtocol & AWSS3InputObjectProtocol, progress: Progress) {
for watcher in watchers.values {
watcher?.didReportProgress(forUploadingObject: object, progress: progress)
}
}
}
Wherever you conform S3TransferUtility to S3ObjectManager, you would do something like:
extension AWSS3TransferUtility: AWSS3ObjectManager {
public func download(s3Object: AWSS3ObjectProtocol, toURL: URL, completion: #escaping ((Bool, Error?) -> Void)) {
let completionBlock: AWSS3TransferUtilityDownloadCompletionHandlerBlock = { task, url, data, error -> Void in
if let _ = error {
completion(false, error)
} else {
completion(true, nil)
}
}
let progressReportingExpression = AppSyncS3ObjectManagerProgressWatcher
.shared
.progressReportingExpression(forDownloadingObject: s3Object)
let _ = self.download(
to: toURL,
bucket: s3Object.getBucketName(),
key: s3Object.getKeyName(),
expression: progressReportingExpression,
completionHandler: completionBlock)
}
public func upload(s3Object: AWSS3ObjectProtocol & AWSS3InputObjectProtocol, completion: #escaping ((_ success: Bool, _ error: Error?) -> Void)) {
let completionBlock : AWSS3TransferUtilityUploadCompletionHandlerBlock = { task, error -> Void in
if let _ = error {
completion(false, error)
} else {
completion(true, nil)
}
}
let progressReportingExpression = AppSyncS3ObjectManagerProgressWatcher
.shared
.progressReportingExpression(forUploadingObject: s3Object)
let _ = self.uploadFile(
s3Object.getLocalSourceFileURL()!,
bucket: s3Object.getBucketName(),
key: s3Object.getKeyName(),
contentType: s3Object.getMimeType(),
expression: progressReportingExpression,
completionHandler: completionBlock
).continueWith { (task) -> Any? in
if let err = task.error {
completion(false, err)
}
return nil
}
}
}
And then in the progress reporting view:
override func awakeFromNib() {
super.awakeFromNib()
progressSubscription = AppSyncS3ObjectManagerProgressWatcher.shared.add(self)
}
func didReportProgress(forUploadingObject object: AWSS3InputObjectProtocol & AWSS3ObjectProtocol, progress: Progress) {
// TODO: Filter by object local URI/key/etc to ensure we're updating the correct progress
print("Progress received for \(object.getKeyName()): \(progress.fractionCompleted)")
self.progress = progress
}
As I noted, this code is untested, but it should outline a general approach for you to start from. I'd welcome your feedback and would like to hear what approach you eventually settle on.
Finally, please feel free to open a feature request on our issues page: https://github.com/awslabs/aws-mobile-appsync-sdk-ios/issues

Passing data to Apple Watch app

I am trying to pass data from my app into my Apple Watch app. Basically, I am using the same method as I used for creating the today widget and so I am passing data through NSUserDefaults.
The problem is, that when I run my app, the data does not update the labels in the Watch app as I would expect it to.
Here is what I have...
override init(context: AnyObject?) {
// Initialize variables here.
super.init(context: context)
// Configure interface objects here.
NSLog("%# init", self)
var defaults = NSUserDefaults(suiteName: "group.AffordIt")
var totalBudgetCalculation = ""
if (defaults!.stringForKey("totalBudgetWidget") != nil) {
println("Worked")
totalBudgetCalculation = defaults!.stringForKey("totalBudgetWidget")!
initialBudgetLabel.setText("Initial: \(totalBudgetCalculation)")
}
var currentBudgetCalculation = ""
if (defaults!.stringForKey("currentBudgetWidget") != nil) {
currentBudgetCalculation = defaults!.stringForKey("currentBudgetWidget")!
currentBudgetLabel.setText("Current: \(currentBudgetCalculation)")
}
}
I tried putting this code in willActivate(), however that doesn't seem to make a difference.
Anyone know where I am going wrong?
This applies to OS 1 only. See below for better answers.
I got it working using your method. I guess there's a couple of things you can check:
1) Are you synchronising the defaults after you set the value:
defaults?.synchronize();
NSLog("%# ", defaults?.dictionaryRepresentation())
2) Have you enabled the App Group in both your app and your extension?
3) Are you using the correctly named app group when constructing the NSDefaults? For example, I use:
NSUserDefaults(suiteName: "group.com.brindysoft.MyWatch");
Once all that's set up I run the app, set the value in the defaults, then run the glance target which reads the value from the default and that seems to work!
Still stuck? check your app groups in your apple account
The accepted answer applies to apple watch os 1. See NSUserDefaults not working on Xcode beta with Watch OS2
For OS2 - you will need to use the WatchConnectivity frameworks and implement the WCSessionDelegate.
import WatchConnectivity
import WatchKit
#available(iOS 9.0, *)
var alertDelegate:HomeIC? = nil
public class WatchData: NSObject,WCSessionDelegate {
var session = WCSession.defaultSession()
//
class var shared: WatchData {
struct Static {
static var onceToken: dispatch_once_t = 0
static var instance: WatchData? = nil
}
dispatch_once(&Static.onceToken) {
Static.instance = WatchData()
}
return Static.instance!
}
public func session(session: WCSession, didReceiveFile file: WCSessionFile){
print(__FUNCTION__)
print(session)
}
public func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
print(__FUNCTION__)
print(session)
alertDelegate?.showMessage("didReceiveApplicationContext")
}
public func sessionReachabilityDidChange(session: WCSession){
print(__FUNCTION__)
print(session)
print("reachability changed:\(session.reachable)")
let text = session.reachable ? "reachable" : "unreachable"
alertDelegate?.showMessage(text)
}
public func sessionWatchStateDidChange(session: WCSession) {
print(__FUNCTION__)
print(session)
print("reachable:\(session.reachable)")
// alertDelegate?.showMessage("sessionWatchStateDidChange")
if !session.receivedApplicationContext.keys.isEmpty {
alertDelegate?.showMessage(session.receivedApplicationContext.description)
}
}
public func session(session: WCSession, didReceiveMessageData messageData: NSData){
if !session.receivedApplicationContext.keys.isEmpty {
alertDelegate?.showMessage(session.receivedApplicationContext.description)
}
}
public func session(session: WCSession, didReceiveMessage message: [String : AnyObject]){
print(__FUNCTION__)
if let data = message["data"] {
alertDelegate?.showMessage(data as! String)
return
}
}
public func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
print(__FUNCTION__)
if let data = message["data"] {
alertDelegate?.showMessage(data as! String)
return
}
guard message["request"] as? String == "showAlert" else {return}
}
public func activate(){
if WCSession.isSupported() { // it is supported
session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
print("watch activating WCSession")
} else {
print("watch does not support WCSession")
}
if(!session.reachable){
print("not reachable")
return
}else{
print("watch is reachable")
}
}
}
Sample Usage
class HomeIC: WKInterfaceController {
// MARK: Properties
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
// Initialize the `WCSession`.
WatchData.shared.activate()
alertDelegate = self
}
internal func showMessage(msg:String){
let defaultAction = WKAlertAction(title: msg, style: WKAlertActionStyle.Default) { () -> Void in }
let actions = [defaultAction]
self.presentAlertControllerWithTitle( "Info", message: "", preferredStyle: WKAlertControllerStyle.Alert, actions: actions)
}
}
in my iphone code / I can invoke sharing data here
if #available(iOS 9.0, *) {
WatchData.shared.sendInbox()
} else {
// Fallback on earlier versions
}
And somewhere else I have another discrete singleton for watch data session.
#available(iOS 9.0, *)
public class WatchData: NSObject,WCSessionDelegate {
var session = WCSession.defaultSession()
var payload:String = ""
class var shared: WatchData {
struct Static {
static var onceToken: dispatch_once_t = 0
static var instance: WatchData? = nil
}
dispatch_once(&Static.onceToken) {
Static.instance = WatchData()
}
return Static.instance!
}
public func sessionReachabilityDidChange(session: WCSession){
print(__FUNCTION__)
print(session)
print("reachability changed:\(session.reachable)")
if (session.reachable){
}
}
public func sessionWatchStateDidChange(session: WCSession) {
print(__FUNCTION__)
print(session)
print("reachable:\(session.reachable)")
}
public func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
print(__FUNCTION__)
guard message["request"] as? String == "showAlert" else {return}
guard let m = message["m"] as? String else { return }
print("msg:",m)
}
public func sendInbox(){
if (!session.reachable){
if WCSession.isSupported() { // it is supported
session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
print("iphone activating WCSession")
} else {
print("iphone does not support WCSession")
}
session.activateSession()
}
if(session.paired){
if(session.watchAppInstalled){
print("paired | watchAppInstalled")
}
}else{
print("not paired | or no watchAppInstalled")
}
if(!session.reachable){
print("not reachable")
return
}else{
/*let transfer:WCSessionUserInfoTransfer = (session.transferUserInfo(["data" : "Test2"]) as WCSessionUserInfoTransfer?)!
if(transfer.transferring){
print("-> iphone")
}else{
print("!-> iphone")
}*/
session.sendMessage(["data" :"test"],
replyHandler: { reply in
},
errorHandler: { error in
print(error)
})
}
}
}
Refer to sample watch os2 app
https://github.com/shu223/watchOS-2-Sampler/tree/20eeebeed66764d0814603e97d3aca5933236299
As #johndpope said, shared NSUserDefaults no longer work on WatchOS2.
I'm posting a simplified solution that's not as full featured as john's but will get the job done in most cases.
In your iPhone App, follow these steps:
Pick find the view controller that you want to push data to the Apple Watch from and add the framework at the top.
import WatchConnectivity
Now, establish a WatchConnectivity session with the watch and send some data.
if WCSession.isSupported() { //makes sure it's not an iPad or iPod
let watchSession = WCSession.defaultSession()
watchSession.delegate = self
watchSession.activateSession()
if watchSession.paired && watchSession.watchAppInstalled {
do {
try watchSession.updateApplicationContext(["foo": "bar"])
} catch let error as NSError {
print(error.description)
}
}
}
Please note, this will NOT work if you skip setting the delegate, so even if you never use it you must set it and add this extension:
extension MyViewController: WCSessionDelegate {
}
Now, in your watch app (this exact code works for Glances and other watch kit app types as well) you add the framework:
import WatchConnectivity
Then you set up the connectivity session:
override func awakeWithContext(context: AnyObject?) {
super.awakeWithContext(context)
let watchSession = WCSession.defaultSession()
watchSession.delegate = self
watchSession.activateSession()
}
and you simply listen and handle the messages from the iOS app:
extension InterfaceController: WCSessionDelegate {
func session(session: WCSession, didReceiveApplicationContext applicationContext: [String : AnyObject]) {
print("\(applicationContext)")
dispatch_async(dispatch_get_main_queue(), {
//update UI here
})
}
}
That's all there is to it.
Items of note:
You can send a new applicationContext as often as you like and it
doesn't matter if the watch is nearby and connected or if the watch
app is running. This delivers the data in the background in an
intelligent way and that data is sitting there waiting when the
watch app is launched.
If your watch app is actually active and running, it should receive
the message immediately in most cases.
You can reverse this code to have the watch send messages to the
iPhone app the same way.
applicationContext that your watch app receives when it is viewed will ONLY be the last message you sent. If you sent 20 messages before the watch app is viewed, it will ignore the first 19 and handle the 20th one.
For doing a direct/hard connection between the 2 apps or for background file transfers or queued messaging, check out the WWDC video.
Another way to communicate between the app and the watch is via wormhole:
https://github.com/mutualmobile/MMWormhole
Send:
[self.wormhole passMessageObject:#{#"titleString" : title}
identifier:#"messageIdentifier"];
id messageObject = [self.wormhole messageWithIdentifier:#"messageIdentifier"];
Recieve:
[self.wormhole listenForMessageWithIdentifier:#"messageIdentifier"
listener:^(id messageObject) {
// Do Something
}];
Just use watch connectivity for communicate between these two platform you can read more about this in apple document
https://developer.apple.com/documentation/watchconnectivity

Resources