I want to use Apples visual format language to constrain a view to the new Safe Area Layout Guide in iOS 11. However, I get an exception:
-[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)
The reason I like visual format language is because you a can add lot of constraints with less code in some cases.
Any Ideas?
I want to use Apples visual format language to constrain a view to the new Safe Area Layout Guide
You can't. There is no access to the safe area layout guide through the visual format language. I've filed a bug on this, and I suggest you do the same.
I know it's not VFL, but there is a factory class called NSLayoutAnchor that makes creating constraints a bit more clean and concise.
For example, I was able to pin the top anchor of a UILabel to the top anchor of the safe area with one compact line:
label.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true
Note that safeAreaLayoutGuide requires iOS 11. For older versions, replace self.view.safeAreaLayoutGuide.topAnchor by self.topLayoutGuide.bottomAnchor.
Again, I know it's not VFL, but this seems to be what we have for now.
We've extended the visual formatting language here a bit, so now you can pin against "
For example, if you have the following pre iOS 11 code:
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_button]-(normalPadding)-|" options:0 metrics:metrics views:views ]];
And now you want to make sure that the button sits above the safe bottom margin on iPhone X, then do this:
[NSLayoutConstraint activateConstraints:[NSLayoutConstraint mmm_constraintsWithVisualFormat:@"V:[_button]-(normalPadding)-
That's it. It'll anchor the button to the bottom of its superview on iOS 9 and 10, but anchor it to the bottom of its safeAreaLayoutGuide on iOS 11.
Please note that using "|>" to pin to the top won't exclude the status bar on iOS 9 and 10.
// In @interface/@implementation NSLayoutConstraint (MMMUtil) // ... +(NSArray *)mmm_constraintsWithVisualFormat:(NSString *)format options:(NSLayoutFormatOptions)opts metrics:(NSDictionary *)metrics views:(NSDictionary *)views { if ([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:@"|"]; 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]; 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 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 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