UITextView won't scroll while background thread busy - ios

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.

Related

How to kill multiple threads in objective-c

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.

Label taking a really long time to change

I'm learning to develop an IOS app. I'm having the following problem. I want to use a label to display a string. It takes a really long time for this string to be displayed (10-15 sec). Is this normal? The following code is inside the viewDidLoad function
NSLog(self.example); //displays almost immediately
_labelOutput.text= [NSString stringWithFormat:#"%#", self.example;//takes 15 seconds
The entire viewDidLoad function:
- (void)viewDidLoad
{
[super viewDidLoad];
double lat = 43.7000;
double lon = -79.4000;
NSArray *users = [[NSArray alloc] initWithObjects:#"user_1",#"user_2",#"user_3", nil];
id prediction = [[Prediction alloc] initWithUsers:users Lat:lat Lon:lon];
[prediction populate:^{
self.resName= [prediction generateRandom][#"id"];
NSLog([NSString stringWithFormat:#"%#", self.resName]);
_labelOutput.text= [NSString stringWithFormat:#"%#", self.resName];
}];
}
What does -[Prediction populate:] do with the block? My guess is it runs the block on a background thread or queue. You aren't allowed to modify the UI from a background thread or queue. Your app might crash or just act unpredictably. Your mysterious delay in updating the screen is a common symptom of this mistake.
You must only modify the UI from the main thread or queue. Try this:
[prediction populate:^{
self.resName= [prediction generateRandom][#"id"];
NSLog([NSString stringWithFormat:#"%#", self.resName]);
dispatch_async(dispatch_get_main_queue(), ^{
_labelOutput.text= [NSString stringWithFormat:#"%#", self.resName];
});
}];
ETA: Rob's answer is probably spot-on...if you'd posted that code initially I would have caught it as well.
Try setting the label's text in viewWillAppear or perhaps viewDidAppear instead.
Setting the "text" property of a label will normally trigger a [setNeedsDisplay] call automatically via key-value observing, and this notifies the system that the label's view needs to be redrawn on the next run loop. However, viewDidLoad is called before your view is actually visible. It's likely that because of this, either [setNeedsDisplay] is not being called, or is being ignored because the label is not yet visible...and thus, you have to wait for some other event to trigger re-drawing of subviews.
You could test this theory by adding a [self.labelOutput setNeedsDisplay] call yourself in viewDidAppear.
For swift 3 you'll want to use
DispatchQueue.main.async(execute: {
_labelOutput.LabelName.text = "Something"
})

autorefreshing uiviewcontroller in ios 5.1

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.

How to execute statements in order in objective c

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.
});
}

Issue updating label in iOS when iterating over array

I am new to iOS programming, and I could not find an answer out there already.
In Xcode 5, I am iterating over an array, and attempting to update a label with the values as they change.
here is the .h file...
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController
#property (strong, nonatomic) NSArray *currentNumber;
#property (strong, nonatomic) IBOutlet UILabel *showLabel;
- (IBAction)start;
#end
here is the main part of the .m file...
#import "ViewController.h"
#interface ViewController ()
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.currentNumber = [NSArray arrayWithObjects:#"1", #"2", #"3", #"4", nil];
}
This is where it gets tricky...
The following works perfectly...
- (IBAction)start {
self.showLabel.text = [NSString stringWithFormat:#"new text"];
}
#end
As does this...
- (IBAction)start {
for (NSString *p in self.currentNumber) {
NSLog(#"%#", p);
sleep(3);
}
}
#end
But when I replace the NSLog with setting the .text attribute, it "fails". The timing still happens, and the label updates with the last item in the array after...
- (IBAction)start {
for (NSString *p in self.currentNumber) {
self.showLabel.text = [NSString stringWithFormat:#"%#", p];
sleep(3);
}
}
#end
And the last bit of weirdness, if I use the NSLog, and try to change the .text attribute before the "for" loop is called, the text change is ignored until AFTER the loop completes...
- (IBAction)start {
self.showLabel.text = [NSString stringWithFormat:#"5"];
for (NSString *p in self.currentNumber) {
NSLog(#"%#", p);
sleep(3);
}
}
#end
What am I missing?
(If you want to see the source files, you can get them at https://github.com/lamarrg/iterate
As you've realized, the UI will only update when the main thread is processing events. In a loop, it won't be.
There's a couple ways around this.
The simplest is to perform your loop in a background thread. There's a wrinkle, though: This will allow the user to continue to interact with your UI. And also, the UI can only be updated from the main thread.
You'll want to dispatch your work to the background, then have the background dispatch your work back to the main thread.
This sounds complicated, and it is. Thankfully, Apple added blocks and Grand Central Dispatch to Objective-C. You can use those to break down the chunks of code and make sure they're executed on the correct thread.
- (IBAction)start {
[self disableMyUI];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_NORMAL, 0), ^{
// this code will be executed "later", probably after start has returned.
// (in all cases, later should be considered "soon but not immediately.")
for (NSString *p in self.currentNumber) {
dispatch_async(dispatch_get_main_queue(),^{
// this code will be executed "later" by the main loop.
// You may have already moved on to the next thing, and even
// dispatched the next UI update.
// Don't worry; the main queue does things in order.
self.showLabel.text = [NSString stringWithFormat:#"%#", p];
});
sleep(3); // do your heavy lifting here, but keep in mind:
// you're on a background thread.
}
dispatch_async(dispatch_get_main_queue,^{
// this occurs "later," but after other all other UI events queued
// to the main queue.
[self enableMyUI];
});
}
// this line of code will run before work is complete
}
You'll have to write disableMyUI and enableMyUI; make sure they disable everything (including the back button if you're using navigation, the tab bar if you're using a tab bar controller, etc).
Another way around this is to use a NSTimer. However, if you do this you're still doing your work on the main thread. It'll work if you can split your work into predictable, small pieces, but you're better off doing it on a background thread.
One thing to keep in mind: Although you're not likely to run into problems while developing, doing heavy work on the main thread will lead to user crashes. On iOS there is a process that watches if applications are responding to events, such as drawing updates. If an application isn't responding to events in a timely fashion, it will be terminated. So living with the lack of UI updates isn't an option for you; you need to only do time consuming operations from background thread.
See also:
Programming with Objective-C: Working with Blocks
If you want to update the label periodically, don't use sleep. If you call it on the main thread you'll be blocking the UI, which is not very desirable.
Use a NSTimer instead, making it fire every N seconds.
Something like this will do:
- (void)startUpdatingLabel {
[NSTimer scheduledTimerWithTimeInterval:0 target:self selector:#selector(updateLabelWithIndex:) userInfo:#0 repeats:NO];
}
- (void)updateLabel:(NSTimer *)timer {
NSInteger index = [timer.userInfo integerValue];
if (index >= self.currentNumber.count) {
return;
}
self.showLabel.text = [NSString stringWithFormat:#"%#", self.currentNumber[index]];
[NSTimer scheduledTimerWithTimeInterval:3 target:self selector:#selector(updateLabelWithIndex:) userInfo:#(index+1) repeats:NO];
}
Every time updateLabel: is invoked it schedules a new timer which will call it again in 3 seconds. Each time the index value is increased and passed along.

Resources