iOS: How to get background thread to redraw view on main thread - ios

My application will query a database 50 times and will then react accordingly by adding the right UIImageView to another UIImageView, which I then want the application to display immediately at each loop.
Alas, after many nights I have failed to get it to work. The application will not immediately display the UIImageView. Having said that, when I zoom in or zoom out during mid loop the UIImageViews will appear! I must be missing something...
So far every code works except the last part [UIScrollView performSelector....
Please help and thank you in advance.
UIScrollView *firstView;
UIImageView *secondView;
UIImageView *thirdView;
NSOperationQueue *queue;
NSInvocationOperation *operation;
- (void)viewDidAppear:(BOOL)animated
{
queue = [NSOperationQueue new];
operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(getData) object:nil];
[queue addOperation:operation];
}
- (void) getData
{
for (i=0 ; i < 50 ; i++)
{
//All the codes to facilitate XML parsing here
[nsXMLParser setDelegate:parser];
[BOOL success = [nsXMLParser parse];
if (success)
{
if ([parser.currentValue isEqualToString:#"G"])
thirdView.image = greenTick.jpg;
[secondView addSubview:thirdView];
}
else
{
NSLog(#"Error parsing document!");
}
}
[thirdView performSelectorOnMainThread:#selector(setNeedsDisplay) withObject:nil waitUntilDone: YES];
}

I discovered the solution and truly hope this will help someone...
if (success)
{
if ([parser.currentValue isEqualToString:#"G"])
// Make the changes here - start
dispatch_queue_t main_queue = dispatch_get_main_queue();
dispatch_async(main_queue, ^{
thirdView.image = greenTick.jpg;
[secondView addSubview:thirdView];
});
// Make the changes here - end
}
else
{
NSLog(#"Error parsing document!");
}
}
// Remove performSelectorOnMainThread

Related

Completion Block of concurrent NSOperation not being called everytime

I would like to run a concurrent NSOperation. For this reason, I have extended the NSOperation class and overridden the start and finish methods. It looks something like this:
#import "AVFrameConversionOP.h"
#implementation AVFrameConversionOP //extends NSOperation
- (void)start
{
[self willChangeValueForKey:#"isFinished"];
_isFinished = NO;
[self didChangeValueForKey:#"isFinished"];
// Begin
[self willChangeValueForKey:#"isExecuting"];
_isExecuting = YES;
[self didChangeValueForKey:#"isExecuting"];
if (_isCancelled == YES) {
NSLog(#"** OPERATION CANCELED **");
[self willChangeValueForKey:#"isFinished"];
_isFinished = YES;
[self didChangeValueForKey:#"isFinished"];
return;
}
[self performTask];
[self finish];
}
- (void)finish
{
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:#"isExecuting"];
[self didChangeValueForKey:#"isFinished"];
NSLog(#"operationfinished, _isFinished is %d", _isFinished);
if (_isCancelled == YES)
{
NSLog(#"** OPERATION CANCELED **");
}
}
- (void)performTask
{
//The task is performed here
sleep(0.1); //This is just for demonstration purposes
}
#end
I have defined the completion block for this NSOperation in the main view controller in the viewDidLoad method:
- (void)viewDidLoad {
NSLog(#"viewDidLoad!");
[super viewDidLoad];
static int counter = 0;
_frameConvOP = [[AVFrameConversionOP alloc] init]; //Initializing the modified NSOperation class
__weak typeof(self) weakSelf = self;
_frameConvOP.completionBlock = ^ {
counter++;
//NSLogs don't seem to work here
/*if(counter>1){
exit(-1); //Never happens because counter only ever becomes =1
}*/
};
[[VideoPreviewer instance] setNSOperation:_frameConvOP]; //This is where I send the NSOperation object to the class where I'm using it
}
I'm starting the NSOperation by doing [frameConvOP start] inside of a loop. It is called continuously every time a frame is decoded, so that is to say it is called very often. I can see that the finish and start methods of the NSOperation are being called frequently through the logs. And the functions inside these methods are also being correctly called. However, the NSLogs inside the completion blocked aren't logged. I put an exit(-1) inside the completion block and the app crashed, but the NSLogs never show up. So I put a static counter in there and it only increments to 1. So the completion block is only being called once.
Can anyone explain why the completion block is being called only once? Is it because of the way I'm (incorrectly) handling the KVO for _isFinished and _isExecuting? The completion block should be called when _isFinished is set to YES and as far as I can see, I am doing that in the finish block. And the finish block is being called quite frequently as it should be.
Any help, guidance or direction is appreciated. Please let me know if I have made any errors or if I have been less-than-clear in the post.
Thanks,
From the NSOperation class reference:
An operation object is a single-shot object—that is, it executes its task once and cannot be used to execute it again.
You are not allowed to invoke -start multiple times on a single operation object.
Another thing, in your -finish method, you need to properly nest the -willChange... and -didChange... calls. That is, you need to call -didChange... in the reverse order of the -willChange... calls:
[self willChangeValueForKey:#"isExecuting"];
[self willChangeValueForKey:#"isFinished"];
_isExecuting = NO;
_isFinished = YES;
[self didChangeValueForKey:#"isFinished"];
[self didChangeValueForKey:#"isExecuting"];

iOS : using activity indicator in loading for second view controller

I have two view controllers; in the second view, a bunch of data are processed which takes pretty much time, while in the first view, there is a button navigating to the second. I want to display an activity indicator for the process in the second view right after the button clicked. But initialising UIActivityIndicatorView in the second view doesn't seem to work. Nothing showed up when the button was clicked, and the app was stuck in the first view when data being processed.
Below are the code I wrote in viewDidLoad in the second view controller.
_activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[_activityIndicator setCenter:CGPointMake(SCREEN_WIDTH/2, SCREEN_HEIGHT/2)];
[self.view addSubview:_activityIndicator];
...............
[_activityIndicator startAnimating];
...............
// data processing
[_activityIndicator stopAnimating];
Anyone know how to solve this?
========EDIT=========
Thank you so much for the advices. Now I've tried using NSThread,but the spinner showed up pretty late. Here are the code I wrote in the first view controller.
- (void)viewDidLoad
{
[super viewDidLoad];
// activity indicator
_activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[_activityIndicator setCenter:CGPointMake(SCREEN_WIDTH/4, SCREEN_HEIGHT/4)];
[self.view addSubview:_activityIndicator];
}
- (IBAction)startButtonClicked:(id)sender
{
[NSThread detachNewThreadSelector:#selector(threadStartAnimating:) toTarget:self withObject:nil];
}
-(void)threadStartAnimating:(id)data
{
NSLog(#"start");
[_activityIndicator startAnimating];
[self performSelectorOnMainThread:#selector(threadStopAnimating:) withObject:nil waitUntilDone:YES];
}
-(void)threadStopAnimating:(id)data
{
NSLog(#"stop");
[_activityIndicator stopAnimating];
}
The spinner appeared around 2 sec after NSLog(#"start"); being executed and showed up in a very short period. I linked - (IBAction)startButtonClicked:(id)sender with the button that navigated to the second view.
Is there any better way to put [_activityIndicator startAnimating];?
Maybe you can try to solve this issue with Grand Central Dispatch (threads).
In you second VC, try this code:
- (void) viewDidLoad{
[super viewDidLoad];
// Main thread
_activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[_activityIndicator setCenter:CGPointMake(SCREEN_WIDTH/4, SCREEN_HEIGHT/4)];
[self.view addSubview:_activityIndicator];
[_activityIndicator startAnimating];
// create a queue
dispatch_queue_t queue = dispatch_queue_create("data_process", 0);
// send a block to the queue - Not in Main thread
dispatch_async(queue, ^{
// data processing
.................
// Interaction with User Interface - Main thread
dispatch_async(dispatch_get_main_queue(), ^{
[_activityIndicator stopAnimating];
_activityIndicator.hidden = YES;
});
});
}
I hope this helps.

Implementing smooth loader in a cocos2d-iphone app

I want to implement startup loader in my app. It should be like this: after startup splash screen, user will watch simple animataion and in meanwhile app preload sound effects, background music, sprite images, spritesheets and so on. Current implementation:
- (id)init {
if((self = [super init])) {
// Some other setup ...
CGRect rect;
rect = waveSprite.textureRect;
waveInitialTexRectOrigin = rect.origin;
rect.size.width = 91;
waveSprite.textureRect = rect;
assetFilenames = [[NSArray alloc] initWithObjects:
// images
#"background.png",
// spritesheets
#"sprites.plist",
// fonts
#"main.png",
// sound effects
#"button.wav",
nil];
assetCounter = 0;
[self loadAsset];
}
return self;
}
- (void)update:(ccTime)dt {
CGRect rect;
rect = waveSprite.textureRect;
rect.origin.x += dt*kLoaderWaveSpeed;
while (rect.origin.x > waveInitialTexRectOrigin.x + kLoaderWavePeriod) {
rect.origin.x -= kLoaderWavePeriod;
}
waveSprite.textureRect = rect;
}
#pragma mark Private
- (void)loadAsset {
// CCLOG(#"loadAsset");
NSString *filename = [assetFilenames objectAtIndex:assetCounter];
CCLOG(#"loading %#", filename);
NSString *ext = [filename pathExtension];
if ([ext doesMatchRegStringExp:#"[png|jpg]"]) {
[[CCTextureCache sharedTextureCache] addImage:filename];
} else if ([ext isEqualToString:#"plist"]) {
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:filename];
} else if ([ext doesMatchRegStringExp:#"[caf|wav|mp3]"]) {
[[SimpleAudioEngine sharedEngine] preloadEffect:filename];
}
assetCounter++;
if (assetCounter < [assetFilenames count]) {
[self performSelector:#selector(loadAsset) withObject:self afterDelay:0.1f];
} else {
[self performSelector:#selector(loadingComplete) withObject:self afterDelay:0.2];
}
But the animation is SO abrupt.
UPD I've already tried
[self performSelectorInBackground: withObject:]
but it didn't seem to work (hung on loading first asset). Maybe I should try better in this direction.
UPD2 Smooth = not abrupt, without delays and flicker. fps doesn't matter, 20 fps quite OK
I'm not experienced with CC2D, but here's what I used in my ViewController to show a smooth animated loading indicator:
//Set up and run your loading scene/indicator here
dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
//Load your scene
dispatch_async( dispatch_get_main_queue(), ^{
//This fires when the loading is complete
//Show menu and destroy the loader scene/indicator here
});
});
I guess it's worth a try.
You should not be using performSelectorInBackground anymore, it is not deprecated but it might be soon, with GCD now in the picture.
What I would try is run the animation in the main thread since it is the only one that can do UI updates. And spawn one or more threads using GCD to load all your assets.
I would use concurrent dispatch queues to load everything in parallel and simply dismiss the animation when this is done.
You can get complete information about concurrent dispatch queues here:
Concurrency Programming Guide
Hope that helps.
That is because you are doing everything on the same thread. Use a separate thread for the loader.
Hope this helps.
Cheers!
EDIT : Take a look at this.

Asynchronously loading sound resources in viewDidLoad crashes

All,
I am attempting to load a set of sounds asynchronously when I load a UIViewController. At about the same time, I am (occasionally) also placing a UIView on the top of my ViewController's hierarchy to present a help overlay. When I do this, the app crashes with a bad exec. If the view is not added, the app does not crash. My ViewController looks something like this:
- (void)viewDidLoad
{
[super viewDidLoad];
__soundHelper = [[SoundHelper alloc] initWithSounds];
// Other stuff
}
- (void)viewDidAppear:(BOOL)animated
{
// ****** Set up the Help Screen
self.coachMarkView = [[FHSCoachMarkView alloc] initWithImageName:#"help_GradingVC"
coveringView:self.view
withOpacity:0.9
dismissOnTap:YES
withDelegate:self];
[self.coachMarkView showCoachMarkView];
[super viewDidAppear:animated];
}
The main asynchronous loading method of SoundHelper (called from 'initWithSounds') looks like this:
// Helper method that loads sounds as needed
- (void)loadSounds {
// Run this loading code in a separate thread
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
NSBlockOperation *loadSoundsOp = [NSBlockOperation blockOperationWithBlock:^{
// Find all sound files (*.caf) in resource bundles
__soundCache = [[NSMutableDictionary alloc]initWithCapacity:0];
NSString * sndFileName;
NSArray *soundFiles = [[NSBundle mainBundle] pathsForResourcesOfType:STR_SOUND_EXT inDirectory:nil];
// Loop through all of the sounds found
for (NSString * soundFileNamePath in soundFiles) {
// Add the sound file to the dictionary
sndFileName = [[soundFileNamePath lastPathComponent] lowercaseString];
[__soundCache setObject:[self soundPath:soundFileNamePath] forKey:sndFileName];
}
// From: https://stackoverflow.com/questions/7334647/nsoperationqueue-and-uitableview-release-is-crashing-my-app
[self performSelectorOnMainThread:#selector(description) withObject:nil waitUntilDone:NO];
}];
[operationQueue addOperation:loadSoundsOp];
}
The crash seems to occur when the block exits. The init of FHSCoachMarkView looks like this:
- (FHSCoachMarkView *)initWithImageName:(NSString *) imageName
coveringView:(UIView *) view
withOpacity:(CGFloat) opacity
dismissOnTap:(BOOL) dismissOnTap
withDelegate:(id<FHSCoachMarkViewDelegate>) delegateID
{
// Reset Viewed Coach Marks if User Setting is set to show them
[self resetSettings];
__coveringView = view;
self = [super initWithFrame:__coveringView.frame];
if (self) {
// Record the string for later reference
__coachMarkName = [NSString stringWithString:imageName];
self.delegate = delegateID;
UIImage * image = [[UIImage alloc] initWithContentsOfFile:[[NSBundle mainBundle] pathForResource:imageName ofType:#"png"]];
// ****** Configure the View Hierarchy
UIImageView *imgView = [[UIImageView alloc] initWithImage:image];
[self addSubview:imgView];
[__coveringView.superview insertSubview:self aboveSubview:__coveringView];
// ****** Configure the View Hierarchy with the proper opacity
__coachMarkViewOpacity = opacity;
self.hidden = YES;
self.opaque = NO;
self.alpha = __coachMarkViewOpacity;
imgView.hidden = NO;
imgView.opaque = NO;
imgView.alpha = __coachMarkViewOpacity;
// ****** Configure whether the coachMark can be dismissed when it's body is tapped
__dismissOnTap = dismissOnTap;
// If it is dismissable, set up a gesture recognizer
if (__dismissOnTap) {
UITapGestureRecognizer * tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self
action:#selector(coachMarkWasTapped:)];
[self addGestureRecognizer:tapGesture];
}
}
return self;
}
I have tried invoking the asynchronous block using both NSBlockOperation and dispatch_async and both have had the same results. Additionally, I've removed the aysnch call altogether and loaded the sounds on the main thread. That works fine. I also tried the solution suggested by #Jason in: NSOperationQueue and UITableView release is crashing my app but the same thing happened there too.
Is this actually an issue with the view being added in FHSCoachMarkView, or is it possibly related to the fact that both access mainBundle? I'm a bit new to asynch coding in iOS, so I'm at a bit of a loss. Any help would be appreciated!
Thanks,
Scott
I figured this out: I had set up a listener on the SoundHelper object (NSUserDefaultsDidChangeNotification) that listened for when NSUserDefaults were changed, and loaded the sounds if the user defaults indicated so. The FHSCoachMarkView was also making changes to NSUserDefaults. In the SoundHelper, I was not properly checking which defaults were being changed, so the asynch sound loading method was being called each time a change was made. So multiple threads were attempting to modify the __soundCache instance variable. it didn't seem to like that.
Question: Is this the correct way to answer your own question? Or should I have just added a comment to the question it self?
Thanks.

UPdate UIProgressView within for loop

I've already searched for the problem, and the only hint was the performSelectorOnMainThread solution. I did that, but the view is not updating while the for loop is running. After the loop the progress value is displayed correctly – but that's not the point of an "Progress" View =)
The for loop is within a controller. I call a method to set the progress value:
CGFloat pr = 0.5; // this value is calculated
[self performSelectorOnMainThread:#selector(loadingProgress:) withObject:[NSNumber numberWithFloat:pr] waitUntilDone:NO];
And this is the loading progress method
-(void)loadingProgress:(NSNumber *)nProgress{
NSLog(#"Set Progress to: %#", nProgress);
[[self progress] setProgress:[nProgress floatValue]];
}
I also tried to put the method with the for loop into a background thread by using this at the top of the method:
if([NSThread isMainThread]) {
[self performSelectorInBackground:#selector(importData) withObject:nil];
return;
}
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
Maybe somebody can help me with this.
CGFloat pr = 0.5; // this value is calculated
[self performSelectorInBackground:#selector(loadingProgress:) withObject:[NSNumber numberWithFloat:pr]];
try this to set the progress value

Resources