How to delay the camera function that uses zxing library? - ios

I use zxing to scan barcodes. But the camera scans real quick ,so that my method gets overloaded with the result. How to slow down it or create a delay to scan the barcodes?
Here is my result method:
- (void)captureResult:(ZXCapture *)capture result:(ZXResult *)result {
if (!result) return;
// We got a result. Display information about the result onscreen.
NSString *formatString = [self barcodeFormatToString:result.barcodeFormat];
NSString *display = [NSString stringWithFormat:#"Scanned!\n\nFormat: %#\n\nContents:\n%#", formatString, result.text];
[self.decodedLabel performSelectorOnMainThread:#selector(setText:) withObject:display waitUntilDone:YES];
// Vibrate
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
}

You could record an NSTimeInterval and reject all results for the next 'x' seconds. Example of detecting at most once every half second:
- (void)captureResult:(ZXCapture *)capture result:(ZXResult *)result {
if ([[NSDate date] timeIntervalSince1970] < _nextUpdateTime) {
return;
}
_nextUpdateTime = [[NSDate date] timeIntervalSince1970] + 0.5;
// remainder of function.
}

I would suggest you to use sleep function. Try to use sleep(timeInSeconds) ,so it will delay the scanner by the seconds you enter.

Related

How to record the exact time stamp for Avcapture Record starts?

Am using AVCapture to recording the video and storing in documents directory.
Right now We are storing the start time of video recording on click of the recording button click.
We are observing some delay in milliseconds between the click of recording button and the actual recording file.
dispatch_async( sessionQueue, ^{
if ( ! self.movieFileOutput.isRecording ) {
// Update the orientation on the movie file output video connection before starting recording.
AVCaptureConnection *movieFileOutputConnection = [self.movieFileOutput connectionWithMediaType:AVMediaTypeVideo];
movieFileOutputConnection.videoOrientation = videoPreviewLayerVideoOrientation;
// Start recording to a temporary file.
NSString *outputFileName = [NSUUID UUID].UUIDString;
NSString *outputFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:[outputFileName stringByAppendingPathExtension:#"mov"]];
dispatch_async(dispatch_get_main_queue(), ^{
VideoInfo *info = [dbManager getVideoWithId:self.currentVideoID];
if (info == nil) {
VideoInfo *tempVid = [[VideoInfo alloc] init];
tempVid.videoId = (int)[tempVid generateVideoID];
[dbManager insertVideoDetails:tempVid];
info = tempVid;
}
[appDelegate.realm beginWriteTransaction];
info.videoDate = [NSString stringWithFormat:#"%lld",[#(floor([[NSDate date] timeIntervalSince1970] * 1000000)) longLongValue]];
[appDelegate.realm commitWriteTransaction];
});
//
[self.movieFileOutput startRecordingToOutputFileURL:[NSURL fileURLWithPath:outputFilePath] recordingDelegate:self];
// Setup socket connection
// [[SocketManager sharedManager] socketThreadStart];
[[SocketManager sharedManager] setupSocket];
}
Can anyone guide that how we can to store the exact starting recording time (With date time with Hours,Minutes,seconds,Milliseconds)of the video with accuracy in milliseconds?
Following is the code that we are storing the date of start recording just before start capturing the output.
info.videoDate = [NSString stringWithFormat:#"%lld",[#(floor([[NSDate date] timeIntervalSince1970] * 1000000)) longLongValue]];
[appDelegate.realm commitWriteTransaction];
});
[self.movieFileOutput startRecordingToOutputFileURL:[NSURL fileURLWithPath:outputFilePath] recordingDelegate:self];
Why don't you use AVCaptureFileOutputRecordingDelegate methods to determine the time for all the operations? Check the list of delegate methods iOS have as follows
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didStartRecordingToOutputFileAtURL:(NSURL *)fileURL fromConnections:(NSArray *)connections{
//WHEN RECORDING STARTED
}
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL fromConnections:(NSArray *)connections error:(NSError *)error{
//WHEN RECORDING ENDED
}
Check others here.
Hope it helps.
Cheers.

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.

NSTimer giving inexact results

I have a camera app where I am trying to limit the capture length to exactly 15 seconds.
I have tried two different approaches, and neither of them are working to my satisfaction.
The first approach is to fire a repeating timer every second:
self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:#selector(countTime:) userInfo:[NSDate date] repeats:YES];
- (void)countTime:(NSTimer*)sender {
NSDate *start = sender.userInfo;
NSTimeInterval duration = [[NSDate date] timeIntervalSinceDate:start];
NSInteger time = round(duration);
if (time > 15) {
[self capture:nil]; // this stops capture
}
}
this gives me a 15 second video 8/10 times, with a periodic 16 second one... and I have tried a mixture of the NSTimeInterval double and the rounded integer here, with no apparent difference...
The second approach is to fire a selector once after the desired duration, like so:
self.timer = [NSTimer scheduledTimerWithTimeInterval:15.0f target:self selector:#selector(capture:) userInfo:nil repeats:NO];
this just calls the capture method - which stops camera capture - directly, and gives me the same results...
Is there something that I am overlooking here?
Now, because I have tested with a number of tweaked floating point values as the cap (14.5, 15.0, 15.1, 15.5, 16.0 etc) and I almost always see a 16 second video after a few tries, I am starting to wonder whether it's just the AVFoundation taking a second to flush the buffer... ???
NSTimer is not guaranteed to fire when you want it to, just after you want it to fire:
From Apple's docs:
A timer is not a real-time mechanism; it fires only when one of the run loop modes to which the timer has been added is running and able to check if the timer’s firing time has passed. Because of the various input sources a typical run loop manages, the effective resolution of the time interval for a timer is limited to on the order of 50-100 milliseconds. If a timer’s firing time occurs during a long callout or while the run loop is in a mode that is not monitoring the timer, the timer does not fire until the next time the run loop checks the timer. Therefore, the actual time at which the timer fires potentially can be a significant period of time after the scheduled firing time. See also Timer Tolerance.
But to answer your question, I used to work for a company that had a max 15 seconds video. I didn't write the video code but I think we used AVComposition after the fact to ensure that the video was no more than 15 seconds. And even then it could be a frame shorter sometimes. See How do I use AVFoundation to crop a video
Thanks to Paul and Linuxious for their comments and answers... and Rory for thinking outside the box (intriguing option).
And yes, in the end it is clear that NSTimer isn't sufficient by itself for this.
In the end, I listen for the captureOutput delegate method to fire, test for the length of the asset, and trim the composition appropriately.
- (void)captureOutput:(AVCaptureFileOutput *)captureOutput didFinishRecordingToOutputFileAtURL:(NSURL *)outputFileURL
fromConnections:(NSArray *)connections error:(NSError *)error
{
_isRecording = NO;
AVURLAsset *videoAsset = [AVURLAsset assetWithURL:outputFileURL];
CMTime length = [videoAsset duration];
CMTimeShow(length);
if(CMTimeGetSeconds(length) > 15)
{
NSLog(#"Capture Longer Than 15 Seconds - Attempting to Trim");
Float64 preferredDuration = 15;
int32_t preferredTimeScale = 30;
CMTimeRange timeRange = CMTimeRangeMake(kCMTimeZero, CMTimeMakeWithSeconds(preferredDuration, preferredTimeScale));
AVAssetExportSession *exportSession = [[AVAssetExportSession alloc] initWithAsset:videoAsset presetName:AVAssetExportPresetHighestQuality];
exportSession.outputURL = outputFileURL;
exportSession.outputFileType = AVFileTypeQuickTimeMovie;
exportSession.timeRange = timeRange;
NSError *err = nil;
[[NSFileManager defaultManager] removeItemAtURL:outputFileURL error:&err];
if (err) {
NSLog(#"Error deleting File: %#", [err localizedDescription]);
}
else {
[exportSession exportAsynchronouslyWithCompletionHandler:^{
if (exportSession.status == AVAssetExportSessionStatusCompleted) {
NSLog(#"Export Completed - Passing URL to Delegate");
if ([self.delegate respondsToSelector:#selector(didFinishRecordingToOutputFileAtURL:error:)]) {
[self.delegate didFinishRecordingToOutputFileAtURL:outputFileURL error:error];
}
}
else if(exportSession.status == AVAssetExportSessionStatusFailed) {
NSLog(#"Export Error: %#", [exportSession.error localizedDescription]);
if ([self.delegate respondsToSelector:#selector(didFinishRecordingToOutputFileAtURL:error:)]) {
[self.delegate didFinishRecordingToOutputFileAtURL:outputFileURL error:exportSession.error ];
}
}
}];
}
}
}

Wait until there is mobile internet to post data in Objective-C

I am rather new to Objective-C development so please be patient!
I am trying to take data from a user & send that information to an external database. I have managed to work out how to push the data rather easily, but the issue is; due to the nature of my application, there is a high probability that the user will have no mobile connectivity while they're using it. How can I continuously check that the user has mobile connectivity, and then send the data when it's connected? My code for the action is below:
(Just to clarify, the action takes 10 readings of signal over 5 seconds, appends them to an array, calculates the average and the updates the location. In turn, the locationManager sends the data to a cloud service including the average signal reading.
- (IBAction)buttonPressed:(id)sender {
// Make Manager Delegate & Set Accuracy
manager.delegate = self;
manager.desiredAccuracy = kCLLocationAccuracyBest;
// Call Timer
myTimer = [NSTimer scheduledTimerWithTimeInterval:0.5
target:self
selector:#selector(arrayBuild)
userInfo:nil
repeats:YES];
// Initialise Array
resultsArray = [[NSMutableArray alloc]init];
}
#pragma mark CLLocationManagerDelegate Methods
- (void)locationManager:(CLLocationManager *)manager didFailWithError:(NSError *)error {
NSLog(#"Error: %#", error);
NSLog(#"Failed to get location!");
}
-(void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation {
NSLog(#"Location: %#",newLocation);
if (curentLocation != nil) {
// Code below uses a third party utility to submit data
PFObject *Object = [PFObject objectWithClassName:#"Issue"];
Object[#"Latitude"] = [NSString stringWithFormat:#"%.8f",curentLocation.coordinate.latitude];
Object[#"Longitude"] = [NSString stringWithFormat:#"%.8f",curentLocation.coordinate.longitude];
Object[#"Signal"] = [NSString stringWithFormat:#"%#",_avgNumber];
[Object saveInBackground];
// Update Text Fields
self.latitude.text = [NSString stringWithFormat:#"%.8f",curentLocation.coordinate.latitude];
self.longitude.text = [NSString stringWithFormat:#"%.8f",curentLocation.coordinate.longitude];
self.signal.text = [NSString stringWithFormat:#"%#",_avgNumber];
}
// Stop the Location Update
[manager stopUpdatingLocation];
}
- (void)arrayBuild {
loopCount++;
if (loopCount >= 11) {
// Find Average
NSNumber *avg = [resultsArray valueForKeyPath:#"#avg.self"];
_avgNumber = avg;
// Change Text of Label to Average & Log
self.signal.text = [NSString stringWithFormat:#"%#",_avgNumber];
NSLog(#"%#",_avgNumber);
// Update Location
[manager startUpdatingLocation];
// Invalidate Timer
[myTimer invalidate];
myTimer = nil;
}else{
// Declare Signal Strength
float signalstrength = CTGetSignalStrength();
// Individual Result & Convert to Integer
NSString *result = [NSString stringWithFormat:#"%f", signalstrength];
NSInteger resultInt = [result integerValue];
// Add Object
[resultsArray addObject:[NSNumber numberWithFloat:resultInt]];
// Change Text of Label Each Second
self.signal.text = [NSString stringWithFormat:#"%d",loopCount];
NSLog(#"%f",signalstrength);
}
}
What you need is called "Network Reachability Monitoring". Once you subscribe to it, your will be notified when there are changes to network connectivity state, i.e. device became online or offline, and even type of current connection (WLAN or WWAN).
There is a sample project from Apple and third-party networking libraries (such as AFNetworking) often provide a convenience class for better experience.
EDIT:: Easier solution is to is to use Parse SDK and their saveEventually method instead of saveInBackground. This will, according to documentation, take care of situations when network is not accessible.
There are many question on forum addressing this issue. Apple provides Reachability class for this purpose. You may check This or this question for further clarification.

How to add time to a countdown timer to iOS Project

I have a game where is uses a countdown timer and when the timer is up, you are brougt to a Game over view. I want to add a feature were if they tap a button, it will add like 1, 2 or 3 more seconds to the timer. I already have the code for the timer (Below), but i just need to know how to add more time to the counter. I thought of it and i have to say when the views will switch and it would need to take into a count the added time.
Code:
-(IBAction)Ready:(id)sender {
[self performSelector:#selector(TimerDelay) withObject:nil afterDelay:1.0];
[self performSelector:#selector(delay) withObject:nil afterDelay:36.5];
}
-(void)TimerDelay {
MainInt = 36;
timer = [NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:#selector(countDownDuration)
userInfo:nil
repeats:YES];
if (timer == 0)
{
[timer invalidate];
}
}
-(void)countDownDuration {
MainInt -= 1;
seconds.text = [NSString stringWithFormat:#"%i", MainInt];
}
-(void)delay {
GameOverView1_4inch *second= [self.storyboard instantiateViewControllerWithIdentifier:#"GameOverView1"];
second.finalScore = self.currentScore;
[self presentViewController:second animated:YES completion:nil];
}
If you're using a timer to manage the game, using a perform selector at the same time (to end the game or anything else) kind of defeats the point and makes the management very complex. Choose one route and stick with it.
When the timer is running, you can change it's fire date using setFireDate:. So, you could get the current fire date, add your time to it and then set the new fire date:
- (void)extendByTime:(NSInteger)seconds
{
NSDate *newFireDate = [[self.timer fireDate] dateByAddingTimeInterval:seconds];
[self.timer setFireDate:newFireDate];
}
Then, your button callbacks are something like:
- (void)buttonOnePressed:(id)sender
{
[self extendByTime:1];
}
- (void)buttonFivePressed:(id)sender
{
[self extendByTime:5];
}
Once you've removed the performSelector which calls delay your game end will be defined by
MainInt reaching zero.
As an aside, don't do this:
if (timer == 0)
The correct approach is:
if (timer == nil)
And if the timer is nil, there's no point in trying to invalidate it...
Also a good idea to take a look at the Objective-C naming guidelines.
Based on your recent comment, it seems that you actually want the timer to continue counting at a second interval, but to add time only to the number of seconds remaining. That's even easier and doesn't require any change to the timer fire date.
- (void)extendByTime:(NSInteger)seconds {
MainInt += seconds;
seconds.text = [NSString stringWithFormat:#"%i", MainInt];
}
And you need to add a check in 'countDownDuration':
if (MainInt <= 0) {
[timer invalidate];
[self delay];
}
To determine when you're done.
You can keep reference of the time you start the timer. Then, when you want to add extra time, calculate how much time has passed since the timer started, invalidate the timer and create a new one passing as time interval the difference between the time left of the previous timer and the extra seconds.
Hy, try to use this:
Put this in -(void)viewDidLoad method
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0f target:self
selector:#selector(countDownTimer) userInfo:nil repeats:YES];
then create the -(void)countDownTimer method
- (void)countDownTimer {
// my method which returns the differences between two dates in my case
double diff = [self getDateDifference];
double days = trunc(diff / (60 * 60 * 24));
double seconds = fmod(diff, 60.0);
double minutes = fmod(trunc(diff / 60.0), 60.0);
double hours = fmodf(trunc(diff / 3600.0), 24);
if(diff > 0) {
NSString *countDownString = [NSString stringWithFormat:#"%02.0f day(s)\n%02.0f:%02.0f:%02.0f",
days, hours, minutes, seconds];
// IBOutlet label, added in .h
self.labelCountDown.text= countDownString;
} else {
// stoping the timer
[timer invalidate];
timer = nil;
// do something after countdown ...
}
}
and you can add a - (double)getDateDifference method which returns the difference between two dates in my case
- (double)getDateDifference {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
NSDate *dateFromString = [[NSDate alloc] init];
NSDate *now = [NSDate date];
NSString *myDateString = #"2013-10-10 10:10:10"; // my initial date with time
// if you want to use only time, than delete the
// date in myDateString and setDateFormat:#"HH:mm:ss"
// this line is not required, I used it, because I need GMT+2
[dateFormatter setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:+2]];
[dateFormatter setDateFormat:#"yyyy-MM-dd HH:mm:ss"];
// get the date
dateFromString = [dateFormatter dateFromString:myDateString];
// this line is also not required, I used it because I need GMT+2
// so I added two hours in seconds to 'now'
now = [now dateByAddingTimeInterval:60*60*2];
// getting the difference
double diff = [dateFromString timeIntervalSinceDate:now];
NSLog(#"dateString: %#", dateString);
NSLog(#"now: %#", now);
NSLog(#"targetDate: %#", dateFromString);
NSLog(#"diff: %f", diff);
return diff;
}
the output is similar to this
dateString: 2013-10-10 00:20:00
now: 2013-10-10 00:20:00 +0000
target: 2013-10-10 00:20:28 +0000
diff: 28.382786
I hope it was helpfull
Here's a library that you can use.
https://github.com/akmarinov/AMClock

Resources