I am wondering what would be best practice to display the heart rate which I get from the BLE heart rate monitor to my IOS app on WatchKit app. A direkt transfer is not possible. I am thinking of a timer which fires every second on the IOS app and transfers the actual heart rate to the SharedDefaults. On the WatchKit app I am also implementing a timer which reads every second from the SharedDefaults. Could that be a good solution?
// Create and share access to an NSUserDefaults object.
NSUserDefaults *mySharedDefaults = [[NSUserDefaults alloc] initWithSuiteName:#"com.example.domain.MyShareExtension"];
// Use the shared user defaults object to update the user's account.
[mySharedDefaults setObject:bpm forKey:#"heartrate"];
You can also pass data from your iPhone app to your WatchKit extension using https://github.com/mutualmobile/MMWormhole and then you don't need to rely on an always running timer.
Related
Recently, the Chinese Ministry of Industry and Information Technology (MIIT) requested that CallKit functionality be deactivated in all apps available on the China App Store. During our review, we found that your app currently includes CallKit functionality and has China listed as an available territory in iTunes Connect.
Now, Question is what next, Which kind of changes require in app
If there isn't any way, How can i remove china from Apple store.
Please share your suggestion if anyone faced this kind of problem.
Regards,
My approach to this issue was inspired by this response on the Apple Developer forums. The general developer consensus right now seems to be that App Review is not giving specific recommendations nor are they currently explaining or requiring a specific technical solution. I think that as long as you can explain to App Review how you’re disabling CallKit for users in China, that would be acceptable.
I updated my app as I discuss below and it passed App Store review first try and we re-released in China on July 24, 2018.
When I submitted my updated app to the App Store, I included a short message in the reviewer info section saying
"In this version and onwards, we do not use CallKit features for users in China. We detect the user's region using NSLocale."
My app was approved 12hr later without any questions or comments from the App Review team.
Detecting users in China
In my app, I use NSLocale to determine if the user is in China. If so, I do not initialize or use CallKit features in my app.
- (void)viewDidLoad {
[super viewDidLoad];
NSLocale *userLocale = [NSLocale currentLocale];
if ([userLocale.countryCode containsString: #"CN"] || [userLocale.countryCode containsString: #"CHN"]) {
NSLog(#"currentLocale is China so we cannot use CallKit.");
self.cannotUseCallKit = YES;
} else {
self.cannotUseCallKit = NO;
// setup CallKit observer
self.callObserver = [[CXCallObserver alloc] init];
[self.callObserver setDelegate:self queue:nil];
}
}
To test this, you can change the region in Settings > General > Language and Region > Region. When I set Region to 'China' but left language set as English, [NSLocale currentLocale] returned "en_CN".
I was using CXCallObserver to observe the state of a call initiated from my app. My general workaround when I could not use CallKit to monitor the call was:
save the NSDate when the call begins
observer for UIApplicationDidBecomeActiveNotification with a UIBackgroundTask with expiration handler (my app already has background modes enabled)
when the app returns from the background, check the elapsed time and if it is was than 5s and less than 90 minutes, assume the call ended and save it (I needed to track call duration).
If the backgroundTaskExpirationHandler is called, assume the call ended and save the end time.
I decided to wait til at least 5s had elapsed because I noticed that -applicationDidBecomeActive was often called once or twice as the call began, usually within the first 1-3 seconds.
Go to “Pricing and Availability” in iTunes Connect.
Availability” (Click blue button Edit).
Deselect China in the list “Deselect” button.
Click “Done”.
How do I trigger a UILocalNotification from the iPhone which would have no alert but only play a sound / haptic feedback on the Apple Watch?
Further background:
I am making a watchOS2 timer app. I use a UIlocalNotification triggered by the iPhone to tell the user that end of timer is reached (to handle the scenario where the watch is not active).
The problem is that Apple uses its own logic to determine if a notification appears on the watch or the phone. If I trigger a notification with no alert but only sound, this notification always plays on phone, never on the watch.
I'd prefer that notification sound/haptic to play on the watch. How can I achieve this?
The downside of what you're asking:
A sound-only notification on the watch would be confusing.
Without a message associated with the sound, the user wouldn't see any reason for the notification, when they glanced at their watch.
The downside of how to currently do what you're asking:
The WKInterfaceDevice documentation points out Apple's intention for playing haptic feedback:
You can also use this object to play haptic feedback when your app is active.
What follows is a misuse of the API to accomplish something it wasn't intended to do. It's fragile, potentially annoying, and may send users in search of a different timer app.
Changes for iOS 10 prevent this from working, unless your complication is on the active watch face.
How you could currently do what you're asking in watchOS 2:
To provide haptic feedback while your app is not active, you'd need a way for the watch extension to immediately wake up in the background, to provide the feedback:
WKInterfaceDevice.currentDevice().playHaptic(.Notification)
To do this, you could misuse the WCSession transferCurrentComplicationUserInfo method. Its proper use is to immediately transfer complication data from the phone to the watch (that a watch face complication might be updated). As a part of that process, it wakes the watch extension in the background to receive its info.
In your phone's timerDidFire method:
After checking that you have a valid Watch Connectivity session with the watch, use transferCurrentComplicationUserInfo to immediately send a dictionary to the watch extension.
guard let session = session where session.activationState == .Activated && session.paired && session.watchAppInstalled else { // iOS 9.3
return
}
let hapticData = ["hapticType": 0] // fragile WKHapticType.Notification.rawValue
// Every time you misuse an API, an Apple engineer dies
session.transferCurrentComplicationUserInfo(hapticData)
As shown, the dictionary could contain a key/value pair specifying the type of haptic feedback, or simply hold a key indicating that the watch should play a hardcoded notification.
Using an internal raw value is fragile, since it may change. If you do need to specify a specific haptic type from the phone, you should setup an enum instead of using a magic number.
In the watch extension's session delegate:
watchOS will have woken your extension in preparation to receive the complication user info. Here, you'd play the haptic feedback, instead of updating a complication, as would be expected.
func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
if let hapticTypeValue = userInfo["hapticType"] as? Int, hapticType = WKHapticType(rawValue: hapticTypeValue) {
WKInterfaceDevice.currentDevice().playHaptic(hapticType)
}
}
The proper solution:
Request that Apple provide a way to schedule local notifications on the watch. Apple's timer app does this already.
You may also wait to see what is announced at WWDC 2016, to decide if any new functionality available in watchOS 3 would help to create a proper standalone watch timer app.
Here is a question that I know has an answer since I see apps that do this functionality. I have tried (writing directly, using background fetch) but nothing works. I found an app currently on the app store with the functionality that I am looking for. With Background Fetch set to OFF and main app NOT running in background. I go to the Widget and add an item. I open HealthKit and I see the data there as expected.
I would like to do the same for my app. I would like my today extension (widget) and/or WatchKit extension to write to the HealthKit store even when app is not running in background.
Like I said I have tested an app that does this functionality even though in Apple documentation it says this:
The HealthKit store can only be accessed by an authorized app. You
cannot access HealthKit from extensions (like the Today view) or from
a WatchKit app.
Because the HealthKit store is encrypted, your app cannot read data
from the store when the phone is locked. This means your app may not
be able to access the store when it is launched in the background.
However, apps can still write data to the store, even when the phone
is locked. The store temporarily caches the data and saves it to the
encrypted store as soon as the phone is unlocked.
Any answers or insights are appreciated. Thanks everybody.
The Health Data Store is indeed encrypted while the device is locked. Locked is defined as requiring a passcode on the device and the screen was turned off (so a passcode or touch id is required before you can get back to the main screen). While the store is encrypted it is not possible to read any data from it, no matter if the app is running in the background or not. Even setting up observer queries while the app is running will not allow it to continue to be read from. I imagine this level of protection is done simply using the Data Protection capability with the NSFileProtectionComplete option.
What HealthKit functionality have you observed in this other app? If it was displaying step and distance data, then they are likely getting this data directly from the pedometer (CMPedometer), which is not restricted when the device is locked.
Lehn0058's comment about authorization was correct. I had to request authorization explicitly from the WatchKit and Today Extension even though authorization was already given in the app. Afterwards both are able to write to the Health Store. The comment from Apple above only has to do with Reading from the Health Store and NOT writing to the Health Store. Here is some sample code for anybody else who gets in to the same problem. Thanks again.
In WatchKit InterfaceController.m
- (void)awakeWithContext:(id)context {
[super awakeWithContext:context];
// Configure interface objects here.
[[HealthKitManager sharedManager] requestHealthKitAccess];
}
In Today Extension TodayViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
[[HealthKitManager sharedManager] requestHealthKitAccess];
}
I am in the midst of revising my app, and one of the things is that I can't get the most recent data when my app is backgrounded. Nor can I delegate my main app to do some call for me when I am using my Today extension.
So this seems to leave me with the option of having my extension reach out to the server and sort the data. And thus, I would have to do the same for my WatchKit app. Is this frowned upon, and if so, is there a better solution?
So in my Main View Controller I have this method:
- (void)loadTableData{
if ([self.numberField.text isEqualToString:#""]) {
tableViewController.busTimeArray = nil;
}
else{
if([self.busNumber.text isEqualToString:#""]){
TimeTableParser *timeParser = [[TimeTableParser alloc] initWithArray:tableViewController.busTimeArray];
tableViewController.busTimeArray = [timeParser parseXML:self.numberField.text];
}
else{
SortedTimeTableParser *timeParser = [[SortedTimeTableParser alloc] initWithArray:tableViewController.busTimeArray];
tableViewController.busTimeArray = [timeParser parseXMLForStop:self.numberField.text andBusNumber:self.busNumber.text];
}
}
//Shows the table
[_busTimeTable reloadData];
//Resign keyboard
[self.view endEditing:true];
[self passInformationToNCWidget];
//Make sure the refresh circle is dismissed
[_refreshController endRefreshing];
}
As you can see, I am passing the info to Today Extension. That is being done through NSUserDefaults and is updated in the background whenever iOS decides it needs updating. On load, my extension reads the standardUserDefaults and formats the data to be presented to the user. However, I have gotten feedback that due to the nature of the app (getting bus times), users expect more current data. Which makes sense. So I need a way to get the most recent data to the extension. Since extension can't directly call to apps controllers, my only option currently seems to be using the sorting methods I have (in a separate class) which do most of the heavy lifting.
Whether it's "frowned upon" is difficult to discern, but I have apps in the App Store currently with both Today extensions and WatchKit apps that make network calls.
That doesn't necessarily mean that it's the right thing to do, as every network call you make slows down your user's experience, but Apple seems to be okay with it in my experience. You may want to use a healthy amount of caching and include failsafes, but it's certainly okay with Apple, and it appears to work just fine.
If you're looking for an alternative, though, there's always NSUserDefaults, which you can use to share small amounts of data between your WatchKit app/Today extension and your companion iOS app.
From the code you've provided, it appears that you're already using NSUserDefaults, and your users' and your concerns with latency are valid – with something like bus times, the most recent data is needed whenever possible.
For an application of Today extensions like this, I think network calls are justifiable (with the proper fallbacks in place) because you need up to the minute information.
I'd also take a look at the Today extension guide under "Updating Content" for more details on how the extension manages new data to be displayed, as there are limitations.
I was wondering if there is any other way besides MMWormhole to pass basic data between iPhone and Apple Watch. Do you know if any existing official Apple framework allows this?
It is possible.
Looking at: The WatchKit Doc's
There is a paragraph on sharing data between the watch app and the extension on the iPhone.
To quote the first paragraph.
Sharing Data with Your Containing iOS App
If your iOS app and WatchKit extension rely on the same data, use a shared app group to store that data. An app group is a secure container that multiple processes can access. Because your WatchKit extension and iOS app run in separate sandbox environments, they normally do not share files or communicate directly with one another. An app group lets the two processes share files or user defaults information between them.
From what I understand MMWormhole is handy for as close to realtime data changes between the 2 binaries. Whereas this method allows for accessing data used saved by the iPhone app that can be read by the Watch App and Vice Versa.
We can pass the data between iPhone & iWatch using groups.
Basically We can share data using the NSUserDefaults.
But for that you need to enable that see steps below:
1)open capabilities section in both your project target
2)open App Groups from that section
3)add container by click on + button with name group.test.demo
sample code to achieve that.
In your iphone app code
NSUserDefaults *myDefaults = [[NSUserDefaults alloc]initWithSuiteName:#"group.test.demo"];
[myDefaults setObject:#"tejas" forKey:#"name"];
now value "tejas" is set for key "name"
code to retrieve that
NSUserDefaults *myDefaults = [[NSUserDefaults alloc]initWithSuiteName:#"group.test.demo"];
[myDefaults objectForKey:#"name"];
best of luck :)
If you check the docs for WKInterfaceController, you'll find a method called openParentApplication:reply: that allows you to communicate with your host app in the background.
As stated above, I have used a shared app group and placed the core data files in that group. Using this technique, both the phone app and the watch can read and write the same data and all is good when they are run discretely. As each process is running in a separate sandbox, you run into the classic distributed database problem of potentially overwriting data from different sources.
To overcome this, you need to put data observers in place. I resorted to using the NSDistributedNotificationCenter to pass some custom messages between the app and the watch extension, but there may be a more elegant solution. Any ideas from others?
Use watch Connectivity.
// 1. In .m viewcontroller on phone & interface controller on iwatch
#import <WatchConnectivity/WatchConnectivity.h>
#interface IController()<WCSessionDelegate>
#end
if ([WCSession isSupported]) {
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];}
// 2. Send Message - Phone or iWatch:
[[WCSession defaultSession] sendMessage:response
replyHandler:^(NSDictionary *reply) {
NSLog(#"%#",reply);
}
errorHandler:^(NSError *error) {
NSLog(#"%#",error);
}
];
// 3. Receive Message - Phone or iWatch
- (void)session:(WCSession *)session didReceiveMessage: (NSDictionary<NSString *, id> *)message
{
}