I copied the code to show the activity indicator from this post. When I called hideActivityViewer nothing happens, and the activityView is still there, and the activityIndicator is still spinning. It is as if hideActivityViewer does not do anything to the activityView at all.
Here is the code I have modified
-(void)showActivityViewer
{
WBAppDelegate *delegate = [[UIApplication sharedApplication] delegate];
UIWindow *window = delegate.window;
_activityView = [[UIView alloc] initWithFrame: CGRectMake(0, 0, window.bounds.size.width, window.bounds.size.height)];
_activityView.backgroundColor = [UIColor blackColor];
_activityView.alpha = 0.5;
UIActivityIndicatorView *activityWheel = [[UIActivityIndicatorView alloc] initWithFrame: CGRectMake(window.bounds.size.width / 2 - 12, window.bounds.size.height / 2 - 12, 24, 24)];
activityWheel.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhite;
activityWheel.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin |
UIViewAutoresizingFlexibleRightMargin |
UIViewAutoresizingFlexibleTopMargin |
UIViewAutoresizingFlexibleBottomMargin);
[_activityView addSubview:activityWheel];
[window addSubview: _activityView];
[[[_activityView subviews] objectAtIndex:0] startAnimating];
}
-(void)hideActivityViewer
{
NSLog(#"Profile: hiding Activity Viewer");
[[[_activityView subviews] objectAtIndex:0] stopAnimating];
[_activityView removeFromSuperview];
_activityView = nil;
}
Update:
I'm using the KVO to detect for change in variable and use it to call showActivityViewer.
Turns out, showActivityViewer was called more than once, as a result there are multiple activityViewer on the screen, so when I remove one, the other is still there and I have no to reference to it. I solved this by checking if the activityView already exist, and if so don't create a new one.
My comment as an answer:
It could be because activityView becomes nil at some point before a call to hideActivityViewer. Lets say u call showActivityViewer two times consecutively, you have two activityViews exactly on top of each other and the first one will never be hidden if you call hideActivityViewer. Even after matching number of hideActivityViewer calls, or more.
You may be calling ur method "hideActivityViewer" from asynchronous or thread completion blocks.
If so, call ur method on mainthread i.e
[self performSelectorOnMainThread:#selector(hideActivityViewer) withObject:nil waitUntilDone:NO];
Related
When loading my UIViewController, I basically put a spinner in the middle of the page until the content loads, then come back on the main thread to add the subviews :
- (void)viewDidLoad {
[super viewDidLoad];
UIActivityIndicatorView *aiv_loading = ... // etc
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Load content
NSString *s_checkout = [[BRNetwork sharedNetwork] getCheckoutInstructionsForLocation:self.locBooking.location];
UIView *v_invoice_content = [[BRNetwork sharedNetwork] invoiceViewForBooking:locBooking.objectId];
// Display the content
dispatch_sync(dispatch_get_main_queue(), ^{
if (s_checkout && v_invoice_content) {
[aiv_loading removeFromSuperview];
[self showContentWithText:s_checkout AndInvoice:v_invoice_content];
} else {
NSLog(#"No data received!"); // is thankfully not called
}
});
});
}
- (void) showContentWithText:(NSString *)s_checkout AndInvoice:(UIView *)v_invoice {
[self.view addSubview:[self checkoutTextWithText:s_checkout]]; // Most of the time displayed text
[self.view addSubview:[self completeCheckout]]; // always Displayed UIButton
[self.view addSubview:[self divider]]; // always displayed UIImageView
// Summary title
UILabel *l_summary = [[UILabel alloc] initWithFrame:CGRectMake(0, [self divider].frame.origin.y + 6 + 10, self.view.bounds.size.width, 20)];
l_summary.text = NSLocalizedString(#"Summary", nil);
[self.view addSubview:l_summary];
CGRect totalRect = CGRectMake([self divider].frame.origin.x, [self divider].frame.origin.y + 6 + 30, self.view.bounds.size.width - [self divider].frame.origin.x, 90);
_v_invoice = v_invoice;
_v_invoice.frame = totalRect;
[self.view addSubview:[self v_invoiceWithData:v_invoice]]; // THIS Pretty much never displayed
UITextView *l_invoice = [[UITextView alloc] initWithFrame:CGRectMake(0, _v_invoice.frame.origin.y + _v_invoice.frame.size.height + offset, 320.0, 50)];
l_invoice.text = NSLocalizedString(#"summary_emailed", nil);
[self.view addSubview:l_invoice]; // Always displayed
}
However, not all the content is displayed. The invoice is never there at first, but gets displayed after a couple of minutes. The other async-created string, s_content is sometimes not displayed.
This seems to be random with the content creation. The end result is pretty neat, but not reliable for a production version.
I used the undocumented [self.view recursiveDescription] to check if everything was there, and even if I don't see it, they are all there with what seems to be correct frames.
Any pointers?
- layoutSubviews did not help!
- putting a background color to the invoice view is showing the background color
I suspect this line is your problem:
UIView *v_invoice_content = [[BRNetwork sharedNetwork] invoiceViewForBooking:locBooking.objectId];
As you are calling this in a background dispatch queue. Any work involving UIKit should be done on the main queue/thread. Either move that into the main thread block, or if building the view is dependent on data from a network call, change your invoiceViewForBooking method to return the data first, and build your view in the main thread with that data.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Load content
NSString *s_checkout = [[BRNetwork sharedNetwork] getCheckoutInstructionsForLocation:self.locBooking.location];
id someData = [[BRNetwork sharedNetwork] invoiceDataForBooking:locBooking.objectId];
// Display the content
dispatch_async(dispatch_get_main_queue(), ^{
UIView *v_invoice_content = [invoiceViewWithData:someData];
});
});
I'd also suggest using dispatch_async instead of dispatch_sync on the main queue.
Solved! I ended up removing the dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
To only leave the async block on the main queue:
// Display the content from a new async block on the main queue.
dispatch_async(dispatch_get_main_queue(), ^{
[aiv_loading removeFromSuperview];
NSString *s_checkout = [[BRNetwork sharedNetwork] getCheckoutInstructionsForLocation:self.locBooking.location];
[self showContentWithText:s_checkout AndInvoice:[[BRNetwork sharedNetwork] invoiceViewForBooking:locBooking.objectId]];
});
which did the trick, viewcontroller's view can appear without waiting for the view to load the remote data!
You should put your code in viewDidAppear instead of viewDidLoad. Whatever is related to display (even launching async blocks) should always be in viewWillAppear or viewDidAppear.
Also, I recommend you to use dispatch_async(dispatch_get_main_queue(), ^{}) instead of your dispatch_sync since you still want your block to be performed async (but on main thread).
Dont forget to call super methods in your viewDidAppear.
I have one problem.
I have table view and when I click on cell I load data from server. Because this could take some time I want to show activity indicator view.
-(void)startSpiner{
CGRect screenRect = [[UIScreen mainScreen] bounds];
UIView * background = [[UIView alloc]initWithFrame:screenRect];
background.backgroundColor = [UIColor blackColor];
background.alpha = 0.7;
background.tag = 1000;
UIActivityIndicatorView * spiner = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
spiner.frame = CGRectMake(0, 0, 50, 50);
[spiner setCenter:background.center];
[spiner startAnimating];
[background addSubview:spiner];
[background setNeedsDisplay];
[self.view addSubview:background];
}
This works fine, but when I put this in
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[self startSpiner];
Server *server = [[Server alloc]init];
self.allItems = [server getDataGLN:self.object.gln type:1];
}
I see that UIActivityIndicatorView is shown after it get data from server.
How to force main view to update immediately?
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
[self startSpiner];
////If your server object is performing some network handling task. dispatch a
////background task.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
Server *server = [[Server alloc]init];
self.allItems = [server getDataGLN:self.object.gln type:1];
});
}
The UI normally isn't updated until your code returns control to the run loop. Your getDataGLN:type: method isn't returning until it gets the data from the server. Thus the UI cannot be updated until you've got the data from the server.
Don't do that. Load your data on a background thread and return control of the main thread to the run loop immediately. You will find lots of help in the Concurrency Programming Guide and in Apple's developer videos.
Why do you call setNeedsDisplay?
Anyway, The right way to do it, is not creating all the UI when you want to show loading indicator.
Do it in advance - in ViewDidLoad for example, and just hide the background view.
When you want to show it, just turn it's hidden property to NO.
In my app, i'm getting the data from database. The data that I get from database is very large like 20000 records. So, I want to show an activity indicator to indicate that data is loading. But activity indicator is not showed when I run the app.
I'm sending code for this. Please help me out.
activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
activityIndicator.frame = CGRectMake(100,100, 20, 20);
[self.view addSubview:activityIndicator];
[activityIndicator startAnimating];
// Code to get data from database
[activityIndicator stopAnimating];
First, you don't need to set the frame for the activiyIndicator. You can just set the centre...
activityIndicator.center = self.view.center; (or soemthing like this).
Second, how are you retrieving the data? Are you doing it all on the main thread? Or are you doing it asynchronously?
::EDIT::
Ah it appears you are doing this...
[actInd startAnimating];
[self getData]; //this is asynchronous
[actInd stopAnimating];
if this is the case then the stopAnimating will get called almost immediately.
What you need is something like this...
[actInd startAnimating];
[self getData]; //this is asynchronous
Then in getData...
- (void)getData
{
//get all your data and process it....
[actInd stopAnimating];
}
This way the stop animating will only get called after doing all the data work.
If that doesn't work try removing the stopAnimating altogether to check it is actually animating in the first place.
#Fogmeister is wright about setting the frame of activityIndicator.
and for showing the activityIndicator just create one function of the code shown in your question and call it using the performSelector:withObject:afterDelay: with delay of 0.001 and the activityIndicator will shown
Happy Coding :)
Do this:
activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
activityIndicator.center = self.view.center;
[self.view addSubview:activityIndicator];
[NSThread detachNewThreadSelector:#selector(startActivityIndicator) toTarget:self withObject:nil];
Add this method:
-(void)startActivityIndicator
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
[activityIndicator startAnimating];
[pool release];
}
Do changes:
- (void)getData
{
//get all your data from database.
[activityIndicator stopAnimating];
}
So we are making a game that has a normal UIView title screen, and when the user presses the "Start Game" button, we attempt to put up the loading screen, remove the title view, we initialize the game view and add it to the superview (window in our case) underneath the loading screen.
Unfortunately, the end result is that the UI is blocked for a few seconds upon touching the "Start Game" button, and our loading screen blips on the screen for a millisecond before being kicked off for the loaded game view. We even added logging, and the logging messages appear in console while the UI is blocked.
Here is the method in question:
-(void)switchToGame{
NSLog(#"adding loading");
loadingScreen = [[UIImageView alloc] initWithImage:
[UIImage imageNamed:#"loading_screen.jpg"]];
[self.window addSubview:loadingScreen];
NSLog(#"removing titlescreen");
[titleView.view removeFromSuperview];
self.titleView = nil;
if(self.gameView == nil){
gmViewController *g = [[gmViewController alloc]
initWithNibName:#"gmViewController"
bundle:nil];
self.gameView = g;
[gameView setIsLoading:YES];
self.gameView.parent = self;
[g release];
}
NSLog(#"inserting gameview");
[self.window insertSubview:gameView.view belowSubview:loadingScreen];
[self.window setRootViewController:gameView];
[self setGameIsActive:YES];
[gameView startAnimation];
//remove the loading screen
NSLog(#"killing loading");
[loadingScreen removeFromSuperview];
[loadingScreen release];
[gameView setIsLoading:NO];
}
Forgot about this question.
We solved it by moving the loading screen add to it's own function:
- (void)addLoadingScreen
{
loadingScreen = [[UIImageView alloc] initWithImage:
[UIImage imageNamed:#"loading_screen.jpg"]];
[self.window addSubview:loadingScreen];
}
And then executing it on its own thread:
[NSThread detachNewThreadSelector:#selector(addLoadingScreen)];
I am presenting a modal view controller which contains a UIScrollView with a bunch of images. When I dismiss my modal view controller I can see in my Allocations (instruments) that memory assigned to the images is hanging around but not causing leaks.
Causing me some confusion and any help would be great. Here is what I have...
UIViewController - DollViewController: This is my main view controller that I am presenting my modal over. Here is the code -
-(IBAction)openDollCloset {
NSLog(#"Open Closet");
[self playSound:#"soundMenuClick"];
ClosetViewController *closet = [[ClosetViewController alloc] initWithNibName:#"ClosetViewController" bundle:nil];
closet.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal;
[self setModalPresentationStyle:UIModalPresentationFullScreen];
[self presentModalViewController: closet animated: YES];
closet.delegate = self;
closet.addToCloset = NO;
[closet setCurrentDoll:myDoll];
[closet openCloset];
[closet release];
[self closeSubmenu];
}
I create my scrollview manually to hold the outfit images.
The object closetScroller is defined with #property (nonatomic, retain) UIScrollView *closetScroller; with a release in dealloc.
- (void)setScrollerValues {
if (closetScroller == nil)
{
self.closetScroller = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 320, 400)];
[self.closetScroller setDecelerationRate:UIScrollViewDecelerationRateFast];
[self.closetScroller setDelegate:self];
[self.closetScroller setPagingEnabled:YES];
[self.closetScroller setShowsHorizontalScrollIndicator:NO];
[self.view addSubview:closetScroller];
}
[self.closetScroller setContentSize:CGSizeMake(outfitCount * 320, 400)];
}
After creating the scrollView I add the images to the scroller. I believe I am releasing everything right. I am creating the images with NSData objects and releasing them.
- (void)addOutfitsToScroller {
// Clear out any old images in case we just deleted an outfit
if ([[closetScroller subviews] count] > 0)
{
CATransition *fade = [CATransition animation];
[fade setDelegate:self];
[fade setType:kCATransitionReveal];
[fade setSubtype:kCATransitionFromRight];
[fade setDuration:0.40f];
[[self.closetScroller subviews] makeObjectsPerformSelector: #selector(removeFromSuperview)];
[[closetScroller layer] addAnimation:fade forKey:#"FadeButtons"];
}
// Set the pageControl to same number of buttons
[pageControl setNumberOfPages:outfitCount];
// Load the outfit image and add it to the scroller
for (int i = 0; i < outfitCount; i++)
{
NSArray *savedDataArray = [[NSArray alloc] initWithArray:[closetItemsArray objectAtIndex:i]];
UIImageView *v = [[UIImageView alloc] initWithFrame:CGRectMake(320*i, 0, 320, 400)];
[v setUserInteractionEnabled:NO];
[v setAlpha:0.40f];
NSData *imageData = [[NSData alloc] initWithContentsOfFile:[savedDataArray objectAtIndex:1]];
UIImage *outfitImage = [[UIImage alloc] initWithData:imageData];
UIImageView *closetImage = [[UIImageView alloc] initWithImage:outfitImage];
[imageData release];
[outfitImage release];
[savedDataArray release];
CGSize frameOffset;
#ifdef LITE_VERSION
frameOffset = CGSizeMake(40, 60);
#else
frameOffset = CGSizeMake(20, 10);
#endif
closetImage.frame = CGRectOffset(closetImage.frame, frameOffset.width , frameOffset.height);
[v addSubview:closetImage];
[closetImage release];
[self.closetScroller addSubview:v];
[v release];
}
}
This all works great. Then when the user selects the "Close" button or selects an outfit I call:
[self.delegate closeClosetWindow];
Which calls the delegate method in the parentViewController which simply makes this call:
[self dismissModalViewControllerAnimated:YES];
I have also tried calling dismissModalViewControllerAnimated:YES from the modal view controller itself. It simply forwards the message to it's parentViewController and closes the modal just the same as the first way. Neither makes a difference with regards to the memory.
So what I do instead is use this method to close the view:
[[self.closetScroller subviews] makeObjectsPerformSelector: #selector(removeFromSuperview)];
[self dismissModalViewControllerAnimated:YES];
When I do that, I can see my Object Allocations decrease in Instruments. Prior to opening the closet view I have object alloc at about 1.85mb. When I open the closet it increases to 2.5 or so depending on how many outfit images are loaded into the UIScrollView. Then when I close the view without the above method it stays at 2.5 and increases the next time I open it. Although when using the above method that removes the outfit images from the scroller, my object alloc drops back down close the the 1.85mb. Maybe a little more which tells me it's hanging on to some other stuff also.
Also note that when looking at the object allocations, I do see the ClosetViewController object created and when I close the modal it's refrence is released. I can see it malloc and free the view as I open and close it. It's not retaining the view object itself. i'm so confused.
Not sure what to do here. I am not getting any leaks and I am releasing everything just fine. Any ideas would be great.
mark
I Noticed object alloc in instruments keeping a live reference to UIScrollView every time I opened modal window. So to fix, when allocating for my scrollview, I instead allocated a new UIScrollView object and assigned it to closetScroller and then released. This took care of the problem
I think you were simply not releasing closetScroller in the dealloc of your view controller.