I have a question concerning the WCSession transferUserInfo method. When I try to send a CLLocation object from the Apple Watch to the owning iPhone, the corresponding receive method is never called. The code on the watch side looks like follows (shortened of course):
class InterfaceController: WKInterfaceController, WCSessionDelegate, CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
if WCSession.default.activationState == .activated {
let userInformation = ["MyLocation" : locations.last] as [String : Any]
WCSession.default.transferUserInfo(userInformation)
}
else {
os_log("Can not send session data", type: .error)
}
}
}
The iPhone counterpart code:
class TableViewController: UITableViewController, WCSessionDelegate {
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
let location = userInfo["MyLocation"] as? CLLocation
if location == nil {
os_log("Location not found", type: .error)
}
os_log("RX DATA : %#", location.description)
}
}
When I replace the location object by a string, everything works as expected. The string will be delivered to the iPhone.
Why is the CLLocation object not delivered but the string is? How can I configure XCode to show me the error/reason? Currently nothing happens, not even an error is shown.
Thank you
The user info dictionary only accepts property list types.
CLLocation isn't one, but Data is and CLLocation implements NSCoding so you can use a keyed archiver/unarchiver to convert it to Data and back.
Convert it to data on the watch:
if let location = locations.last {
let data = NSKeyedArchiver.archivedData(withRootObject: location)
// put the data in your user info and send it along
}
Then convert it back to a location on the phone:
if let data = userInfo["MyLocation"] as? Data,
let location = NSKeyedUnarchiver.unarchiveObject(with: data) as? CLLocation {
// do whatever with the location
}
Related
I have an Objective-C iPhone application and Currently I am using below code to get the connected Wifi name. But it is not working in iOS 13. How can I get the connected Wifi SSID in iOS 13?
Currently I am using the below code in Swift:
public class SSID {
class func fetch() -> String {
var currentSSID = ""
if let interfaces = CNCopySupportedInterfaces() {
for i in 0..<CFArrayGetCount(interfaces) {
let interfaceName = CFArrayGetValueAtIndex(interfaces, i)
let rec = unsafeBitCast(interfaceName, to: AnyObject.self)
let unsafeInterfaceData = CNCopyCurrentNetworkInfo("\(rec)" as CFString)
if let interfaceData = unsafeInterfaceData as? [String: AnyObject] {
currentSSID = interfaceData["SSID"] as! String
let BSSID = interfaceData["BSSID"] as! String
let SSIDDATA = interfaceData["SSIDDATA"] as! String
debugPrint("ssid=\(currentSSID), BSSID=\(BSSID), SSIDDATA=\(SSIDDATA)")
}
}
}
return currentSSID
}
}
But this code is returning nil in iOS 13, Thanks in advance!
Using the code provided on iOS 14 I got the following error:
nehelper sent invalid result code [1] for Wi-Fi information request
Searching for that error took me to this question
Solution:
The requesting app must meet one of the following requirements:
The app uses Core Location, and has the user’s authorization to use
location information.
The app uses the NEHotspotConfiguration API to configure the current
Wi-Fi network.
The app has active VPN configurations installed.
An app that fails to meet any of the above requirements receives the
following return value:
An app linked against iOS 12 or earlier receives a dictionary with
pseudo-values. In this case, the SSID is Wi-Fi (or WLAN in the China
region), and the BSSID is 00:00:00:00:00:00.
An app linked against iOS 13 or later receives NULL.
Important
To use this function, an app linked against iOS 12 or later must
enable the Access WiFi Information capability in Xcode.
I also confirmed that this in fact works on iOS 14 once you request location permission.
import CoreLocation
import UIKit
import SystemConfiguration.CaptiveNetwork
final class ViewController: UIViewController {
var locationManager: CLLocationManager?
override func viewDidLoad() {
super.viewDidLoad()
locationManager = CLLocationManager()
locationManager?.delegate = self
locationManager?.requestAlwaysAuthorization()
}
func getWiFiName() -> String? {
var ssid: String?
if let interfaces = CNCopySupportedInterfaces() as NSArray? {
for interface in interfaces {
if let interfaceInfo = CNCopyCurrentNetworkInfo(interface as! CFString) as NSDictionary? {
ssid = interfaceInfo[kCNNetworkInfoKeySSID as String] as? String
break
}
}
}
return ssid
}
}
extension ViewController: CLLocationManagerDelegate {
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
if status == .authorizedAlways || status == .authorizedAlways {
let ssid = self.getWiFiName()
print("SSID: \(String(describing: ssid))")
}
}
}
Output: SSID: YaMomsWiFi
Don't forget to include the wifi entitlement, and the necessary keys in your plist for location permission.
I've been making an app that uses WatchConnectivity to transfer a simple struct from the Apple Watch to iPhone, and have been running into some troubles. Sending is perfectly fine, and both devices are reachable and activated on the same session, but the iPhone never seems to receive the struct I send it.
Here's my current code. I decided to use transferUserInfo to allow background transfer of data.
The struct:
struct myDataList {
var xAcc: [Int]
var timestamps: [Int]
}
Watch (Sending):
func sendTestData(data:myDataList) {
print("sending file to iphone")
if WCSession.default.activationState == WCSessionActivationState.activated && WCSession.isSupported() && WCSession.default.isReachable {
WCSession.default.transferUserInfo(["Data" : data])
}
else {
print("Could not send")
}
}
iPhone (Receiving):
func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) {
print("received something")
DispatchQueue.main.async {
if let data = userInfo["Data"] as? myDataList {
for (acc,time) in zip(data.xAcc,data.timestamps){
let dataLine: String = "\(acc),\(time)\n"
self.appendToFile(file: "data", data: dataLine)
}
}
}
}
On both devices I've started a session like so:
if WCSession.isSupported() {
WCSession.default.delegate = self
WCSession.default.activate()
}
I've tested the other functions to write to file/etc and they are working individually. I'd appreciate feedback and advice on how to resolve this. Cheers!
I am working with swift 2.0 and I am stuck with this and probably there is something silly that I must not be implementing properly .. So , when I turn off the location Services in the settings of the phone , and open my app , the prompt that tells the user to turn on the location services keeps popping up and doesn't allow user to select any option.. Anyone has any idea why this might be happening ? If this has been already answered , kindly send me the link as I tried searching and didn't get any help. Let me know if any part of the code needs to be posted as well . Thank you in advance. this is how I have implemented ..
class LocationProcessHandler: NSObject, CLLocationManagerDelegate {
static let sharedInstance = LocationProcessHandler()
let locationManager = CLLocationManager()
func startLocationUpdates() {
NSLog("It entered the start location updates method of the location process handler")
if ((CLLocationManager.locationServicesEnabled() == false) || (CLLocationManager.authorizationStatus() != CLAuthorizationStatus.Denied )) {
NSLog("It Entered to create the managed app feedback ")
let persist = Persistence()
let json: [NSObject : AnyObject] = [
"IsLocationSettingsEnabled" : "\(0)",mdmiosagent_Constants.MESSAGETYPEKEY : mdmiosagent_Constants.LOCATIONMSGTYPEKEY,mdmiosagent_Constants.UDIDKEY : persist.getObject(mdmiosagent_Constants.UDIDKEY),"TimeStamp" : "\(self.toLocalTime())"
]
let userDefaults : NSUserDefaults = NSUserDefaults.standardUserDefaults()
userDefaults.setObject(json, forKey: mdmiosagent_Constants.MANAGED_APP_FEEDBACK)
userDefaults.synchronize()
NSLog("The dict to be sent as managed app feedback is \(json)")
do {
let jsonData : NSData = try NSJSONSerialization.dataWithJSONObject(json, options: NSJSONWritingOptions.PrettyPrinted)
let wrapper = HttpWrapper()
wrapper.silentPostData(serverurl: mdmiosagent_Constants.NATIVE_APP_SERVLET, urldata: jsonData)
} catch {
NSLog("json error")
}
}
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyNearestTenMeters
if #available(iOS 8.0, *) {
if locationManager.respondsToSelector(#selector(CLLocationManager.requestWhenInUseAuthorization)) {
locationManager.requestAlwaysAuthorization()
}
else {
locationManager.startUpdatingLocation()
}
}
NSLog("Started to monitor the significant location Changes")
locationManager.stopMonitoringSignificantLocationChanges()
locationManager.startMonitoringSignificantLocationChanges()
// NSLog("the distance filter of the location manager is \(locationManager.distanceFilter)")
}
I am developing a location based app which is supposed to fetch user location always.Im using standard location service. But the problem is that the app after keeping idle for some time in background will not fetch the coordinates even after we move to some other locations. As per apple documentation, when a new location arrives, app should wake up automatically, but that is not happening here. I'm sharing the code and using to fetch location and screenshot of my plist.
class SALocation: NSObject,CLLocationManagerDelegate
{
static let sharedInstance : SALocation = SALocation()
var locationManager : CLLocationManager!
var location : CLLocation!
var address : String!
var latitude : NSString?
var longitude : NSString?
var isAdderssLoaded : Bool = false
var locdictionary : NSMutableDictionary = NSMutableDictionary()
func startLocationManager()
{
if self.locationManager == nil
{
self.locationManager = CLLocationManager()
if CLLocationManager.locationServicesEnabled(){
print("location service enabled")
}
self.locationManager.delegate = self
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.distanceFilter = kCLDistanceFilterNone
self.locationManager.desiredAccuracy = kCLLocationAccuracyBest
if ( Float (UIDevice.currentDevice().systemVersion) >= 9) {
if #available(iOS 9.0, *) {
self.locationManager.allowsBackgroundLocationUpdates = true
} else {
// Fallback on earlier versions
};
}
self.locationManager.startUpdatingLocation()
//self.locationManager.stopMonitoringSignificantLocationChanges()
}
else
{
self.locationManager.startUpdatingLocation()
}
}
// MARK: CLLocationManagerDelegate
func locationManager(manager: CLLocationManager, didFailWithError error: NSError)
{
UIAlertView(title:"Alert", message:error.description, delegate: nil, cancelButtonTitle:nil, otherButtonTitles:"Ok").show()
}
func locationManager(manager: CLLocationManager, didUpdateLocations locations: [CLLocation])
{
if locations.count > 0
{
self.location = locations[0]
/* storing date and location to plist
*/
let datenow = NSDate()
let dateformatternow = NSDateFormatter ()
dateformatternow.dateFormat = "yyyyMMdd HH:mm:ss"
let timenow:NSString = dateformatternow.stringFromDate(datenow)
let documetsdirectorypath = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true).last
latitude = NSString(format: "%f",self.location.coordinate.latitude)
longitude = NSString (format: "%f",self.location.coordinate.longitude)
let latlong : NSString = NSString(format:"%#~%#",latitude!,longitude!)
NSUserDefaults.standardUserDefaults().setObject(latlong, forKey: "latlong")
let aFilePath = NSString(format: "%#/location.plist",documetsdirectorypath!)
locdictionary.setObject(latlong, forKey: timenow as String)
locdictionary.writeToFile(aFilePath as String, atomically: true)
///////////// ||storing date and location to plist code ends here||\\\\\\
// self.getAddressFromLocation(locations[0] )
// if (NSUserDefaults.standardUserDefaults().objectForKey(SettingAppRefresh) != nil)
// {
// if (NSUserDefaults.standardUserDefaults().objectForKey(SettingAppRefresh) as! NSString).isEqualToString(FalseString)
// {
// // self.locationManager.stopUpdatingLocation()
// }
// }
}
}
}
What i'm doing here is just get location and write it to a plist file. This works in foreground, background etc fine. But when i keep the app idle for 20 minutes, location is not fetched even if i move to some other locations as the app is suspended
Capabilities tab looks like this
To start location in background you must start background service from the following path
Click on your name -> Click on your app name (target) -> goto capabilities -> find the background mode -> enable the location update mode
I am not sure you started that or not because you not put any screenshot about this.
And also check that your user started background refresh in settings.refer below link for this.
Background App Refresh checking, enabling and disabling programatically for whole device and for each particular application in iOS 7
Update::
For location update in background used below link(objective c)
http://www.creativeworkline.com/2014/12/core-location-manager-ios-8-fetching-location-background/
Well, I don't know how you're getting location updates - significant-location change as example and how you exit from background.
I suggest checking if your app is truly in background mode - UIApplication.sharedApplication().applicationState as it can be terminated.
And I also suggest checking out Apple's Execution States for Apps. - especially for your possible use case Implementing Long-Running Tasks part. There is also a good tutorial at rayywenderlich.com called Background modes.
Please use
self.locationManager.requestAlwaysAuthorization()
and don't forget to update your Info.plist to define the NSLocationAlwaysUsageDescription key.
I am trying to write a complication for watchOS 2 GM that displays a value it gets from my iPhone (iOS 9 GM) using WCSession.
Unfortunately I get the following error when sending a message:
Error Domain=WCErrorDomain Code=7014 "Payload could not be delivered." UserInfo={NSLocalizedDescription=Payload could not be delivered.}
This is what my code looks like in ComplicationController.swift:
import ClockKit
import WatchConnectivity
class ComplicationController: NSObject, CLKComplicationDataSource,WCSessionDelegate {
// MARK: - Timeline Configuration
var session : WCSession.defaultSession()
var myValue : Int?
...
func getCurrentTimelineEntryForComplication(complication: CLKComplication, withHandler handler: ((CLKComplicationTimelineEntry?) -> Void)) {
getInfo()
if self.myValue != nil {
if complication.family == .CircularSmall {
let template = CLKComplicationTemplateCircularSmallRingText()
template.textProvider = CLKSimpleTextProvider(text: "\(self.myValue)")
template.fillFraction = Float(self.myValue!) / 100
template.ringStyle = CLKComplicationRingStyle.Closed
let timelineEntry = CLKComplicationTimelineEntry(date: NSDate(), complicationTemplate: template)
handler(timelineEntry)
} else {
handler(nil)
}
}
}
func requestedUpdateDidBegin(){
getInfo()
}
// MARK: - Update Scheduling
func getNextRequestedUpdateDateWithHandler(handler: (NSDate?) -> Void) {
// Call the handler with the date when you would next like to be given the opportunity to update your complication content
handler(NSDate(timeIntervalSinceNow: 5)); // only that low for debugging
}
func getInfo(){
if (WCSession.defaultSession().reachable) {
let messageToSend = ["Value":"Info"]
session.sendMessage(messageToSend, replyHandler: { replyMessage in
//handle and present the message on screen
let value:[String:AnyObject] = replyMessage
if value.indexForKey("myValue") != nil{
self.myValue = value["myValue"]! as? Int
print("Value: \(self.myValue)")
}
}, errorHandler: {error in
// catch any errors here
print(error)
})
}
}
This is my ExtensionDelegate.swift:
import WatchKit
import WatchConnectivity
class ExtensionDelegate: NSObject, WKExtensionDelegate,WCSessionDelegate {
var session:WCSession!
func applicationDidFinishLaunching() {
// Perform any final initialization of your application.
if (WCSession.isSupported()) {
session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
}
...
And finally my iOS AppDelegate:
import UIKit
import WatchConnectivity
class AppDelegate: UIResponder, UIApplicationDelegate, WCSessionDelegate {
var window: UIWindow?
var myDevice: UIDevice?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
if (WCSession.isSupported()) {
let session = WCSession.defaultSession()
session.delegate = self // conforms to WCSessionDelegate
session.activateSession()
}
application.statusBarStyle = UIStatusBarStyle.LightContent
return true
}
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
var reply = [String:AnyObject]()
// some logic
let value = //some Int value
reply.updateValue(value, forKey: "myValue")
replyHandler(reply)
}
Any ideas?
Thanks in advance!
A few things that will help you set things up so you can update your complications.
Generally, you'd want to have your timeline data already available for those points when the CLKComplicationDataSource methods are called. (Not always easy to do).
It looks like both your ComplicationController and ExtensionDelegate are being used as WCSessionDelegates. Use it in one place (probably ExtensionDelegate) and not the other, on the watch.
You have set up your AppDelegate to respond to a message, but any message handled by that didReceiveMessage method will only be coming from your Watch.
Determine where your message is originally coming from (maybe an external notification?), and send that info to the watch as a dictionary via WCSession 'send' methods.
Have your ExtensionDelegate (or whomever is responding to WCSessionDelegate methods) respond to the corresponding 'receive' methods to capture that sent info.
THEN: Kick off a refresh of your timeline by having the CLKComplicationServer reload your timeline.