HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

UIView subclass

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
uiviewsubclassstackoverflow

Problem

Generally, UI code is painful and full of kludges (at least, mine was like that). Recently I have decided to put an end to it and try to learn how to write good and reusable UI code.

So here is the pattern (in a general sense of word) I came up with when subclassing UIViews:

  • Subviews are properties and are initialized lazily;



  • Lazy initialization creates subviews with specific configuration;



  • Some of the parameters – especially platform-specific or those regarding the appearance – are extracted to class methods that are easily overridable in case it is needed;



  • Positioning is done in -layoutSubviews



  • Custom views = custom and more contextual delegate protocols



  • To display app-specific models, it is possible to use a category on the view providing a method to bind model fields and view fields together (to avoid cluttering the controller.)



So far it has proven itself to be rather effective and producing compact code. Moreover, UI code is more segmented, each part doing its job and nothing more.

I would like to know whether it is done right and whether this pattern can be followed. Also it would be great to know if there are any possible pitfalls with it I don't see.

IMInputField.h

#import 

//----------------------------------------------------
#pragma mark - Delegate Protocol
//----------------------------------------------------

@class IMInputField;

@protocol IMInputFieldDelegate 

- (void)inputFieldChangedText:(IMInputField *)field;

- (void)inputFieldBecameFirstResponder:(IMInputField *)field;

- (void)inputFieldResignedFirstResponder:(IMInputField *)field;

@end

//----------------------------------------------------
#pragma mark - Class
//----------------------------------------------------

@interface IMInputField : UIView

@property (nonatomic) NSString *text;
@property (nonatomic) UIView *inputAccessory;

@property (nonatomic, assign) BOOL exitWithReturn;

@property (nonatomic, weak) IBOutlet id delegate;

@end


IMInputField.m

`

Solution

The first thing that stands out to me immediately is the lack of @required/@optional in the @protocol.

This is mostly a readability thing, but you should certainly mark each of your methods to make your intent explicit. Once you've done this, internally, you can skip the respondsToSelector: check for any methods marked as @required.

I'm not familiar with these:

  • im_is_iOS7



  • im_appPlatformIsIPad



I mean, I can guess at what they do. Are they part of UIDevice and I just don't know about them? If so, ignore this. But if not...

They're not defined in either of your files, and I see no #imports. If you want this code to truly be reusable, everything needs to be self-contained. You can create a UIDevice class category in this same .m if you only need to add a couple methods to it.

The three methods in your section labeled Elements could be improved using a model something like this:

+ (UIImage *)backgroundImage {
    static UIImage *ret = nil;

    if (!ret) {
        if ([UIDevice im_is_iOS7]) {
            ret = [UIImage stretchableImageNamed:@"textfield_background.png"];
        } else /* iOS 6 and older */ {
            ret = [UIImage stretchableImageNamed:@"text_field.png"];
        }
    }

    return ret;
}


This pattern can be applied to all three methods. There's no need to go through the process of creating or reassigning these values each time the method is called. Background image is only different depending on iOS version, which won't change while your app is running. And textInserts is only different depending on whether or not the device is an iPad... and that will never change. And default font never changes.

There's not a big difference here, but using this pattern, you'll run ever so slightly faster, and potentially be using a bit less memory.

[self addSubview:_textView];
[self bringSubviewToFront:_textView];


I'm quite sure the bringSubviewToFront is redundant here. addSubview: adds the view at the front. The only place I can imagine bringSubviewToFront is perhaps if you added an else to match if (_textView == nil). If _textView isn't nil, then we can at least make sure it's the front-most view.

Code Snippets

+ (UIImage *)backgroundImage {
    static UIImage *ret = nil;

    if (!ret) {
        if ([UIDevice im_is_iOS7]) {
            ret = [UIImage stretchableImageNamed:@"textfield_background.png"];
        } else /* iOS 6 and older */ {
            ret = [UIImage stretchableImageNamed:@"text_field.png"];
        }
    }

    return ret;
}
[self addSubview:_textView];
[self bringSubviewToFront:_textView];

Context

StackExchange Code Review Q#41066, answer score: 4

Revisions (0)

No revisions yet.