I have written network class that is managing all network calls for my application. There are two methods showLoadingAnimationView and hideLoadingAnimationView that will show UIActivityIndicatorView in a view over my current viewcontroller with fade background. I am getting memory leaks somewhere on these two methods. Here is the code
-(void)showLoadingAnimationView
{
textmeAppDelegate *textme = (textmeAppDelegate *)[[UIApplication sharedApplication] delegate];
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
if(wrapperLoading != nil)
{
[wrapperLoading release];
}
wrapperLoading = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 480.0)];
wrapperLoading.backgroundColor = [UIColor clearColor];
wrapperLoading.alpha = 0.8;
UIView *_loadingBG = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 320.0, 480.0)];
_loadingBG.backgroundColor = [UIColor blackColor];
_loadingBG.alpha = 0.4;
circlingWheel = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
CGRect wheelFrame = circlingWheel.frame;
circlingWheel.frame = CGRectMake(((320.0 - wheelFrame.size.width) / 2.0), ((480.0 - wheelFrame.size.height) / 2.0), wheelFrame.size.width, wheelFrame.size.height);
[wrapperLoading addSubview:_loadingBG];
[wrapperLoading addSubview:circlingWheel];
[circlingWheel startAnimating];
[textme.window addSubview:wrapperLoading];
[_loadingBG release];
[circlingWheel release];
}
-(void)hideLoadingAnimationView
{
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
wrapperLoading.alpha = 0.0;
[self.wrapperLoading removeFromSuperview];
//[NSTimer scheduledTimerWithTimeInterval:0.8 target:wrapperLoading selector:#selector(removeFromSuperview) userInfo:nil repeats:NO];
}
Here is how I am calling these two methods
[NSThread detachNewThreadSelector:#selector(showLoadingAnimationView) toTarget:self withObject:nil];
and then somewhere later in the code i am using following function call to hide animation.
[self hideLoadingAnimationView];
I am getting memory leaks when I call showLoadingAnimationView function. Anything wrong in the code or is there any better technique to show loading animation when we do network calls?
The method showLoadingAnimationView returns a non auto-released view (retainCount -> 1), that you later (I assume) add to another view (retainCount -> 2).
In hideLoadingAnimationView, you only remove the view from its superview (retainCount -> 1). There is a missing release in this method. That means that you should not call release in the showLoadingAnimationView.
Related
I am using a scrollview to display a viewcontrollers view. if the user reaches the end of the cached views my method reloads the new views. My NSLogs say that the method is finished but it takes additional 5 seconds to display the view.
I think that the [scrollView addSubview:vc.view] is very very slow but I found nothing to improve it.
the whole method gets called in -(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
scrollView.userInteractionEnabled = NO;
UIActivityIndicatorView *activityIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[activityIndicator setFrame:CGRectMake((320.f*index)+135.f, 151, 50, 50)];
[activityIndicator setHidesWhenStopped:YES];
[activityIndicator startAnimating];
[scrollView addSubview:activityIndicator];
MBBAppDelegate *delegate = (MBBAppDelegate *)[UIApplication sharedApplication].delegate;
[delegate fetchDealsWithFilter:dealFilter fromIndex:index toIndex:(index+3) onSuccess:^(id object){
MBBDealList *list = object;
int i = 0;
ProductDetailViewController *vc;
for (MBBDeal *deal in [list deals]) {
NSLog(#"start %i",i);
int indexInArray = i;//[list.deals indexOfObject:deal];
if (indexInArray+index >= numDeals) {
return;
}
vc = [[ProductDetailViewController alloc] init];
//fetch the deal and insert
vc.numberOfDeals = numDeals;
vc.dealIndex = index+1+indexInArray;
vc.dealFilter = dealFilter;
vc.deal = deal;
vc.view.frame = CGRectMake(320.f*(index+indexInArray), 0.f, 320.f, 436.f);
[scrollView addSubview:vc.view];
[productDetailViewControllers insertObject:vc atIndex:(index+indexInArray)];
i++;
}
[activityIndicator stopAnimating];
scrollView.userInteractionEnabled = YES;
}];
}
Does anyone know how I can improve my method?
What I can understand from your problem is that, the activity indicator that you have used to show the data fetch process is not used properly.
You data fetch process is still working, even after the activity indicator disappears.
You can do 2 things for that:
ether call the data fetch method in background using performSelectorInBackground method or place the activity indicator in the app delegate where you have created your data fetch method.
I want to get list from server it is taking some time So at that time I'm displaying progress indicator. My problem sometimes the progress indicator is appearing and some times it is not appearing. The code I used is
-(void)viewWillAppear:(BOOL)animated
{
[self loadprogressIndicator];
}
-(void)loadprogressIndicator
{
MessengerAppDelegate* appDelegate = (MessengerAppDelegate*) [ [UIApplication sharedApplication] delegate];
[appDelegate showWaitingView];
[NSThread detachNewThreadSelector:#selector(statusIndicatorForRefresh) toTarget:self withObject:nil];
}
-(void)statusIndicatorForRefresh
{
NSAutoreleasePool* pool=[[NSAutoreleasePool alloc]init];
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
[self performSelectorOnMainThread:#selector(loadList) withObject:nil waitUntilDone:NO];
[NSThread exit];
[pool release];
}
-(void)loadList
{
MessengerAppDelegate* appDelegate = (MessengerAppDelegate*) [ [UIApplication sharedApplication] delegate];
customerList = [appDelegate getCustomersArray];
[theTableView reloadData];
[appDelegate removeWaitingView];
}
And in appdelegate.m I implemeted these two methods
- (void)showWaitingView
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
CGRect frame = CGRectMake(90, 190, 32, 32);
UIActivityIndicatorView* progressInd = [[UIActivityIndicatorView alloc] initWithFrame:frame];
[progressInd startAnimating];
progressInd.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge;
frame = CGRectMake(130, 193, 140, 30);
UILabel *waitingLable = [[UILabel alloc] initWithFrame:frame];
waitingLable.text = #"Processing...";
waitingLable.textColor = [UIColor whiteColor];
waitingLable.font = [UIFont systemFontOfSize:20];
waitingLable.backgroundColor = [UIColor clearColor];
frame = [[UIScreen mainScreen] applicationFrame];
UIView *theView = [[UIView alloc] initWithFrame:frame];
theView.backgroundColor = [UIColor blackColor];
theView.alpha = 0.7;
theView.tag = 999;
[theView addSubview:progressInd];
[theView addSubview:waitingLable];
[progressInd release];
[waitingLable release];
[window addSubview:[theView autorelease]];
[window bringSubviewToFront:theView];
[pool drain];
}
- (void)removeWaitingView
{
UIView *v = [window viewWithTag:999];
if(v) [v removeFromSuperview];
}
Can anyone please help me. Happy Coding..
Thanks in advance.
Praveena..
You are doing all your activity on the main thread. Your app will appear to be blocked because you block the UI thread.
Also what is the use of launching an NSThread if all it does is call a method on your main thread and not even wait for it to complete? You are doing no multithreading at all.
Your app does this:
in main thread:
show waiting view
do 1 run loop cycle
do your data processing
hide waiting view
since your data processing is in the main thread you block everything there. You should do the processing in a secondary thread and leave the main thread for UI handling.
I have a View based application for iPhone/iPod that plays audio, having a VU meter showing the input dB level. This VU meter is animated using Core Animation.
The app runs fine, with the VU meter working great, having just one single View. But now, sadly, that I have inserted a Tab bar controller, to have another View with some internet content, I can't get the VU meter to work on my first View, as before.
The app crashes (receiving the EXC_BAD_ACCESS message) because the UIView (that contains the VU meter .png file for Core Animation) is not being properly inserted during launching time as it did before as a View only application.
Here is my code, in the myAppDelegate.m file:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
PlayerViewController *newController = [[PlayerViewController alloc] initWithNibName: #"PlayerView"
bundle: [NSBundle mainBundle]];
self.viewController = newController;
[newController release];
UIView *controllerView = [self.viewController view];
[self.window addSubview: controllerView];
[self.viewController addBargraphToView: controllerView];
[self.window makeKeyAndVisible];
self.window.rootViewController = self.tabBarController;
[self.window makeKeyAndVisible];
return YES;
}
And here is my PlayerViewController.m file, where I have the addBargraphToView method containing my image to be inserted. It remains invisible until the VU meter starts working:
- (void) addBargraphToView: (UIView *) parentView {
// static image for showing average level
UIImage *soundbarImage = [[UIImage imageNamed: #"vu-meter.png"] retain];
// background colors for generated image for showing peak level
self.peakClear = [UIColor clearColor];
self.peakGray = [UIColor lightGrayColor];
self.peakOrange = [UIColor orangeColor];
self.peakRed = [UIColor redColor];
levelMeter = [CALayer layer];
levelMeter.anchorPoint = CGPointMake (0.0, 20.0); // anchor to halfway up the left edge
levelMeter.frame = CGRectMake (15, 200, 0, kLevelMeterHeight); // set width to 0 to start to completely hide the bar graph segements
levelMeter.contents = (UIImage *) soundbarImage.CGImage;
peakLevelMeter = [CALayer layer];
peakLevelMeter.frame = CGRectMake (41, 233, 0, kLevelMeterHeight);
peakLevelMeter.anchorPoint = CGPointMake (0.5, 10.0);
peakLevelMeter.backgroundColor = peakGray.CGColor;
peakLevelMeter.bounds = CGRectMake (0, 0, 0, kLevelMeterHeight);
peakLevelMeter.contentsRect = CGRectMake (0, 0, 1.0, 1.0);
[parentView.layer addSublayer: levelMeter];
[parentView.layer addSublayer: peakLevelMeter];
[soundbarImage release];
}
How can I solve this problem? Should I insert the UIView image at some other point, and not during the application's launch time?
Many thanks for your help, dear friends!
Move this call:
[self.viewController addBargraphToView: controllerView];
To -viewDidLoad inside your PlayerViewController implementation:
- (void) viewDidLoad
{
[super viewDidLoad];
[self addBargraphToView: self.view];
}
Edit
OK, I think the problem lies in double adding viewControllers. Make this the content of your application:didFinishLaunchingWithOptions: method:
PlayerViewController *newController = [[PlayerViewController alloc] initWithNibName: #"PlayerView"
bundle: [NSBundle mainBundle]];
self.viewController = newController;
[newController release];
self.tabBarController.viewControllers = [NSArray arrayWithObjects: newController, yourInterntContenViewController, nil];
[self.window addSubview: self.tabBarController.view];
[self.window makeKeyAndVisible];
return YES;
I have a file called 'LoginViewController' that needs to do an action from the 'RootViewController.m' file. I did a NSLog and it functions, but the actual script doesn't.
I have also made sure to #import "RootViewController.h" in my LoginViewController.
LoginViewController
RootViewController *test = [[RootViewController alloc] init];
[NSThread detachNewThreadSelector:#selector(updateAlbumsAfterLogin) toTarget:test withObject:nil];
RootViewController
- (void)updateAlbumsAfterLogin {
NSLog(#"Updating albums after login!");
// Set up loading box and show it
UIImage *imageLoop = [UIImage imageNamed:#"loading_box.png"];
imagefour = [[UIImageView alloc] initWithImage:imageLoop];
imagefour.frame = CGRectMake(100, 80, 116, 116);
[self.view addSubview:imagefour];
// Set up loading text and show it
myLabel = [[UILabel alloc] initWithFrame:CGRectMake(97, 60, 130, 100)];
myLabel.text = #"Loading...";
myLabel.textColor = [UIColor whiteColor];
myLabel.textAlignment = UITextAlignmentCenter;
myLabel.backgroundColor = [UIColor clearColor];
myLabel.font = [UIFont fontWithName:#"Helvetica" size: 16.0];
myLabel.numberOfLines = 0;
//[myLabel sizeToFit];
[self.view addSubview:myLabel];
// Set up spinner and show it
myIndicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
myIndicator.center = CGPointMake(158, 155);
myIndicator.hidesWhenStopped = YES;
[self.view addSubview:myIndicator];
[myIndicator startAnimating];
[NSThread detachNewThreadSelector:#selector(updateAlbums) toTarget:self withObject:nil];
//myTimer = [NSTimer scheduledTimerWithTimeInterval:10.0 target:self selector: #selector(updateAlbums) userInfo: nil repeats: YES];
}
Thanks in advance,
Coulton.
If you need any other code, feel free to ask for it!
You are detaching a new thread to do this work and update your view, but only the main thread is ever allowed to update the view. That's why you can see your NSLog() messages happening but nothing in your UI updates.
The standard method to handle this is for your main thread to detach a new thread (or perform selector on a background thread), then once the fetching and data handling is complete you perform selector on the main thread which uses the data you fetched to update the UI.
I have a UIViewController that I'm using to control a "pop-up" view for viewing images throughout my application. It supports autorotation, as it automatically sizes the image to fit properly regardless of orientation. This works perfectly, but only the first time I initialize and display the view controller. When it closes, I am removing the UIView from my view hierarchy and releasing the view controller - but the next time I instantiate and add it to my view hierarchy, it stops receiving the -shouldAutorotateToInterfaceOrientation messages when the phone is rotated.
This is how I instantiate and display it:
popupVC = [[PopupVC alloc] init];
[popupVC viewWillAppear:NO];
[[[UIApplication sharedApplication] keyWindow] addSubview:popupVC.view];
[popupVC viewDidAppear:NO];
this is how I remove/release it when it's finished:
[popupVC viewWillDisappear:NO];
[popupVC.view removeFromSuperview];
[popupVC viewDidDisappear:NO];
[popupVC release];
popupVC = nil;
I've tried looping through [[UIApplication sharedApplication] keyWindow] subviews to see if somehow my popup view isn't on top, but it always is. And it has a different address each time so I do know that it's a different instance of the view controller class.
As requested, here is the complete loadView method from PopupVC:
- (void)loadView {
UIView *myView = [[UIView alloc] initWithFrame:[UIScreen mainScreen].applicationFrame];
myView.backgroundColor = self.overlayColor;
myView.autoresizesSubviews = NO;
myView.hidden = YES;
myView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.view = myView;
[myView release];
_isVisible = NO;
UIView *myMaskView = [[UIView alloc] initWithFrame:self.view.bounds];
myMaskView.backgroundColor = [UIColor clearColor];
myMaskView.clipsToBounds = YES;
myMaskView.hidden = YES;
myMaskView.autoresizesSubviews = NO;
myMaskView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self.view addSubview:myMaskView];
self.imageMaskView = myMaskView;
[myMaskView release];
UIImageView *myImageView = [[UIImageView alloc] initWithFrame:CGRectZero];
myImageView.center = self.view.center;
myImageView.hidden = NO;
myImageView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleHeight;
[self.imageMaskView addSubview:myImageView];
self.imageView = myImageView;
[myImageView release];
UIButton *myImageButton = [UIButton buttonWithType:UIButtonTypeCustom];
myImageButton.frame = self.view.frame;
myImageButton.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleHeight;
[myImageButton addTarget:self action:#selector(clickImage:) forControlEvents:UIControlEventTouchUpInside];
[self.imageMaskView addSubview:myImageButton];
self.imageButton = myImageButton;
UIActivityIndicatorView *myActivityView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];
myActivityView.hidden = YES;
[self.view addSubview:myActivityView];
myActivityView.center = self.view.center;
self.activityView = myActivityView;
[myActivityView release];
}
The shouldAutorotateToInterfaceOrientation handler isn't the place for custom code to respond to rotation events. It's purpose is just to tell the OS that your view controller can rotate. If you want to have custom code to handle the rotation events you should overide - didRotateFromInterfaceOrientation or one of the other similar callback methods depending on your needs:
willRotateToInterfaceOrientation:duration:
willAnimateRotationToInterfaceOrientation:duration:
didRotateFromInterfaceOrientation:
willAnimateFirstHalfOfRotationToInterfaceOrientation:duration:
didAnimateFirstHalfOfRotationToInterfaceOrientation:
willAnimateSecondHalfOfRotationFromInterfaceOrientation:duration:
See Autorotating Views in the developer docs.
I think that might be a bug in the new OS 3.0. A workaround to this would be to use NSNotificationCenter after turning on beginGeneratingDeviceOrientationNotifications.
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
[[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receivedRotate:) name:UIDeviceOrientationDidChangeNotification object:nil];
}
-(void) receivedRotate: (NSNotification *) notification {
DebugLog(#"ORIENTATION CHANGE");
UIDeviceOrientation interfaceOrientation = [[UIDevice currentDevice] orientation];
if(interfaceOrientation == UIDeviceOrientationPortrait) {
self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(0));
self.view.bounds = CGRectMake(0, 0, 320, 480);
}
else if(interfaceOrientation == UIDeviceOrientationLandscapeLeft) {
self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(90));
self.view.bounds = CGRectMake(0, 0, 480, 320);
}
else if(interfaceOrientation == UIDeviceOrientationLandscapeRight) {
self.view.transform = CGAffineTransformMakeRotation(degreesToRadian(-90));
self.view.bounds = CGRectMake(0, 0, 480, 320);
}
}
My problem is that I get the rotation event for my rootViewController, but as soon as I launch my second viewController, it won't get any signals except motion (shaking) and touch. Is that the same issue as the original post -- it is certainly similiar??
I followed-up with above recommendation and I don't have the selector coded correctly so it throws an exception as soon as I try to rotate. Meanwhile, I found this other discussion on the web which confirms that the problem was introduced in 3.0.
link text