I am having a UIViewController that shows progress status of an calculations that are done on my Iphone app, to see the percent of the progress I need to push button called refresh on the same UIViewController, how can I make that automatically done without the need to push the button manually
here is my code:
- (void)viewDidLoad{
[NSThread detachNewThreadSelector:#selector(autoRefresh) toTarget:self withObject:nil];
...
}
and then :
- (void) autoRefresh{
while (1) {
sleep(2);
sendFromButton = false;
if (flag == 1) { // which button pushed last
[self CaptureButton:self];
}
if (flag == 2) { // which button pushed last
[self ExampleButtonfun:self];
}
if (flag == 3) { // which button pushed last
[self CommuintyButton:self];
}
}
}
when the controller is viewed for the first time the viewDidLoad is called that creates a thread to run the autorefresh function , but that controller is not refreshed although I did it in the right way I guess!, please help me with that.
If you want to do a series of calculations, and show that progress, you have to do the calculations in a background thread, and then update the UI on the main thread. If you did your calculations in the main thread, your UI would never have a chance to update itself.
But, assuming that you've successfully initiated your time consuming calculations on a background thread, you could then use a timer (or display link) to update your progress bar, for example, define a timer property:
#property (nonatomic, weak) NSTimer *timer;
Then schedule a repeating timer from the main queue and start your background process:
// start the timer
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0/60.0
target:self
selector:#selector(updateProgress:)
userInfo:nil
repeats:YES];
self.timer = timer;
// use whatever mechanism you want for initiating your background process (though dispatch queues and operation queues may be easier than dealing with threads directly)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[self startTimeConsumingProcess]
});
You'd then obviously implement an updateProgress: method that updates your UI:
- (void)updateProgress:(NSTimer *)timer
{
// update the UI progress bar here
}
And don't forget to invalidate that timer when your calculation is done or when the view is dismissed, because if you don't, the timer will maintain strong reference to your view controller and you'll have a strong reference cycle (aka retain cycle).
By the way, the other logical approach, instead of using a timer, is to just have the background calculation dispatch UI updates that update the progress bar back to the main queue, e.g.
- (void) startSomeTimeConsumingProcess
{
// start time consuming process in background queue
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
BOOL finished = NO;
while (!finished)
{
// do something, updating `finished` when done
// update UI to report the progress in the main queue, though:
dispatch_async(dispatch_get_main_queue(), ^{
// update progress UI here
});
}
});
}
The only consideration with this approach is how quickly this background process dispatches the main queue with progress bar updates. If it updates too quickly, you can backlog the main queue with progress updates. Hence, the appeal of the timer (or display link) based approach, above. But if you're confident that these updates will happen slowly enough, this alternative approach might be easier.
Related
I have created a UIButton and on click event, I am showing an image in the web view. Also, I am refreshing the image in every 30 sec. But when I click on button multiple times, refresh method get called multiple time as well.
I want it to work like, It saves last click time and refreshes as per that time instead of multiple times.
What can I do for it?
I tried to kill all previous thread instead of the current thread but that's not working.
Please help if anyone already know the answer.
Below is my image refresh code:
- (void)refreshBanner:(id)obj {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
if (![SNRunTimeConfiguration sharedInstance].isInternetConnected) {
[self removeBannerAdWithAdState:kADViewStateNotConnectedToInternet];
return;
}
if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) {
self.bannerPaused = YES;
return;
}
self.adView.hidden = YES;
UIViewController *topController = [UIApplication sharedApplication].keyWindow.rootViewController;
topController = [SNADBannerView topViewControllerWithRootViewController:topController];
if ([self checkInViewHierarchy:self parentView:topController.view]) {
// NSLog(#"Visible View Is: %#", self.adId);
SNADMeta *meta = [[SNADDataBaseManager singletonInstance] adToShowWithBanner:YES excludeTyrooAd:YES audio:NO zoneId:self.adSoptZoneId fixedView:NO condition:nil contextualKeyword:nil onlyFromAJ:NO];
SNADAdLocationType type = SNADAdLocationTypeHeader;
if (self.bannerType == SmallViewTypeFooter) {
type = SNADAdLocationTypeFooter;
}
if (self.isFromCustomEvent) {
type = SNADAdLocationTypeAdMobBanner;
}
NSString *message = meta ? nil : kSNADOppMissReason_NoAdToShow;
[SNRunTimeConfiguration fireOpportunityForAdLocation:type zoneId:self.adSoptZoneId reason:message];
NSLog(#"******************* Opportuninty fired for refresh banner ***************************");
if (meta) {
self.meta = meta;
[self updateContentForWebAd:nil];
[self updateStatsForAd];
//fireImpression
[SNADBannerView fireImpression:self.meta];
if ([meta.adSource isEqualToString:kSNADParameter_APC]) {
self.sdkMediation = [[SdkMediation alloc] init];
[self.sdkMediation fireTrackingAdType:self.meta.type isFill:YES];
}
// Ad Height Delegate.
if ([self.meta.displayType isEqualToString:kSNADDisplayType_web]) {
self.adHeightDelegateCalled = YES;
NSInteger height = self.meta.height.integerValue;
self.bannerCH.constant = height;
if ([self.callBackDelegate respondsToSelector:#selector(adWillPresentWithHeight:adId:adType:)]) {
[self.callBackDelegate adWillPresentWithHeight:height adId:self.adId adType:SeventynineAdTypeMainStream];
}
}
} else {
[self removeBannerAdWithAdState:kADViewStateNoAdToShow];
if ([meta.adSource isEqualToString:kSNADParameter_APC]) {
[self.sdkMediation fireTrackingAdType:self.meta.type isFill:NO];
}
return;
}
} else {
// NSLog(#"View Which Is Not Visible Now: %#", self.adId);
}
SNAdConfiguration *configuration = [SNAdConfiguration sharedInstance];
[self.timer invalidate];
self.timer = [NSTimer scheduledTimerWithTimeInterval:configuration.autoRefRate target:self selector:#selector(refreshBanner:) userInfo:nil repeats:NO];
}];
}
Use GCD, and not NSOperationQueue.
Then you step away from your immediate task. You do lots and lots of complicated things inside refreshBanner. And you will do more complicated things to make it work when the user taps multiple times.
Think about what exactly you need. Abstract the "refresh automatically, and when the button is clicked, but not too often" into a class. Then you create a class that takes a dispatch_block_t as an action, where a caller can trigger a refresh anytime they want, and the class takes care of doing it not too often. Then you create an instance of the class, set all the needed refresh actions as its action block, refreshBanner just triggers a refresh, and that class takes care of the details.
You do that once. When you've done it, you actually learned stuff and are a better programmer than before, and you can reuse it everywhere in your application, and in new applications that are coming.
NSOperationQueue have cancelAllOperations method. But for the main queue it's not a good decision to use this method, cause main queue is shared between different application components. You can accidentally cancel some iOS/other library operation together with your own.
So you can create NSOperation instances and store them in an array. Then you can call cancel for all scheduled operations by iterating trough this array, and it will only affect your operations.
Note that block operations doesn't support cancellation. You will need to create your own NSOperation subclass, extract code from your execution block into that subclass main method. Also, you'll need to add [self isCancelled] checks that will abort your logic execution at some points.
I forgot to mention that currently your execution block is fully performed on the main queue. So, you'll need to move any heavy-lifting to background thread if you want to cancel your operation in the middle of processing from main thread.
I need to add that I agree with #gnasher729 - this doesn't look like an optimal solution for the problem.
I have resolved the issue.
Multiple threads created because a new view is created every time I call the API to display image. So now I am removing views if any available before displaying image, then only last object remains and refresh is called as per last called time.
Every View has it's own object that's why multiple threads has created.
By removing views my issue has been resolved.
Thanks everyone for replying.
Here is my need:
I'm making an ios app that controls a device. It has an API that lets me do things like:
turnOnLights()
turnOffLights()
rotate(degrees)
move(speed)
etc... (the api is completely objective c, im just giving an example in c synthax)
From this API, I need to build high level sequences, for example:
turn on all lights
wait 1 second
turn off all lights
wait 1 second
Or
move
rotate 30 degrees
wait 1 second
move
rotate -30 degrees
I can think of hacky ways to do these with timers, but I am wondering if ObjectiveC has a nice way that I could build some high level methods so I could for example:
ReturnValue flashLights()
ReturnValue moveAndRotate()
The idea behind this would be that, the commands needed to do the flashing action would be sent repeatedly forever, and, I can do:
stopAction(returnValue)
To stop it. (I know I'm writing in C synthax but I find it clearer to explain things).
So essentially, is there a convenient way to make a script-like thing where I can call a method that starts an action. The action makes method calls, waits some time, does more method calls, and repeats this forever until the action is stopped.
Thanks
I am not sure if I understand your question properly, but if you want to repeatedly call a set of methods with delays in between, you can use aperformSelector:withObject:afterDelay, or dispatch_after to build a loop. (And there are many ways to leave the loop)
[self performSelector:#selector(resetIsBad) withObject:nil afterDelay:0.1];
or
int delayInSecond = 10;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delayInSecond * NSEC_PER_SEC),
dispatch_get_main_queue(), ^{
//doing something
});
performSelector:withObject:afterDelay invokes a method of the receiver on the current thread using the default mode after a delay.
This method sets up a timer to perform the aSelector message on the
current thread’s run loop. The timer is configured to run in the
default mode (NSDefaultRunLoopMode). When the timer fires, the thread
attempts to dequeue the message from the run loop and perform the
selector. It succeeds if the run loop is running and in the default
mode; otherwise, the timer waits until the run loop is in the default
mode.
dispatch_after add your block to a queue and if the queue is empty, it runs immediately once being added to the queue. Else it will have to wait for other tasks in the queue to finish before it can run.
More on dispatch_after:
dispatch_after
Enqueue a block for execution at the specified time.
void dispatch_after( dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
Parameters:
when The temporal
milestone returned by dispatch_time or dispatch_walltime.
queue The
queue on which to submit the block. The queue is retained by the
system until the block has run to completion. This parameter cannot be
NULL.
block The block to submit. This function performs a Block_copy
and Block_release on behalf of the caller. This parameter cannot be
NULL.
Discussion
This function waits until the specified time and then
asynchronously adds block to the specified queue.
Passing DISPATCH_TIME_NOW as the when parameter is supported, but is
not as optimal as calling dispatch_async instead. Passing
DISPATCH_TIME_FOREVER is undefined.
Declared In dispatch/queue.h
Personally I don't think using an NSTimer would be 'hacky' as long as you implement it properly. You do need to make sure you invalidate the timer once you're finished with it though; check out this thread for more information about NSTimer best practices.
// in YourViewController.h
#property (nonatomic) BOOL flag;
#property (nonatomic, strong) NSTimer* timer;
// in YourViewController.m
-(void)viewDidLoad
{
[super viewDidLoad];
self.flag = YES;
[self flashLights];
// other code here
}
-(void)flashLights
{
CGFloat interval = 1.0f; // measured in seconds
self.timer = [NSTimer scheduledTimerWithTimeInterval:interval
target:self
selector:#selector(timerEventHandler)
userInfo:nil
repeats:YES];
}
-(void)timerEventHandler
{
// your API calls might take a while to execute, so consider running them asynchronously:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
{
if (self.flag) turnOnLights();
else turnOffLights();
self.flag = !self.flag;
});
}
-(void)stopAction
{
[self.timer invalidate];
}
When i tap on my button, my function was called
[myBtn addTarget:self action:#selector(myFunction) forControlEvents:UIControlEventTouchUpInside];
In my function, a collection of complex statement will be executed and take a litte bit time to run, so i want to show Loading (UIActivityIndicatorView) as the following:
-(void) addTradeAction {
//Show Loading
[SharedAppDelegate showLoading];
//disable user interaction
self.view.userInteractionEnabled = NO;
//execute call webservice in here - may be take 10s
//Hide Loading
[ShareAppDelegate hideLoading];
}
When tap on myBtn (my Button) -> after 3s or 4s, [ShareAppDelegate showLoading] was called.
It is unusual when i use [ShareAppDelegate showLoading] on other Function, -> it work very nice, i mean all the statement be executed in order.
All i want, when i tap on My Button, Loading will be called immediatelly.
Tks in advance
A correct way to perform a tasks in background, and in your case showing an activity indicator, is :
-(void)myBackGroundTask
{
//here showing the 'loading' and blocking interaction if you want so
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//here everything you want to perform in background
dispatch_async(dispatch_get_main_queue(), ^{
//call back to main queue to update user interface
});
});
}
With this kind of block, you are sure that your interface do not freeze, and keep a smooth animation.
If your complex statements do not any UI animations or UI related code, then you can execute that part in a different thread(other than the mainThread). Once the statements are done(or in completion block), you can remove the loadingOverlay there.
Put myFunction to run on a background queue as it probably makes the system hang:
- (void)myFunction {
dispatch_queue_t myQueue = dispatch_queue_create("myQueue", NULL);
// execute a task on that queue asynchronously
dispatch_async(myQueue, ^{
// Put the current myFunction code here.
});
}
I'm running mathematical computation in a background thread. Attempting to post results in real time in a UITextView. However the results don't show up until the background thread completes. Why not?
I kick off a method in the background,
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^() {
[self v2];
});
The background thread method is of the form,
- (void) v2 {
NSString *result;
// ... loop a bunch of times generating lots of results
for (bunch of stuff to compute) {
// If using dispatch_async, nothing is displayed until this method finishes
// If dispatch_sync then it does display and update
result = [self computeNextValue];
dispatch_async(dispatch_get_main_queue(), ^() {
textView.text = result;
});
} // end computation
}
This actually hadn't been much of a problem until I started trying to scroll the view. Painfully slow. So I created a NSTimer to periodically scroll the UITextView. However, even though the timer popped and the method is run to request the scroll, the UITextView doesn't scroll until the background method completes.
First, make sure that you are using weakSelf rather than self within the block.
__weak MyClass weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^() {
[weakSelf v2];
});
...
__weak MyClass weakSelf = self;
dispatch_async(dispatch_get_main_queue(), ^() {
weakSelf.textView.text = result;
});
But that won't cause the delayed update. I am very suspect of the queue's priority, DISPATCH_QUEUE_PRIORITY_LOW. Try using DISPATCH_QUEUE_PRIORITY_DEFAULT instead.
I think I figured it out, at least empirically.
It looks like setting text on a UITextView or programmatically initiating scrolling sets up an animation sequence thats run in another thread. What seems to be happening is that the requests to set the text of the UITextView is coming in faster than the ability of UITextView to setup the animation. When the property changes again, it apparently cancels the earlier animation that it was setting up on another thread. As I continue to flood it with change requests, its never able to finish what it wants to do before the value of 'text' has changed again.
My solution involves multiple steps.
In my original approach I was setting textView.text very rapidly to some new value. I set up a timer to periodically request the UITextView to scroll.
In my modification, I calculate the new value of the result string, but do not set it to the UITextView. Instead the text is set on the textview periodically based on the timer. This allows the text view to catch up.
However, I noticed that this still wasn't reliable. If I happened to set the text again while it was still scrolling, weird effects would occur, such as a very slow scroll. It seems that the scroll animation and repeated settings of text were still causing a problem.
So solve this problem, I created a property to indicate if the view is scrolling. Set the view controller as the UITextField delegate. When I request the view to scroll, I set the flag to indicate its scrolling. Only update the content and request scroll if its not already scrolling. Ends up working great. Doesn't matter how fast I set the timer, it ends up waiting appropriately.
// ViewController.h
#property BOOL isViewScrolling;
// ViewController.m
// initialize property in viewDidLoad
- (void)viewDidLoad {
self.isViewScrolling = FALSE;
textView.delegate = self;
self.timer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:#selector(scrollIt) userInfo:nil repeats:TRUE];
}
- (void)scrollIt {
NSLog(#"scrollit thread=%d", [[NSThread currentThread]isMainThread]);
if (!self.isViewScrolling) {
textView.text = self.result;
NSRange range = NSMakeRange(textView.text.length - 1, 1);
self.isViewScrolling = TRUE;
[textView scrollRangeToVisible:range];
}
}
// UITextView delegate
- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
NSLog(#"Animation stopped");
self.isViewScrolling = FALSE;
}
Try changing the predefined dispatch queue string to end with background.
__weak MyClass weakSelf = self;
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^() {
weakSelf.textView.text = result;
});
Also you should add a UIActivityIndicator and start animating at the beginning of the operation, then stop animating after the textview.text field has been updated.
It's a nice feature to have to show the user there is a process currently being done
Also I would stay away from NSThread as I have read a few forums and docs from apple that emphasize the use of GCD and block operations.
After selecting an option from a popover controller, the delegate is informed that a selection has been made.
I want to dismiss the popover, have it removed from the screen, and display an activity indicator to the user.
Unfortunately, the code below the dismissPopover runs before the popover actually disappears, resulting in a long wait without anything appearing to be happening.
- (void)itemSelected:(int)option {
[popController dismissPopoverAnimated:YES];
activityIndicator.hidden = NO;
[activityIndicator startAnimating];
switch (option) {
case 0:
// Do something that takes some time
break;
case 1:
// Do something that takes even longer
break;
}
}
What's the best way to return control back to the calling ViewController after dismissing the popover?
The problem is that when you change the UI, it doesn't happen instantly. The changes are actually queued up to occur next time the main event loop finishes. Since that usually happens right away, we usually don't have to worry about the difference. All UI updates happen on the main thread, and since your long operations are also on the main thread, the app never gets around to updating the UI until the long operations are done.
One solution would be to use Grand Central Dispatch to offload those operations to another thread, which will allow the main thread to continue executing (and the UI to continue updating) until the operation is done.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
[self performReallyLongOperation];
});
dispatch_release(queue);
You can use UIPopOverController's delegate method popoverControllerDidDismissPopover to execute your code after the popover is done dismissing:
Header
<UIPopoverControllerDelegate>
Implementation
- (void)popoverControllerDidDismissPopover:(UIPopoverController *)popoverController
{
activityIndicator.hidden = NO;
[activityIndicator startAnimating];
switch (option) {
case 0:
// Do something that takes some time
break;
case 1:
// Do something that takes even longer
break;
}
}