8

Je veux utiliser un langage de format visuel pommes pour contraindre une vue à la nouvelle Safe Area Layout Guide dans iOS 11. Cependant, je reçois une exception:Swift Safe Area Guide mise en page et le format visuel Langue

- [NSLayoutYAxisAnchor nsli_superitem] : sélecteur non reconnu envoyé à l'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) 

La raison pour laquelle j'aime la langue de format visuel est parce que vous pouvez ajouter un grand nombre de contraintes avec moins de code dans certains cas.

Des idées?

Répondre

16

Je veux utiliser un langage de format visuel pommes pour contraindre une vue à la nouvelle mise en page Guide de la zone de sécurité

Vous ne pouvez pas. Il n'y a pas d'accès au guide de disposition de la zone de sécurité à travers le langage de format visuel. J'ai déposé un bug à ce sujet, et je vous suggère de faire la même chose.

+0

Pouvez-vous lier votre rapport de bogue? –

+0

Le numéro de radar est 33865966. Ma description est: "Il n'y a aucun moyen de créer, dans le langage de format visuel iOS, une contrainte épinglant une vue à la zone de sécurité de sa vue d'ensemble. – matt

4

Nous avons étendu le langage de formatage visuel ici un peu, alors maintenant vous pouvez épingler contre "< |" quand vous voulez dire safeAreaLayoutGuide. Je souhaite que Apple a fait quelque chose comme ça.

Par exemple, si vous avez le code pré iOS 11 suivant:

[NSLayoutConstraint activateConstraints:[NSLayoutConstraint 
    constraintsWithVisualFormat:@"V:[_button]-(normalPadding)-|" 
    options:0 metrics:metrics views:views 
]]; 

Et maintenant, vous voulez vous assurer que le bouton se trouve au-dessus de la marge inférieure en sécurité sur iPhone X, faites ceci:

[NSLayoutConstraint activateConstraints:[NSLayoutConstraint 
    mmm_constraintsWithVisualFormat:@"V:[_button]-(normalPadding)-<|" 
    options:0 metrics:metrics views:views 
]]; 

C'est tout. Il va ancrer le bouton au bas de sa superview sur iOS 9 et 10, mais l'ancrer au bas de son safeAreaLayoutGuide sur iOS 11.

Veuillez noter que vous utilisez "|>" pour épingler le top won ' t exclure la barre d'état sur iOS 9 et 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 
+0

Ceci est une mise en œuvre très lisse! Devrait être la réponse acceptée! – SwingerDinger

4

je sais que ce n'est pas VFL, mais il y a une classe usine appelée NSLayoutAnchor qui permet de créer des contraintes un peu plus propre et concis.

Par exemple, j'ai pu épingler le point d'ancrage haut d'une UILabel à l'ancre en haut de la zone de sécurité avec une ligne compacte:

label.topAnchor.constraint(equalTo: self.view.safeAreaLayoutGuide.topAnchor).isActive = true 

Notez que safeAreaLayoutGuide nécessite iOS 11. Pour les versions plus anciennes, remplacer Par self.topLayoutGuide.bottomAnchor.

Encore une fois, je sais que ce n'est pas VFL, mais cela semble être ce que nous avons pour l'instant.

+0

Merci pour le pourboire! Notez que cela nécessite iOS 11+ – webo80