Reputation: 3359
I'm having a hard time figuring out how to get my desired result with constraints. I could do this via programmatically but it seems like there should be a better way.
Basically I have a keyboard.
The leftmost key (Q) and the rightmost key (P) should be 3 points from the left and right sides.
The 8 keys in-between the Q and the P keys should be equally spaced between the Q and P keys
The keys should stretch in width to fill up the entire width of space between the Q and P keys, while still maintaining equal space between each other.
Basically the keyboard should do what Apple's native keyboard does when it rotates in landscape.
Can this be done with constraints or do I need to do this programmatically without constraints?
Upvotes: 3
Views: 599
Reputation: 13887
Yes. Auto layout can do this.
Provided that you want the layout to remain the same for landscape and portrait orientations (I'm pretty sure that you do), then once your constraints are set up, everything should all just work. If you want them to change, that's possible, but I'm not going to address this as it doesn't seem required.
In your question, you say:
Can this be done with constraints or do I need to do this programmatically without constraints?
I'm not certain, but I want to check you understand that Auto Layout isn't an interface builder only thing. You can also very naturally use Auto Layout from code by manipulating NSLayoutConstraint
s and adding them to views. So, you have probably at least three options:
layoutSubviews:
or something like that.You could set all of this up in IB. Each key will need two horizontal constraints and two vertical constraints, so you'll be managing at least 104 constraints for a 26 key keyboard. Plus digits. And symbols. Doing this by hand will be excruciating, and when you get it right, someone in your team (maybe even you) will decide the key spacing just has to be one pixel more.
A keyboard grid has so much regularity and so many constraints that it will be much easier to just use a bit of code to set the constraints up.
To be clear: you will still be using Auto Layout, but you will be creating the constraints with code instead of maintaining them by hand.
I'm assuming you want the keys to have equal sizes. You can impose this, along with equal (specified) spacing between them and a fixed margin at the left and right to the superview with a constraint like this:
// Substitute your view names here.
UIButton *q, *w, *e, *r, *t, *y, *u, *i, *o, *p;
NSDictionary *const metrics = @{@"margin": @(3), @"gap": @(4)};
NSDictionary *const views = NSDictionaryOfVariableBindings(q,w,e,r,t,y,u,i,o,p);
NSArray *const constraints =
[NSLayoutConstraint
constraintsWithVisualFormat:
@"H:|-margin-[q]-gap-[w(q)]-gap-[e(q)]-gap-[r(q)]-gap-[t(q)]-gap-[y(q)]-gap-[u(q)]-gap-[i(q)]-gap-[o(q)]-gap-[p(q)]-margin-|"
options: NSLayoutFormatDirectionLeadingToTrailing
metrics: metrics
views: views];
// Add constraints to your container view so that they do something.
UIView *const container = [self view];
[container addConstraints: constraints];
But I'd be tempted to build a helper method that would take an array of views and generate the constraints from that. It'd go something like this:
-(NSArray *) constraintsForEqualSizeViews: (NSArray *) keys
withSuperviewMargin: (CGFloat) margin
andSpace: (CGFloat) space
{
NSMutableArray *const constraints = [NSMutableArray new];
// Note: the keys must be in a superview by now so that
// a constraint can be set against its edge.
UIView *const leftKey = [keys firstObject];
[constraints addObject:
[NSLayoutConstraint constraintWithItem: leftKey
attribute: NSLayoutAttributeLeft
relatedBy: NSLayoutRelationEqual
toItem: [leftKey superview]
attribute: NSLayoutAttributeLeft
multiplier: 0
constant: margin]];
UIView *const rightKey = [keys firstObject];
[constraints addObject:
[NSLayoutConstraint constraintWithItem: rightKey
attribute: NSLayoutAttributeRight
relatedBy: NSLayoutRelationEqual
toItem: [rightKey superview]
attribute: NSLayoutAttributeRight
multiplier: 0
constant: margin]];
UIView *const templateKeyForEqualSizes = leftKey;
NSDictionary *const metrics = @{@"space": @(space)};
for(NSUInteger i=1; i<[keys count]; ++i)
{
NSDictionary *const views = @{@"previousKey": keys[i-1],
@"thisKey": keys[i],
@"templateKey": templateKeyForEqualSizes};
[constraints addObjectsFromArray:
[NSLayoutConstraint constraintsWithVisualFormat: @"H:[previousKey]-space-[thisKey(templateKey)]"
options: NSLayoutFormatDirectionLeftToRight
metrics: metrics
views: views]];
}
NSArray *const immutableArrayToReturn = [constraints copy];
return immutableArrayToReturn;
}
The auto-layout language is described in Apple's documentation.
UIView
FramesYou don't need to think about setting up the original frames for the keys' views – this is another reason to keep away from interface builder for describing the keys. You could create your views like this:
-(NSArray*) keyRowArray: (NSString*) keyNamesString
{
const NSUInteger keyCount = [keyNamesString length];
NSMutableArray *const keys = [NSMutableArray arrayWithCapacity: keyCount];
for(NSUInteger i=0; i<keyCount; ++i)
{
NSString *const keyName = [keyNamesString substringWithRange:NSMakeRange(i, 1)];
// Look! No frame needed!
XXYourCustomKeyView *const key = [XXYourCustomKeyView new];
// Do key setup – you probably want to add button actions, and such.
[key setName: keyName];
[keys addObject: keyName];
}
return [keys copy];
}
UIView
ownershipI wouldn't manage all these keys in a UIViewController directly. I'd encapsulate them inside a XXYourCustomKeyboardView
class that can take a keyboard description (something like an array of strings of the keyboard rows). Your controller can create this, pass in the configuration, set itself as a delegate and happy days.
It's also possible that you could use a hybrid approach where you set up some keys and constraints in IB:
You can then add the keys between those using code similar to the above.
Working from the constraint creation method I gave above, you might find it more flexible if you split it in to separate methods in a category on NSLayoutConstraint
:
@interface NSLayoutConstraint (Keyboard)
// This is what it constraint method currently creates in the for loop.
+(NSArray*) constraintsForViews: (NSArray*) views imposingEqualSpace: (CGFloat) space;
+(NSArray*) constraintsForViewsImposingEqualWidth: (NSArray*) views;
// This will also be useful to align a whole row.
+(NSArray*) constraintsForViewsImposingSameTopAndBottom: (NSArray*) views;
@end
Let me know in comments if any of this isn't clear. I'll add clarifications if necessary.
Upvotes: 9