ProgressView block on scroll table - ios

I have an issue. I have a UITableView and a View with a UIProgressView and when i scroll the table, the progressview not refresh the progress value... Only when the scroll is finish, the progress refresh..
I have no clue why is this happening.
I tried to refresh the progress with dispatch_async
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
//I don't know what i have to put here, maybe the NSTimer??
dispatch_async(dispatch_get_main_queue(), ^(void){
//here i set the value of the progress
});
});
but nothing changes...

You're almost there!
I've replicated your problem and fixed it.
This is it without the fix, which is the problem I think you have (note that the progress indicator doesn't update while scrolling):
and this is it with the fix:
The problem is that the scrolling also occurs on the main thread and blocks it. To fix this you just need a small adjustment to your timer. After you initialise your timer, add this:
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
Here's some example minimal code:
-(instancetype)init{
self = [super init];
if (self) {
_progressSoFar = 0.0;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.progressIndicator.progress = 0.0;
self.myTimer = [NSTimer scheduledTimerWithTimeInterval: 0.1 target: self selector: #selector(callAfterSomeTime:) userInfo: nil repeats: YES];
[[NSRunLoop currentRunLoop] addTimer:self.myTimer forMode:NSRunLoopCommonModes];
[self.myTimer fire];
}
-(void)callAfterSomeTime:(NSTimer *)timer {
if (self.progressSoFar == 1.0) {
[self.myTimer invalidate];
return;
}
// Do anything intensive on a background thread (probably not needed)
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void){
// Update the progress on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
self.progressSoFar += 0.01;
self.progressIndicator.progress = self.progressSoFar;
});
});
}
The scroll occurs in UITrackingRunLoopMode. You need to make sure that your timer is also in that mode. You shouldn't need any background thread stuff unless you do some fancy calculations but I've included it just in case. Just do any intensive stuff inside the global_queue call but before the main thread call.

Related

Objective C - Iphone app development. If main thread hung, how can we terminate the app?

My Main objective is, if dispatch_get_main_queue() - hung & not responds, terminate the app.
I have this piece of code,
dispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0));
double interval = 10.0;
dispatch_time_t startTime = dispatch_time(DISPATCH_TIME_NOW, 0);
uint64_t intervalTime = (int64_t)(interval * NSEC_PER_SEC);
dispatch_source_set_timer(dispatchSource, startTime + intervalTime, intervalTime, 0);
dispatch_source_set_event_handler(dispatchSource, ^{
NSLog(#"Main Thread not reponding for 10 secs. Capture App terminated");
exit(0);
});
NSLog(#"LOW");
dispatch_resume(dispatchSource);
dispatch_async(dispatch_get_main_queue(), ^{
dispatch_source_cancel(dispatchSource);
This was working fine XCode simulator but not working in Real Devices as expected.
Why this piece of code is not working in real device?
We use UIWebView to load a page. The page has some flash video. When trying to load it, App gets hung. That particular webpage is not belongs us. Yes agreed, ideal way to avoid its get stuck. But we want a solution or a hack to come out of this problem
If there is anyother way to identify dispatch_get_main_queue() takes time - and closing it, that would be more helpful.
This is not the right way to do. But if you know you are doing wrong, and still want to do it wrong, use like below.
Have a function like this,
- (void)terminateApp:(NSTimer*)timer
{
if(mainQueueDispatchSuccessful)
{
[timer invalidate];
timer = nil;
}
else
{
NSString *reasonString = #"Main thread not responding at SBLAppDelegate for 10 secs. Force Closing app.";
NSLog(#"%#",reasonString);
exit(0);
}
}
Use it before and after main queue dispatch like this.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
t = [NSTimer scheduledTimerWithTimeInterval:10 target:self selector:#selector(terminateApp:) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:t forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
});
mainQueueDispatchSuccessful = false;
dispatch_async(dispatch_get_main_queue(), ^{
mainQueueDispatchSuccessful = true;
.......
.......
}
Thanks for all, for helping me.

NSTimer from secondary NSThread doesn't work

As per the documentation of Run Loop if there is any input source NSThread will be running otherwise it will go to sleep. I configured the timer same as provided under "Configuring Timer Sources" in above link but its not triggering. I am using below code.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
[NSThread detachNewThreadSelector:#selector(testOnThread) toTarget:self withObject:nil];
}
- (void) testThread
{
NSLog(#"Test");
}
-(void)testOnThread
{
#autoreleasepool {
NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop];
// Create and schedule the first timer.
NSDate* futureDate = [NSDate dateWithTimeIntervalSinceNow:1.0];
NSTimer* myTimer = [[NSTimer alloc] initWithFireDate:futureDate
interval:0.1
target:self
selector:#selector(testThread)
userInfo:nil
repeats:YES];
[myRunLoop addTimer:myTimer forMode:NSDefaultRunLoopMode];
}
}
Above code never prints "Test".
But timer is firing every time if I put [[NSRunLoop currentRunLoop] run]; at the end of -(void)testOnThread methods it works fine(Stackoverflow Question). My query is if we are already providing timer input Source to run loop than what is the need to explicitly start it using [[NSRunLoop currentRunLoop] run];
I'll let others answer the question why you have to run the runloop yourself. But I'd like to suggest an alternative:
If you want to run timer on background thread, using dispatch timer is easiest, IMHO, with no runloop required at all. Just define timer property:
#property (nonatomic, strong) dispatch_source_t timer;
And then schedule the timer to run on a custom queue:
dispatch_queue_t queue = dispatch_queue_create("com.domain.app.timer", 0);
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_source_set_timer(self.timer, dispatch_walltime(NULL, 0), 20ull * NSEC_PER_SEC, 1ull * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
// code to be performed periodically on background thread here
});
dispatch_resume(self.timer);

What is the correct way to update UI asynchronously?

So I have this code:
int i = 3;
dispatch_async(queue, ^{
dispatch_async(dispatch_get_main_queue(), ^{
for (;;)
{
t1 = [NSTimer scheduledTimerWithTimeInterval:i target:self selector:#selector(updateUI) userInfo:nil repeats:NO];
}
});
});
And so what i'm doing is i'm updating the UI every 3 seconds in a game, but I want to be able to change the interval on which the timer goes off so I used an infinite for loop with the timer in it. My problem is that when I use the main queue it doesn't do it async but I need to use the main queue for updating the UI so I don't know what to do.
If you create NSTimer in main thread, then selector call in main thread.
- (void)updateTimer
{
// destroy timer
[t1 invalidate];
// start new timer
t1 = [NSTimer scheduledTimerWithTimeInterval:i target:self selector:#selector(updateUI) userInfo:nil repeats:NO];
}
- (void)updateUI
{
......
[self updateTimer];
}
Need call [self updateTimer] in start application, to begin the update UI.

How to set up an animation thread in iOS?

I have a function drawView which is thread safe and does drawing for short periods of game animation. I have functions startAnimating and stopAnimating. I want a background thread to call drawView at a regular rate but only during the period that the animation is enabled.
In startAnimating I was going to call the view's performSelectorInBackground:withObject: to get the thread running.
I'm a little confused about how to do the thread communication and initialize the drawing thread: specifically, setting up a runloop to receive display link messages and then at the end notifying the thread that it should exit and exiting the run loop cleanly when stopAnimating is called from the main thread. I want to ensure that drawView is never called after stopAnimating, and also that the drawing thread is not cancelled abruptly in the middle of the drawing operation. I have seen a lot of very poor answers to this kind of question on line.
OK after reading the Apple pages all evening, I finally solved it with this code:
// object members
NSThread *m_animationthread;
BOOL m_animationthreadrunning;
- (void)startAnimating
{
//called from UI thread
DEBUG_LOG(#"creating animation thread");
m_animationthread = [[NSThread alloc] initWithTarget:self selector:#selector(animationThread:) object:nil];
[m_animationthread start];
}
- (void)stopAnimating
{
// called from UI thread
DEBUG_LOG(#"quitting animationthread");
[self performSelector:#selector(quitAnimationThread) onThread:m_animationthread withObject:nil waitUntilDone:NO];
// wait until thread actually exits
while(![m_animationthread isFinished])
[NSThread sleepForTimeInterval:0.01];
DEBUG_LOG(#"thread exited");
[m_animationthread release];
m_animationthread = nil;
}
- (void)animationThread:(id)object
{
#autoreleasepool
{
DEBUG_LOG(#"animation thread started");
m_animationthreadrunning = YES;
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
CADisplayLink *displaylink = [CADisplayLink displayLinkWithTarget:self selector:#selector(displayLinkAction:)];
[displaylink setFrameInterval:3];
[displaylink addToRunLoop:runLoop forMode:NSDefaultRunLoopMode];
while(m_animationthreadrunning)
{
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
DEBUG_LOG(#"runloop gap");
}
[displaylink removeFromRunLoop:runLoop forMode:NSDefaultRunLoopMode];
DEBUG_LOG(#"animation thread quit");
}
}
- (void)quitAnimationThread
{
DEBUG_LOG(#"quitanimationthread called");
m_animationthreadrunning = NO;
}
- (void)displayLinkAction:(CADisplayLink *)sender
{
DEBUG_LOG(#"display link called");
//[self drawView];
}
The reason I use the line [self performSelector:#selector(quitAnimationThread) onThread:m_animationthread withObject:nil waitUntilDone:NO] and not simply set m_animationthreadrunning = NO in stopAnimating is because the run loop may not return in a timely fashion, but calling a selector forces it to return.

NSTimer requiring me to add it to a runloop

I am wondering if someone can explain why dispatching back to the main queue and creating a repeating NSTimer I am having to add it to RUN LOOP for it too fire? Even when using performselectorOnMainThread I still have to add it to a RUN LOOP to get it to fire.
Below is an example of my question:
#define queue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
#define mainqueue dispatch_get_main_queue()
- (void)someMethodBeginCalled
{
dispatch_async(queue, ^{
int x = 0;
dispatch_async(mainqueue, ^(void){
if([_delegate respondsToSelector:#selector(complete:)])
[_delegate complete:nil];
});
});
}
- (void)compelete:(id)object
{
[self startTimer];
//[self performSelectorOnMainThread:#selector(startTimer) withObject:nil waitUntilDone:NO];
}
- (void)startTimer
{
NSTimer timer = [NSTimer timerWithTimeInterval:3 target:self selector:#selector(callsomethingelse) userInfo:nil repeats:YES];
//NSDefaultRunLoopMode
[[NSRunLoop currentRunLoop] addTimer:_busTimer forMode:NSRunLoopCommonModes];
}
EDIT:
I believe I worded this question very poorly. I would like to know why [[NSRunLoop currentRunLoop] addTimer:_busTimer forMode:NSRunLoopCommonModes]; is necessary in startTimer if I call someMethodBeginCalled. If I don't include that line, the timer doesn't fire.
If I call startTimer from viewDidLoad for example, I can remove the NSRunLoop line and the timer will fire every 60 seconds.
And here's how to add an NSTimer to a runloop:
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
You could always use this method instead:
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:3 target:self selector:#selector(getBusLocation) userInfo:nil repeats:YES];
This will save you a line, as it will add it to the run loop automatically.
Because, as the docs say:
Timers work in conjunction with run loops. To use a timer effectively,
you should be aware of how run loops operate—see NSRunLoop and
Threading Programming Guide. Note in particular that run loops retain
their timers, so you can release a timer after you have added it to a
run loop.
It is a design decision that Apple made when they wrote the code for NSTimer (and I'm sure they had good reason to do so) and there is nothing we can do to get around it. Is it really that burdensome?
Like #sosborn said, NSTimers depend on NSRunLoops, and since GCD queues create threads that don't have run loops, NSTimer doesn't play well with GCD.
Check out this other StackOverflow question on the matter: Is it safe to schedule and invalidate NSTimers on a GCD serial queue?
To solve that problem, I implemented MSWeakTimer: https://github.com/mindsnacks/MSWeakTimer (and had the implementation checked by a libdispatch engineer at the last WWDC!)
Timer method won't be called since GCD queues create threads that don't have run loops
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(#"Timer method from GCD main queue");
}];
});
However when dispatched on main queue the timer method will be called as it will get added to main threads run loop.
dispatch_async(dispatch_get_main_queue(), ^{
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(#"Timer method from GCD main queue");
}];
});
Adding the timer to the runloop didn't work in my case. I had to create the timer on the main thread. I was doing this thread creation in a MultipeerConnectivity delegate.
dispatch_async(dispatch_get_main_queue(), ^{
self.timer = [NSTimer scheduledTimerWithTimeInterval:self.interval invocation: self.invocation repeats:YES];
});

Resources