6
\$\begingroup\$

I don't use constraints or IB to create views so it's easier to create rects this way. Does it wrong totaly?

@interface Client ()

@property (nonatomic, strong) UITextField *name;
@property (nonatomic, strong) UITextField *phoneNumber;
@property (nonatomic, strong) UITextField *address;
@property (nonatomic, strong) UIButton *submit;

@end

@implementation ClientAddVC

- (void)viewDidLoad {
    [super viewDidLoad];
    [self createSubviews];
    [self createViewsRects:@[_name, _phoneNumber, _address, _submit]];

}

- (void)createSubviews{

    _name = [Utility textFieldWithPadding:@"Name" frame:[Utility rectForFormFields]];
    _name.delegate = self;
    _phoneNumber = [Utility textFieldWithPadding:@"Phone Number" frame:[Utility rectForFormFields]];
    _phoneNumber.keyboardType = UIKeyboardTypePhonePad;
    _phoneNumber.delegate = self;
    _address = [Utility textFieldWithPadding:@"Address" frame:[Utility rectForFormFields]];
    _address.delegate = self;

    _submit = [UIButton buttonWithType:UIButtonTypeCustom];
    [_submit setTitle:@"Submit" forState:UIControlStateNormal];
    [_submit addTarget:self action:@selector(saveClient) forControlEvents:UIControlEventTouchUpInside];
    _submit.backgroundColor = [[Cache sharedCache]blackColor];
    _submit.frame = [Utility rectForFormFields];

    [self.view addSubview:_name];
    [self.view addSubview:_phoneNumber];
    [self.view addSubview:_address];
    [self.view addSubview:_specialRequest];
    [self.view addSubview:_averageOrderSize];
    [self.view addSubview:_submit];
}

- (void)saveClient{
}

- (void)textFieldDidBeginEditing:(UITextField *)textField{
    _currentField = textField;
}

- (void)createViewsRects:(NSArray *)views{
    CGFloat padding = 16;
    CGFloat rowPadding = 8;
    CGFloat totalHeight = padding;

    for (UIView *view in views) {
        CGRect viewRect = view.frame;
        viewRect.origin.x = padding;
        viewRect.origin.y = totalHeight;
        view.frame = viewRect;
        totalHeight += (view.frame.size.height + rowPadding);
    }
    self.bgView.contentSize = CGSizeMake(self.view.frame.size.width, totalHeight);
}
@end
\$\endgroup\$
  • 2
    \$\begingroup\$ Why aren't you using auto-layout? \$\endgroup\$ – nhgrif Nov 5 '14 at 21:17
  • \$\begingroup\$ I don't like it. I know I write too much code here, but I feel more comfortable that way. \$\endgroup\$ – Pkwies Nov 5 '14 at 21:22
7
\$\begingroup\$

We should be using auto-layout. Not being as comfortable with auto-layout is not an excuse to not use it. Manually setting rects is not very good at all. Manually set rects don't play well with rotation. Manually set rects mean we probably need a special set of code for iPad versus iPhone. Manually set rects mean we probably need a special set of code for iPhone 4 vs iPhone 5 vs iPhone 6 vs iPhone 6+.

I'm not really going to touch the createSubviews method, which has some issues that should be address, but I do want to point out the problems with createViewRects: and how we can more easily write this code with auto-layout and using the Visual Format Language quite simply.

The method sets up the position for four views. The spacing follows two rules:

  1. Each view has an X of 16 pixels from the left edge.
  2. Each view has a Y that leaves a gap of 8 pixels between the other views (plus 8 pixels between the top of the super view and the top of the top view and 8 pixels between the bottom of the super view and the bottom of the bottom view).

For this, I'm going to assume that bgView is a scroll view. Scroll view's content doesn't really work particularly well with multiple top-level subviews. Instead, we give the bgView a single subview and add all of the subviews to that subview. This will allow auto-layout to work well.

Let's define a couple dictionaries to use with VFL.

For starters, so we can put them in a dictionary, let's use NSNumber objects rather than CGFloat for our metrics:

NSNumber *padding = @16.0;
NSNumber *rowPadding = @8.0;

We don't need a total height. Auto-layout will resolve this for us. Anyway, creating a dictionary is as simple as this:

NSDictionary *metrics = NSDictionaryOfVariableBindings(padding, rowPadding);

Now, using a mutable string, a mutable dictionary, let's set up these constraints:

int viewIndex = 0;
NSMutableString *vertConstraintsStr = [@"V:|-[rowPadding]-" mutableCopy];
NSMutableDictionary *views = [NSMutableDictionary dictionary];

UIView *backgroundView = [[UIView alloc] init];

for (UIView *view in views) {
    NSString *viewKey = [NSString stringWithFormat:@"view%i", viewIndex];
    ++viewIndex;

    [backgroundView addSubview:view];

    views[viewKey] = view;

    view.translatesAutoresizingMaskIntoConstraints = NO;

    [vertConstraintsStr addFormat:@"[%@]-[rowPadding]-", viewKey];

    NSString *horizConstraintStr = 
        [NSString stringWithFormat:@"H:|-[padding]-[%@]", viewKey];

    NSArray *horizontalConstraints = 
        [NSLayoutConstraint constraintsWithVisualFormat:horizConstraintStr
                                                options:0
                                                metrics:metrics
                                                  views:views];

    [backgroundView addConstraints:horizontalConstraints];
}

[vertConstraintsStr addString:@"[rowPadding]-|"];

NSArray *verticalConstraints = 
    [NSLayoutConstraint constraintsWithVisualFormat:vertConstraintStr
                                            options:0
                                            metrics:metrics
                                              views:views];

[backgroundView addConstraints:verticalConstraints];

[self.bgView addSubview:backgroundView];

Ultimately, this is more lines of code, but this is much more flexible.

Consider what happens if we were to change the size of one of our subviews. With your code, we must be sure to call createViewsRect: each time any view changes sizes in order to ensure everything still looks right. With auto layout, the view will automatically adjust to the new size of one of the subviews. Plus, now you've got some experience working with auto layout so you'll be less intimidated and more comfortable with it in the future, particularly when you want to start doing more complicated layouts.

\$\endgroup\$
  • \$\begingroup\$ actually I don't need a set of special code for each size of iPhone. every frame sets itself according to view size(using screen bounds). so is there any performance gain with autolayout? and check this floriankugler.com/blog/2013/4/21/auto-layout-performance-on-ios \$\endgroup\$ – Pkwies Nov 6 '14 at 11:59
  • \$\begingroup\$ That blog post is a year and a half old. The frame sets itself because you put in a lot of code to handle each different type of device. As soon as a new device size comes out, you are going to have to revisit that code and retest it. Plus, explicit frame setting code doubles if you want to rotate. \$\endgroup\$ – nhgrif Nov 6 '14 at 12:27
  • 2
    \$\begingroup\$ Auto layout is the right way to do it. There's no reason not to use auto layout besides being uncomfortable with it, and that is an absurdly poor excuse if you ask me. \$\endgroup\$ – nhgrif Nov 6 '14 at 12:28
  • \$\begingroup\$ " that is an absurdly poor excuse if you ask me.", "Not being as comfortable with auto-layout is not an excuse to not use it. " beside giving your own opinions, can you provide me an article about pros and cons of using autolayout? if not,please stop saying it is wrong.I agree that, I should follow new stuff that Apple provides but for now, it is not the case. \$\endgroup\$ – Pkwies Nov 6 '14 at 14:05
  • \$\begingroup\$ You argument for it not being the case is that you're not comfortable with it. I've given you good reasons to use it. You've said you don't like it and cited an article written during Xcode 4 and iOS 6. \$\endgroup\$ – nhgrif Nov 6 '14 at 14:14

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.