HG's
HG's

Reputation: 828

Toggling Views EXC_BAD_ACCESS

I have a UIView in my class (besides the original view) made in interface builder.

@interface TimeLineGrid : UIViewController {

UIView *toggleView;

}

@property (nonatomic, retain) IBOutlet UIView *toggleView;

I have synthesized it as well. I have implemented a swipe gesture so that when swiped up, the toggle view is added and when swiped down the toggle view is removed.

-(void)swipedUp {

    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:2.0f];

    [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.view cache:NO];

    [self.view addSubview:self.toggleView];

    [UIView commitAnimations];



}

-(void)swipedDown {

    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:2.0f];

    [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:NO];

    [self.toggleView removeFromSuperview];

    [UIView commitAnimations];

}

It works fine when I swipe up once and when I swipe down after that. But when i swipe up once again, it crashes with EXC_BAD_ACCESS error. I know this has something to do with the retain count increasing when I addSubview and reducing when I removeSubview. Can someone shed more light on this? How do I achieve this toggle?

EDIT: My view hierarchy is as follows:

->UIView (toggleView)
->UIView (mainView to which toggleView is being added)
 -->UIToolBar

Upvotes: 2

Views: 1426

Answers (4)

Robin
Robin

Reputation: 10011

I have made an application containing UITabbar and I got the same behavior but with UIButton.

That happens when you dont release the object on which you call removeFromSuperview, I have the same effect when I was removing a UIButton from my viewControllers view.

In my case I could still see the button and if I click on it, I get an exception like unrecognized selector send to object of type NSCFString.

But if I change the tab then return to my view controller where the button was showing previously after removing it from my view, then it was gone.

So my conclusion is that if you dont release the object on which your are calling removeFromSuperview then the app will show some unexpected behavior if you have the object as an instance variable of your class and added it to the view programmatically by addSubView method.

What are your options:-

  1. You can call removeFromSuperview on a uiview or its subclass objects and call release on it or make it nil.

  2. You can hide or unhide those objects using hidden property instead of removing them from superview.

  3. (I haven't tried it exactly)You can create a UIView object in your class implementation and then add it as a subview and then assign it to your instance variable your class using dot operator and then release the previously created UIView object.

I dont know how my answer might help you but I think its good to share your experience with others.

Upvotes: 1

PengOne
PengOne

Reputation: 48398

Most likely, the view is being released when you call [self.toggleView removeFromSuperview];. I recommend that you restructure your code a little.

Since you are creating the view in IB, declare it in the header file as

IBOutlet UIView *toggleView;

Do not include the property call or the synthesize. Since you are not setting or getting the toggleView, just refer to it by the pointer.

In the implementation, set the pointer to nil in viewDidUnload and release it in dealloc. Then, adjust your toggle methods as follows:

-(void)swipedUp {
    if (![toggleView superview]) {

        [UIView beginAnimations:nil context:nil];
        [UIView setAnimationDuration:2.0f];

        [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromLeft forView:self.view cache:NO];

        [self.view addSubview:toggleView];

        [UIView commitAnimations];
    }
}

-(void)swipedDown {

    if ([toggleView superview]) {

        [UIView beginAnimations:nil context:nil];
        [UIView setAnimationDuration:2.0f];

        [UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:self.view cache:NO];

        [toggleView removeFromSuperview];

        [UIView commitAnimations];
    }
}

Here, the if clause will catch the event that the user swipes up twice in a row and not respond to the second swipe. Notice also that this uses toggleView in place of self.toggleView.

This should resolve the problem.

Upvotes: 3

octy
octy

Reputation: 6545

I suggest making the views you want to show/hide, be both subviews of a container view. Then, in your animation block you first remove a view that dissapears and add the view that you want to show. For example:

@interface TimeLineGrid : UIViewController {
    // declare the views you want to flip between
    UIView *mainView;
    UIView *toggleView;
}

@property (nonatomic, retain) IBOutlet UIView *mainView;
@property (nonatomic, retain) IBOutlet UIView *toggleView;

In your implementation:

// self.view is the container view (an instance of UIView).
// Initially, self.mainView is a subview of self.view

-(void)transitionFromView:(UIView *)fromView toView:(UIView *)toView withAnimation:(UIViewAnimationTransition)animation {

    if ([toView superview]) {
        // toView already shown
        return;
    }

    [UIView beginAnimations:nil context:nil];
    [UIView setAnimationDuration:2.0f];

    [UIView setAnimationTransition:animation forView:self.view cache:NO];

    [self.view removeFromSuperview:fromView];
    [self.view addSubview:toView];

    [UIView commitAnimations];

}

-(void)swipedUp {
    [self transitionFromView:self.mainView 
                      toView:self.toggleView 
               withAnimation:UIViewAnimationTransitionFlipFromLeft];
}

-(void)swipedDown {
    [self transitionFromView:self.toggleView 
                      toView:self.mainView 
               withAnimation:UIViewAnimationTransitionFlipFromRight];
}

Note however, that use of the method setAnimationTransition:forView:cache: is now discouraged. Apple recommends using the UIView transitionWithView:duration:options:animations:completion: method (see the UIView reference).

I hope this helps!

Upvotes: 1

Gloomcore
Gloomcore

Reputation: 950

Could you please apply such changes and tell which UIView is exactly has proble.

  1. Open XCode/Product/Edit Scheme...
  2. Move to Environment Variables and then click the plus to add variable
  3. Add variable NSZombieEnables - value: YES
  4. Add variable MallocStackLoggingNoCompact - value: 1

After starting the application, you will see, which exactly object was disposed, when you call it.

Upvotes: 3

Related Questions