Why does my app occasionally crash when I change a frame?

1

I have a custom UISegmentedControl and when I initialize it, it crashes sometimes. It is uncommon, about 1% of the time, (just uncommon enough to pass apple's "rigorous" app testing), and even though I have this exact same code in three other views, it only crashes on one particular one of them.

Code:

NSArray *providers = [[NSArray alloc] initWithObjects:@"All", @"Soon", @"Attn", @"Late",     @"Done", nil]; //categories for segmented control
FancySegmentedControl *fancy = [[FancySegmentedControl alloc] initWithItems:providers];
fancy.backgroundColor = [UIColor clearColor]; //change bg color
fancy.frame = CGRectMake(11, 86, 263, 29); //lldb crashes here
[fancy setBackgroundColor:[UIColor colorWithRed:42/255.0f
                                          green:82/255.0f
                                           blue:164/255.0f alpha:1] forState:UIControlStateNormal];

So, my symptoms are:

-Crash does not happen most of the time.

-Crash only happens on one of four view controllers even though the code is identical.

-Crash has only been noticed in the simulator (anything to do with that??) It is possible this is just because testing happens a lot more in simulator than on device, though.

-My project uses ARC.

The code for FancySegmentedControl is as follows:

@interface FancySegmentedControl : UISegmentedControl
{
    UIColor *bgNotSelected;
    UIColor *bgSelected;
    UIColor *barNotSelected;
    UIColor *barSelected;
}

-(void)setBackgroundColor:(UIColor *)color forState:(UIControlState)state;

-(void)setBarColor:(UIColor *)color forState:(UIControlState)state;

-(void)setTextAttributes:(NSDictionary *)attrs forState:(UIControlState) state;

@end

@implementation FancySegmentedControl

- (id)initWithItems:(NSArray *)items
{
    self = [super initWithItems:items];
    bgSelected = [UIColor blueColor];
    bgNotSelected = [UIColor whiteColor];
    barNotSelected = [UIColor lightGrayColor];
    barSelected = [UIColor orangeColor];
    self.selectedSegmentIndex = 0;
    if (self) {
        //change text stuff
        NSDictionary *attributes = [NSDictionary dictionaryWithObjectsAndKeys:
                                    [UIFont boldSystemFontOfSize:17], UITextAttributeFont,
                                    [UIColor blackColor], UITextAttributeTextColor,
                                    nil];
        [self setTitleTextAttributes:attributes forState:UIControlStateNormal];
        NSDictionary *highlightedAttributes = [NSDictionary dictionaryWithObject:[UIColor whiteColor] forKey:UITextAttributeTextColor];
        [self setTitleTextAttributes:highlightedAttributes forState:UIControlStateHighlighted];

        //set all dividers to nothing
        UIView *yourView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 1, 30)];
        UIGraphicsBeginImageContext(yourView.bounds.size);
        [yourView.layer renderInContext:UIGraphicsGetCurrentContext()];
        UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        [self setDividerImage:image
          forLeftSegmentState:UIControlStateNormal
            rightSegmentState:UIControlStateNormal
                   barMetrics:UIBarMetricsDefault];
        [self setDividerImage:image
          forLeftSegmentState:UIControlStateSelected
            rightSegmentState:UIControlStateNormal
                   barMetrics:UIBarMetricsDefault];
        [self setDividerImage:image
          forLeftSegmentState:UIControlStateNormal
            rightSegmentState:UIControlStateSelected
                   barMetrics:UIBarMetricsDefault];


        yourView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 30)];
        yourView.backgroundColor = bgNotSelected;
        UIView *barView = [[UIView alloc] initWithFrame:CGRectMake(0, yourView.frame.size.height - 3, 40, 3)];
        barView.backgroundColor = barNotSelected;
        [yourView addSubview:barView];
        UIGraphicsBeginImageContext(yourView.bounds.size);
        [yourView.layer renderInContext:UIGraphicsGetCurrentContext()];
        image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();

        UIImage *normalBackgroundImage = image;
        [self setBackgroundImage:normalBackgroundImage
                        forState:UIControlStateNormal
                      barMetrics:UIBarMetricsDefault];

        yourView.backgroundColor = bgSelected;
        barView = [[UIView alloc] initWithFrame:CGRectMake(0, yourView.frame.size.height - 3, 40, 3)];
        barView.backgroundColor = barSelected;
        [yourView addSubview:barView];
        UIGraphicsBeginImageContext(yourView.bounds.size);
        [yourView.layer renderInContext:UIGraphicsGetCurrentContext()];
        image = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        UIImage *selectedBackgroundImage = image;
        [self setBackgroundImage:selectedBackgroundImage
                        forState:UIControlStateSelected
                      barMetrics:UIBarMetricsDefault];
    }
    return self;
}

-(void)setBackgroundColor:(UIColor *)color forState:(UIControlState)state
{
    UIColor *barColor;
    if (state == UIControlStateSelected)
    {
        bgSelected = color;
        barColor = barSelected;
    }
    else
    {
        bgNotSelected = color;
        barColor = barNotSelected;
    }
    UIView *yourView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 30)];
    yourView.backgroundColor = color;
    UIView *barView = [[UIView alloc] initWithFrame:CGRectMake(0, yourView.frame.size.height - 3, 40, 3)];
    barView.backgroundColor = barColor;
    [yourView addSubview:barView];
    UIGraphicsBeginImageContext(yourView.bounds.size);
    [yourView.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    [self setBackgroundImage:image
                    forState:state
                  barMetrics:UIBarMetricsDefault];
}

-(void)setBarColor:(UIColor *)color forState:(UIControlState)state
{
    UIColor *bgColor;
    if (state == UIControlStateSelected)
    {
        barSelected = color;
        bgColor = bgSelected;
    }
    else
    {
        barNotSelected = color;
        bgColor = bgNotSelected;
    }
    UIView *yourView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 30)];
    yourView.backgroundColor = bgColor;
    UIView *barView = [[UIView alloc] initWithFrame:CGRectMake(0, yourView.frame.size.height - 3, 40, 3)];
    barView.backgroundColor = color;
    [yourView addSubview:barView];
    UIGraphicsBeginImageContext(yourView.bounds.size);
    [yourView.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    [self setBackgroundImage:image
                    forState:state
                  barMetrics:UIBarMetricsDefault];
}

-(void)setTextAttributes:(NSDictionary *)attrs forState:(UIControlState) state
{
    if (state == UIControlStateSelected)
    {
        //in case user mistakes the states
        state = UIControlStateHighlighted;
    }
    [self setTitleTextAttributes:attrs forState:state];
}


/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
    // Drawing code
}
*/

@end

The full error message:

* thread #1: tid = 0x1f03, 0x0134609b libobjc.A.dylib`objc_msgSend + 15, stop reason =     EXC_BAD_ACCESS (code=2, address=0xb0000008)
frame #0: 0x0134609b libobjc.A.dylib`objc_msgSend + 15
frame #1: 0x0059bcd5 UIKit`-[UISegmentedControl _setBackgroundImage:forState:barMetrics:] + 148
frame #2: 0x0059bd69 UIKit`-[UISegmentedControl setBackgroundImage:forState:barMetrics:] + 73
frame #3: 0x0004f9f5 Services`-[FancySegmentedControl setBarColor:forState:](self=0x0ea5d740, _cmd=0x000518fd, color=0x08db4630, state=0x00000004) + 1141 at FancySegmentedControl.m:135
frame #4: 0x0000538c Services`-[ServicesViewController fixSearchBar](self=0x07f791a0, _cmd=0x0005170b) + 1132 at ServicesViewController.m:174
frame #5: 0x00003f38 Services`-[ServicesViewController viewDidLoad](self=0x07f791a0, _cmd=0x017fe1dd) + 616 at ServicesViewController.m:49
frame #6: 0x0056964e UIKit`-[UIViewController view] + 184
frame #7: 0x00569941 UIKit`-[UIViewController contentScrollView] + 36
frame #8: 0x0057b47d UIKit`-[UINavigationController _computeAndApplyScrollContentInsetDeltaForViewController:] + 36
frame #9: 0x0057b66f UIKit`-[UINavigationController _layoutViewController:] + 43
frame #10: 0x0057b93b UIKit`-[UINavigationController _startTransition:fromViewController:toViewController:] + 303
frame #11: 0x0057c3df UIKit`-[UINavigationController _startDeferredTransitionIfNeeded] + 288
frame #12: 0x0057c561 UIKit`-[UINavigationController __viewWillLayoutSubviews] + 33
frame #13: 0x006984ca UIKit`-[UILayoutContainerView layoutSubviews] + 222
frame #14: 0x004e2301 UIKit`-[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 145
frame #15: 0x019b8e72 CoreFoundation`-[NSObject performSelector:withObject:] + 66
frame #16: 0x003c292d QuartzCore`-[CALayer layoutSublayers] + 266
frame #17: 0x003cc827 QuartzCore`CA::Layer::layout_if_needed(CA::Transaction*) + 231
frame #18: 0x00352fa7 QuartzCore`CA::Context::commit_transaction(CA::Transaction*) + 377
frame #19: 0x00354ea6 QuartzCore`CA::Transaction::commit() + 374
frame #20: 0x00354580 QuartzCore`CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*) + 80
frame #21: 0x0198b9ce CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 30
frame #22: 0x01922670 CoreFoundation`__CFRunLoopDoObservers + 384
frame #23: 0x018ee4f6 CoreFoundation`__CFRunLoopRun + 1174
frame #24: 0x018eddb4 CoreFoundation`CFRunLoopRunSpecific + 212
frame #25: 0x018edccb CoreFoundation`CFRunLoopRunInMode + 123
frame #26: 0x01b49879 GraphicsServices`GSEventRunModal + 207
frame #27: 0x01b4993e GraphicsServices`GSEventRun + 114
frame #28: 0x004a3a9b UIKit`UIApplicationMain + 1175
frame #29: 0x000036d0 Services`main(argc=1, argv=0xbffff214) + 80 at main.m:16
frame #30: 0x000025e5 Services`start + 53
iphone
ios
objective-c
lldb
asked on Stack Overflow Aug 30, 2013 by Lugubrious • edited Aug 30, 2013 by Lugubrious

2 Answers

2

UIControlState is an enum. You are passing a pointer to a value that is supposed to be a UIControlState, but actually pointer's value is used directly, not even dereferenced.

Remove the pointer from the UIControlState parameter in the signature and you will be OK.

-(void)setBarColor:(UIColor *)color forState:(UIControlState)state

This will then meet the UISegmentedControl's method signature:

-(void)setBackgroundImage:(UIImage *)backgroundImage forState:(UIControlState)state barMetrics:(UIBarMetrics)barMetrics
answered on Stack Overflow Aug 30, 2013 by allprog
0

Have you tried running this with zombies turned on?

EXC_BAD_ACCESS mostly occurs because you are trying to do something with memory that has already been freed and the stuff that's there is junk now.

It looks like in your code:

frame #1: 0x0059bcd5 UIKit`-[UISegmentedControl _setBackgroundImage:forState:barMetrics:] + 148

Is the last thing that we see before you have this error.

Put a breakpoint in here and check that the params are real, etc. Or

The reason you only see this crash sometimes probably has to do with memory usage. Other stuff going in in your code, the simulator, the device, etc is causing memory to cleaned up and re-used. For whatever reason, deallocators are not getting called most of the time, and so the pointer you have to that block of memory remains valid even though according to the semantics from your program it doesn't necessarily have to be. In other more stressed situations, it's probably that memory is being reclaimed, deallocators are run, and now the system knows that that memory has been returned to the system's allocation heap and so it throws an exception when you try to fiddle with it.

answered on Stack Overflow Aug 30, 2013 by Jeb

User contributions licensed under CC BY-SA 3.0