Swift 安全区域布局指南和视觉格式语言
Swift Safe Area Layout Guide and Visual Format Language
我想使用 Apple 的视觉格式语言将视图限制为 iOS 11 中的新 Safe Area Layout Guide。但是,我得到一个例外:
-[NSLayoutYAxisAnchor nsli_superitem]: unrecognized selector sent to instance 0x1c447ed40
//Make View Dictionary
var views: [String: Any] = ["left": self.leftContainer]
//Check swift version and add appropriate piece to the view dictionary
if #available(iOS 11, *) {
views["topGuide"] = self.view.safeAreaLayoutGuide.topAnchor
}else{
views["topGuide"] = self.topLayoutGuide
}
//Make the constraint using visual format language
let leftVertical = NSLayoutConstraint.constraints(withVisualFormat: "V:[topGuide][left]|", options: [], metrics: nil, views: views)
//Add the new constraint
self.view.addConstraints(vertical)
我喜欢可视格式语言的原因是因为在某些情况下您可以用更少的代码添加很多约束。
有什么想法吗?
I want to use Apples visual format language to constrain a view to the new Safe Area Layout Guide
你不能。无法通过视觉格式语言访问安全区域布局指南。我已经提交了一个错误,我建议你也这样做。
我们在这里稍微扩展了视觉格式语言,所以现在您可以固定“<|”当您指的是 safeAreaLayoutGuide 时。我希望苹果能做那样的事情。
例如,如果您有以下 iOS 11 之前的代码:
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"V:[_button]-(normalPadding)-|"
options:0 metrics:metrics views:views
]];
现在您要确保按钮位于 iPhone X 上的安全底部边距上方,然后执行此操作:
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint
mmm_constraintsWithVisualFormat:@"V:[_button]-(normalPadding)-<|"
options:0 metrics:metrics views:views
]];
就是这样。它会将按钮锚定在 iOS 9 和 10 上其超级视图的底部,但在 iOS 11 上将其锚定在其 safeAreaLayoutGuide 的底部。
请注意,使用“|>”置顶不会排除 iOS 9 和 10 上的状态栏。
// In @interface/@implementation NSLayoutConstraint (MMMUtil)
// ...
+(NSArray<NSLayoutConstraint *> *)mmm_constraintsWithVisualFormat:(NSString *)format
options:(NSLayoutFormatOptions)opts
metrics:(NSDictionary<NSString *,id> *)metrics
views:(NSDictionary<NSString *,id> *)views
{
if ([format rangeOfString:@"<|"].location == NSNotFound && [format rangeOfString:@"|>"].location == NSNotFound ) {
// No traces of our special symbol, so do nothing special.
return [self constraintsWithVisualFormat:format options:opts metrics:metrics views:views];
}
if (![UIView instancesRespondToSelector:@selector(safeAreaLayoutGuide)]) {
// Before iOS 11 simply use the edges of the corresponding superview.
NSString *actualFormat = [format stringByReplacingOccurrencesOfString:@"<|" withString:@"|"];
actualFormat = [actualFormat stringByReplacingOccurrencesOfString:@"|>" withString:@"|"];
return [NSLayoutConstraint constraintsWithVisualFormat:actualFormat options:opts metrics:metrics views:views];
}
//
// OK, iOS 11+ time.
// For simplicity we replace our special symbols with a reference to a stub view, feed the updated format string
// to the system, and then replace every reference to our stub view with a corresponding reference to safeAreaLayoutGuide.
//
UIView *stub = [[UIView alloc] init];
static NSString * const stubKey = @"__MMMLayoutStub";
NSString *stubKeyRef = [NSString stringWithFormat:@"[%@]", stubKey];
NSDictionary *extendedViews = [@{ stubKey : stub } mmm_extendedWithDictionary:views];
NSString *actualFormat = [format stringByReplacingOccurrencesOfString:@"<|" withString:stubKeyRef];
actualFormat = [actualFormat stringByReplacingOccurrencesOfString:@"|>" withString:stubKeyRef];
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:actualFormat options:opts metrics:metrics views:extendedViews];
NSMutableArray *processedConstraints = [[NSMutableArray alloc] init];
for (NSLayoutConstraint *c in constraints) {
UIView *firstView = c.firstItem;
UIView *secondView = c.secondItem;
NSLayoutConstraint *processed;
if (firstView == stub) {
if (![secondView isKindOfClass:[UIView class]]) {
NSAssert(NO, @"We only support UIView with <| and |> anchors, got %@", secondView.class);
continue;
}
processed = [self
constraintWithItem:secondView.superview.safeAreaLayoutGuide attribute:_MMMOppositeAttribute(c.firstAttribute)
relatedBy:c.relation
toItem:secondView attribute:c.secondAttribute
multiplier:c.multiplier constant:c.constant
priority:c.priority
identifier:@"MMMSafeAreaFirstItemConstraint"
];
} else if (secondView == stub && [firstView isKindOfClass:[UIView class]]) {
if (![firstView isKindOfClass:[UIView class]]) {
NSAssert(NO, @"We only support UIView with <| and |> anchors, got %@", secondView.class);
continue;
}
processed = [self
constraintWithItem:firstView attribute:c.firstAttribute
relatedBy:c.relation
toItem:firstView.superview.safeAreaLayoutGuide attribute:_MMMOppositeAttribute(c.secondAttribute)
multiplier:c.multiplier constant:c.constant
priority:c.priority
identifier:@"MMMSafeAreaSecondItemConstraint"
];
} else {
processed = c;
}
[processedConstraints addObject:processed];
}
return processedConstraints;
}
+ (instancetype)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1
relatedBy:(NSLayoutRelation)relation
toItem:(id)view2 attribute:(NSLayoutAttribute)attr2
multiplier:(CGFloat)multiplier constant:(CGFloat)c
priority:(UILayoutPriority)priority
identifier:(NSString *)identifier
{
NSLayoutConstraint *result = [NSLayoutConstraint constraintWithItem:view1 attribute:attr1 relatedBy:relation toItem:view2 attribute:attr2 multiplier:multiplier constant:c];
result.priority = priority;
result.identifier = identifier;
return result;
}
// @end
static inline NSLayoutAttribute _MMMOppositeAttribute(NSLayoutAttribute a) {
switch (a) {
// TODO: support trailing/leading in the same way
case NSLayoutAttributeLeft:
return NSLayoutAttributeRight;
case NSLayoutAttributeRight:
return NSLayoutAttributeLeft;
case NSLayoutAttributeTop:
return NSLayoutAttributeBottom;
case NSLayoutAttributeBottom:
return NSLayoutAttributeTop;
// These two are special cases, we see them when align all X or Y flags are used.
case NSLayoutAttributeCenterY:
return NSLayoutAttributeCenterY;
case NSLayoutAttributeCenterX:
return NSLayoutAttributeCenterX;
// Nothing more.
default:
NSCAssert(NO, @"We don't expect other attributes here");
return a;
}
}
@interface NSDictionary (MMMUtil)
- (NSDictionary *)mmm_extendedWithDictionary:(NSDictionary *)d;
@end
@implementation NSDictionary (MMMUtil)
- (NSDictionary *)mmm_extendedWithDictionary:(NSDictionary *)d {
if (!d || [d count] == 0)
return self;
NSMutableDictionary *result = [[NSMutableDictionary alloc] initWithDictionary:self];
[result addEntriesFromDictionary:d];
return result;
}
@end
我知道这不是 VFL,但是有一个名为 NSLayoutAnchor
的工厂 class 可以使创建约束更加简洁明了。
例如,我可以用一根紧凑的线将 UILabel 的顶部锚点固定到安全区域的顶部锚点:
label.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true
请注意 safeAreaLayoutGuide
需要 iOS 11。对于旧版本,将 self.view.safeAreaLayoutGuide.topAnchor
替换为 self.topLayoutGuide.bottomAnchor
。
再一次,我知道这不是 VFL,但这似乎是我们目前拥有的。
虽然您目前无法创建与安全区域相关的视觉约束,但您可以将安全区域包含在您的约束中。例如:
int safeInsetTop = self.view.safeAreaInsets.top;
int safeInsetBottom = self.view.safeAreaInsets.bottom;
NSString *verticalConstraints = [NSString stringWithFormat:@"V:|-%d-[myView]-%d-|", safeInsetTop, safeInsetBottom];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:verticalConstraints options:0 metrics:nil views:viewsDictionary];
比理想的冗长,但有效,而且相当有效。
我想使用 Apple 的视觉格式语言将视图限制为 iOS 11 中的新 Safe Area Layout Guide。但是,我得到一个例外:
-[NSLayoutYAxisAnchor nsli_superitem]: unrecognized selector sent to instance 0x1c447ed40
//Make View Dictionary
var views: [String: Any] = ["left": self.leftContainer]
//Check swift version and add appropriate piece to the view dictionary
if #available(iOS 11, *) {
views["topGuide"] = self.view.safeAreaLayoutGuide.topAnchor
}else{
views["topGuide"] = self.topLayoutGuide
}
//Make the constraint using visual format language
let leftVertical = NSLayoutConstraint.constraints(withVisualFormat: "V:[topGuide][left]|", options: [], metrics: nil, views: views)
//Add the new constraint
self.view.addConstraints(vertical)
我喜欢可视格式语言的原因是因为在某些情况下您可以用更少的代码添加很多约束。
有什么想法吗?
I want to use Apples visual format language to constrain a view to the new Safe Area Layout Guide
你不能。无法通过视觉格式语言访问安全区域布局指南。我已经提交了一个错误,我建议你也这样做。
我们在这里稍微扩展了视觉格式语言,所以现在您可以固定“<|”当您指的是 safeAreaLayoutGuide 时。我希望苹果能做那样的事情。
例如,如果您有以下 iOS 11 之前的代码:
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint
constraintsWithVisualFormat:@"V:[_button]-(normalPadding)-|"
options:0 metrics:metrics views:views
]];
现在您要确保按钮位于 iPhone X 上的安全底部边距上方,然后执行此操作:
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint
mmm_constraintsWithVisualFormat:@"V:[_button]-(normalPadding)-<|"
options:0 metrics:metrics views:views
]];
就是这样。它会将按钮锚定在 iOS 9 和 10 上其超级视图的底部,但在 iOS 11 上将其锚定在其 safeAreaLayoutGuide 的底部。
请注意,使用“|>”置顶不会排除 iOS 9 和 10 上的状态栏。
// In @interface/@implementation NSLayoutConstraint (MMMUtil)
// ...
+(NSArray<NSLayoutConstraint *> *)mmm_constraintsWithVisualFormat:(NSString *)format
options:(NSLayoutFormatOptions)opts
metrics:(NSDictionary<NSString *,id> *)metrics
views:(NSDictionary<NSString *,id> *)views
{
if ([format rangeOfString:@"<|"].location == NSNotFound && [format rangeOfString:@"|>"].location == NSNotFound ) {
// No traces of our special symbol, so do nothing special.
return [self constraintsWithVisualFormat:format options:opts metrics:metrics views:views];
}
if (![UIView instancesRespondToSelector:@selector(safeAreaLayoutGuide)]) {
// Before iOS 11 simply use the edges of the corresponding superview.
NSString *actualFormat = [format stringByReplacingOccurrencesOfString:@"<|" withString:@"|"];
actualFormat = [actualFormat stringByReplacingOccurrencesOfString:@"|>" withString:@"|"];
return [NSLayoutConstraint constraintsWithVisualFormat:actualFormat options:opts metrics:metrics views:views];
}
//
// OK, iOS 11+ time.
// For simplicity we replace our special symbols with a reference to a stub view, feed the updated format string
// to the system, and then replace every reference to our stub view with a corresponding reference to safeAreaLayoutGuide.
//
UIView *stub = [[UIView alloc] init];
static NSString * const stubKey = @"__MMMLayoutStub";
NSString *stubKeyRef = [NSString stringWithFormat:@"[%@]", stubKey];
NSDictionary *extendedViews = [@{ stubKey : stub } mmm_extendedWithDictionary:views];
NSString *actualFormat = [format stringByReplacingOccurrencesOfString:@"<|" withString:stubKeyRef];
actualFormat = [actualFormat stringByReplacingOccurrencesOfString:@"|>" withString:stubKeyRef];
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:actualFormat options:opts metrics:metrics views:extendedViews];
NSMutableArray *processedConstraints = [[NSMutableArray alloc] init];
for (NSLayoutConstraint *c in constraints) {
UIView *firstView = c.firstItem;
UIView *secondView = c.secondItem;
NSLayoutConstraint *processed;
if (firstView == stub) {
if (![secondView isKindOfClass:[UIView class]]) {
NSAssert(NO, @"We only support UIView with <| and |> anchors, got %@", secondView.class);
continue;
}
processed = [self
constraintWithItem:secondView.superview.safeAreaLayoutGuide attribute:_MMMOppositeAttribute(c.firstAttribute)
relatedBy:c.relation
toItem:secondView attribute:c.secondAttribute
multiplier:c.multiplier constant:c.constant
priority:c.priority
identifier:@"MMMSafeAreaFirstItemConstraint"
];
} else if (secondView == stub && [firstView isKindOfClass:[UIView class]]) {
if (![firstView isKindOfClass:[UIView class]]) {
NSAssert(NO, @"We only support UIView with <| and |> anchors, got %@", secondView.class);
continue;
}
processed = [self
constraintWithItem:firstView attribute:c.firstAttribute
relatedBy:c.relation
toItem:firstView.superview.safeAreaLayoutGuide attribute:_MMMOppositeAttribute(c.secondAttribute)
multiplier:c.multiplier constant:c.constant
priority:c.priority
identifier:@"MMMSafeAreaSecondItemConstraint"
];
} else {
processed = c;
}
[processedConstraints addObject:processed];
}
return processedConstraints;
}
+ (instancetype)constraintWithItem:(id)view1 attribute:(NSLayoutAttribute)attr1
relatedBy:(NSLayoutRelation)relation
toItem:(id)view2 attribute:(NSLayoutAttribute)attr2
multiplier:(CGFloat)multiplier constant:(CGFloat)c
priority:(UILayoutPriority)priority
identifier:(NSString *)identifier
{
NSLayoutConstraint *result = [NSLayoutConstraint constraintWithItem:view1 attribute:attr1 relatedBy:relation toItem:view2 attribute:attr2 multiplier:multiplier constant:c];
result.priority = priority;
result.identifier = identifier;
return result;
}
// @end
static inline NSLayoutAttribute _MMMOppositeAttribute(NSLayoutAttribute a) {
switch (a) {
// TODO: support trailing/leading in the same way
case NSLayoutAttributeLeft:
return NSLayoutAttributeRight;
case NSLayoutAttributeRight:
return NSLayoutAttributeLeft;
case NSLayoutAttributeTop:
return NSLayoutAttributeBottom;
case NSLayoutAttributeBottom:
return NSLayoutAttributeTop;
// These two are special cases, we see them when align all X or Y flags are used.
case NSLayoutAttributeCenterY:
return NSLayoutAttributeCenterY;
case NSLayoutAttributeCenterX:
return NSLayoutAttributeCenterX;
// Nothing more.
default:
NSCAssert(NO, @"We don't expect other attributes here");
return a;
}
}
@interface NSDictionary (MMMUtil)
- (NSDictionary *)mmm_extendedWithDictionary:(NSDictionary *)d;
@end
@implementation NSDictionary (MMMUtil)
- (NSDictionary *)mmm_extendedWithDictionary:(NSDictionary *)d {
if (!d || [d count] == 0)
return self;
NSMutableDictionary *result = [[NSMutableDictionary alloc] initWithDictionary:self];
[result addEntriesFromDictionary:d];
return result;
}
@end
我知道这不是 VFL,但是有一个名为 NSLayoutAnchor
的工厂 class 可以使创建约束更加简洁明了。
例如,我可以用一根紧凑的线将 UILabel 的顶部锚点固定到安全区域的顶部锚点:
label.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true
请注意 safeAreaLayoutGuide
需要 iOS 11。对于旧版本,将 self.view.safeAreaLayoutGuide.topAnchor
替换为 self.topLayoutGuide.bottomAnchor
。
再一次,我知道这不是 VFL,但这似乎是我们目前拥有的。
虽然您目前无法创建与安全区域相关的视觉约束,但您可以将安全区域包含在您的约束中。例如:
int safeInsetTop = self.view.safeAreaInsets.top;
int safeInsetBottom = self.view.safeAreaInsets.bottom;
NSString *verticalConstraints = [NSString stringWithFormat:@"V:|-%d-[myView]-%d-|", safeInsetTop, safeInsetBottom];
constraints = [NSLayoutConstraint constraintsWithVisualFormat:verticalConstraints options:0 metrics:nil views:viewsDictionary];
比理想的冗长,但有效,而且相当有效。