Search code examples
objective-cuiviewuiviewcontrollercore-animationios5

"Compiler Bug", "Segmentation Fault 11" On Custom View Controller


I'm having a very weird problem, that I think I know why I'm getting it but I can't seem to think of a fix. I am convinced there is no compiler bug here. I have had many of those and all of them occurred because of my faulty code.

Basically, I have a view controller called UnlockKeyboardViewController that inherits from UIViewController, and a custom view (inheriting directly from UIView) called UnlockKeyboard. The header for UnlockKeyboard looks like this:

@interface UnlockKeyboard : UIView
{
    NSArray *buttons;
    NSArray *passcodeFields;

    UIImage *buttonBackgroundImage;
    UIImage *buttonBackgroundHighlightedImage;
    UIImage *middleButtonBackgroundImage;
    UIImage *middleButtonBackgroundImageHighlighted;
    UIImage *screenBackgroundImage;

    UIImage *infoViewContainerImage;
    UIImage *keypadViewContainerImage;
    UIImage *passcodeFieldsContainerImage;

    UIImage *infoViewImage;
    UIImage *passcodeViewImage;

    UIView *infoViewContainer;
    UIView *keypadViewContainer;
    UIView *passcodeFieldsContainer;

    UIView *infoView;
    UIView *keypadView;
    UIView *passcodeFieldsView;

    UIView *infoToDisplayView; //The view the programmer passes to show in infoView.
}
@property(nonatomic, retain)UIImage *buttonBackgroundImage;
@property(nonatomic, retain)UIImage *buttonBackgroundHighlightedImage;
@property(nonatomic, retain)UIImage *screenBackgroundImage;
@property(nonatomic, retain)UIImage *keypadViewBackgroundImage;
@property(nonatomic, retain)UIImage *infoViewContainerImage;
@property(nonatomic, retain)UIImage *keypadViewContainerImage;
@property(nonatomic, retain)UIImage *passcodeFieldsContainerImage;
@property(nonatomic, retain)UIImage *infoViewImage;
@property(nonatomic, retain)UIImage *passcodeViewImage;
@property(nonatomic, retain)UIView *infoToDisplayView;

//Properties for container views.
@property(nonatomic, retain)UIView *infoViewContainer;
@property(nonatomic, retain)UIView *keypadViewContainer;
@property(nonatomic, retain)UIView *passcodeFieldsContainer;

@end

The UnlockKeyboardViewController implementation looks like this so far:

@implementation UnlockKeyboardViewController
-(id)init
{
    if((self = [super init]))
    {
        self.view = [[UnlockKeyboard alloc] init];
    }
    return self;
}

-(void)viewDidLoad
{
    [super viewDidLoad];
}

-(void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
    [UIView animateWithDuration:0.7 animations:^{
        self.view.keypadViewContainer.frame = CGRectMake(0, 261, 320, 200);
    } completion:^(BOOL finished){

    }];
}

-(void)dealloc
{
    [super dealloc];
}
@end

This is were my problem gets interesting. Whenever I try to compile, the Terminal (this is a jailbreak app, so no Xcode) gives me the following error:

In function ‘void __-[UnlockKeyboardViewController viewDidAppear:]_block_invoke_1(void*)’:
Segmentation fault: 11
Please submit a full bug report,
with preprocessed source if appropriate.
See <URL:http://developer.apple.com/bugreporter> for instructions.

BUT, this error only appears when I have this line in:

self.view.keypadViewContainer.frame = CGRectMake(0, 261, 320, 200);

If I don't that line in my animation block, the compiler won't give me that error and it wil compile just fine. Although, no matter were I put this line, I always get segmentation line 11.

I THINK this may have something to do with the fact that UIView doesn't have a member called keypadViewContainer, although UnlockKeyboard has that property and is a subclass of UIView. I believe this is happening because the compiler can't actually see the class hierarchy between UIView and UnlockKeyboard.

If I'm right, I have no idea of how to go around the problem. Been thinking for a while. Any input to help me solve this problem will be really appreciated.


Solution

  • First, you should only set a view controller's view property in its loadView method:

    - (void)loadView
    {
        self.view = [[UnlockKeyboard alloc] init];
    }
    

    This will trigger other methods, such as viewDidLoad, that will expect the object to have been full initialized. Moral of the story: don't set the view property until the controller asks you to.

    Then, in your viewDidAppear: method, casing the view property into the specific subclass you are using should avoid any warnings and hopefully also that weird compiler crash:

    [UIView animateWithDuration:0.7 animations:^{
        UnlockKeyboard *ukView = (UnlockKeyboard *)self.view;
        ukView.keypadViewContainer.frame = CGRectMake(0, 261, 320, 200);
    

    The code as you have written it should have triggered a compiler warning, because the property keypadViewContainer isn't defined on UIView (the type of the expression self.view).

    The type cast is admittedly a little bit ugly, but it's necessary since the view controller is a generic parent class and doesn't know what kind of view it is controlling. You may find it useful to define a property that eliminates the need to do the cast all the time:

    @property (nonatomic,readonly) UnlockKeyboard *unlockKeyboardView;
    

    And the corresponding method:

    - (UnlockKeyboard *)unlockKeyboardView
    {
        return self.view;
    }