I've created an animation loop which I run as an operation in order to keep the rest of my interface responsive. Whilst almost there, there is still one remaining issue. My UIScrollViews don't seem to be reliably picking up when a user touch ends. What this means is, for example, if a user drags down on a scroll view, when they lift their fingers the scrollview doesn't bounce back into place and the scrollbar remains visible. As if the finger hasn't left the screen. It takes another tap on the scrollview for it to snap to its correct position and the scrollbar to fade away...
Here's the loop I created in a subclassed NSOperation:
- (void)main
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
_displayLink = [[CADisplayLink displayLinkWithTarget: self selector: #selector(animationLoop:)] retain];
[_displayLink setFrameInterval: 1.0f];
[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode: NSRunLoopCommonModes];
while (![self isCancelled])
{
NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
[loopPool drain];
}
[_displayLink invalidate];
[pool release];
}
It seems that whilst this operation is running things go a little weird...
DOes anyone have any idea what might be going on here, and even better how to fix it...
Thanks!
I do not understand why are you using a so complicated solution for your need which seems very classic.
In my opinion you'd better use the Quartz basic features to create your animation and let the OS choose when to compose for rendering. You can also use the setNeedsLayout and setNeedDisplay method to notify the rendering loop you've modify something instead of implementing your own loop.
Or maybe you have a specific need I did not understand.
Related
I am having an app where I load images in a UITableView. Each row has an image view.
I download images from URL and assign it to UIImageView. The problem here is that the image views are not updating if I was dragging the table view. It only updates when I release the finger.
Another problem is that I have a label in all cells that shows a counter which is incremented using a timer. Timer action is also not getting called when my finger is on the table view.
How do I do the updates in both these cases?
May be this is a very basic question. But I have no clue.
Thanks everyone!
EDIT: I have noticed that -connection:didReceiveResponse: method is not getting called when the view is being touched.
EDIT 2: I tried adding run loop with NSURLConnection. My method looks like this.
- (void)start {
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:self.imageURL cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:self.timeoutInterval];
[request setValue:#"gzip" forHTTPHeaderField:#"Accept-Encoding"];
_connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
NSPort* port = [NSPort port];
NSRunLoop* rl = [NSRunLoop currentRunLoop]; // Get the runloop
[rl addPort:port forMode:NSRunLoopCommonModes];
[_connection scheduleInRunLoop:rl forMode:NSRunLoopCommonModes];
[_connection start];
[rl run];
}
This loads the images even when I am touching on the view. But the table view is scrolling very slowly. Its not even scrolling. It only moves when I drag. No movement after I release the finger.
Problem 1
The solution is to use downloading mechanism that doesn't get blocked while you drag the table view. SDWebImage is an asynchronous image downloader with cache support with an UIImageView category. This will let your image view to be updated even if you are dragging your table. I have faced a the same issue in my project where i used SDWebImage. Just a call like this will do.
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
Problem 2
If its scheduledTimer will not get called while the main thread is tracking touches. Do something like this. Just create a timer and add to loop
NSTimer* timer = [NSTimer timerWithTimeInterval:1.0f target:self selector:#selector(updateLabel:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
this will not interrupt your timer.
this might be some helpful to you because you downloading images for displaying in UItableViewCells here i am giving you the example of the anything in background without blocking UI there you can get good tutorial link.they have mentioned enough good way same thing.go through this link
And Rest of the thing could be solved with the help of as Meera j Pai mentioned in his Answer.
I solved the problem with both NSConnection & NSTimer using the answers given by #Amar & #Meera J Pai, by using NSRunLoop.
I have changed my connection code to be like this.
_connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO]
[_connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[_connection start];
It worked great!
Thanks everyone.
I run my animations in a UITAbleViewCell.
Each cell has its own animation and the cells are reusable.
I use [mView performSelectorInBackground:#selector(layoutSubview) withObject:nil];
There in the background thread I initiate the runLoop to perform tasks like this:
- (void)startAnimation
{
NSRunLoop *mLoop = [NSRunLoop currentRunLoop];
self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:#selector(setNeedsDisplay) userInfo:nil repeats:YES];
mRunLoop = YES;
while (mRunLoop == YES && [mLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.01]]);
}
and stop it:
- (void)stopAnimation
{
if (![NSThread isMainThread]) {
[[NSThread currentThread] cancel];
}
mRunLoop = NO;
self.animationTimer = nil;
CFRunLoopStop(CFRunLoopGetCurrent());
}
I run into problems when I fast scroll through table, because on the first cell initiation I begin the animation, so the first runLoop call occures which performs a setNeedDisplay and all the methods from it. But before finishing the first runLoop cycle the cell disappears from the view and is already available for reuse. So I begin clearing it, while the cycle is still performing operations and here I meet situations like
message sent to deallocated instance
So could you please give me some hints of how should I correctly stop performing the operations in that thread? I mean if I want to realese for example an object, which is performing some actions how to immediately stop'em?
Hope I gave enough info.
Thanks
UPDATE: No ideas at all?
I'll take a completely different stab on it:
Get rid of the cell's timers and background threads altogether!
Animation is not something where NSTimer is a good fit in the first place and having multiple timers won't help much, either.
UITableView has a method visibleCells and a method indexPathsForVisibleRows. I'd suggest to use a single CADisplayLink — which is suited for animation, as it calls you back with the actual refresh rate of the display or a fraction thereof — in your tableview-controller and in the callback of that display-link iterate over the visible cells.
If you want to schedule the display-link on the run-loop of a secondary thread, feel free to do so, but I'd check if you can get away without extra threading first.
Some code:
#interface AnimatedTableViewController ()
#property (strong, nonatomic) CADisplayLink *cellAnimator;
- (void)__cellAnimatorFired:(CADisplayLink *)animator;
#end
#implementation AnimatedTableViewController
#synthesize cellAnimator = cellAnimator_;
- (void)setCellAnimator:(CADisplayLink *)animator
{
if (animator == cellAnimator_)
return;
[cellAnimator_ invalidate];
cellAnimator_ = animator;
[cellAnimator_ addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSCommonRunLoopModes];
}
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
self.cellAnimator = [CADisplayLink displayLinkWithTarget:self selector:#selector(__cellAnimatorFired:)];
...
}
- (void)viewWillDisappear:(BOOL)animated
{
self.cellAnimator = nil;
...
[super viewWillDisappear:animated];
}
- (void)__cellAnimatorFired:(CADisplayLink *)animator
{
NSArray *visibleCells = [self.tableView visibleCells];
[visibleCells enumerateObjectsUsingBlock:^(UITableViewCell *cell, NSUInteger unused, BOOL *stop){
[cell setNeedsDisplay];
}];
}
...
#end
NSTimer has a -cancel method that stops the timer from firing. Calling it in -prepareForReuse (and, for that matter, in -stopAnimation) may help.
However, this code looks rather dangerous. Nesting run loops like this is almost never a good idea—and moreover, as far as I can tell it's totally unnecessary. If you let -startAnimation return, your animation timer will still get run on the main run loop. And if you're doing it this way because there's some code after -startAnimation that you want to delay, you should restructure your code so this isn't needed.
(If you drop the runloop stuff in -startAnimation, don't stop the runloop in -stopAnimation either.)
Something like the approach danyowdee recommends would be even better, but at least get rid of this runloop stuff. It's just asking for trouble.
I think you can use this method for your problem
[NSObject cancelPreviousPerformRequestsWithTarget:yourTarget selector:aSelector object: anArgument];
I think that the best way to avoid that behavior is assigning the delegate that receives the cancel method in other class that won't be reused. For example, you can have a private array of instances that process all the cancel methods, each row mapped into an array element.
I recommend you the lazy tables example provided by Apple in Xcode documentation. It's a great example of how to load images asynchroniously in background with a table. I think that also it would be useful for you for the scrolling subjects (decelerating and paging).
Only one more consideration, i don't recommend messing up with several cfrunloopstop, test it hard!
I have an app that needs to signal continuously a word in morse code. I did this by creating an NSThread and running some code inside the selector with a "while loop". Here is the code:
#implementation MorseCode
-(void)startContinuousMorseBroadcast:(NSString *)words{
if (!(threadIsOn)) {
threadIsOn = YES; s
myThread = [[NSThread alloc] initWithTarget:self selector:#selector(threadSelector:) object:words];
[myThread start];
}
if (morseIsOn) {
morseIsOn = NO;
}
else{
morseIsOn = YES;
}
}
-(void)threadSelector:(NSString *)words{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
while (![myThread isCancelled]) {
// ///it Does some code here
} //end While
NSLog(#"Cleaning the pool");
[pool drain];
}
#end
When exiting the application (the user presses the button), in the applicationDidEnterBackground the following selector is executed:
-(void)cleanUpMorseObject{ //this is defined in the MorseCode class, same as threadSelector
if (threadIsOn) {
NSLog(#"cleanUpMorseObject, threadIsOn");
threadIsOn = NO;
morseIsOn = NO;
[myThread cancel];
[myThread release];
}
}
The application responds correctly to the event, I’ve checked with nslog.
And then [MorseCode release] is called.
The code looks like this:
-(void)applicationDidEnterBackground{ //this happens in the ViewController
[theMorse cleanUpMorseObject]; //theMorse is an instance of MorseCode
[theMorse release];
}
The problem: Although I call [myThread release] and then [theMorse release] the retainCount of the theMorse is still above 0 (It doesn’t call the dealloc).
The Leaks Instrument doesn’t say I have a leak, but if I open and close the application for like 10 times eventually the Iphone resets. Also in the debugger eventually I see the “Received memory warning. Level=2”.
Also I never see the NSLog before the pool drain…
The app doesn't run in the background.
Any ideas? Thank you
You really should schedule the sending of the message on the RunLoop, the probably easiest way being to schedule a timer (repeat infinitely, and short repeat period like FLT_EPSILON or similar) instead of using threads for that.
Working with threads is complicated and as everyone should avoid it (as Apple stated in its Concurrency Programming Guide, and as most documentation said, "Threads are evil" ;)).
That's because multithreading is a vast and complicated subject, that needs synchronizations, resources protection, being aware of dead locks, critical sections & so on, good and adapted memory mgmt, and much much more. In general if you need to do stuff in the background:
Use mechanisms already in place (like asynchronous implementation of some operations and being signalled by delegate methods or notifications) if available
Use methods like performInBackground:
Use NSOperationQueues
Use GCD
And only in last resort and if there are no other options (or for really specific cases), use NSThread.
This will avoid you a lot of issues as all the other, higher APIs will take care of a lot of things for you.
Moreover, using threads for this task like you do is likely to use much more CPU (will probably reach 100% usage quickly) as there won't be any time left for the task scheduler (that also why even GCD that takes care of all stuff like that is way better than NSThreads, and scheduling the sending in the RunLoop is even better for the CPU if you don't need strong RT constraints)
First, retainCount can never return 0. It is a useless method. Don't call it.
Secondly, leaks only detects objects that are no longer referenced. If a thread is still running, it isn't leaked.
Finally, a thread doesn't stop when you call cancel. It just sets a flag that you have to check via isCancelled to see if it is time to stop work in the thread. Are you doing that?
OK -- easy stuff answered. Next? Try build and analyze. Then use the Allocations instrument and turn on reference count tracking. Then see what is calling retain an extra time.
I decided to give up the NSThread class and used another aproach:
-(void)playSOSMorse{
if ([myTimer isValid]) {
[myTimer invalidate];
[myTimer release];
myTimer = nil;
}
myTimer = [[NSTimer scheduledTimerWithTimeInterval:0.001
target:self
selector:#selector(tymerSelector)
userInfo:nil
repeats:NO] retain];
//the timer calls a selector that performs a selector in background
}
-(void)tymerSelector{
[self performSelectorInBackground:#selector(threadSelector2) withObject:nil];
}
-(void)threadSelector2 {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
//some code here
[pool drain];
//calls another selector on the main thread to see if it needs to fire the timer again and restart the cycle
[self performSelectorOnMainThread:#selector(selectorOnMainThread) withObject:nil waitUntilDone:NO];
}
-(void)selectorOnMainThread{
[myTimer invalidate];
[myTimer release];
myTimer = nil;
if (morseIsOn) { //this is a boolean that if it is true (YES) calls the timer again
[self playSOSMorse];
}
}
I hope this helps somebody :)
Thank you
I am trying to create a simple translation animation to an UIImageView.
At first I was willing to use the [UIView animation] class, but I realized it doesn't give me enough control on the events during the animation(for example, simple collisions with other objects).
I then thought about using a simple thread that would modify the center coordinates of my UIImageView, then sleep and repeat until the animation is complete or until something happens(see:collisions or the object itself disappearing)
This is the code I wrote:
-(void)animateImageView:(UIImageView*)view{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
float totalTime=6.0f;
float elapsedTime=0;
while(totalTime<=elapsedTime){
if(conditions){...}//here are my checks
CGPoint newCenter=CGPointMake(view.center.x+10, view.center.y);
view.center=newCenter;
NSLog(#"%f",view.center.x);
elapsedTime+=0.1;
[NSThread sleepForTimeInterval:0.1];
}
[view performSelectorOnMainThread:#selector(removeFromSuperview) withObject:nil waitUntilDone:YES];
[pool release];
}
Fact is, my image won't move, but the NSLog command displays the right coordinates where the image's center is.
Ani idea why?
Maybe this is the wrong approach?
All UIKit classes are not thread-safe. So work with them from main thread only (view.center=newCenter;).
I want to create two threads in my game. One thread is for timer and other for touch events. Because when I am running my game on iPhone, timer conflicts with touch events and touch events are not reported. It works smooth in simulator but on iPhone or on iPod Touch it's becomes very slow. So I'm using separate threads for touch events and timer.
When I use [NSThread sleepForTimeInterval:0.01] it makes all threads sleep. I mean touch events are also stop to come through. I want to stop the timer thread only.
This is my code:
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
BOOL Done = NO;
while (!Done)
{
NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
[NSThread sleepForTimeInterval:0.01];
[self performSelectorOnMainThread:#selector(callTapCode) withObject:tapView waitUntilDone:YES];
//performSelectorOnMainThread:#selector(callTapCode) withObject:nil waitUntilDone:YES];
[loopPool release];
Done=YES;
}
[pool drain];
}
If you are trying to create a timer, you should probably just use NSTimer, because that's simpler and more efficient than creating a thread just to send a "go" event every so often.
edit: As is, you aren't creating a second you are merely stalling the main thread by calling [NSThread sleepForTimeInterval:0.01] in a loop. This means your main thread is no longer running the event loop which can cause all kinds of things to no longer work.
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.01]];