Автоматический и динамический разрыв подвидов с помощью AutoLayout (›iOS 6)

Я хотел бы, чтобы подпредставления выравнивались по горизонтали внутри их суперпредставления (с фиксированной шириной) и автоматически «разрывали строку» при достижении задней кромки суперпредставления. Я ищу решение, которое позволяет Auto Layout выполнять всю работу за меня - без (пере) расчета структуры строк, когда размер подпредставления изменяется динамически (например, текстовое поле, которое увеличивается/уменьшается с его содержание, например) и добавление или удаление ограничений.

У меня уже есть (рабочее) решение, которое (повторно) создает ограничения в соответствии с предварительно (и повторно) рассчитанной строковой структурой подпредставлений.

Итак, весь мой вопрос заключается в следующем: можно ли найти решение, которое использует ограничения в таком созвездии (с гибкостью, приоритетами и т. д.), которое позволяет Auto Layout автоматически выполнять разрыв строки в подпредставлении, если это необходимо (на первая загрузка И во время выполнения).

Я создал пример проекта с UILabels в качестве «подвидов». Пожалуйста, поиграйте с этим - или попробуйте убедить меня, что моя идея просто фантастична, но нереализуема в данный момент.

Фактический снимок экрана для кода ниже:

метки, нарисованные друг над другом в соответствии с приоритетами, установленными в методе addConstraintsForLabels:superview:

Этот метод проверяет динамическую автоматическую разметку новой строки:

- (void) addConstraintsForLabels : (NSArray*) labels
                   superview : (UIView*) superview {

    for (int i = 0; i < labels.count; i++) {
        UILabel* precedingLabel = i == 0 ? nil : labels[i - 1];
        UILabel* label = labels[i];
        [label setTranslatesAutoresizingMaskIntoConstraints: NO];

        //// set all contentHuggingPriorities to no growing and no compression
        [label setContentHuggingPriority: UILayoutPriorityRequired forAxis: UILayoutConstraintAxisHorizontal];
        [label setContentHuggingPriority: UILayoutPriorityRequired forAxis: UILayoutConstraintAxisVertical];
        [label setContentCompressionResistancePriority: UILayoutPriorityRequired forAxis: UILayoutConstraintAxisHorizontal];
        [label setContentCompressionResistancePriority: UILayoutPriorityRequired forAxis: UILayoutConstraintAxisVertical];

        //// constraints affecting superview
        // top to superview
        NSLayoutConstraint* topToSup =  [NSLayoutConstraint alignEdge: NSLayoutAttributeTop                                                      ofViews: @[label, superview]                                                           relation: i == 0 ? NSLayoutRelationEqual : NSLayoutRelationGreaterThanOrEqual
                                                          spacing: 0];
        // leading to superview
        NSLayoutConstraint* leadingToSup = [NSLayoutConstraint alignEdge: NSLayoutAttributeLeading
                                                             ofViews: @[label, superview]
                                                            relation: i == 0 ? NSLayoutRelationEqual : NSLayoutRelationGreaterThanOrEqual
                                                             spacing: 0];
        // bottom to superview
        NSLayoutConstraint* bottomToSup = [NSLayoutConstraint alignEdge: NSLayoutAttributeBottom
                                                            ofViews: @[label, superview]
                                                           relation: i == labels.count - 1 ? NSLayoutRelationEqual : NSLayoutRelationLessThanOrEqual
                                                            spacing: 0];
        // trailing to superview
        NSLayoutConstraint* trailingToSup = [NSLayoutConstraint alignEdge: NSLayoutAttributeTrailing
                                                              ofViews: @[label, superview]
                                                             relation: NSLayoutRelationLessThanOrEqual
                                                              spacing: 0];

        //// example constraints affecting preceding label
        // leading to preceding label
        NSLayoutConstraint* leadingToPrec = precedingLabel == nil ? nil : [NSLayoutConstraint horizontalSpacing: 0
                                                                                               betweenViews: @[precedingLabel, label]
                                                                                                   flexible: NO
                                                                                                  inMaximum: NO];
        // y position to preceding label
        NSLayoutConstraint* centerYToPrec = precedingLabel == nil ? nil :  [NSLayoutConstraint alignEdge: NSLayoutAttributeCenterY
                                                                                             ofViews: @[label, precedingLabel]
                                                                                            relation: NSLayoutRelationEqual
                                                                                             spacing: 0];
        // top to preceding view
        NSLayoutConstraint* topToPrec = precedingLabel ==  nil ? nil : [NSLayoutConstraint verticalSpacing: 0
                                                                                               ofViews: @[precedingLabel, label]
                                                                                              flexible: NO
                                                                                             inMaximum: NO];

        //// priorities
        // affecting the superview
        topToSup.priority = UILayoutPriorityRequired; // is either flexible or non-flexible for the first
        leadingToSup.priority = UILayoutPriorityRequired; // is either flexible or non-flexible for the first
        bottomToSup.priority = UILayoutPriorityRequired; // is either flexible or non-flexible for the last
        trailingToSup.priority = UILayoutPriorityRequired; // it is required, that the view does not exceed the right edge of its superview
        [superview addConstraints: @[trailingToSup, topToSup, leadingToSup, bottomToSup]];

        // affecting the preceding view
        if (i > 0) {
            leadingToPrec.priority = UILayoutPriorityDefaultHigh;
            centerYToPrec.priority = UILayoutPriorityDefaultHigh;
            topToPrec.priority = UILayoutPriorityDefaultHigh;
            [superview addConstraints: @[leadingToPrec, centerYToPrec, topToPrec]];
        }
    }
}

Импортированная категория, расширяющая NSLayoutConstraint:

@implementation NSLayoutConstraint (ConstructorAdditions)


// align edges of two views
// if one view is the superview, put it at the second position in the array
+ (NSLayoutConstraint*) alignEdge : (NSLayoutAttribute) edge
                      ofViews : (NSArray*) /*UIView*/ views
                     relation : (NSLayoutRelation) relation
                      spacing : (CGFloat) spacing {
    NSLayoutConstraint* constraint = nil;
    if (views.count == 2) {
        if (edge == NSLayoutAttributeBaseline || edge == NSLayoutAttributeTrailing || edge == NSLayoutAttributeBottom || edge == NSLayoutAttributeRight) {
            spacing = -spacing;
        }
        constraint = [NSLayoutConstraint constraintWithItem : [views objectAtIndex: 0]
                                              attribute : edge
                                              relatedBy : relation
                                                 toItem : [views objectAtIndex: 1]
                                              attribute : edge
                                             multiplier : 1.0
                                               constant : spacing];
    }
    return constraint;
}


// vertical spcacing between views
// the method assumes, that the first view in the array is above the second view
+ (NSLayoutConstraint*) verticalSpacing : (CGFloat) spacing
                            ofViews : (NSArray*) /*UIView*/ views
                           flexible : (BOOL) flexible
                           inMaximum: (BOOL) inMax {
    NSLayoutConstraint* constraint = nil;

    NSLayoutRelation relation = flexible ? (inMax ? NSLayoutRelationLessThanOrEqual : NSLayoutRelationGreaterThanOrEqual) : NSLayoutRelationEqual;

    if (views.count == 2) {
        constraint = [NSLayoutConstraint constraintWithItem : [views objectAtIndex: 0]
                                              attribute : NSLayoutAttributeBottom
                                              relatedBy : relation
                                                 toItem : [views objectAtIndex: 1]
                                              attribute : NSLayoutAttributeTop
                                             multiplier : 1.0
                                               constant : -spacing];
    }
    return constraint;
}


// horizontal spacing between views
// the method assumes, that the first view in the array is left of the second view
+ (NSLayoutConstraint*) horizontalSpacing : (CGFloat) spacing
                         betweenViews : (NSArray*) /*UIView*/ views
                             flexible : (BOOL) flexible
                             inMaximum: (BOOL) inMax {
    NSLayoutConstraint* constraint = nil;
    NSLayoutRelation relation = flexible ? (inMax ? NSLayoutRelationLessThanOrEqual : NSLayoutRelationGreaterThanOrEqual) : NSLayoutRelationEqual;
    if (views.count == 2) {
        constraint = [NSLayoutConstraint constraintWithItem : [views objectAtIndex: 0]
                                              attribute : NSLayoutAttributeTrailing
                                              relatedBy : relation
                                                 toItem : [views objectAtIndex: 1]
                                              attribute : NSLayoutAttributeLeading
                                             multiplier : 1.0
                                               constant : -spacing];
    }
    return constraint;
}



+ (NSArray*) equalWidthOfViews : (NSArray*) views
                    toView : (UIView*) view
                  distance : (CGFloat) distance
                  relation : (NSLayoutRelation) relation
                multiplier : (CGFloat) multiplier {
    NSMutableArray* constraints = [[NSMutableArray alloc] initWithCapacity: views.count];

    for (UIView* aView in views) {
        [constraints addObject: [NSLayoutConstraint  constraintWithItem : aView
                                                          attribute : NSLayoutAttributeWidth
                                                          relatedBy : relation
                                                             toItem : view
                                                          attribute : NSLayoutAttributeWidth
                                                         multiplier : multiplier
                                                           constant : distance]];
    }
    return constraints;
}

person anneblue    schedule 27.08.2014    source источник
comment
Пожалуйста, сократите предоставленный код до тех частей, которые содержат вашу проблему. Вы должны выяснить сами, что вызывает ошибку, вместо того, чтобы делегировать это сообществу!   -  person Gottlieb Notschnabel    schedule 27.08.2014
comment
У меня нет проблем с ошибкой, и я не знаю, откуда берется приведенный выше снимок экрана. Но тем не менее, я немного уменьшу код для большей наглядности. Спасибо.   -  person anneblue    schedule 28.08.2014


Ответы (1)


Это довольно сложная задача для Autolayout. Время решения ограничений увеличивается экспоненциально по мере того, как вы добавляете все больше и больше ограничений. Похоже, UICollectionView может лучше соответствовать вашим потребностям. Он уже очень оптимизирован для чего-то подобного, в частности для макета UICollectionViewFlowLayout.

person Acey    schedule 27.08.2014
comment
Спасибо за Ваш ответ. Итак, мой вопрос о UICollectionViewFlowLayout: сможет ли UICollectionView обрабатывать UICollectionViewCell, который содержит растущий UITextField? Я имею в виду, работает ли UICollectionView с такими динамическими ячейками или мне нужно перезагружать UICollectionView каждый раз, когда пользователь меняет один символ текстового поля из-за изменения его размера? - person anneblue; 29.08.2014
comment
Хороший вопрос! CollectionView для начала может перезагрузить только одну ячейку. Макеты также могут аннулировать атрибуты макета только для отдельных ячеек (iOS 8 делает это проще, но я думаю, что это возможно и в iOS 7). Это может быть ваш лучший подход, хотя у меня не так много опыта в этом. - person Acey; 29.08.2014
comment
Еще один трюк, который вам, возможно, придется использовать, заключается в том, чтобы вытащить TextView из ячейки сразу после того, как он станет первым ответчиком. По мере ввода пользователем определите ширину ячейки, аннулируйте макет (или перезагрузите) ячейку, а также обновите рамку textField, чтобы она следовала за ячейкой. Таким образом, вы не рискуете потерять первого респондента в этом текстовом представлении, если ячейка нуждается в перезагрузке. С аннулированием атрибутов макета в этом может не быть необходимости, но firstResponder может быть непостоянным зверем. - person Acey; 29.08.2014