【问题标题】:Swift Safe Area Layout Guide and Visual Format LanguageSwift 安全区域布局指南和可视化格式语言
【发布时间】:2018-03-10 19:06:17
【问题描述】:

我想使用 Apple 的视觉格式语言将视图限制为 iOS 11 中的新 Safe Area Layout Guide。但是,我得到了一个例外:

-[NSLayoutYAxisAnchor nsli_superitem]:无法识别的选择器发送到实例 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)

我喜欢可视化格式语言的原因是因为在某些情况下你可以用更少的代码添加很多约束。

有什么想法吗?

【问题讨论】:

    标签: swift autolayout ios11 visual-format-language safearealayoutguide


    【解决方案1】:

    我想使用 Apple 的视觉格式语言将视图约束到新的安全区域布局指南

    你不能。无法通过可视格式语言访问安全区域布局指南。我已经提交了一个错误,我建议你也这样做。

    【讨论】:

    • 你能链接你的错误报告吗?
    • 雷达编号是 33865966。我的描述是:“在 iOS 可视化格式语言中,无法创建将视图固定到其父视图安全区域的约束。需要有一个。”
    • 对此有任何更新,如果已修复?我在 Open Radar 中找不到“33865966”了。
    • @pasevin 它从来没有出现在 Open Radar 中。我建议我们都停止使用视觉格式语言;它总是不足的,随着时间的推移变得越来越不足,显然它永远不会更新。使用锚表示法和堆栈视图并快乐。
    【解决方案2】:

    我们在这里稍微扩展了视觉格式化语言,所以现在您可以锁定“

    例如,如果您有以下 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 上将按钮锚定到其 superview 的底部,但在 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
    

    【讨论】:

    • 这是一个非常顺利的实现!应该是公认的答案!
    【解决方案3】:

    我知道这不是 VFL,但有一个名为 NSLayoutAnchor 的工厂类可以让创建约束更加简洁明了。

    例如,我可以用一条紧凑的线将 UILabel 的顶部锚点固定到安全区域的顶部锚点:

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

    请注意,safeAreaLayoutGuide 需要 iOS 11。对于旧版本,请将 self.view.safeAreaLayoutGuide.topAnchor 替换为 self.topLayoutGuide.bottomAnchor

    再说一次,我知道这不是 VFL,但这似乎是我们目前所拥有的。

    【讨论】:

    【解决方案4】:

    虽然您目前无法创建相对于安全区域的视觉约束,但您可以在约束中包含安全区域。例如:

    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];
    

    比理想的更冗长,但有效,并且相当有效。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-03-02
      • 2018-06-26
      • 1970-01-01
      • 2018-02-28
      • 1970-01-01
      • 1970-01-01
      • 2018-10-13
      相关资源
      最近更新 更多