iOS 6 vibration duration - ios

I'm trying to measure the default vibration duration on iPhone, iOS 6:
#property (nonatomic, retain) NSDate *startDate;
- (void) runTest
{
AudioServicesAddSystemSoundCompletion(kSystemSoundID_Vibrate, NULL, NULL, playSoundFinished, self);
self.startDate = [NSDate date];
AudioServicesPlayAlertSound(kSystemSoundID_Vibrate);
}
void playSoundFinished(SystemSoundID soundID, void *data)
{
MyClassName *foo = (MyClassName*)data;
double vibrationDurationMs = [foo.startDate timeIntervalSinceNow] * -1000.0;
}
But it returns just 1-2 milliseconds, regardless of whether vibration is activated in system settings or not.
I heard the default duration should be 400 ms, followed by 100 ms of silence.
Is the above code correct?

Related

Apple Watch ClockKit Complications don't update their timeline entries if the clock face isn't hidden during execution

Has anyone else noticed a problem with complication entries not updating properly. I've just added some initial support to my app, but noticed that they weren't displaying what i expected them to display. For example's sake, and for ease of testing this issue quickly I'd create a timeline
A -> B -> C
0s 10s 20s
Yet all I'd see was complication entry A staying around past the time that B and C should have displayed.
My normal app itself isn't set to create regularly spaced complications like this, it has many aspects of timers that it exposes that can be set by the user, but one such aspect just allows the user to start multiple timers at once, which all will finish after the user defined durations they choose. Unlike the iOS clock app's timer you're able to specify the timer durations in seconds, and so its perfectly possible that 2 timers would finish within seconds of each other, on the whole though its more likely that they'll be minutes apart. Also there shouldn't be too many complication entries being added, though other more complicated aspects of my app could easily add 10s or even ~100 complication entries depending on how complex a task the user has setup within it. For now though, this simpler example is easier to discuss and test.
I updated Xcode to the latest version (7.3.2) with no improvements, and sent a build to my actual phone and watch and again no improvement. Until once it did work. On further debugging I discovered that i could make the timeline behave itself by simply lowering my watch (to turn off the screen) and then wake it up again, all whilst it was mid executing my timeline. Having done this the timeline would then work properly from then on.
I've created a test app to demonstrate the problem, which does reproduce the problem fully, so I'm going to send it to apple on a bug report. Just thought i'd see if anyone else had noticed this issue.
Also when my test app executes i get the following logging output with an error that doesn't make sense
-[ExtensionDelegate session:didReceiveUserInfo:]:67 - complication.family=1 in activeComplications - calling reloadTimelineForComplication
-[ComplicationController getTimelineStartDateForComplication:withHandler:]:43 - calling handler for startDate=2016-06-15 22:08:26 +0000
-[ComplicationController getTimelineEndDateForComplication:withHandler:]:73 - calling handler for endDate=2016-06-15 22:08:46 +0000
-[ComplicationController getCurrentTimelineEntryForComplication:withHandler:]:148 - calling handler for entry at date=2016-06-15 22:08:26 +0000
-[ComplicationController getTimelineEntriesForComplication:afterDate:limit:withHandler:]:202 - adding entry at date=2016-06-15 22:08:36 +0000; with timerEndDate=2016-06-15 22:08:46 +0000 i=1
getTimelineEntriesForComplication:afterDate:limit:withHandler: -- invalid entries returned. (1 entries before start date 2016-06-15 22:08:46 +0000). Excess entries will be discarded.
The relevant information from this log is as follows
getTimelineStartDateForComplication - calling handler for startDate=22:08:26
getTimelineEndDateForComplication - calling handler for endDate=22:08:46
getCurrentTimelineEntryForComplication - calling handler for entry at date=22:08:26
getTimelineEntriesForComplication:afterDate - adding entry at date=22:08:36
getTimelineEntriesForComplication:afterDate:limit:withHandler: -- invalid entries returned. (1 entries before start date 22:08:46). Excess entries will be discarded.
Which you can see in the error from the system at the end that it is using the start date of 22:08:46, which was actually what I told Clockkit was my timeline's endDate, NOT the startDate. I'm not sure if this is related to the behaviour i'm seeing as I see the same error when it works after I hide/show the screen.
I've put a video of this behaviour in my test app online here. The details of this test app are as follows
Full code that should just run in the relevant simulators is available here, the relevant complication modules are also listed here for reference.
In my extension delegate, i receive userInfo from the iOS app and schedule a reload of my complication timeline
ExtensionDelegate.m
- (void)session:(WCSession *)session didReceiveUserInfo:(NSDictionary<NSString *, id> *)userInfo
{
DbgLog(#"");
WKExtension *extension = [WKExtension sharedExtension];
DbgLog(#"self=%p; wkExtension=%p; userInfo=%#", self, extension, userInfo);
self.lastReceivedUserInfo = userInfo;
CLKComplicationServer *complicationServer = [CLKComplicationServer sharedInstance];
for (CLKComplication *complication in complicationServer.activeComplications)
{
DbgLog(#"complication.family=%d in activeComplications - calling reloadTimelineForComplication", complication.family);
[complicationServer reloadTimelineForComplication:complication];
}
}
Then in my ComplicationController are the following methods to handle the complication side of things
ComplicationController.m
#define DbgLog(fmt, ...) NSLog((#"%s:%d - " fmt), __PRETTY_FUNCTION__, __LINE__, ##__VA_ARGS__)
#interface ComplicationController ()
#end
#implementation ComplicationController
#pragma mark - Timeline Configuration
- (void)getSupportedTimeTravelDirectionsForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationTimeTravelDirections directions))handler
{
handler(CLKComplicationTimeTravelDirectionForward|CLKComplicationTimeTravelDirectionBackward);
}
- (void)getTimelineStartDateForComplication:(CLKComplication *)complication withHandler:(void(^)(NSDate * __nullable date))handler
{
NSDate *startDate;
WKExtension *extension = [WKExtension sharedExtension];
assert(extension.delegate);
assert([extension.delegate isKindOfClass:[ExtensionDelegate class]]);
ExtensionDelegate *extensionDelegate = (ExtensionDelegate *)extension.delegate;
if (extensionDelegate.lastReceivedUserInfo)
{
NSDictionary *userInfo = extensionDelegate.lastReceivedUserInfo;
startDate = [userInfo objectForKey:#"date"];
}
DbgLog(#"calling handler for startDate=%#", startDate);
handler(startDate);
}
- (NSDate*)getTimelineEndDate
{
NSDate *endDate;
WKExtension *extension = [WKExtension sharedExtension];
assert(extension.delegate);
assert([extension.delegate isKindOfClass:[ExtensionDelegate class]]);
ExtensionDelegate *extensionDelegate = (ExtensionDelegate *)extension.delegate;
if (extensionDelegate.lastReceivedUserInfo)
{
NSDictionary *userInfo = extensionDelegate.lastReceivedUserInfo;
NSDate *startDate = [userInfo objectForKey:#"date"];
NSNumber *duration = [userInfo objectForKey:#"duration"];
NSNumber *count = [userInfo objectForKey:#"count"];
NSTimeInterval totalDuration = duration.floatValue * count.floatValue;
endDate = [startDate dateByAddingTimeInterval:totalDuration];
}
return endDate;
}
- (void)getTimelineEndDateForComplication:(CLKComplication *)complication withHandler:(void(^)(NSDate * __nullable date))handler
{
NSDate *endDate=[self getTimelineEndDate];
DbgLog(#"calling handler for endDate=%#", endDate);
handler(endDate);
}
- (void)getPrivacyBehaviorForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationPrivacyBehavior privacyBehavior))handler {
handler(CLKComplicationPrivacyBehaviorShowOnLockScreen);
}
#pragma mark - Timeline Population
- (CLKComplicationTemplate *)getComplicationTemplateForComplication:(CLKComplication *)complication
forEndDate:(NSDate *)endDate
orBodyText:(NSString *)bodyText
withHeaderText:(NSString *)headerText
{
assert(complication.family == CLKComplicationFamilyModularLarge);
CLKComplicationTemplateModularLargeStandardBody *template = [[CLKComplicationTemplateModularLargeStandardBody alloc] init];
template.headerTextProvider = [CLKSimpleTextProvider textProviderWithText:headerText];
if (endDate)
{
template.body1TextProvider = [CLKRelativeDateTextProvider textProviderWithDate:endDate style:CLKRelativeDateStyleTimer units:NSCalendarUnitHour|NSCalendarUnitMinute|NSCalendarUnitSecond];
}
else
{
assert(bodyText);
template.body1TextProvider = [CLKSimpleTextProvider textProviderWithText:bodyText];
}
return template;
}
- (CLKComplicationTimelineEntry *)getComplicationTimelineEntryForComplication:(CLKComplication *)complication
forStartDate:(NSDate *)startDate
endDate:(NSDate *)endDate
orBodyText:(NSString *)bodyText
withHeaderText:(NSString *)headerText
{
CLKComplicationTimelineEntry *entry = [[CLKComplicationTimelineEntry alloc] init];
entry.date = startDate;
entry.complicationTemplate = [self getComplicationTemplateForComplication:complication forEndDate:endDate orBodyText:bodyText withHeaderText:headerText];
return entry;
}
- (void)getCurrentTimelineEntryForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationTimelineEntry * __nullable))handler
{
// Call the handler with the current timeline entry
CLKComplicationTimelineEntry *entry;
assert(complication.family == CLKComplicationFamilyModularLarge);
WKExtension *extension = [WKExtension sharedExtension];
assert(extension.delegate);
assert([extension.delegate isKindOfClass:[ExtensionDelegate class]]);
ExtensionDelegate *extensionDelegate = (ExtensionDelegate *)extension.delegate;
if (extensionDelegate.lastReceivedUserInfo)
{
NSDictionary *userInfo = extensionDelegate.lastReceivedUserInfo;
NSDate *startDate = [userInfo objectForKey:#"date"];
NSNumber *duration = [userInfo objectForKey:#"duration"];
//NSNumber *count = [userInfo objectForKey:#"count"];
NSTimeInterval totalDuration = duration.floatValue;
NSDate *endDate = [startDate dateByAddingTimeInterval:totalDuration];
entry = [self getComplicationTimelineEntryForComplication:complication forStartDate:startDate endDate:endDate orBodyText:nil withHeaderText:#"current"];
}
if (!entry)
{
NSDate *currentDate = [NSDate date];
entry = [self getComplicationTimelineEntryForComplication:complication forStartDate:currentDate endDate:nil orBodyText:#"no user info" withHeaderText:#"current"];
}
DbgLog(#"calling handler for entry at date=%#", entry.date);
handler(entry);
}
- (void)getTimelineEntriesForComplication:(CLKComplication *)complication beforeDate:(NSDate *)date limit:(NSUInteger)limit withHandler:(void(^)(NSArray<CLKComplicationTimelineEntry *> * __nullable entries))handler
{
NSArray *retArray;
assert(complication.family == CLKComplicationFamilyModularLarge);
WKExtension *extension = [WKExtension sharedExtension];
assert(extension.delegate);
assert([extension.delegate isKindOfClass:[ExtensionDelegate class]]);
ExtensionDelegate *extensionDelegate = (ExtensionDelegate *)extension.delegate;
if (extensionDelegate.lastReceivedUserInfo)
{
NSDictionary *userInfo = extensionDelegate.lastReceivedUserInfo;
NSDate *startDate = [userInfo objectForKey:#"date"];
if ([startDate timeIntervalSinceDate:date] < 0.f)
{
assert(0);
// not expected to be asked about any date earlier than our startDate
}
}
// Call the handler with the timeline entries prior to the given date
handler(retArray);
}
- (void)getTimelineEntriesForComplication:(CLKComplication *)complication afterDate:(NSDate *)date limit:(NSUInteger)limit withHandler:(void(^)(NSArray<CLKComplicationTimelineEntry *> * __nullable entries))handler
{
NSMutableArray *timelineEntries = [[NSMutableArray alloc] init];
assert(complication.family == CLKComplicationFamilyModularLarge);
WKExtension *extension = [WKExtension sharedExtension];
assert(extension.delegate);
assert([extension.delegate isKindOfClass:[ExtensionDelegate class]]);
ExtensionDelegate *extensionDelegate = (ExtensionDelegate *)extension.delegate;
if (extensionDelegate.lastReceivedUserInfo)
{
NSDictionary *userInfo = extensionDelegate.lastReceivedUserInfo;
NSDate *startDate = [userInfo objectForKey:#"date"];
NSNumber *duration = [userInfo objectForKey:#"duration"];
NSNumber *count = [userInfo objectForKey:#"count"];
NSInteger i;
for (i=0; i<count.integerValue && timelineEntries.count < limit; ++i)
{
NSTimeInterval entryDateOffset = duration.floatValue * i;
NSDate *entryDate = [startDate dateByAddingTimeInterval:entryDateOffset];
if ([entryDate timeIntervalSinceDate:date] > 0)
{
NSDate *timerEndDate = [entryDate dateByAddingTimeInterval:duration.floatValue];
DbgLog(#"adding entry at date=%#; with timerEndDate=%# i=%d", entryDate, timerEndDate, i);
CLKComplicationTimelineEntry *entry = [self getComplicationTimelineEntryForComplication:complication forStartDate:entryDate endDate:timerEndDate orBodyText:nil withHeaderText:[NSString stringWithFormat:#"After %d", i]];
[timelineEntries addObject:entry];
}
}
if (i==count.integerValue && timelineEntries.count < limit)
{
NSDate *timelineEndDate = [self getTimelineEndDate];
CLKComplicationTimelineEntry *entry = [self getComplicationTimelineEntryForComplication:complication forStartDate:timelineEndDate endDate:nil orBodyText:#"Finished" withHeaderText:#"Test"];
[timelineEntries addObject:entry];
}
}
NSArray *retArray;
if (timelineEntries.count > 0)
{
retArray = timelineEntries;
}
// Call the handler with the timeline entries after to the given date
handler(retArray);
}
#pragma mark Update Scheduling
/*
// don't want any updates other than the ones we request directly
- (void)getNextRequestedUpdateDateWithHandler:(void(^)(NSDate * __nullable updateDate))handler
{
// Call the handler with the date when you would next like to be given the opportunity to update your complication content
handler(nil);
}
*/
- (void)requestedUpdateBudgetExhausted
{
DbgLog(#"");
}
#pragma mark - Placeholder Templates
- (void)getPlaceholderTemplateForComplication:(CLKComplication *)complication withHandler:(void(^)(CLKComplicationTemplate * __nullable complicationTemplate))handler
{
CLKComplicationTemplate *template = [self getComplicationTemplateForComplication:complication forEndDate:nil orBodyText:#"doing nothing" withHeaderText:#"placeholder"];
// This method will be called once per supported complication, and the results will be cached
handler(template);
}
#end
Perhaps you can see if you have the same problems with your own complications in your own apps.
I don't think I'm doing anything wrong, nothing that should cause this odd behaviour, just feels like a bug to me. Unfortunately its one that undermines my app which can work with quite small scale timeline entries in some cases and I'd rather not have them just not work if a user pays attention and keeps the watch screen on whilst testing it out.
Thanks for your time,
Cheers
The error about an entry before the start date is accurate, and the entry is correctly discarded.
The reason why is that getTimelineEntriesForComplication:afterDate: is meant to return future entries after the specified date. What you did was return an entry before the specified date.
The starting date for providing future entries. The dates for your timeline entries should occur after this date and be as close to the date as possible.
Without any posted code to examine, I'd guess that your beforeDate:/afterDate: conditional code is reversed.
Other issues regarding multiple timeline entries per minute:
Regarding the ten second time interval, I can point out several issues for you to consider:
The complication server will request a limited number of entries.
Providing your entries will always be ten seconds apart, and that the server requested 100 entries (either in the past or the future direction), that amounts to 1000 seconds worth (under 17 minutes). This introduces two problems in general:
Given the short span of the timeline, the timeline would need to be updated several times an hour. This is not energy efficient, and you may exhaust your daily complication budget if you extend or reload it too often.
With such a short duration, time travel is effectively useless, as rotating the digital crown could quickly travel more than 16 minutes into the (past or) future, where there would not (initially) be any further entries to display.
The complication server caches a limited number of entries.
Even if you extend your timeline, the server will eventually prune (discard) entries from an extended timeline. Again assuming a 10-second interval, the complication server would only be able to keep about 2 hours worth of entries cached. Furthermore, you have no control over which entries would be discarded.
From a time travel perspective, 5 of every 6 timeline entries would never be displayed while time traveling (since time travel changes by minutes).
This would certainly present some confusion to the user, as they would not be able to view every entry.
A note about update limits:
While you could add multiple entries per minute, you may want to rethink whether that is practical. Considering that most users won't observe most of the frequent changes, it's likely a lot of wasted effort for little gain.
Regarding energy efficiency, you should also consider the hard limits that Apple imposes for updating the complication. Even in watchOS 3 (where you're encouraged to keep the complication and dock snapshot regularly updated in the background), you'll run up against limits of 4 background updates per hour, and 50 complication updates per day. See watchOS - Show realtime departure data on complication for specifics.

iOS 9 How to Detect Silent Mode?

As AudioSessionInitialize and AudioSessionGetProperty are deprecated, I am getting the wrong return values:
CFStringRef state = nil;
UInt32 propertySize = sizeof(CFStringRef);
AudioSessionInitialize(NULL, NULL, NULL, NULL);
OSStatus status = AudioSessionGetProperty(kAudioSessionProperty_AudioRoute, &propertySize, &state);
[[AVAudioSession sharedInstance] setActive:YES error:nil];
if (status == kAudioSessionNoError) {
return CFStringGetLength(state) == 0; // YES = silent
}
return NO;
From this code (I found it here), I get the same incorrect result no matter what state is actually device on. How can I detect if the silent mode is ON on device right now?
The API is no longer available. But the work around is simple:
Play a short audio and detect time that it finishes playing
If the time that it finishes playing is shorter than the actual length of the audio, than the device is muted
Hoishing posted a helper class MuteChecker on his blog. Use it as the following:
self.muteChecker = [[MuteChecker alloc] initWithCompletionBlk:^(NSTimeInterval lapse, BOOL muted) {
NSLog(#"muted: %d", muted);
}];
[self.muteChecker check];
This is the complete code for the class, you can simple copy past to your project:
MuteChecker.h
#import <Foundation/Foundation.h>
#import <AudioToolbox/AudioToolbox.h>
typedef void (^MuteCheckCompletionHandler)(NSTimeInterval lapse, BOOL muted);
// this class must use with a MuteChecker.caf (a 0.2 sec mute sound) in Bundle
#interface MuteChecker : NSObject
-(instancetype)initWithCompletionBlk:(MuteCheckCompletionHandler)completionBlk;
-(void)check;
#end
MuteChecker.cpp
#import "MuteChecker.h"
void MuteCheckCompletionProc(SystemSoundID ssID, void* clientData);
#interface MuteChecker ()
#property (nonatomic,assign) SystemSoundID soundId;
#property (strong) MuteCheckCompletionHandler completionBlk;
#property (nonatomic, strong)NSDate *startTime;
-(void)completed;
#end
void MuteCheckCompletionProc(SystemSoundID ssID, void* clientData){
MuteChecker *obj = (__bridge MuteChecker *)clientData;
[obj completed];
}
#implementation MuteChecker
-(void)playMuteSound
{
self.startTime = [NSDate date];
AudioServicesPlaySystemSound(self.soundId);
}
-(void)completed
{
NSDate *now = [NSDate date];
NSTimeInterval t = [now timeIntervalSinceDate:self.startTime];
BOOL muted = (t > 0.1)? NO : YES;
self.completionBlk(t, muted);
}
-(void)check {
if (self.startTime == nil) {
[self playMuteSound];
} else {
NSDate *now = [NSDate date];
NSTimeInterval lastCheck = [now timeIntervalSinceDate:self.startTime];
if (lastCheck > 1) { //prevent checking interval shorter then the sound length
[self playMuteSound];
}
}
}
- (instancetype)initWithCompletionBlk:(MuteCheckCompletionHandler)completionBlk
{
self = [self init];
if (self) {
NSURL* url = [[NSBundle mainBundle] URLForResource:#"MuteChecker" withExtension:#"caf"];
if (AudioServicesCreateSystemSoundID((__bridge CFURLRef)url, &_soundId) == kAudioServicesNoError){
AudioServicesAddSystemSoundCompletion(self.soundId, CFRunLoopGetMain(), kCFRunLoopDefaultMode, MuteCheckCompletionProc,(__bridge void *)(self));
UInt32 yes = 1;
AudioServicesSetProperty(kAudioServicesPropertyIsUISound, sizeof(_soundId),&_soundId,sizeof(yes), &yes);
self.completionBlk = completionBlk;
} else {
NSLog(#"error setting up Sound ID");
}
}
return self;
}
- (void)dealloc
{
if (self.soundId != -1){
AudioServicesRemoveSystemSoundCompletion(self.soundId);
AudioServicesDisposeSystemSoundID(self.soundId);
}
}
#end
Important note: you will also have to provide a short audio MuteChecker.caf for the code to work. You could download one from his blog directly or generate one yourself.

GCD serial queue with NSTimer, dispatch_sync doesn't wait for the NSTimer to complete

I'm currently trying make a queueHandler that takes an object array as input for executing drive commands on a simple Double robot. I'm currently trying to use GCD in order to serially execute my functions, but when I'm using dispatch_sync on my queue in won't wait until the NSTimer has run its course, but will continue to try and execute the commands from the next object in my array.
I have 3 functions, one which simply initializes an NSMutableArray(loadCommands) with 2 objects and runs the queueHandler, this is called when I toggle a switch. Then the queueHandler reads the variables from the objects(type, timing, queueNr) to determine what type of drive function will be executed and for how long. This I thought could be done in a switch statement, and I figured it would be great if the app could execute the function on the main thread(that is ok!) but it should wait until the NSTimer has run its course. I thought encapsulating the switch case with a dispatch_sync would solve this but it promptly skips to the next iteration in the loop and tries to execute the next function instead, which is drive backwards for 3 seconds.
When I test this with a single object in the array the command will be executed without trouble. I suppose I'm locking up the main thread somehow. Would perhaps waiting for a return value from the function in the #selector in the NSTimer statement help?
I've only played with Objective C for about 10 days, I'd appreciate any help I could get with this bit!
- (void)loadCommands {
//create an objectArray and put 2 objects inside it.
NSMutableArray *driveCommandsArray = [[NSMutableArray alloc] initWithCapacity:4];
//Command 1
DRCommands *C1 = [[DRCommands alloc] init];
C1.timing = 3;
C1.type = 1;
C1.queueNr = 1;
[driveCommandsArray addObject:C1];
//Command 2
DRCommands *C2 = [[DRCommands alloc] init];
C2.timing = 3;
C2.type = 2;
C2.queueNr = 2;
[driveCommandsArray addObject:C2];
//call queueHandler
[self queueHandler:driveCommandsArray];
}
Queue handler:
- (void)queueHandler: (NSMutableArray*) commandArray {
//Now, I'm not sure what I'm doing here, I watched a tutorial that
//solved a vaguely similar problem and he put a dispatch_async before the
//dispatch_sync. I can't run the dispatch_sync clause inside the case
//statement without this.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSLog(#"Inside handler!");
unsigned long count;
count = [commandArray count]; //retrieve length/number of objects from the array.
unsigned long a;
for (a = 0; a < count;) {
//run the loop until all objects has been managed.
DRCommands* myObj = (DRCommands*)[commandArray objectAtIndex:a];
//create 2 serial queues.
dispatch_queue_t myQ1;
myQ1 = dispatch_queue_create("myQ1", NULL);
dispatch_queue_t myQ2;
myQ2 = dispatch_queue_create("myQ2", NULL);
int queueID = myObj.queueNr; //retrieve place in queue (not really used yet)
int timeID = myObj.timing; //retrieve the amount of time the command shall be run through the NSTimer
int typeID = myObj.type; //type of command
NSLog(#"Inside for loop!");
if (queueID == a+1) {
a++;
switch (typeID) {
{
case 1:
NSLog(#"inside case 1");
dispatch_sync(myQ1, ^{ //doesn't wait for NSTimer to finish,
//letting the Double drive forward for 3 seconds,
//before resuming operations.
counter_ = timeID;
seconds.text = [NSString stringWithFormat:#"%d", counter_];
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(jDriveForward) userInfo:nil repeats:YES];
});
break;
}
{
case 2:
NSLog(#"inside case 2");
dispatch_sync(myQ2, ^{
counter_ = timeID;
seconds.text = [NSString stringWithFormat:#"%d", counter_];
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(jDriveBackward) userInfo:nil repeats:YES];
});
break;
}
//add more cases
{
default:
break;
}
}
}
NSLog(#"Exited for loop, and count is %lu", a);
}
});
}
Drive commands:
//Go forward X seconds.
- (void)jDriveForward {
shouldDriveForward_ = YES; //sets a condition which is recognized by a callback function to run the device forward.
counter_ -= 1;
seconds.text = [NSString stringWithFormat:#"%d", counter_];
if (counter_ <= 0) {
[timer invalidate];
shouldDriveForward_ = NO;
}
}
//Go backwards X seconds.
- (void)jDriveBackward {
shouldDriveBackward_ = YES;
counter_ -= 1;
seconds.text = [NSString stringWithFormat:#"%d", counter_];
if (counter_ <= 0) {
[timer invalidate];
shouldDriveBackward_ = NO;
}
}
Provided drive function from the experimental API I'm using
I'm using a "token" such as "shouldDriveForward_" inside the function driveDoubleShouldUpdate which is TRUE for the duration of an NSTimer. I must call my drive methods inside that function for the robot not to default to idle mode. So whenever it is true for X duration, the function for driving forwards or backwards is active.
- (void)doubleDriveShouldUpdate:(DRDouble *)theDouble {
float drive = (driveForwardButton.highlighted) ? kDRDriveDirectionForward : ((driveBackwardButton.highlighted) ? kDRDriveDirectionBackward : kDRDriveDirectionStop);
float turn = (driveRightButton.highlighted) ? 1.0 : ((driveLeftButton.highlighted) ? -1.0 : 0.0);
[theDouble drive:drive turn:turn];
//below are custom functions
//The NSTimer I'm using keep the BOOL values below TRUE for X seconds,
//making the robot go forward/backward through this callback
//method, which I must use
if(shouldDriveForward_ == YES) {
[theDouble variableDrive:(float)1.0 turn:(float)0.0];
}
if(shouldDriveBackward_ == YES) {
[theDouble variableDrive:(float)-1.0 turn:(float)0.0];
}
}
You're kind of jumbled up here with the combination of GCD and NSTimer. There's nothing to say they can't be intermixed, but an all-GCD approach might be easier to get your head around. I think I've discerned the gist of what you're trying to do here, and hacked something together that might be helpful. I've put the whole project up on GitHub, but here's the meat of it:
#import "ViewController.h"
typedef NS_ENUM(NSUInteger, DRCommandType) {
DRCommandUnknown = 0,
DRCommandTypeForward = 1,
DRCommandTypeBackward = 2,
};
#interface DRCommand : NSObject
#property DRCommandType type;
#property NSTimeInterval duration;
#end
#implementation DRCommand
#end
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UILabel *commandNameLabel;
#property (weak, nonatomic) IBOutlet UILabel *secondsRemainingLabel;
#property (strong, atomic) DRCommand* currentlyExecutingCommand;
#property (copy, atomic) NSNumber* currentCommandStarted;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
// Do an initial UI update
[self updateUI];
}
- (IBAction)loadCommands:(id)sender
{
DRCommand *C1 = [[DRCommand alloc] init];
C1.duration = 3.0;
C1.type = DRCommandTypeForward;
DRCommand *C2 = [[DRCommand alloc] init];
C2.duration = 3.0;
C2.type = DRCommandTypeBackward;
[self handleCommands: #[ C1, C2 ]];
}
- (void)handleCommands: (NSArray*)commands
{
// For safety... it could be a mutable array that the caller could continue to mutate
commands = [commands copy];
// This queue will do all our actual work
dispatch_queue_t execQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
// We'll target the main queue because it simplifies the updating of the UI
dispatch_set_target_queue(execQueue, dispatch_get_main_queue());
// We'll use this queue to serve commands one at a time...
dispatch_queue_t latchQueue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
// Have it target the execQueue; Not strictly necessary but codifies the relationship
dispatch_set_target_queue(latchQueue, execQueue);
// This timer will update our UI at 60FPS give or take, on the main thread.
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_main_queue());
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, (1.0/60.0) * NSEC_PER_SEC, (1.0/30.0) * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{ [self updateUI]; });
// Suspend the latch queue until we're ready to go
dispatch_suspend(latchQueue);
// The first thing to do for this command stream is to start UI updates
dispatch_async(latchQueue, ^{ dispatch_resume(timer); });
// Next enqueue each command in the array
for (DRCommand* cmd in commands)
{
dispatch_async(latchQueue, ^{
// Stop the queue from processing other commands.
dispatch_suspend(latchQueue);
// Update the "machine state"
self.currentlyExecutingCommand = cmd;
self.currentCommandStarted = #([NSDate timeIntervalSinceReferenceDate]);
// Set up the event that'll mark the end of the command.
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(cmd.duration * NSEC_PER_SEC)), execQueue, ^{
// Clear out the machine state for the next command
self.currentlyExecutingCommand = nil;
self.currentCommandStarted = nil;
// Resume the latch queue so that the next command starts
dispatch_resume(latchQueue);
});
});
}
// After all the commands have finished, add a cleanup block to stop the timer, and
// make sure the UI doesn't have stale text in it.
dispatch_async(latchQueue, ^{
dispatch_source_cancel(timer);
[self updateUI];
});
// Everything is queued up, so start the command queue
dispatch_resume(latchQueue);
}
- (void)updateUI
{
// Make sure we only ever update the UI on the main thread.
if (![NSThread isMainThread])
{
dispatch_async(dispatch_get_main_queue(), ^{ [self updateUI]; });
return;
}
DRCommand* currentCmd = self.currentlyExecutingCommand;
switch (currentCmd.type)
{
case DRCommandUnknown:
self.commandNameLabel.text = #"None";
break;
case DRCommandTypeForward:
self.commandNameLabel.text = #"Forward";
break;
case DRCommandTypeBackward:
self.commandNameLabel.text = #"Backward";
break;
}
NSNumber* startTime = self.currentCommandStarted;
if (!startTime || !currentCmd)
{
self.secondsRemainingLabel.text = #"";
}
else
{
const NSTimeInterval startTimeDbl = startTime.doubleValue;
const NSTimeInterval currentTime = [NSDate timeIntervalSinceReferenceDate];
const NSTimeInterval duration = currentCmd.duration;
const NSTimeInterval remaining = MAX(0, startTimeDbl + duration - currentTime);
self.secondsRemainingLabel.text = [NSString stringWithFormat: #"%1.3g", remaining];
}
}
#end
Let me know in a comment if there's any part you'd like more explanation on.
Note: The other answer here has the command doing a sleep; my approach is fully asynchronous. Which approach is right for you will depend on what your commands are actually doing which wasn't clear from the question.
You only need a single serial dispatch queue to which you will add your tasks.
I would start by defining a task class which implements your various commands - you can subclass if required.
DRCommand.h
#import <Foundation/Foundation.h>
#interface DRCommand : NSObject
#property uint duration;
-(void) dispatch;
#end
DRCommand.m
#import "DRCommand.h"
#implementation DRCommand
-(void)dispatch {
[self startCommand];
sleep(self.duration);
[self stopCommand];
}
-(void) startCommand {
NSLog(#"Override this method to actually do something");
}
-(void) stopCommand {
NSLog(#"Override this method to stop doing something");
}
#end
Then your run queue code will be something like
-(void) runQueue {
DRCommand *c1=[DRCommand new];
c1.duration=5;
DRCommand *c2=[DRCommand new];
c2.duration=7;
DRCommand *c3=[DRCommand new];
c3.duration=3;
NSArray *taskArray=#[c1,c2,c3];
dispatch_queue_t queue;
queue = dispatch_queue_create("com.example.MyQueue", NULL);
for (DRCommand *command in taskArray) {
dispatch_async(queue, ^{
[command dispatch];
});
}
}
Note that you would have subclasses of DRCommand such as DRForwardCommand, DRBackwardCommand and so on, each with appropriate startCommand and stopCommand methods.

Difference between 2 dates in seconds ios

I have an app where content is displayed to the user. I now want to find out how many seconds a user actually views that content for. So in my header file, I've declared an
NSDate *startTime;
NSDate *endTime;
Then in my viewWillAppear
startTime = [NSDate date];
Then in my viewWillDisappear
endTime = [NSDate date];
NSTimeInterval secs = [endTime timeIntervalSinceDate:startTime];
NSLog(#"Seconds --------> %f", secs);
However, the app crashes, with different errors sometimes. Sometimes it's a memory leak, sometimes it's a problem with the NSTimeInterval, and sometimes it crashes after going back to the content for a second time.
Any ideas on to fix this?
since you are not using ARC, when you write
startTime = [NSDate date];
you do not retain startTime, so it is deallocated before -viewWillDisappear is called. Try
startTime = [[NSDate date] retain];
Also, I recommend to use ARC. There should be much less errors with memory management with it, than without it
You should declare a property with retain for the start date. Your date is getting released before you can calculate the time difference.
So declare
#property (nonatomic, retain) NSDate *startDate
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[self setStartDate: [NSDate date]];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
NSLog(#"Seconds --------> %f",[[NSDate date] timeIntervalSinceDate: self.startDate]);
}
Don't forget to cleanup.
- (void)dealloc
{
[self.startDate release];
[super dealloc];
}

How to detect Network Signal Strength in iOS Reachability

I am creating a new Traveling Application in iOS, this application is highly dependent on Maps and will include two Maps.
My first Map will work when the user has a strong Network Signal (Apple Maps).
My second Map will be used when their isn't any Network or really Low signal (Offline
MapBox).
Why do I have two different maps in one Application? My Application is a Direction App, so when the user has really low network or none it will go to the offline Map MapBox. Also the Apple Maps will have Yelp integration and not the offline Map MapBox.
So my Question: How can I detect the network signal in WiFi, 4G Lte, and 3G.
My original thought was to time the download of a file, and see how long it takes:
#interface ViewController () <NSURLSessionDelegate, NSURLSessionDataDelegate>
#property (nonatomic) CFAbsoluteTime startTime;
#property (nonatomic) CFAbsoluteTime stopTime;
#property (nonatomic) long long bytesReceived;
#property (nonatomic, copy) void (^speedTestCompletionHandler)(CGFloat megabytesPerSecond, NSError *error);
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self testDownloadSpeedWithTimout:5.0 completionHandler:^(CGFloat megabytesPerSecond, NSError *error) {
NSLog(#"%0.1f; error = %#", megabytesPerSecond, error);
}];
}
/// Test speed of download
///
/// Test the speed of a connection by downloading some predetermined resource. Alternatively, you could add the
/// URL of what to use for testing the connection as a parameter to this method.
///
/// #param timeout The maximum amount of time for the request.
/// #param completionHandler The block to be called when the request finishes (or times out).
/// The error parameter to this closure indicates whether there was an error downloading
/// the resource (other than timeout).
///
/// #note Note, the timeout parameter doesn't have to be enough to download the entire
/// resource, but rather just sufficiently long enough to measure the speed of the download.
- (void)testDownloadSpeedWithTimout:(NSTimeInterval)timeout completionHandler:(nonnull void (^)(CGFloat megabytesPerSecond, NSError * _Nullable error))completionHandler {
NSURL *url = [NSURL URLWithString:#"http://insert.your.site.here/yourfile"];
self.startTime = CFAbsoluteTimeGetCurrent();
self.stopTime = self.startTime;
self.bytesReceived = 0;
self.speedTestCompletionHandler = completionHandler;
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
configuration.timeoutIntervalForResource = timeout;
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
[[session dataTaskWithURL:url] resume];
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
self.bytesReceived += [data length];
self.stopTime = CFAbsoluteTimeGetCurrent();
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
CFAbsoluteTime elapsed = self.stopTime - self.startTime;
CGFloat speed = elapsed != 0 ? self.bytesReceived / (CFAbsoluteTimeGetCurrent() - self.startTime) / 1024.0 / 1024.0 : -1;
// treat timeout as no error (as we're testing speed, not worried about whether we got entire resource or not
if (error == nil || ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorTimedOut)) {
self.speedTestCompletionHandler(speed, nil);
} else {
self.speedTestCompletionHandler(speed, error);
}
}
#end
Note, this measures the speed including the latency of starting the connection. You could alternatively initialize startTime in didReceiveResponse, if you wanted to factor out that initial latency.
Having done that, in retrospect, I don't like spending time or bandwidth downloading something that has no practical benefit to the app. So, as an alternative, I might suggest a far more pragmatic approach: Why don't you just try to open a MKMapView and see how long it takes to finish downloading the map? If it fails or if it takes more than a certain amount of time, then switch to your offline map. Again, there is quite a bit of variability here (not only because network bandwidth and latency, but also because some map images appear to be cached), so make sure to set a kMaximumElapsedTime to be large enough to handle all the reasonable permutations of a successful connection (i.e., don't be too aggressive in using a low value).
To do this, just make sure to set your view controller to be the delegate of the MKMapView. And then you can do:
#interface ViewController () <MKMapViewDelegate>
#property (nonatomic, strong) NSDate *startDate;
#end
static CGFloat const kMaximumElapsedTime = 5.0;
#implementation ViewController
// insert the rest of your implementation here
#pragma mark - MKMapViewDelegate methods
- (void)mapViewWillStartLoadingMap:(MKMapView *)mapView {
NSDate *localStartDate = [NSDate date];
self.startDate = localStartDate;
double delayInSeconds = kMaximumElapsedTime;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
// Check to see if either:
// (a) start date property is not nil (because if it is, we
// finished map download); and
// (b) start date property is the same as the value we set
// above, as it's possible this map download is done, but
// we're already in the process of downloading the next
// map.
if (self.startDate && self.startDate == localStartDate)
{
[[[UIAlertView alloc] initWithTitle:nil
message:[NSString stringWithFormat:#"Map timed out after %.1f", delayInSeconds]
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil] show];
}
});
}
- (void)mapViewDidFailLoadingMap:(MKMapView *)mapView withError:(NSError *)error {
self.startDate = nil;
[[[UIAlertView alloc] initWithTitle:nil
message:#"Online map failed"
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil] show];
}
- (void)mapViewDidFinishLoadingMap:(MKMapView *)mapView
{
NSTimeInterval elapsed = [[NSDate date] timeIntervalSinceDate:self.startDate];
self.startDate = nil;
self.statusLabel.text = [NSString stringWithFormat:#"%.1f seconds", elapsed];
}
For Swift
class NetworkSpeedProvider: NSObject {
var startTime = CFAbsoluteTime()
var stopTime = CFAbsoluteTime()
var bytesReceived: CGFloat = 0
var speedTestCompletionHandler: ((_ megabytesPerSecond: CGFloat, _ error: Error?) -> Void)? = nil
func test() {
testDownloadSpeed(withTimout: 5.0, completionHandler: {(_ megabytesPerSecond: CGFloat, _ error: Error?) -> Void in
print("%0.1f; error = \(megabytesPerSecond)")
})
}
}
extension NetworkSpeedProvider: URLSessionDataDelegate, URLSessionDelegate {
func testDownloadSpeed(withTimout timeout: TimeInterval, completionHandler: #escaping (_ megabytesPerSecond: CGFloat, _ error: Error?) -> Void) {
// you set any relevant string with any file
let urlForSpeedTest = URL(string: "https://any.jpg")
startTime = CFAbsoluteTimeGetCurrent()
stopTime = startTime
bytesReceived = 0
speedTestCompletionHandler = completionHandler
let configuration = URLSessionConfiguration.ephemeral
configuration.timeoutIntervalForResource = timeout
let session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
guard let checkedUrl = urlForSpeedTest else { return }
session.dataTask(with: checkedUrl).resume()
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
bytesReceived += CGFloat(data.count)
stopTime = CFAbsoluteTimeGetCurrent()
}
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
let elapsed = (stopTime - startTime) //as? CFAbsoluteTime
let speed: CGFloat = elapsed != 0 ? bytesReceived / (CGFloat(CFAbsoluteTimeGetCurrent() - startTime)) / 1024.0 / 1024.0 : -1.0
// treat timeout as no error (as we're testing speed, not worried about whether we got entire resource or not
if error == nil || ((((error as NSError?)?.domain) == NSURLErrorDomain) && (error as NSError?)?.code == NSURLErrorTimedOut) {
speedTestCompletionHandler?(speed, nil)
}
else {
speedTestCompletionHandler?(speed, error)
}
}
}
I believe a google search will help.
Look out for the following thread on StackOverflow—
iOS wifi scan, signal strength
iPhone signal strength
So, I don't think you can still do this without using private APIs.

Resources