I've implemented a UIProgressView as a count down timer, its value is decreased from 1.0 to 0.0 in 1.5 seconds.
It already worked but the problem is the time it took was longer than 1.5
seconds, it was about 2.0 - 2.5 seconds until the progress view value reach 0.0
For it to run in just 1.5, I decreased the value of the progress by 0.001 each time it's called. The decrease method is call after 0.0015 second interval.
Below is how I do it. I don't know if there's anything wrong that make it run longer than just 1.5 seconds?
- (void)decreaseProgress {
if (currentProgress > 0.0000f) {
currentProgress -= 0.001f;
[_timeLineProgress setProgress:currentProgress animated:YES];
[self performSelector:#selector(decreaseProgress) withObject:self afterDelay:0.0015 inModes:#[NSDefaultRunLoopMode]];
} else {
[self stopTimer];
}
}
To animate the progress, try this code in your decreaseProgress method
[UIView animateWithDuration:1.5 animations:^{
[_timeLineProgress setProgress:0.0 animated:YES];
}];
You're attempting to update the progress view 1000 times in 1.5 seconds. That's way too fast, since the screen only updates 60 times per second. In other words, you're updating the progress bar more than 10 times between each time that the progress bar is actually redrawn on the screen.
Instead I would suggest 15 updates at 0.1 second intervals, and change the progress bar by 1/15 each time.
One way to check how well the code is performing is to use the CACurrentMediaTime function to get timestamps. Here's some sample code that demonstrates how to do that. The progressStart variable is the timestamp when the button press event occurred, and the NSLog prints the amount of time elapsed relative to the start time.
An important feature of the code is that the performSelector method is called as early as possible in the updateProgress method, to minimize slippage.
#interface ViewController ()
{
CFTimeInterval progressStart;
int progressCount;
}
#property (weak, nonatomic) IBOutlet UIProgressView *progressView;
#end
- (void)updateProgress
{
if ( progressCount > 0 )
[self performSelector:#selector(updateProgress) withObject:nil afterDelay:0.1];
self.progressView.progress = progressCount / 15.0;
NSLog( #"%2d %.3lf", progressCount, CACurrentMediaTime() - progressStart );
progressCount--;
}
- (IBAction)someButtonPressed
{
self.progressView.progress = 1.0;
progressStart = CACurrentMediaTime();
progressCount = 15;
[self updateProgress];
}
And here are the results from a typical run
2015-07-01 13:05:57.610 Progress[8354:907] 15 0.000
2015-07-01 13:05:57.711 Progress[8354:907] 14 0.101
2015-07-01 13:05:57.813 Progress[8354:907] 13 0.203
2015-07-01 13:05:57.914 Progress[8354:907] 12 0.304
2015-07-01 13:05:58.015 Progress[8354:907] 11 0.405
2015-07-01 13:05:58.116 Progress[8354:907] 10 0.506
2015-07-01 13:05:58.218 Progress[8354:907] 9 0.608
2015-07-01 13:05:58.319 Progress[8354:907] 8 0.709
2015-07-01 13:05:58.420 Progress[8354:907] 7 0.810
2015-07-01 13:05:58.520 Progress[8354:907] 6 0.910
2015-07-01 13:05:58.621 Progress[8354:907] 5 1.011
2015-07-01 13:05:58.722 Progress[8354:907] 4 1.112
2015-07-01 13:05:58.823 Progress[8354:907] 3 1.213
2015-07-01 13:05:58.924 Progress[8354:907] 2 1.314
2015-07-01 13:05:59.024 Progress[8354:907] 1 1.415
2015-07-01 13:05:59.125 Progress[8354:907] 0 1.515
Note that the performSelector:afterDelay method has about 1 millisecond of slippage on each event. The total slippage was 15 milliseconds. The device screen update rate is 60 frames/sec, which is 16.7 msec/frame. So the total slippage was less than one frame time, and won't be noticeable to the user.
As rmaddy pointed out in the comments, using an NSTimer allows you to avoid most of the slippage. However, the last timer event could still slip by an arbitrary amount of time.
Related
I'm having issues with implementing MIDI into my iOS app as the receiver callback seems to be skipping MIDI messages and packets. I'm using Midi Monitor to check what MIDI messages I'm missing, skipping over, etc.
So the million dollar question is why is iOS skipping certain MIDI messages? Sometimes it doesn't skip MIDI messages, but other times it does. I'm not sure how to approach debugging this as I have exhausted my brain at this point.
My receiver code:
void MidiReceiver(const MIDIPacketList *packets,
void *context, void *sourceContext) {
dispatch_async(dispatch_get_main_queue(), ^{
if (packets->numPackets > 0) {
MIDIPacket *packet = (MIDIPacket *)packets->packet;
// Loop through total number of packets
for (int i = 0; i < packets->numPackets; i++) {
// Go through each packet, iOS sometimes clumps all data into one packet
// if the MIDI messages are triggered at the same time
for (int j = 0; j < packet->length; j += 3) {
NSArray *array = [[NSArray alloc] initWithObjects:[NSNumber numberWithUnsignedInt:packet->data[j]],
[NSNumber numberWithUnsignedInt:packet->data[j+1]],
[NSNumber numberWithUnsignedInt:packet->data[j+2]], nil];
// Use the data to create do meaningful in the app
[myViewController processMidiData:array];
}
// Next packet
packet = MIDIPacketNext(packet);
}
}
});
The monitor code format is : (TIME) - (MIDI Command Type) - (CC Val or Velocity)
Midi Monitor Debug:
12:45:32.697 Control 0
12:45:32.720 Control 1
12:45:32.737 Control 1
12:45:32.740 Control 2
12:45:32.750 Control 3
12:45:32.763 Note Off A♯1 0
12:45:32.763 Note Off F2 0
12:45:32.763 Note Off D3 0
12:45:32.763 Control 4
12:45:32.770 Control 5
12:45:32.780 Control 6
12:45:32.790 Control 8
12:45:32.800 Control 9
12:45:32.810 Control 11
12:45:32.820 Control 13
12:45:32.832 Control 14
12:45:32.845 Control 16
12:45:32.850 Control 18
12:45:32.873 Control 21
12:45:32.883 Control 22
12:45:32.898 Control 24
12:45:32.913 Control 26
12:45:32.933 Control 27
12:45:32.948 Control 28
12:45:33.020 Control 27
12:45:33.030 Control 26
12:45:33.040 Control 25
12:45:33.050 Control 24
12:45:33.060 Control 22
My App's Debug Monitor:
12:45:33.050 Control 0
12:45:33.051 Control 1
12:45:33.051 Control 1
12:45:33.051 Control 2
12:45:33.051 Control 3
12:45:33.083 Note Off D3 0 <----- Where's A#1 and F2!!! :(
12:45:33.087 Control 4
12:45:33.087 Control 4
12:45:33.097 Control 5
12:45:33.100 Control 6
12:45:33.110 Control 8
12:45:33.120 Control 9
12:45:33.130 Control 11
12:45:33.140 Control 13
12:45:33.153 Control 14
12:45:33.165 Control 16
12:45:33.170 Control 18
12:45:33.193 Control 21
12:45:33.203 Control 22
12:45:33.218 Control 24
12:45:33.233 Control 26
12:45:33.256 Control 27
12:45:33.268 Control 28
12:45:33.341 Control 27
12:45:33.351 Control 26
12:45:33.361 Control 25
12:45:33.374 Control 24
12:45:33.381 Control 22
Got some help from Kurt Revis and it seemed like I was sending the packets too late due to my usage of dispatch_async.
My revised code (I parsed the packets first):
void MidiReceiver(const MIDIPacketList *packets,
void *context, void *sourceContext) {
NSMutableArray *packetData = [[NSMutableArray alloc] init];
if (packets->numPackets > 0 && object != nil) {
MIDIPacket *packet = &packets->packet[0];
// Loop through total number of packets
for (int i = 0; i < packets->numPackets; ++i) {
int idx = 0;
while (idx < packet->length) {
NSArray *array = [[NSArray alloc] initWithObjects:[NSNumber numberWithUnsignedInt:packet->data[idx]],
[NSNumber numberWithUnsignedInt:packet->data[idx+1]],
[NSNumber numberWithUnsignedInt:packet->data[idx+2]], nil];
[packetData addObject:array];
idx += 3;
}
packet = MIDIPacketNext(packet);
}
}
dispatch_async(dispatch_get_main_queue(), ^{
for (NSArray *packet in packetData) {
[object receiveMIDIInput:packet];
}
});
}
This question already has answers here:
Getting date from [NSDate date] off by a few hours
(3 answers)
Closed 6 years ago.
I saved a date in a sqlite database. Know I try to get the hours and the minutes. But the hours are shifted by 2.
print(calendar.timeZone)
while result.next() {
var hour = 0
var minute = 0
let calendar = NSCalendar.currentCalendar()
if #available(iOS 8.0, *) {
calendar.getHour(&hour, minute: &minute, second: nil, nanosecond: nil, fromDate: result.dateForColumn("time"))
print(result.dateForColumn("time"))
print("the hour is \(hour) and minute is \(minute)")
}
}
I get the following output:
Europe/Berlin (GMT+2) offset 7200 (Daylight)
2016-08-17 18:44:57 +0000
the hour is 20 and minute is 44
2016-08-18 18:44:57 +0000
the hour is 20 and minute is 44
2016-08-19 15:44:57 +0000
the hour is 17 and minute is 44
2016-08-18 16:44:57 +0000
the hour is 18 and minute is 44
2016-08-17 18:44:57 +0000
the hour is 20 and minute is 44
2016-08-18 18:44:57 +0000
the hour is 20 and minute is 44
2016-08-19 15:44:57 +0000
the hour is 17 and minute is 44
2016-08-18 16:44:57 +0000
the hour is 18 and minute is 44
The timezone is correct. I tryed two other solutions. But it is always the same problem.
The result.dateForColumn("time") is in UTC since you have +0000 whereas the second output is in another timezone (Europe/Berlin (GMT+2)), so the date is the same.
I have an app that shows certain states of hardware. I know the time when the hardware entered said state (failed, active, idle, unconnected, etc). I've been experimenting with showing the state with an "ago" time. But I've struggled with how to adjust the resolution.
Under an hour is easy, just show "minutes ago". As you move into hours ago though, the minutes become less and less important. I'm not sure that suddenly jumping to "1 hour ago" at 61 minutes is the right way though. And when to switch to "2 hours ago". Do you round, or truncated that computation. The same dilemma exists at the 24 hour point. Do you show 25 hours ago, or just say 1 day ago. Or should there be a period where I show "1 hour and 13 minutes ago" and then at some point drop the minutes.
I know these "ago" labels aren't original. I'm curious how others have implemented this.
Moment.js is a very popular library used in web development that can output time from now just as you are asking. Below is a listing of how they break down their intervals into a more digestible format.
From their documentation:
0 to 45 seconds seconds ago
45 to 90 seconds a minute ago
90 seconds to 45 minutes 2 minutes ago ... 45 minutes ago
45 to 90 minutes an hour ago
90 minutes to 22 hours 2 hours ago ... 22 hours ago
22 to 36 hours a day ago
36 hours to 25 days 2 days ago ... 25 days ago
25 to 45 days a month ago
45 to 345 days 2 months ago ... 11 months ago
345 to 547 days (1.5 years) a year ago
548 days+ 2 years ago ... 20 years ago
As a sumplement to #JimmyBoh 's answer, I thought I'd add the ObjectiveC code I used:
#import "NSDate+AgoPrint.h"
#define SECONDS(s) (s)
#define MINUTES(m) ((m) * 60)
#define HOURS(h) ((h) * 3600)
#define DAYS(d) ((d) * 3600 * 24)
#define YEARS(y) ((y) * 3600 * 24 * 365)
#implementation NSDate (AgoPrint)
- (NSString*) simpleAgoString {
NSTimeInterval ago = -self.timeIntervalSinceNow;
if (ago <= SECONDS(45)) { // 0 to 45 seconds seconds ago
return [NSString stringWithFormat: #"%u seconds ago", (unsigned int)round(ago)];
}
else if (ago <= SECONDS(90)) { //45 to 90 seconds a minute ago
return #"a minute ago";
}
else if (ago <= MINUTES(45)) { //90 seconds to 45 minutes 2 minutes ago ... 45 minutes ago
return [NSString stringWithFormat: #"%u minutes ago", (unsigned int)round(ago / MINUTES(1))];
}
else if (ago <= MINUTES(90)) { // 45 to 90 minutes an hour ago
return #"an hour ago";
}
else if (ago <= HOURS(22)) { // 90 minutes to 22 hours 2 hours ago ... 22 hours ago
return [NSString stringWithFormat: #"%u hours ago", (unsigned int)round(ago / HOURS(1))];
}
else if (ago <= HOURS(36)) { // 22 to 36 hours a day ago
return #"a day ago";
}
else if (ago <= DAYS(25)) { // 36 hours to 25 days 2 days ago ... 25 days ago
return [NSString stringWithFormat: #"%u days ago", (unsigned int)round(ago / DAYS(1))];
}
else if (ago <= DAYS(45)) { // 25 to 45 days a month ago
return #"a month ago";
}
else if (ago <= DAYS(345)) { // 45 to 345 days 2 months ago ... 11 months ago
return [NSString stringWithFormat: #"%u months ago", (unsigned int)round(ago / DAYS(30))];
}
else if (ago < DAYS(547)) { // 345 to 547 days (1.5 years) a year ago
return #"a year ago";
}
else { // 548 days+ 2 years ago ... 20 years ago
return [NSString stringWithFormat: #"%u years ago", (unsigned int)round(ago / YEARS(1))];
}
}
#end
I may end up adding weeks or replacing months or weeks, but the general approach seems pretty clear how I would slot that in.
I'm implementing an iOS app. and found that sometimes the screen became black little by little.
for example, in a view controller, there are a collection view, a button, a page controller, and sometimes I found the collection view became black(or invisible), only black background is shown, after 1-2 seconds, the button is gone, then the whole screen is black. but if I put the app into background, then bring it back, everything is ok.
here is the code:
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSTimeInterval timeLeft = [UIApplication sharedApplication].backgroundTimeRemaining - 1.0;
if(timeLeft < 0)
{
completionHandler(UIBackgroundFetchResultNoData);
}
//create new thread to do something, and sleep at current thread
...create new threads
[NSThread sleep:timeLeft];
completionHandler(UIBackgroundFetchResultNewData);
});
}
the issue could be reproduced if I repeat the following actions several times:
Do some thing
Put app to background(press home button)
Bring app back to foreground
repeat 1 - 3
we found following error in organizer for our app:
: CoreAnimation: warning, deleted thread with uncommitted CATransaction; set CA_DEBUG_TRANSACTIONS=1 in environment to log backtraces.
after added , I got the following log:
Myapp[11496] : CoreAnimation: warning, deleted thread with uncommitted CATransaction; created by:
0 QuartzCore 0x31ca6a75 + 268
1 QuartzCore 0x31ca6929 + 224
2 QuartzCore 0x31cabddb + 86
3 QuartzCore 0x31cab9f7 + 150
4 UIKit 0x3203f919 + 344
5 UIKit 0x320bb11b + 138
6 UIKit 0x322b0ebf + 218
7 UIKit 0x322b1169 + 104
8 UIKit 0x322b1735 + 36
9 Myapp 0x002e538d __61-[AppDelegate application:performFetchWithCompletionHandler:]_block_invoke + 632
10 libdispatch.dylib 0x3a487d53 + 10
11 libdispatch.dylib 0x3a48d689 + 228
12 libdispatch.dylib 0x3a48d8dd + 56
13 libsystem_pthread.dylib 0x3a5b8c17 _pthread_wqthread + 298
14 libsystem_pthread.dylib 0x3a5b8adc start_wqthread + 8
my questions is:
how could application:performFetchWithCompletionHandler cause animation issue?
to answer questions:
1. I'm sure that phone is not going to sleep
2. source code. sorry the project is too big
Check that you are working with UI on main thread only, this should be the case.
Edit: - I've seen this one, although never used it(written by Peter Steinberger).
Also this answer can help avoiding several more problems.
The thread created to perform your fetch invokes a call to update your ui, when it finishes performing whatever task it is doing, and its no longer needed it is deallocated. UIUpdates MUST be performed on the main thread or they might not be performed right away.
Have you tried dispatching to the main thread whatever ui update you are performing there?
It's hard to know what you your completion handler is doing, but if it's updating UI try to dispatch them on the main thread
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSTimeInterval timeLeft = [UIApplication sharedApplication].backgroundTimeRemaining - 1.0;
if(timeLeft < 0)
{
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(UIBackgroundFetchResultNoData);
});
}
//create new thread to do something, and sleep at current thread
...create new threads
[NSThread sleep:timeLeft];
dispatch_async(dispatch_get_main_queue(), ^{
completionHandler(UIBackgroundFetchResultNewData);
});
});
}
I'm getting the following error:
*** -[S3PutObjectOperation_Internal respondsToSelector:]: message sent to deallocated instance 0x43c18fd0
I don't understand the stack trace from Zombies inspector very well, I'm not sure this is telling me anything I can fix:
# Address Category Event Type RefCt Timestamp Size Responsible Library Responsible Caller
0 0xc071f60 S3PutObjectOperation_Internal Malloc 1 00:14.903.021 48 MyApp -[S3TransferManager upload:]
1 0xc071f60 S3PutObjectOperation_Internal Retain 2 00:14.903.032 0 Foundation ____addOperations_block_invoke_0
2 0xc071f60 S3PutObjectOperation_Internal Release 1 00:14.903.036 0 MyApp -[S3TransferManager upload:]
3 0xc071f60 S3PutObjectOperation_Internal Retain 2 00:14.904.154 0 libsystem_sim_blocks.dylib _Block_object_assign
4 0xc071f60 S3PutObjectOperation_Internal Retain 3 00:14.904.163 0 libsystem_sim_blocks.dylib _Block_object_assign
5 0xc071f60 S3PutObjectOperation_Internal Release 2 00:14.904.164 0 Foundation __destroy_helper_block_474
6 0xc071f60 S3PutObjectOperation_Internal Retain 3 00:14.906.549 0 MyApp -[S3PutObjectOperation_Internal start]
7 0xc071f60 S3PutObjectOperation_Internal Release 2 00:14.906.554 0 MyApp -[S3PutObjectOperation_Internal start]
8 0xc071f60 S3PutObjectOperation_Internal Release 1 00:14.907.624 0 MyApp __destroy_helper_block_
9 0xc071f60 S3PutObjectOperation_Internal Retain 2 00:15.243.299 0 MyApp -[S3PutObjectOperation_Internal finish]
10 0xc071f60 S3PutObjectOperation_Internal Retain 3 00:15.243.302 0 MyApp -[S3PutObjectOperation_Internal finish]
11 0xc071f60 S3PutObjectOperation_Internal Release 2 00:15.243.307 0 MyApp -[S3PutObjectOperation_Internal finish]
12 0xc071f60 S3PutObjectOperation_Internal Release 1 00:15.243.330 0 MyApp -[S3PutObjectOperation_Internal finish]
13 0xc071f60 S3PutObjectOperation_Internal Release 0 00:15.244.420 0 Foundation __release_object_op
14 0xc071f60 S3PutObjectOperation_Internal Zombie -1 00:15.386.107 0 MyApp -[S3Response connection:didSendBodyData:totalBytesWritten:totalBytesExpectedToWrite:]
This error happens when the application is uploading photos. The Photo object uses and implements the following methods:
-(void)request:(AmazonServiceRequest *)request didSendData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
{
NSLog(#"didSendData called: %d - %d / %d", bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
self.transferProgress += bytesWritten;
float p = (float) self.transferProgress / self.uploadSize;
self.uploadProgress = [NSNumber numberWithFloat:p];
}
-(void)request:(AmazonServiceRequest *)request didCompleteWithResponse:(AmazonServiceResponse *)response
{
NSLog(#"didCompleteWithResponse called.");
}
-(void)request:(AmazonServiceRequest *)request didFailWithError:(NSError *)error
{
NSLog(#"didFailWithError called: %#", error);
}
As far as I know this is the only point in the app in which the S3 request is talking to any other objects/instances. Any idea what is causing this error?
I think this is a bug in the AWS SDK. I'm not sure where exactly the bug is but it's triggered by request retries if the response was a service exception (and possibly only in conjunction with S3TransferManager).
I didn't want to disable all retries, so I fixed this by forcing no retries for service exceptions. You can do this by using a subclass of AmazonS3Client where you override one method like this:
- (BOOL)shouldRetry:(AmazonServiceResponse *)response exception:(NSException *)exception
{
// There is some kind of bug that is triggered on request retries when S3 requests
// return an explicit service exception (e.g., auth token expired). In these cases
// we force no retry to avoid the bug.
NSException *exceptionHolder = response.exception;
if(exceptionHolder == nil)
{
exceptionHolder = exception;
}
if([exceptionHolder isKindOfClass:[AmazonServiceException class]])
{
return NO;
}
return [super shouldRetry:response exception:exception];
}