Leak when adding subview - objective-c

So Instruments tells me I have three memory leaks originating in this method (specifically, it points out the line:
[self.view addSubview:menuBar.view];
I can't see a leak and am racking my brains. I'm keeping a reference to the menuBar object and am releasing it. Anyone smarter than me that can explain? Is it a coincidence that I have three menubar items in my XIB and I'm getting three leaks?
Here is the entire method:
//
root vc calls to toggle display state of menu bar on screen
-(IBAction) showToolBar {
//if no toolbar exists, create one and add it to the view
if (!menuBarView) {
MenuBarViewController *menuBar = [[MenuBarViewController alloc] initWithNibName:#"MenuBarViewController" bundle:nil];
menuBar.book = self.selectedTitleDeck;
menuBar.booksArray = self.allTitleDeck;
self.menuBarView = menuBar;
[self.view addSubview:menuBar.view];
[menuBar release];
}
CGRect frame = menuBarView.view.frame;
[UIView beginAnimations:nil context:NULL];
if (self.toolBarIsDisplayed == NO) {
//show the toolbar
frame.origin.y = 725;
self.toolBarIsDisplayed = YES;
} else if (self.toolBarIsDisplayed == YES) {
//hide the toolbar
frame.origin.y = 788;
self.toolBarIsDisplayed = NO;
}
self.menuBarView.view.frame = frame;
[UIView commitAnimations];
}

addSubview: retains the view passed into it (see the reference). Once you call addSubView, you can release that view, like:
MenuBarViewController *menuBar = [[MenuBarViewController alloc] initWithNibName:#"MenuBarViewController" bundle:nil];
menuBar.book = self.selectedTitleDeck;
menuBar.booksArray = self.allTitleDeck;
self.menuBarView = menuBar;
[self.view addSubview:menuBar.view];
[menuBar.view release];
[menuBar release];
}

Show us what happens in MenuBarViewController's dealloc method. I suspect you are forgetting to release its instance variables.

As suggested in the comments to my question, the problem was not in the code but in running my app in the simulator and trying to detect memory leaks.
When Instruments is run against the code on the device, no leaks are reported.
My consolation prize is a far more profound understanding of memory management unearthed in two days of trying to locate a leak that didn't exist.
Thanks to all for your advice, much appreciated.

Related

UITapGesture only works after reLoad UIViewController

I have 1 year works with Objective-C and is my first question that I make because I always found the answer in this site, but this situation is completely insane and did not found anything similar.
I have a custom class (called BallClass from UIView). The UIView have an UIImageView inside on it and a UITapGesture to make an action.
So when I create the object in a UIViewController, does not works the tapAction when press in the object, but if I change to other screen and return it, the tapGesture works perfect.
I tried many options, but I cant makes work in the first load.
I work with ARC, Xcode (4.6.3) and OSX (10.8.4) and IB but this objects was created with no IB .
Thanks in advance.
Here is the class BallClass.m
- (void)tapGesture:(UIGestureRecognizer *)tapGesture {
NSLog(#"hola");
}
- (id)initBall:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Initialization code
// Recibir las variables iniciales para el control del timer
ball = [[Ball alloc] init];
// Agregar la imagen de la pelota en el view
UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, self.frame.size.height)];
[imageView setCenter:CGPointMake(CGRectGetMidX([self bounds]), CGRectGetMidY([self bounds]))];
[imageView setContentMode:UIViewContentModeScaleAspectFit];
[imageView setImage:[UIImage imageNamed:#"pelota.png"]];
[imageView setTag:100];
[imageView setNeedsDisplay];
// Add TAP
UITapGestureRecognizer *tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapGesture:)];
// Tap Properties
[tapGesture setDelaysTouchesBegan:YES];
[tapGesture setNumberOfTapsRequired:1];
[tapGesture setDelegate:(id)self];
[imageView addGestureRecognizer:tapGesture];
[self addSubview:imageView];
[self setNeedsDisplay];
// Propiedades del View
[self setUserInteractionEnabled:YES];
self.backgroundColor = [UIColor clearColor];
[self setTag:100];
// BALL BOUNCE
[NSTimer scheduledTimerWithTimeInterval:1.0 / 30.0 target:self selector:#selector(onTimer:) userInfo:nil repeats:YES];
}
return self;
}
- (void)onTimer:(NSTimer *)timerLocal {
// Do something
}
... // more methods
Here is the instantiate:
ballView00 = [[BallView alloc] initBall:CGRectMake(10, 10, 40, 40)];
The UiView show perfect, and the animation works fine, but the UITapGesture just works, as mentioned early, after reLoad the ViewController.
Here is the link to image the Ball and UIView (#kBall). Sorry, about image, but I cant load until I have 10 points :(
This might be an issue with the superview's frame. What view is holding ballView00? Does ballView lie entirely within it's superview's bounds?
Usually when you encounter problem where you're expecting a touch but don't get it it's because the view set to receive the touch is outside one of its parents' bounds.
So if the superview bounds are 100x100 and the ball is at x=125 y=125 it will not receive a touch.
A lot of magic happens when your views get laid out, it's not surprising to sometimes find one with 0x0 dimensions because of some incorrect constraints.
You can easily check the bounds of all your views by giving them a background color.
I found the solution.
The ball make a spin with this:
[UIView animateWithDuration: 0.5f
delay: 0.0f
options: UIViewAnimationOptionCurveEaseIn
animations: ^{
self.transform = CGAffineTransformRotate(self.transform, M_PI / 2);
}
completion: ^(BOOL finished) {
if (finished) {
if (animating) {
// if flag still set, keep spinning with constant speed
[self spinWithOptions: UIViewAnimationOptionCurveLinear];
}
}
}];
And generate conflict with the main timer. I change the spin action with simple add self.transform = CGAffineTransformRotate(self.transform, M_PI / 2); inside drawRect method in the class.
Apparently, I can't nested two timers, because "disable" the tapGesture, so when I change of the screen and return, the spin was stopped and this help to found this situation.
Thanks for your help.

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.

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];
}];
}

applicationMusicPlayer volume notification

I am using an applicationMusicPlayer and when i try to change the volume appear the visual notification, as shown in the picture.
Here the code I am using:
[MPMusicPlayerController applicationMusicPlayer] setVolume:newVolune];
Anyone knows how to hide this notification?
I don't know where the docs says so, but if you add a MPVolumeView view to your app the system volume overlay goes away. Even if it is not visible:
- (void) viewDidLoad
{
[super viewDidLoad];
MPVolumeView *volumeView = [[MPVolumeView alloc] initWithFrame: CGRectZero];
[self.view addSubview: volumeView];
[volumeView release];
...
}
You can use the hardware volume buttons, the setVolume method or directly interact with the control (if visible) that the overlay doesn't show up.
For iOS6 I had to set an image with alpha 0 and non-zero size to the MPVolumeView's image fields in order to get the default volume change notification to disappear.
// hide the hardware volume slider
UIImage *thumb = [[UIImage alloc] initWithCIImage:[UIImage imageNamed:#"volumeHider"].CIImage scale:0.0 orientation:UIImageOrientationUp];
MPVolumeView *hwVolume = [[MPVolumeView alloc] initWithFrame:self.frame];
[hwVolume setUserInteractionEnabled:NO];
hwVolume.showsRouteButton = NO;
[hwVolume setVolumeThumbImage:thumb forState:UIControlStateNormal];
[hwVolume setMinimumVolumeSliderImage:thumb forState:UIControlStateNormal];
[hwVolume setMaximumVolumeSliderImage:thumb forState:UIControlStateNormal];
[self addSubview:hwVolume];
This made the MPVolumeView be "visible" on the screen, but invisible to the user.
I encountered the same issue recently. Instead of adding the MPVolumeView to current view controller's view, I add it to the application's window once at the start of the app:
CGRect rect = CGRectMake(-500, -500, 0, 0);
MPVolumeView *volumeView = [[MPVolumeView alloc] initWithFrame:rect];
[self.window addSubview:volumeView];
This works in both iOS 7 and 8.
Swift 3
You can hide the System MPVolumeView using
override func viewDidLoad() {
super.viewDidLoad()
let volumeView = MPVolumeView(frame: CGRect.zero)
self.view.addSubview(volumeView)
}
I had success with this in iOS 6. Although it wouldn't perform well. It caused quite a bit of lag when sliding the thumbImage. I did have to take out the last 2 lines of code in order for this to work.
[volumeView release];
...
For me, on iOS 7, none of above solutions worked. Here is how I did it:
_volume = [[MPVolumeView alloc] initWithFrame: CGRectMake(-100,-100,16,16)];
_volume.showsRouteButton = NO;
_volume.userInteractionEnabled = NO;
[self.view addSubview:_volume];
[_volume release];
That is, simply set MPVolumeView's frame to an off-screen location such as (-100,-100).

problem with changing view

hi I have a problem I made an application based on apples sample app called photoscroller and i want to add a login screen.but when i add it the showed view move upward 10-15 pixels and the main window is visible underneath.I ask why?
Parts of my code:
at view did load:
InfoViewController *infoView = [[InfoViewController alloc] initWithNibName:nil bundle:nil];
self.view = infoView.view;
[infoView release];
then later after validating the login:
CGRect pagingScrollViewFrame = [self frameForPagingScrollView];
pagingScrollView = [[UIScrollView alloc] initWithFrame:pagingScrollViewFrame];
pagingScrollView.pagingEnabled = YES;
pagingScrollView.backgroundColor = [UIColor blackColor];
pagingScrollView.showsVerticalScrollIndicator = NO;
pagingScrollView.showsHorizontalScrollIndicator = NO;
pagingScrollView.contentSize = [self contentSizeForPagingScrollView];
pagingScrollView.delegate = self;
self.view = pagingScrollView;
...
I do not know why is pushed upward when i add other view.
thank for every answer
Could you past the method - (CGRect) frameForPagingScrollView, without that method it's bit harder to understand your code.
But i guess the self.view.frame and the CGRect returned from that method differ.
Further guess would be, that the difference are not 10-15 pixels, but 20 pixels (for the height of the UIStatusBar), which might be enabled in your .xib-File but is actually not displayed.