CADisplay link seems to be speeding up each time method is called - iphone

I'm working on an iPhone app and in my app there's an object that moves from the top to the bottom of the screen. To do this I use a CADisplay link. Once the object leaves the screen it is supposed to restart its route. The problem that I'm encountering is that each time the object restarts its route, it speeds up. This continues until the object is going so fast that you can barely see it. Any ideas why this is happening and how to stop it? Any help is appreciated, thanks in advance!
-(void)spawnButton{
int x = (arc4random() % (240) + 40;
int y = -100;
button1.center = CGPointMake(x,y);
displayLink = [CADisplayLink displayLinkWithTarget:self selector:#selector(moveObject)];
[displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
}
-(void) moveObject {
int z = 1;
button1.center = CGPointMake(button1.center.x , button1.center.y +z);
if (button1.center.y >= 480) {
[self spawnButton];
}
}

You are creating a new display link on each call to spawnButton. If you're not doing anything to remove the old display link from the run loop, then the old display link will continue sending moveObject: messages. So after two calls to spawnButton you will get two moveObject: messages per video frame, and after three calls you will get three moveObject: messages per video frame, and so on.

I seem to have solved the problem by invalidating the display link each time I restart the objects route.
if (button1.center.y >= 480) {
[self spawnButton];
[displaylink invalidate];
}
}

Related

Create a UISlider progress bar and timer (like iPod player) within app

I've searched Google and StackOverflow for two days and have yet to find a clear, concise answer.
My goal is to create a slider/progress bar that allows the user to scrub to a different part of the song, with two labels on either side, one showing the elapsed time and the other, the time left on the song.
I'm using DiracAudioPlayer, which also incorporates AVAudioPlayer functionality. Any help or direction to a tutorial is greatly appreciated.
Not sure if this is an issue with doing the scrubbing or updating the time elapsed so I'll show what I think you can do for both.
To update the progress bar and time labels, Apple does the following in their example project avTouch
updateTimer = [NSTimer scheduledTimerWithTimeInterval:.01 target:self selector:#selector(updateCurrentTime) userInfo:player repeats:YES];
- (void)updateCurrentTime
{
currentTime.text = [NSString stringWithFormat:#"%d:%02d", (int)self.player.currentTime / 60, (int)p.currentTime % 60, nil];
progressBar.value = self.player.currentTime;
}
And then the seeking is simply done by setting the currentTime
- (IBAction)progressSliderMoved:(UISlider *)sender
{
player.currentTime = sender.value;
[self updateCurrentTimeForPlayer:player];
}

Invoke get current coordinates every few seconds without NSTimer

I know how to make this with NSTimer but I wan't to get current iPhone coordinates without timer on every few seconds. I can't use timer because I am getting coordinates while application is in background.
I have tried something but this calls every second not every 10 seconds as I wan't.
What am I doing wrong here?
- (void)locationManager:(CLLocationManager *)manager didUpdateLocations:(NSArray *)locations
{
CLLocation *loc = [locations objectAtIndex:0];
NSDate* eventDate = loc.timestamp;
NSTimeInterval howRecent = [eventDate timeIntervalSinceNow];
if (howRecent < 10)
{
CLLocation* location = [locations lastObject];
double lat = location.coordinate.latitude;
double lng = location.coordinate.longitude;
NSLog(#"lat:%f lng:%f", lat, lng);
First try to do background task every 10 seconds, but don't know where to set time if I do anything right here:
- (void)applicationDidEnterBackground:(UIApplication *)application
{
backgroundTask = [application beginBackgroundTaskWithExpirationHandler: ^{
dispatch_async(dispatch_get_main_queue(), ^{
if (backgroundTask != UIBackgroundTaskInvalid)
{
[application endBackgroundTask:backgroundTask];
backgroundTask = UIBackgroundTaskInvalid;
}
});
}];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
_pendingLocationsTimer = [NSTimer timerWithTimeInterval:_pendingLocationsTimerDuration
target:self
selector:#selector(_acceptBestAvailableLocation:)
userInfo:nil repeats:NO];
NSRunLoop *loop = [NSRunLoop currentRunLoop];
[loop addTimer:_pendingLocationsTimer forMode:NSRunLoopCommonModes];
[loop run];
dispatch_async(dispatch_get_main_queue(), ^{
if (backgroundTask != UIBackgroundTaskInvalid)
{
// if you don't call endBackgroundTask, the OS will exit your app.
[application endBackgroundTask:backgroundTask];
backgroundTask = UIBackgroundTaskInvalid;
}
});
});
}
I have had issues with timers in the background state. There is no guarantee that you will have an active run loop and so the timer will never fire until you come back to foreground. What I did is:
NSRunLoop *loop = [NSRunLoop currentRunLoop];
[loop addTimer:myTimer forMode:NSRunLoopCommonModes];
[loop run];
I do this on a background thread inside BackgroundIdentifierTask begin and end calls. I am not fully confident that I have it all correct, in fact I looked at your question to see if an answer may help me confirm or correct my understanding of the requirements.
You also must have the background mode for location services enabled in your default info.pList if you want to continually monitor location. You do not need that if you are just using significantLocationUpdates or geofence.
My timer does work and does fire as intended set up this way. It did not work reliably prior to that.
Aside from that, you may be best off with no timer. Using your code above, just change the properties of your location manager. Desired accuracy and distance filter are properties that will reduce how often the manager sends out a new location notification.
See an example of my own location handler I just posted on github for anyone that is interested:
http://github.com/dsdavids/TTLocationHandler
I don't think the timer is the key. If you drop the TTLocationHandler class in your project, then take a look at how the LMViewController class responds to the handler.
NSNotificationCenter *defaultNotificatoinCenter = [NSNotificationCenter defaultCenter];
[defaultNotificatoinCenter addObserver:self selector:#selector(handleLocationUpdate) name:LocationHandlerDidUpdateLocation object:nil];
sets up the controller as an observer. Whenever the handler determines a new or more accurate location has been received, handleLocationUpdate is called.
The locationManager, when startUpdating is invoked will send out new locations, many per second at first and when moving until the device is stationary and accuracy achieved. The TTLocationHandler is filtering these events and only sending notification as needed based on configuration.
Note that in -(id)init, _recencyThreshold is set to 60. So we are saving or displaying a pin every 60 seconds. If you want smaller interval, change this.
As it is, TTLocationHandler will always switch to significant location changes when in background unless the device is plugged in to power. You will not get that small an interval between updates if it is on battery power, but you will get updates.
I added a property to configure for continuous background updates without charging.
Example Use of TTLocationHandler
I have added another class to the repository to show how I would go about achieving your goals with the my handler. I have added LMPinTracker class where you can add your code for storing and uploading the locations.
Look at LMAppDelegate.m
self.pinTracker.uploadInterval = 30.00;
change that to whatever interval you want between uploads to the server.
self.sharedLocationHandler.recencyThreshold = 5.0;
Change that 5.0 to the minimum time you want between stored locations. It won't be exact, will vary on the rate of travel and the signal strength but basically, after x seconds interval it will consider the stored location stale and attempt to get another accurate location.
Now look at the LMPinTracker.m file
Put your data storing code right after these two lines:
// Store the location into your sqlite database here
NSLog(#"Received location info: %# \n Ready for store to database",lastKnowLocationInfo);
Put your web upload code right here after this comment:
// Do your upload to web operations here
Comments
This should do what you are looking to do, while still respecting user battery and background operation.
Basically, when a user is traveling, updates will be continuous at about the interval you have set.
When a user is stationary, there will be no updates and no significant activity.
When there is no new activity, you won't upload to web either.
Logging and updating resumes when user starts moving again.
If I were you, looking to refine it further, I would consider setting a region and time to check. If the user remains stationary for a length of time, switch to significantLocationUpdates only. Then you will power down the location services but be brought back up when the user gets underway once again.
I think you can do it in background with a Timer:
[NSTimer scheduledTimerWithTimeInterval:kAlertInterval target:self selector:#selector(checkLoc:) userInfo:nil repeats:YES];
And in your method:
- (void)checkLoc:(NSTimer *)timer {
//...get your location and...
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
//Do your background stuff
}
}
I have this code working in some apps. Please, tell me if it works for you.

Wait for random amount of time, then start updating elapsed time in a UILabel (iPhone)

I'm trying to implement a button that starts a timer after a random period of time (between 0-10s). While the timer is running it should update a label every 0.005s to show how much time has elapsed. The problem i'm having is 2-fold:
I'm not sure how to get the label to update with the elapsed time every 0.005s.
I'm having trouble getting the app to wait the random amount of time before starting timer. At present I'm using sleep(x) however it seems to cause the app to ignore all the other code in the if statement and causes the button image to freeze up (i.e. it looks like its still clicked).
Here is the code I have so far...
- (IBAction)buttonPressed:(id)sender
{
if ([buttonLabel.text isEqualToString:#"START"])
{
buttonLabel.text = #" "; // Clear the label
int startTime = arc4random() % 10; // Find the random period of time to wait
sleep(startTime); // Wait that period of time
startTime = CACurrentMediaTime(); // Set the start time
buttonLabel.text = #"STOP"; // Update the label
}
else
{
buttonLabel.text = #" ";
double stopTime = CACurrentMediaTime(); // Get the stop time
double timeTaken = stopTime - startTime; // Work out the period of time elapsed
}
}
If anyone has any suggestions on..
A) How to get the label to update with the elapsed time.
or
B) How to fix the 'delay' period from freezing up the app
... it would be really helpful as I'm pretty much stumped at this point. Thanks in advance.
You should use an NSTimer to do this. Try the code:
- (void)text1; {
buttonLabel.text = #" ";
}
- (void)text2; {
buttonLabel.text = #"STOP";
}
- (IBAction)buttonPressed:(id)sender; {
if ([buttonLabel.text isEqualToString:#"START"]) {
int startTime = arc4random() % 10; // Find the random period of time to wait
[NSTimer scheduledTimerWithTimeInterval:(float)startTime target:self selector:#selector(text2:) userInfo:nil repeats:NO];
}
else{
// I put 1.0f by default, but you could use something more complicated if you want.
[NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:#selector(text1:) userInfo:nil repeats:NO];
}
}
I'm not exactly sure how you want to update label based on the time, but if you post more code, or give an example, I'll post the code on how to do that, but it would just be using an NSTimer as well. Hope that Helps!
The answer to A could be:
Once the random amount of time has passed, (#MSgambel has a good suggestion), then execute:
timer = [NSTimer scheduledTimerWithTimeInterval:kGranularity target:self selector:#selector(periodicallyUpdateLabel) userInfo:nil repeats:YES];
(The above line could go into #MSgambel's -text2 method.)
That will call the -periodicallyUpdateLabel method once every kGranularity seconds, repeatedly. In that method, you could do things like update your label, check for user actions, or end the game if the time is up or some other condition has been met.
And here is the -periodicallyUpdateLabel method:
- (void)periodicallyUpdateView {
counter++;
timeValueLabel.text = [NSString stringWithFormat:#"%02d", counter];
}
You'll have to format the text differently to get what you want. Also, translate from the counter value to time using kGranularity. However, and this is what I found, there is only so many cpu cycles in iOS devices. Trying to go down to microsecond level made the interface sluggish and the time displayed started to drift from the actual time. In other words, you may have to limit your updates of the label to once every one hundredth of a second or tenths. Experiment.

Custom event loop and UIKit controls. What extra magic Apple's event loop does?

Does anyone know or have good links that explain what iPhone's event loop does under the hood?
We are using a custom event loop in our OpenGL-based iPhone game framework. It calls our game rendering system, calls presentRenderbuffer and pumps events using CFRunLoopRunInMode. See the code below for details.
It works well when we are not using UIKit controls (as a proof, try Facetap, our first released game).
However, when using UIKit controls, everything almost works, but not quite. Specifically, scrolling of UIKit controls doesn't work properly.
For example, let's consider following scenario.
We show UIImagePickerController on top of our own view.
UIImagePickerController covers our custom view
We also pause our own rendering, but keep on using the custom event loop.
As said, everything works, except scrolling.
Picking photos works.
Drilling down to photo albums works and transition animations are smooth.
When trying to scroll photo album view, the view follows your finger.
Problem: when scrolling, scrolling stops immediately after you lift your finger. Normally, it continues smoothly based on the speed of your movement, but not when we are using the custom event loop. It seems that iPhone's event loop is doing some magic related to UIKit scrolling that we haven't implemented ourselves.
Now, we can get UIKit controls to work just fine and dandy together with our own system by using Apple's event loop and calling our own rendering via NSTimer callbacks. However, I'd still like to understand, what is possibly happening inside iPhone's event loop that is not implemented in our custom event loop.
- (void)customEventLoop { OBJC_METHOD;
float excess = 0.0f;
while(isRunning) {
animationInterval = 1.0f / openGLapp->ticks_per_second();
// Calculate the target time to be used in this run of loop
float wait = max(0.0, animationInterval - excess);
Systemtime target = Systemtime::now().after_seconds(wait);
Scope("event loop");
NSAutoreleasePool* pool = [[ NSAutoreleasePool alloc] init];
// Call our own render system and present render buffer
[self drawView];
// Pump system events
[self handleSystemEvents:target];
[pool release];
excess = target.seconds_to_now();
}
}
- (void)drawView { OBJC_METHOD;
// call our own custom rendering
bool bind = openGLapp->app_render();
// bind the buffer to be THE renderbuffer and present its contents
if (bind) {
opengl::bind_renderbuffer(renderbuffer);
[context presentRenderbuffer:GL_RENDERBUFFER_OES];
}
}
- (void) handleSystemEvents:(Systemtime)target { OBJC_METHOD;
SInt32 reason = 0;
double time_left = target.seconds_since_now();
if (time_left <= 0.0) {
while((reason = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, TRUE)) == kCFRunLoopRunHandledSource) {}
} else {
float dt = time_left;
while((reason = CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, FALSE)) == kCFRunLoopRunHandledSource) {
double time_left = target.seconds_since_now();
if (time_left <= 0.0) break;
dt = (float) time_left;
}
}
}
If you NSLog [[NSRunLoop currentRunLoop] currentMode] from [UIScrollView setContentOffset:] when [UIScrollView isDecelerating] is true you will see UITrackingRunLoopMode.
In general, the system will use modes besides kCFRunLoopDefaultMode on the main UI thread run loop, only some of which are documented. The only way to get full system behavior is to cooperate with the system run loop on the main thread.
You could try using an NSTimer and letting the system call you instead of calling CFRunLoopRunInMode yourself. An NSTimer is free to run over time, and when no other UI is shown the system run loop would not be doing anything besides calling the timer.
The alternative would be to return from your customEventLoop function while system controls are being displayed, and call it again when resuming your custom UI.

for loop execution doesn't permit touches on iPhone

Ok. I know the title may be confusing.
Logic That I have implemented is something like this.
There is an detector in application ( like bike speedometer - moving arrow )
When user taps start scan button - first method executes.
NowStartMovements decides random rotations & random number to stop
There are 1 to 10 numbers on detector.
Every thing works fine up to now.
Following code is error free.
Arrow moves perfectly & stops at proper position ( decided randomly )
But the problem is " I have implemented for loop for the movements "
So, while executing for loop, User interaction isn't enabled.
I have also added the code that I have implemented.
-(IBAction)ScanStart:(id)sender
{
btnScan.enabled=NO; stopThroughButtons=NO; shouldNeedleGoRightSide=YES; currentNeedleValue=1; nxtNeedleValue=2;
[NSTimer scheduledTimerWithTimeInterval:0 target:self selector:#selector(nowStartMovements) userInfo:nil repeats:NO];
}
-(void)nowStartMovements{
totalRotations=arc4random()%9; if(totalRotations<3) totalRotations+=3;
currentRotation=0;stopValue=arc4random()%11; if(stopValue<1)stopValue=1;
int totalMovements=(totalRotations-1)*10 + ( (totalRotations%2==0)?10-stopValue:stopValue ), i;
for(i=0;i<totalMovements;i++){
if (stopThroughButtons) return;
[NSThread detachNewThreadSelector:#selector(moveNeedle) toTarget:self withObject:nil];
usleep(200000);
}
}
-(void)moveNeedle{
spinAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
double fromValue=[[arrayOfFloatValues objectAtIndex:currentNeedleValue-1] doubleValue];
double toValue=[[arrayOfFloatValues objectAtIndex:nxtNeedleValue-1] doubleValue];
spinAnimation.duration=0.2;
spinAnimation.fromValue=[NSNumber numberWithFloat:fromValue];
spinAnimation.toValue = [NSNumber numberWithFloat:toValue];
[imgNideel.layer addAnimation:spinAnimation forKey:#"spinAnimation"];
[NSThread detachNewThreadSelector:#selector(MoveActualNeedle) toTarget:self withObject:nil];
}
-(void)MoveActualNeedle{
if(shouldNeedleGoRightSide){
if(currentNeedleValue<9) { currentNeedleValue++; nxtNeedleValue++;}
else { shouldNeedleGoRightSide=NO; currentNeedleValue=10; nxtNeedleValue=9;
}
imgNideel.transform=CGAffineTransformMakeRotation([[arrayOfFloatValues objectAtIndex:currentNeedleValue-1] doubleValue]);
} else {
if(currentNeedleValue>2){ currentNeedleValue--; nxtNeedleValue--;}
else { shouldNeedleGoRightSide=YES; currentNeedleValue=1; nxtNeedleValue=2;
}
imgNideel.transform=CGAffineTransformMakeRotation([[arrayOfFloatValues objectAtIndex:currentNeedleValue-1] doubleValue]);
}
}
You will need to rewrite your logic so it is the Timer that is doing the sleeping, not the usleep. Rewrite your function so that every iteration of the repeatable timer does what is in the for loop.
The problem is the for loop is sleeping on the main thread. If you use a timer and set repeats to YES, then this essesntially does the for/sleep pattern you are doing. When you want to stop it call [timer invalidate];
Ideally, you'd use a timer to schedule the needle movements. The quickest solution to your existing code is this:
In StartScan, change -scheduledTimerWithTimeInterval: to -performSelectorInBackground:
In nowStartMovements, change -detachNewThreadSelector: to -performSelectorOnMainThread:
This way, the usleep happens on a background thread and doesn't block the main thread. The UI will be frozen as long as the main thread is blocked.