Objective-C: Activity Indicator during method call - objective-c

I have a method which checks for bluetooth devices ([service scanForSensBoxes]).
The method scanForSensBoxes has a timeout of 5 seconds. During this time I would like to animate an Activity Indicator.
There for I start the spinning in beginn of the attached method, and stop it after the call of "scanForSensBoxes".
Apparently this doesn't work. The result of the code I have at the moment is, that the start and stop animation of the spinner is only processed when the "buttonScanPressed" method is completed instead of during the processing of it. Means the spinner never does animate.
How do I have to change the approach to that, so the spinner will animate during the call of "buttonScanPressed"?
Thanks for any help.
- (IBAction)buttonScanPressed:(id)sender {
[_waitingSpinner startAnimating];
SensBoxServiceLib *service = [[SensBoxServiceLib alloc]init];
NSString *tmp=[NSString stringWithFormat:#"%d",[service scanForSensBoxes]];
[_waitingSpinner stopAnimating];
_sensBoxCount.text=tmp;
}

If scanForSensBoxes is blocking the main thread, the activity indicator won't animate. Assuming this is this case you need to perform the blocking activities within that method on a background queue.
-(IBAction)buttonScanPressed:(id)sender {
// animate activity indicator
[_waitingSpinner startAnimating];
// perform blocking activity in background
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0) {
SensBoxServiceLib *service = [[SensBoxServiceLib alloc] init];
NSString *tmp = [NSString stringWithFormat:#"%d", [service scanForSensBoxes]];
// perform UI updates on main thread
dispatch_async(dispatch_get_main_queue(), {
[_waitingSpinner stopAnimating];
});
});
}

You need to hide it
- (IBAction)buttonScanPressed:(id)sender {
_waitingSpinner.hidden = NO;
[_waitingSpinner startAnimating];
SensBoxServiceLib *service = [[SensBoxServiceLib alloc]init];
NSString *tmp=[NSString stringWithFormat:#"%d",[service scanForSensBoxes]];
[_waitingSpinner stopAnimating];
_waitingSpinner.hidden = YES;
_sensBoxCount.text=tmp;
}

Related

Wait until UI has updated before removing UIActivityIndicator

I am having an issue with an iPad app I am developing. I have the following code:
CFTimeInterval startTime = CFAbsoluteTimeGetCurrent();
[self showSpinnerWithMessage:#"Loading settings..."]; // Shows a UIActivityIndicator on screen
dispatch_async(dispatch_get_global_queue(0, 0), ^
{
//Updating items on screen here
[label1 setText:#"Test1"];
[label2 setText:#"Test3"];
//...
CFTimeInterval difference = CFAbsoluteTimeGetCurrent() - startTime;
if (difference < MINIMUM_INDICATOR_SECONDS)
{
[NSThread sleepForTimeInterval:MINIMUM_INDICATOR_SECONDS - difference];
}
dispatch_async(dispatch_get_main_queue(), ^(void)
{
[self removeSpinner]; // Removes UIActivityIndicator from screen
});
};
The problem is that sometimes the UI takes a while to update, and in these particular instances the spinner (UIActivityIndicator) goes away before the UI has actually updated. I realize this is because the items on screen do not actually update until the next run loop so my question is, how can make my [self removeSpinner] call wait until the UI has updated?
Try putting the UI-updating code in the block with removeSpinner.
dispatch_async(dispatch_get_main_queue(), ^(void)
{
[label1 setText:#"Test1"];
[label2 setText:#"Test3"];
[self removeSpinner]; // Removes UIActivityIndicator from screen
});
Put a [self.view setNeedsDisplay]; just before the [self removeSpinner].

Objective c - adding threading to UIView animation method results in only one animation running

I've written a method, sortAndSlideHandCards(), that moves 6 UIButtons. Each UIButton is moved to the same position. This is done via a for-each loop and the animateWithDuration method being called on each UIButton.
This method is called for a number of players at the same time. Currently the behaviour results in UIButtons from each player moving but only one at a time. No more than one UIButton can move at any time, as if each animation is waiting for whatever animation that is currently running to stop before attempting it's own animation, essentially the code is executed sequentially for each player/UIButton. I hoped threading would help me fix this.
when I added the threading code:
backgroundQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
sortAndSlideHandCardsGroup = dispatch_group_create();
for(Player* player in _playersArray) {
dispatch_group_async(sortAndSlideHandCardsGroup, backgroundQueue, ^(void) {
[player sortAndSlideHandCards];
});
dispatch_group_wait(sortAndSlideHandCardsGroup,DISPATCH_TIME_FOREVER);
I found that only the first UIButton animation is triggered for each player and that the code gets held up in the runloop "while" because "_animationEnd" never gets set as it would appear the second animation never gets going.
I can see the method launching in its own thread
- (void) sortAndSlideHandCards {
NSLog(#"PLAYER:sortAndSlideHandCards");
CGPoint newCenter;
Card* tempCard = nil;
int count = 1;
float duration = 0.2 / _speedMultiplyer;
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
for(Card *card in _handCards) { //move cards in hand to one postion in hand
if(count == 1) {
tempCard = [[Card alloc] init:_screenWidth:_screenHeight :[card getNumber] :[card getCardWeight] :[card getSuit] :[card getIsSpecial]];
[tempCard setImageSrc: _playerNumber :!_isPlayerOnPhone :count : true :_view: _isAI: [_handCards count]];
newCenter = [tempCard getButton].center;
}
_animationStillRunning = true;
if(![[DealViewController getCardsInPlayArray] containsObject:card] ) {
[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionLayoutSubviews animations:^{[card getButton].center = newCenter;} completion:^(BOOL finished){[self animationEnd];}];
while (_animationStillRunning){ //endAnimation will set _animationStillRunning to false when called
//stuck in here after first UIButton when threading code is in play
[runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
} //endAnimation will set _animationStillRunning to false when called
}
count++;
}
}
When i comment out the threading code each UIButton (Card) will animate one after another.
With the threading code is in play the first UIButton will animate but during the second run through the for-loop, the code will be stuck in the while-loop, waiting for the animation to end. I'm guessing the second animation doesn't even start.
I also tried this for the threading code:
[player performSelectorInBackground:#selector(sortAndSlideHandCards) withObject:nil];
Same outcome
Anyone have any ideas why animateWithDuration doesn't like getting called in a loop when in a thread other than the main one?
You should just be able to kick off the animations you want from whatever UI action is performed. The UIView animateWith... methods return immediately, so you don't need to worry about waiting for them to complete.
If you have an unknown number of animations to kick off sequentially, use the delay parameter. Pseudocode:
NSTimeInterval delay = 0;
NSTimeInterval duration = 0.25;
for (UIView *view in viewsToAnimate)
{
[UIView animateWithDuration:duration delay:delay animations:^{ ... animations ...}];
delay += duration;
}
This will increase the delay for each successive animation, so it starts at the end of the previous one.

Program crashes when segueing back and dispatch queue has animation method

Ok so i have a uitableview and when an item is selected it segues to a new view controller to show an image (inside of a uiscrollview). The image begins downloading in a dispatch_queue from prepareforsegue. To be clear I am doing all UI updates in the main queue. The problem is that if i hit the back button quick enough then my program crashes with an exc_bad_address. I think the problem has to deal with zoomToRect animated:YES because when i set animated to NO i cant get it to crash. Plus the call stack below deals with animation. What is the best way to go about fixing this and is there a better way to get what i need done?
Also when debugging the problem print 'Block completed' and then crashes shortly after.
stack trace
Here is the method called. It is called in a setter of the destination controller in prepareForSegue.
-(void) updateDisplay {
dispatch_queue_t queue = dispatch_queue_create("Load Flickr Photo", NULL);
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:spinner];
[spinner startAnimating];
dispatch_async(queue, ^{
UIImage *image = [FlickrFetcher imageForPhoto:self.currentPhoto format:FlickrPhotoFormatLarge];
dispatch_async(dispatch_get_main_queue(), ^{
self.navigationItem.rightBarButtonItem = nil;
self.imageView.image = image;
self.title = [FlickrFetcher titleForPhoto:self.currentPhoto];
CGAffineTransform transform = CGAffineTransformMakeScale(1.0, 1.0);
self.imageView.transform = transform;
self.imageView.frame = CGRectMake(0, 0, self.imageView.image.size.width, self.imageView.image.size.height);
self.scrollView.maximumZoomScale = 4.0;
self.scrollView.minimumZoomScale = .2;
self.scrollView.zoomScale = 1;
self.scrollView.contentSize = self.imageView.bounds.size;
//i think problem is here
[self.scrollView zoomToRect:self.imageView.frame animated:YES];
NSLog(#"Block completed");
});
});
}
I think a potential problem could be that while your block retains your self the view controller while its active in queue...
If you send -zoomToRect:animated:NO, the block will still be active and blocking on the call which ensures that all the objects are still valid in memory.
If you send -zoomToRect:animated:YES, the block will exit potentially generating a race condition where your view controller would be released since storyboards will also release your view controller when you go back in a segue leaving it with an effective retain count of zero.

need help in start with GCD one method - many calls

i want to learn GCD on my own app-project and i've got problem with it. i think is easy but i don't know how solve it. so i've got 2 methods:
- (void)viewDidLoad
{
[super viewDidLoad];
queue = dispatch_queue_create("com.fe.effect.load", NULL);
__block UIImage *th;
dispatch_sync(queue, ^{
th = [self doThumbWithImage:thumbnail andTag:1];
UIButton *btn1 = [self buttonWithFrame:CGRectMake(0, 0, 200, 100) andBackgroundImage:th andSelector:#selector(doEffect:) andTag:1];
[self.view addSubview:btn1];
});
dispatch_sync(queue, ^{
th = [self doThumbWithImage:thumbnail andTag:2];
UIButton *btn2 = [self buttonWithFrame:CGRectMake(0, 100, 200, 100) andBackgroundImage:th andSelector:#selector(doEffect:) andTag:2];
[self.view addSubview:btn2];
});
}
and 2 method:
-(UIImage *)doThumbWithImage:(UIImage *)_img andTag:(int)_tag {
ImageProcessing *ip = [[ImageProcessing alloc] initWithImage:_img andTag:_tag];
[ip doImageProcessing];
_img = ip.image;
ip = nil;
return _img;
}
And in doThumbWithImage:andTag: i've got ImageProcessing class where i do something with my thumbnail object.
when i use dispatch_sync, time for this operation is the same as without GCD. When i use dispatch_async, i can't see thumbanils of buttons. I know something is wrong, but i don't know what.
How can i repair this?
thank you for help.
You probably need to do the addSubview in the main thread. You can nest the dispatch_async calls to handle that.

How to use UIActivityIndicatorView on custom UIButton?

I want to use UIActivityIndicatorView on my custom UIButton.
Here is my code:
if (sender.tag == 1)
{
// Start animating
activityIndicator.hidden = NO;
[activityIndicator startAnimating];
// Check if the network is available
if ([self reachable]) {
// Stop animating
activityIndicator.hidden = YES;
[activityIndicator stopAnimating];
}
}
What I want to do here is:
Once the user touch the button, I want to start the ActivityIndicatior while Reachable checking the network availability. Once it done pass it to the next view.
Update
UIActivityIndicator is on top of my custom UIButton. It build successfully, but ActivityIndicator is not showing when I touch the button.
I'm sure you had your answer since then.
After 4 years, language has changed to Swift, but I had the same problem : UIActivityIndicatorView is not showing when I add it through :
self.myButton.addSubview(self.myIndicatorView)
I had to climb a level up :
self.myButton.superview!.addSubview(self.myIndicatorView)
Assuming everything else is correct in your project, the problem here is that you're showing and hiding an activity indicator in the same event loop, without giving a pause to draw it. Let me explain:
If you have the code:
UIView* view = someView;
view.backgroundColor = [UIColor redColor];
// various synchronous operations
view.backgroundColor = [UIColor yellowColor];
The view will only ever have a background color of yellow.
To answer your question, you probably want to animate the spinner, do some asynchronous operation, then stop the animation. The key being to not stall on the main event loop while your asynchronous task is running. If your comfortable with blocks it would look something like this:
if (sender.tag == 1) {
// Start animating
activityIndicator.hidden = NO;
[activityIndicator startAnimating];
// Check if the network is available
[self checkReachableWithCallback:^{
// Stop animating
activityIndicator.hidden = YES;
[activityIndicator stopAnimating];
}];
}