forked from PureLayout/PureLayout
-
Notifications
You must be signed in to change notification settings - Fork 0
/
NSLayoutConstraint+PureLayout.m
executable file
·552 lines (493 loc) · 21.9 KB
/
NSLayoutConstraint+PureLayout.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
//
// NSLayoutConstraint+PureLayout.m
// https://github.com/PureLayout/PureLayout
//
// Copyright (c) 2013-2015 Tyler Fox
//
// This code is distributed under the terms and conditions of the MIT license.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
//
#import "NSLayoutConstraint+PureLayout.h"
#import "ALView+PureLayout.h"
#import "NSArray+PureLayout.h"
#import "PureLayout+Internal.h"
#pragma mark - NSLayoutConstraint+PureLayout
@implementation NSLayoutConstraint (PureLayout)
#pragma mark Batch Constraint Creation
/**
A global variable that stores a stack of arrays of constraints created without being immediately installed.
When executing a constraints block passed into the +[autoCreateConstraintsWithoutInstalling:] method, a new
mutable array is pushed onto this stack, and all constraints created with PureLayout in the block are added
to this array. When the block finishes executing, the array is popped off this stack. Automatic constraint
installation is prevented if this stack contains at least 1 array.
NOTE: Access to this variable is not synchronized (and should only be done on the main thread).
*/
static __NSMutableArray_of(__NSMutableArray_of(NSLayoutConstraint *) *) *_al_arraysOfCreatedConstraints = nil;
/**
A global variable that is set to YES when installing a batch of constraints collected from a call to +[autoCreateAndInstallConstraints].
When this flag is YES, constraints are installed immediately without checking for or adding to the +[al_currentArrayOfCreatedConstraints].
This is necessary to properly handle nested calls to +[autoCreateAndInstallConstraints], where calls whose block contains other call(s)
should not return constraints from within the blocks of nested call(s).
*/
static BOOL _al_isInstallingCreatedConstraints = NO;
/**
Accessor for the global state that stores arrays of constraints created without being installed.
*/
+ (__NSMutableArray_of(__NSMutableArray_of(NSLayoutConstraint *) *) *)al_arraysOfCreatedConstraints
{
NSAssert([NSThread isMainThread], @"PureLayout is not thread safe, and must be used exclusively from the main thread.");
if (!_al_arraysOfCreatedConstraints) {
_al_arraysOfCreatedConstraints = [NSMutableArray new];
}
return _al_arraysOfCreatedConstraints;
}
/**
Accessor for the current mutable array of constraints created without being immediately installed.
*/
+ (__NSMutableArray_of(NSLayoutConstraint *) *)al_currentArrayOfCreatedConstraints
{
return [[self al_arraysOfCreatedConstraints] lastObject];
}
/**
Accessor for the global state that determines whether automatic constraint installation should be prevented.
*/
+ (BOOL)al_preventAutomaticConstraintInstallation
{
return (_al_isInstallingCreatedConstraints == NO) && ([[self al_arraysOfCreatedConstraints] count] > 0);
}
/**
Creates all of the constraints in the block, then installs (activates) them all at once.
All constraints created from calls to the PureLayout API in the block are returned in a single array.
This may be more efficient than installing (activating) each constraint one-by-one.
Note: calls to this method may be nested. The constraints returned from a call will NOT include constraints
created in nested calls; constraints are only returned from the inner-most call they are created within.
@param block A block of method calls to the PureLayout API that create constraints.
@return An array of the constraints that were created from calls to the PureLayout API inside the block.
*/
+ (PL__NSArray_of(NSLayoutConstraint *) *)autoCreateAndInstallConstraints:(ALConstraintsBlock)block
{
NSArray *createdConstraints = [self autoCreateConstraintsWithoutInstalling:block];
_al_isInstallingCreatedConstraints = YES;
[createdConstraints autoInstallConstraints];
_al_isInstallingCreatedConstraints = NO;
return createdConstraints;
}
/**
Creates all of the constraints in the block but prevents them from being automatically installed (activated).
All constraints created from calls to the PureLayout API in the block are returned in a single array.
Note: calls to this method may be nested. The constraints returned from a call will NOT include constraints
created in nested calls; constraints are only returned from the inner-most call they are created within.
@param block A block of method calls to the PureLayout API that create constraints.
@return An array of the constraints that were created from calls to the PureLayout API inside the block.
*/
+ (PL__NSArray_of(NSLayoutConstraint *) *)autoCreateConstraintsWithoutInstalling:(ALConstraintsBlock)block
{
NSAssert(block, @"The constraints block cannot be nil.");
NSArray *createdConstraints = nil;
if (block) {
[[self al_arraysOfCreatedConstraints] addObject:[NSMutableArray new]];
block();
createdConstraints = [self al_currentArrayOfCreatedConstraints];
[[self al_arraysOfCreatedConstraints] removeLastObject];
}
return createdConstraints;
}
#pragma mark Set Priority For Constraints
/**
A global variable that stores a stack of layout priorities to set on constraints.
When executing a constraints block passed into the +[autoSetPriority:forConstraints:] method, the priority for
that call is pushed onto this stack, and when the block finishes executing, that priority is popped off this
stack. If this stack contains at least 1 priority, the priority at the top of the stack will be set for all
constraints created by this library (even if automatic constraint installation is being prevented).
NOTE: Access to this variable is not synchronized (and should only be done on the main thread).
*/
static __NSMutableArray_of(NSNumber *) *_al_globalConstraintPriorities = nil;
/**
Accessor for the global stack of layout priorities.
*/
+ (__NSMutableArray_of(NSNumber *) *)al_globalConstraintPriorities
{
NSAssert([NSThread isMainThread], @"PureLayout is not thread safe, and must be used exclusively from the main thread.");
if (!_al_globalConstraintPriorities) {
_al_globalConstraintPriorities = [NSMutableArray new];
}
return _al_globalConstraintPriorities;
}
/**
Returns the current layout priority to use for constraints.
When executing a constraints block passed into +[autoSetPriority:forConstraints:], this will return
the priority for the current block. Otherwise, the default Required priority is returned.
*/
+ (ALLayoutPriority)al_currentGlobalConstraintPriority
{
__NSMutableArray_of(NSNumber *) *globalConstraintPriorities = [self al_globalConstraintPriorities];
if ([globalConstraintPriorities count] == 0) {
return ALLayoutPriorityRequired;
}
return [[globalConstraintPriorities lastObject] floatValue];
}
/**
Accessor for the global state that determines if we're currently in the scope of a priority constraints block.
*/
+ (BOOL)al_isExecutingPriorityConstraintsBlock
{
return [[self al_globalConstraintPriorities] count] > 0;
}
/**
Sets the constraint priority to the given value for all constraints created using the PureLayout
API within the given constraints block.
NOTE: This method will have no effect (and will NOT set the priority) on constraints created or added
without using the PureLayout API!
@param priority The layout priority to be set on all constraints created in the constraints block.
@param block A block of method calls to the PureLayout API that create and install constraints.
*/
+ (void)autoSetPriority:(ALLayoutPriority)priority forConstraints:(ALConstraintsBlock)block
{
NSAssert(block, @"The constraints block cannot be nil.");
if (block) {
[[self al_globalConstraintPriorities] addObject:@(priority)];
block();
[[self al_globalConstraintPriorities] removeLastObject];
}
}
#pragma mark Identify Constraints
#if PL__PureLayout_MinBaseSDK_iOS_8_0 || PL__PureLayout_MinBaseSDK_OSX_10_10
/**
A global variable that stores a stack of identifier strings to set on constraints.
When executing a constraints block passed into the +[autoSetIdentifier:forConstraints:] method, the identifier for
that call is pushed onto this stack, and when the block finishes executing, that identifier is popped off this
stack. If this stack contains at least 1 identifier, the identifier at the top of the stack will be set for all
constraints created by this library (even if automatic constraint installation is being prevented).
NOTE: Access to this variable is not synchronized (and should only be done on the main thread).
*/
static __NSMutableArray_of(NSString *) *_al_globalConstraintIdentifiers = nil;
/**
Accessor for the global state of constraint identifiers.
*/
+ (__NSMutableArray_of(NSString *) *)al_globalConstraintIdentifiers
{
NSAssert([NSThread isMainThread], @"PureLayout is not thread safe, and must be used exclusively from the main thread.");
if (!_al_globalConstraintIdentifiers) {
_al_globalConstraintIdentifiers = [NSMutableArray new];
}
return _al_globalConstraintIdentifiers;
}
/**
Returns the current identifier string to use for constraints.
When executing a constraints block passed into +[autoSetIdentifier:forConstraints:], this will return
the identifier for the current block. Otherwise, nil is returned.
*/
+ (NSString *)al_currentGlobalConstraintIdentifier
{
__NSMutableArray_of(NSString *) *globalConstraintIdentifiers = [self al_globalConstraintIdentifiers];
if ([globalConstraintIdentifiers count] == 0) {
return nil;
}
return [globalConstraintIdentifiers lastObject];
}
/**
Sets the identifier for all constraints created using the PureLayout API within the given constraints block.
NOTE: This method will have no effect (and will NOT set the identifier) on constraints created or added
without using the PureLayout API!
@param identifier A string used to identify all constraints created in the constraints block.
@param block A block of method calls to the PureLayout API that create and install constraints.
*/
+ (void)autoSetIdentifier:(NSString *)identifier forConstraints:(ALConstraintsBlock)block
{
NSAssert(block, @"The constraints block cannot be nil.");
NSAssert(identifier, @"The identifier string cannot be nil.");
if (block) {
if (identifier) {
[[self al_globalConstraintIdentifiers] addObject:identifier];
}
block();
if (identifier) {
[[self al_globalConstraintIdentifiers] removeLastObject];
}
}
}
/**
Sets the string as the identifier for this constraint. Available in iOS 7.0 and OS X 10.9 and later.
The identifier will be printed along with the constraint's description.
This is helpful to document a constraint's purpose and aid in debugging.
@param identifier A string used to identify this constraint.
@return This constraint.
*/
- (instancetype)autoIdentify:(NSString *)identifier
{
if ([self respondsToSelector:@selector(setIdentifier:)]) {
self.identifier = identifier;
}
return self;
}
#endif /* PL__PureLayout_MinBaseSDK_iOS_8_0 || PL__PureLayout_MinBaseSDK_OSX_10_10 */
#pragma mark Install & Remove Constraints
/**
Activates the constraint.
*/
- (void)autoInstall
{
#if PL__PureLayout_MinBaseSDK_iOS_8_0 || PL__PureLayout_MinBaseSDK_OSX_10_10
if ([self respondsToSelector:@selector(setActive:)]) {
[NSLayoutConstraint al_applyGlobalStateToConstraint:self];
if ([NSLayoutConstraint al_preventAutomaticConstraintInstallation]) {
[[NSLayoutConstraint al_currentArrayOfCreatedConstraints] addObject:self];
} else {
self.active = YES;
}
return;
}
#endif /* PL__PureLayout_MinBaseSDK_iOS_8_0 || PL__PureLayout_MinBaseSDK_OSX_10_10 */
NSAssert(self.firstItem || self.secondItem, @"Can't install a constraint with nil firstItem and secondItem.");
if (self.firstItem) {
if (self.secondItem) {
NSAssert([self.firstItem isKindOfClass:[ALView class]] && [self.secondItem isKindOfClass:[ALView class]], @"Can only automatically install a constraint if both items are views.");
ALView *commonSuperview = [self.firstItem al_commonSuperviewWithView:self.secondItem];
[commonSuperview al_addConstraint:self];
} else {
NSAssert([self.firstItem isKindOfClass:[ALView class]], @"Can only automatically install a constraint if the item is a view.");
[self.firstItem al_addConstraint:self];
}
} else {
NSAssert([self.secondItem isKindOfClass:[ALView class]], @"Can only automatically install a constraint if the item is a view.");
[self.secondItem al_addConstraint:self];
}
}
/**
Deactivates the constraint.
*/
- (void)autoRemove
{
#if PL__PureLayout_MinBaseSDK_iOS_8_0 || PL__PureLayout_MinBaseSDK_OSX_10_10
if ([self respondsToSelector:@selector(setActive:)]) {
self.active = NO;
return;
}
#endif /* PL__PureLayout_MinBaseSDK_iOS_8_0 || PL__PureLayout_MinBaseSDK_OSX_10_10 */
if (self.secondItem) {
ALView *commonSuperview = [self.firstItem al_commonSuperviewWithView:self.secondItem];
while (commonSuperview) {
if ([commonSuperview.constraints containsObject:self]) {
[commonSuperview removeConstraint:self];
return;
}
commonSuperview = commonSuperview.superview;
}
}
else {
[self.firstItem removeConstraint:self];
return;
}
NSAssert(nil, @"Failed to remove constraint: %@", self);
}
#pragma mark Internal Methods
/**
Applies the global constraint priority and identifier to the given constraint.
This should be done before installing all constraints.
@param constraint The constraint to set the global priority and identifier on.
*/
+ (void)al_applyGlobalStateToConstraint:(NSLayoutConstraint *)constraint
{
if ([NSLayoutConstraint al_isExecutingPriorityConstraintsBlock]) {
constraint.priority = [NSLayoutConstraint al_currentGlobalConstraintPriority];
}
#if PL__PureLayout_MinBaseSDK_iOS_8_0 || PL__PureLayout_MinBaseSDK_OSX_10_10
NSString *globalConstraintIdentifier = [NSLayoutConstraint al_currentGlobalConstraintIdentifier];
if (globalConstraintIdentifier) {
[constraint autoIdentify:globalConstraintIdentifier];
}
#endif /* PL__PureLayout_MinBaseSDK_iOS_8_0 || PL__PureLayout_MinBaseSDK_OSX_10_10 */
}
/**
Returns the corresponding NSLayoutAttribute for the given ALAttribute.
@return The layout attribute for the given ALAttribute.
*/
+ (NSLayoutAttribute)al_layoutAttributeForAttribute:(ALAttribute)attribute
{
NSLayoutAttribute layoutAttribute = NSLayoutAttributeNotAnAttribute;
switch (attribute) {
case ALEdgeLeft:
layoutAttribute = NSLayoutAttributeLeft;
break;
case ALEdgeRight:
layoutAttribute = NSLayoutAttributeRight;
break;
case ALEdgeTop:
layoutAttribute = NSLayoutAttributeTop;
break;
case ALEdgeBottom:
layoutAttribute = NSLayoutAttributeBottom;
break;
case ALEdgeLeading:
layoutAttribute = NSLayoutAttributeLeading;
break;
case ALEdgeTrailing:
layoutAttribute = NSLayoutAttributeTrailing;
break;
case ALDimensionWidth:
layoutAttribute = NSLayoutAttributeWidth;
break;
case ALDimensionHeight:
layoutAttribute = NSLayoutAttributeHeight;
break;
case ALAxisVertical:
layoutAttribute = NSLayoutAttributeCenterX;
break;
case ALAxisHorizontal:
layoutAttribute = NSLayoutAttributeCenterY;
break;
case ALAxisBaseline: // same value as ALAxisLastBaseline
layoutAttribute = NSLayoutAttributeBaseline;
break;
#if PL__PureLayout_MinBaseSDK_iOS_8_0
case ALAxisFirstBaseline:
NSAssert(PL__PureLayout_MinSysVer_iOS_8_0, @"ALAxisFirstBaseline is only supported on iOS 8.0 or higher.");
layoutAttribute = NSLayoutAttributeFirstBaseline;
break;
case ALMarginLeft:
NSAssert(PL__PureLayout_MinSysVer_iOS_8_0, @"ALEdgeLeftMargin is only supported on iOS 8.0 or higher.");
layoutAttribute = NSLayoutAttributeLeftMargin;
break;
case ALMarginRight:
NSAssert(PL__PureLayout_MinSysVer_iOS_8_0, @"ALEdgeRightMargin is only supported on iOS 8.0 or higher.");
layoutAttribute = NSLayoutAttributeRightMargin;
break;
case ALMarginTop:
NSAssert(PL__PureLayout_MinSysVer_iOS_8_0, @"ALEdgeTopMargin is only supported on iOS 8.0 or higher.");
layoutAttribute = NSLayoutAttributeTopMargin;
break;
case ALMarginBottom:
NSAssert(PL__PureLayout_MinSysVer_iOS_8_0, @"ALEdgeBottomMargin is only supported on iOS 8.0 or higher.");
layoutAttribute = NSLayoutAttributeBottomMargin;
break;
case ALMarginLeading:
NSAssert(PL__PureLayout_MinSysVer_iOS_8_0, @"ALEdgeLeadingMargin is only supported on iOS 8.0 or higher.");
layoutAttribute = NSLayoutAttributeLeadingMargin;
break;
case ALMarginTrailing:
NSAssert(PL__PureLayout_MinSysVer_iOS_8_0, @"ALEdgeTrailingMargin is only supported on iOS 8.0 or higher.");
layoutAttribute = NSLayoutAttributeTrailingMargin;
break;
case ALMarginAxisVertical:
NSAssert(PL__PureLayout_MinSysVer_iOS_8_0, @"ALAxisVerticalMargin is only supported on iOS 8.0 or higher.");
layoutAttribute = NSLayoutAttributeCenterXWithinMargins;
break;
case ALMarginAxisHorizontal:
NSAssert(PL__PureLayout_MinSysVer_iOS_8_0, @"ALAxisHorizontalMargin is only supported on iOS 8.0 or higher.");
layoutAttribute = NSLayoutAttributeCenterYWithinMargins;
break;
#endif /* PL__PureLayout_MinBaseSDK_iOS_8_0 */
default:
NSAssert(nil, @"Not a valid ALAttribute.");
break;
}
return layoutAttribute;
}
/**
Returns the corresponding ALLayoutConstraintAxis for the given ALAxis.
@return The constraint axis for the given axis.
*/
+ (ALLayoutConstraintAxis)al_constraintAxisForAxis:(ALAxis)axis
{
ALLayoutConstraintAxis constraintAxis;
switch (axis) {
case ALAxisVertical:
constraintAxis = ALLayoutConstraintAxisVertical;
break;
case ALAxisHorizontal:
case ALAxisBaseline: // same value as ALAxisLastBaseline
#if PL__PureLayout_MinBaseSDK_iOS_8_0
case ALAxisFirstBaseline:
#endif /* PL__PureLayout_MinBaseSDK_iOS_8_0 */
constraintAxis = ALLayoutConstraintAxisHorizontal;
break;
default:
NSAssert(nil, @"Not a valid ALAxis.");
constraintAxis = ALLayoutConstraintAxisHorizontal; // default to an arbitrary value to satisfy the compiler
break;
}
return constraintAxis;
}
#if PL__PureLayout_MinBaseSDK_iOS_8_0
/**
Returns the corresponding margin for the given edge.
@param edge The edge to convert to the corresponding margin.
@return The margin for the given edge.
*/
+ (ALMargin)al_marginForEdge:(ALEdge)edge
{
NSAssert(PL__PureLayout_MinSysVer_iOS_8_0, @"Margin attributes are only supported on iOS 8.0 or higher.");
ALMargin margin;
switch (edge) {
case ALEdgeLeft:
margin = ALMarginLeft;
break;
case ALEdgeRight:
margin = ALMarginRight;
break;
case ALEdgeTop:
margin = ALMarginTop;
break;
case ALEdgeBottom:
margin = ALMarginBottom;
break;
case ALEdgeLeading:
margin = ALMarginLeading;
break;
case ALEdgeTrailing:
margin = ALMarginTrailing;
break;
default:
NSAssert(nil, @"Not a valid ALEdge.");
margin = ALMarginLeft; // default to an arbitrary value to satisfy the compiler
break;
}
return margin;
}
/**
Returns the corresponding margin axis for the given axis.
@param axis The axis to convert to the corresponding margin axis.
@return The margin axis for the given axis.
*/
+ (ALMarginAxis)al_marginAxisForAxis:(ALAxis)axis
{
NSAssert(PL__PureLayout_MinSysVer_iOS_8_0, @"Margin attributes are only supported on iOS 8.0 or higher.");
ALMarginAxis marginAxis;
switch (axis) {
case ALAxisVertical:
marginAxis = ALMarginAxisVertical;
break;
case ALAxisHorizontal:
marginAxis = ALMarginAxisHorizontal;
break;
case ALAxisBaseline:
case ALAxisFirstBaseline:
NSAssert(nil, @"The baseline axis attributes do not have corresponding margin axis attributes.");
marginAxis = ALMarginAxisVertical; // default to an arbitrary value to satisfy the compiler
break;
default:
NSAssert(nil, @"Not a valid ALAxis.");
marginAxis = ALMarginAxisVertical; // default to an arbitrary value to satisfy the compiler
break;
}
return marginAxis;
}
#endif /* PL__PureLayout_MinBaseSDK_iOS_8_0 */
@end