[ios] 자동 레이아웃을 사용하여 UICollectionView에서 셀의 1 차원 지정

iOS 8에서는 UICollectionViewFlowLayout자체 콘텐츠 크기에 따라 자동으로 셀 크기 조정을 지원합니다. 내용에 따라 너비와 높이 모두에서 셀 크기가 조정됩니다.

모든 셀의 너비 (또는 높이)에 대해 고정 값을 지정하고 다른 치수의 크기를 조정할 수 있습니까?

간단한 예를 들어 셀의 측면에 배치하는 제약 조건이있는 셀의 여러 줄 레이블을 고려하십시오. 여러 줄 레이블은 텍스트에 맞게 다양한 방식으로 크기를 조정할 수 있습니다. 셀은 컬렉션 뷰의 너비를 채우고 그에 따라 높이를 조정해야합니다. 대신 셀 크기가 임의로 조정되며 셀 크기가 컬렉션 뷰의 스크롤 할 수없는 차원보다 클 때 충돌이 발생합니다.


iOS 8에서는 systemLayoutSizeFittingSize: withHorizontalFittingPriority: verticalFittingPriority:컬렉션 뷰의 각 셀에 대해 레이아웃이 셀에서이 메서드를 호출하여 예상 크기를 전달합니다. 나에게 의미있는 것은 셀 에서이 방법을 재정의하고 주어진 크기를 전달하고 수평 제약 조건을 필수로 설정하고 수직 제약 조건에 낮은 우선 순위를 설정하는 것입니다. 이렇게하면 가로 크기가 레이아웃에 설정된 값으로 고정되고 세로 크기가 유연해질 수 있습니다.

이 같은:

- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
    UICollectionViewLayoutAttributes *attributes = [super preferredLayoutAttributesFittingAttributes:layoutAttributes];
    attributes.size = [self systemLayoutSizeFittingSize:layoutAttributes.size withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
    return attributes;
}

그러나이 방법으로 반환 된 크기는 완전히 이상합니다. 이 방법에 대한 문서는 저에게 매우 불분명하며 UILayoutFittingCompressedSize UILayoutFittingExpandedSize크기가 0이고 매우 큰 상수 를 사용하는 것에 대해 언급합니다 .

size이 메서드 의 매개 변수는 실제로 두 개의 상수를 전달하는 방법입니까? 주어진 크기에 대해 적절한 높이를 얻을 것으로 예상되는 동작을 달성 할 방법이 없습니까?


대체 솔루션

1) 셀의 특정 너비를 지정하는 제약 조건을 추가하면 올바른 레이아웃을 얻을 수 있습니다. 이 제약 조건은 안전한 참조가없는 셀 컬렉션 뷰의 크기로 설정되어야하므로 이는 좋지 않은 솔루션입니다. 해당 제약 조건에 대한 값은 셀이 구성 될 때 전달 될 수 있지만 완전히 반 직관적으로 보입니다. 셀이나 콘텐츠 뷰에 ​​직접 제약 조건을 추가하면 많은 문제가 발생하기 때문에 이것은 또한 어색합니다.

2) 테이블보기를 사용합니다. 표보기는 셀의 너비가 고정되어 있으므로 상자에서 꺼내는 방식으로 작동하지만 여러 열에 고정 된 너비 셀이있는 iPad 레이아웃과 같은 다른 상황에는 적용되지 않습니다.



답변

요청하는 것은 UICollectionView를 사용하여 UITableView와 같은 레이아웃을 생성하는 방법 인 것처럼 들립니다. 이것이 실제로 원하는 경우,이를 수행하는 올바른 방법은 사용자 정의 UICollectionViewLayout 하위 클래스 ( SBTableLayout유사 할 수 있음 )를 사용하는 것입니다.

반면에 기본 UICollectionViewFlowLayout을 사용하여이를 수행 할 수있는 깨끗한 방법이 있는지 정말로 묻는다면 방법이 없다고 생각합니다. iOS8의 자체 크기 조정 셀을 사용하더라도 간단하지 않습니다. 근본적인 문제는 흐름 레이아웃의 기계가 한 차원을 수정하고 다른 차원이 응답하도록 할 방법을 제공하지 않는다는 것입니다. (또한 가능하더라도 여러 줄 레이블의 크기를 조정하는 데 두 개의 레이아웃 패스가 필요하다는 점에서 추가적인 복잡성이있을 수 있습니다. 이것은 자체 크기 조정 셀이 systemLayoutSizeFittingSize에 대한 한 번의 호출을 통해 모든 크기 조정을 계산하려는 방식과 맞지 않을 수 있습니다.)

그러나 흐름 레이아웃을 사용하여 자체 크기를 결정하고 컬렉션보기의 너비에 자연스럽게 반응하는 셀을 사용하여 테이블보기와 같은 레이아웃을 만들고 싶다면 물론 가능합니다. 여전히 지저분한 방법이 있습니다. “셀 크기 조정”, 즉 컨트롤러가 셀 크기를 계산할 때만 유지하는 표시되지 않는 UICollectionViewCell로 수행했습니다.

이 접근 방식에는 두 부분이 있습니다. 첫 번째 부분은 컬렉션 뷰 대리자가 컬렉션 뷰의 너비를 취하고 크기 조정 셀을 사용하여 셀의 높이를 계산하여 올바른 셀 크기를 계산하는 것입니다.

UICollectionViewDelegateFlowLayout에서 다음과 같은 메소드를 구현합니다.

func collectionView(collectionView: UICollectionView,
  layout collectionViewLayout: UICollectionViewLayout,
  sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize
{
  // NOTE: here is where we say we want cells to use the width of the collection view
   let requiredWidth = collectionView.bounds.size.width

   // NOTE: here is where we ask our sizing cell to compute what height it needs
  let targetSize = CGSize(width: requiredWidth, height: 0)
  /// NOTE: populate the sizing cell's contents so it can compute accurately
  self.sizingCell.label.text = items[indexPath.row]
  let adequateSize = self.sizingCell.preferredLayoutSizeFittingSize(targetSize)
  return adequateSize
}

이렇게하면 컬렉션 뷰가 둘러싸는 컬렉션 뷰를 기반으로 셀의 너비를 설정하지만 크기 조정 셀에 높이를 계산하도록 요청합니다.

두 번째 부분은 높이를 계산하기 위해 자체 AL 제약 조건을 사용하도록 크기 조정 셀을 가져 오는 것입니다. 이것은 다중 라인 UILabel이 효과적으로 2 단계 레이아웃 프로세스를 요구하는 방식으로 인해 예상보다 어려울 수 있습니다. 작업은 preferredLayoutSizeFittingSize다음과 같이 메서드에서 수행됩니다 .

 /*
 Computes the size the cell will need to be to fit within targetSize.

 targetSize should be used to pass in a width.

 the returned size will have the same width, and the height which is
 calculated by Auto Layout so that the contents of the cell (i.e., text in the label)
 can fit within that width.

 */
 func preferredLayoutSizeFittingSize(targetSize:CGSize) -> CGSize {

   // save original frame and preferredMaxLayoutWidth
   let originalFrame = self.frame
   let originalPreferredMaxLayoutWidth = self.label.preferredMaxLayoutWidth

   // assert: targetSize.width has the required width of the cell

   // step1: set the cell.frame to use that width
   var frame = self.frame
   frame.size = targetSize
   self.frame = frame

   // step2: layout the cell
   self.setNeedsLayout()
   self.layoutIfNeeded()
   self.label.preferredMaxLayoutWidth = self.label.bounds.size.width

   // assert: the label's bounds and preferredMaxLayoutWidth are set to the width required by the cell's width

   // step3: compute how tall the cell needs to be

   // this causes the cell to compute the height it needs, which it does by asking the 
   // label what height it needs to wrap within its current bounds (which we just set).
   let computedSize = self.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize)

   // assert: computedSize has the needed height for the cell

   // Apple: "Only consider the height for cells, because the contentView isn't anchored correctly sometimes."
   let newSize = CGSize(width:targetSize.width,height:computedSize.height)

   // restore old frame and preferredMaxLayoutWidth
   self.frame = originalFrame
   self.label.preferredMaxLayoutWidth = originalPreferredMaxLayoutWidth

   return newSize
 }

(이 코드는 “고급 컬렉션보기”에있는 WWDC2014 세션의 샘플 코드에있는 Apple 샘플 코드에서 수정되었습니다.)

주목해야 할 몇 가지 사항이 있습니다. 레이블의 너비를 계산하고 설정하기 위해 layoutIfNeeded ()를 사용하여 전체 셀의 레이아웃을 강제 적용합니다. 하지만 그것만으로는 충분하지 않습니다. preferredMaxLayoutWidth레이블이 자동 레이아웃에서 해당 너비를 사용 하도록 설정해야한다고 생각합니다 . 그리고 systemLayoutSizeFittingSize나서야 레이블을 고려하면서 셀이 높이를 계산하도록 할 수 있습니다.

이 접근 방식이 마음에 드나요? 아니!! 너무 복잡하고 레이아웃을 두 번 수행합니다. 그러나 성능이 문제가되지 않는 한, 코드에서 레이아웃을 두 번 정의하는 것보다 런타임에 레이아웃을 두 번 수행하는 것이 유일한 대안 인 것 같습니다.

내 희망은 결국 자체 크기 조정 세포가 다르게 작동하고이 모든 것이 훨씬 더 간단해질 것이라는 것입니다.

직장에서 그것을 보여주는 예제 프로젝트 .

그러나 왜 자체 크기 조정 셀을 사용하지 않습니까?

이론적으로 iOS8의 새로운 “셀 자체 크기 조정”기능은이를 불필요하게 만들어야합니다. 자동 레이아웃 (AL)을 사용하여 셀을 정의한 경우 컬렉션보기는 자체 크기를 조정하고 올바르게 배치 할 수있을만큼 스마트해야합니다. 실제로 여러 줄 레이블과 함께 작동하도록 만든 예제는 본 적이 없습니다. 나는 이것이 부분적으로 자체 크기 조정 세포 메커니즘이 여전히 버그가 있기 때문 이라고 생각 합니다 .

하지만 대부분은 자동 레이아웃 및 라벨의 일반적인 까다로운 문제 때문일 것입니다. UILabels에는 기본적으로 2 단계 레이아웃 프로세스가 필요하다는 것입니다. 자체 크기 조정 셀로 두 단계를 모두 수행하는 방법이 명확하지 않습니다.

그리고 제가 말했듯이 이것은 실제로 다른 레이아웃을위한 작업입니다. 너비를 고정하고 높이를 선택할 수 있도록하는 것이 아니라 크기가있는 항목을 배치하는 것이 흐름 레이아웃의 핵심입니다.

그리고 preferredLayoutAttributesFittingAttributes 😕

preferredLayoutAttributesFittingAttributes:방법은 훈제 청어, 나는 생각한다. 그것은 새로운 자체 크기 조정 셀 메커니즘과 함께 사용됩니다. 따라서 메커니즘이 신뢰할 수없는 한 이것은 답이 아닙니다.

그리고 systemlayoutSizeFittingSize :는 무엇입니까?

문서가 혼란 스럽습니다.

의 문서 systemLayoutSizeFittingSize:systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority:모두 만 통과해야한다고 제안 UILayoutFittingCompressedSize하고 UILayoutFittingExpandedSize는 AS targetSize. 그러나 메서드 서명 자체, 헤더 주석 및 함수 동작은 targetSize매개 변수 의 정확한 값에 응답하고 있음을 나타냅니다 .

실제로 UICollectionViewFlowLayoutDelegate.estimatedItemSize새로운 자체 크기 조정 셀 메커니즘을 사용하기 위해을 설정하면 해당 값이 targetSize로 전달되는 것처럼 보입니다. 그리고와 UILabel.systemLayoutSizeFittingSize정확히 동일한 값을 반환하는 것 같습니다 UILabel.sizeThatFits. 인수가 systemLayoutSizeFittingSize대략적인 대상이고 인수가 sizeThatFits:최대 외접 크기 여야 한다는 점을 감안할 때 이것은 의심 스럽습니다 .

더 많은 리소스

그러한 일상적인 요구 사항에 “연구 자원”이 필요하다고 생각하는 것은 슬프지만 나는 그렇게 생각합니다. 좋은 예와 토론은 다음과 같습니다.


답변

여기에 다른 답변보다 더 깨끗한 방법이 있으며 잘 작동합니다. 성능이 뛰어나야하며 (컬렉션 뷰가 빠르게로드되고, 불필요한 자동 레이아웃 패스가 없음 등) 고정 된 컬렉션 뷰 너비와 같은 ‘매직 넘버’가 없어야합니다. 예를 들어 회전에서 컬렉션보기 크기를 변경 한 다음 레이아웃을 무효화하는 것도 잘 작동합니다.

1. 다음 흐름 레이아웃 하위 클래스를 만듭니다.

class HorizontallyFlushCollectionViewFlowLayout: UICollectionViewFlowLayout {

    // Don't forget to use this class in your storyboard (or code, .xib etc)

    override func layoutAttributesForItemAtIndexPath(indexPath: NSIndexPath) -> UICollectionViewLayoutAttributes? {
        let attributes = super.layoutAttributesForItemAtIndexPath(indexPath)?.copy() as? UICollectionViewLayoutAttributes
        guard let collectionView = collectionView else { return attributes }
        attributes?.bounds.size.width = collectionView.bounds.width - sectionInset.left - sectionInset.right
        return attributes
    }

    override func layoutAttributesForElementsInRect(rect: CGRect) -> [UICollectionViewLayoutAttributes]? {
        let allAttributes = super.layoutAttributesForElementsInRect(rect)
        return allAttributes?.flatMap { attributes in
            switch attributes.representedElementCategory {
            case .Cell: return layoutAttributesForItemAtIndexPath(attributes.indexPath)
            default: return attributes
            }
        }
    }
}

2. 자동 크기 조정을 위해 컬렉션보기 등록

// The provided size should be a plausible estimate of the actual
// size. You can set your item size in your storyboard
// to a good estimate and use the code below. Otherwise,
// you can provide it manually too, e.g. CGSize(width: 100, height: 100)

flowLayout.estimatedItemSize = flowLayout.itemSize

3. 셀 하위 클래스에서 미리 정의 된 너비 + 사용자 지정 높이 사용

override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
    layoutAttributes.bounds.size.height = systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
    return layoutAttributes
}


답변

몇 줄의 코드로 iOS 9에서 작업을 수행하는 간단한 방법-수평 방법의 예 (높이를 Collection View 높이로 고정) :

estimatedItemSize자체 크기 조정 셀을 사용 하려면 컬렉션보기 흐름 레이아웃을 초기화합니다 .

self.scrollDirection = UICollectionViewScrollDirectionHorizontal;
self.estimatedItemSize = CGSizeMake(1, 1);

Collection View Layout Delegate (대부분의 경우 View Controller에서)를 구현합니다 collectionView:layout:sizeForItemAtIndexPath:. 여기서 목표는 고정 높이 (또는 너비)를 컬렉션 뷰 차원으로 설정하는 것입니다. 10 값은 무엇이든 될 수 있지만 제약 조건을 위반하지 않는 값으로 설정해야합니다.

- (CGSize)collectionView:(UICollectionView *)collectionView
                  layout:(UICollectionViewLayout *)collectionViewLayout
  sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
    return CGSizeMake(10, CGRectGetHeight(collectionView.bounds));
}

사용자 지정 셀 preferredLayoutAttributesFittingAttributes:방법을 재정의합니다 .이 부분은 실제로 자동 레이아웃 제약 조건과 방금 설정 한 높이를 기반으로 동적 셀 너비를 계산합니다.

- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes
{
    UICollectionViewLayoutAttributes *attributes = [layoutAttributes copy];
    float desiredWidth = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].width;
    CGRect frame = attributes.frame;
    frame.size.width = desiredWidth;
    attributes.frame = frame;
    return attributes;
}


답변

선호하는 레이아웃 속성에서 너비를 수정 해보십시오.

- (UICollectionViewLayoutAttributes *)preferredLayoutAttributesFittingAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes {
    UICollectionViewLayoutAttributes *attributes = [[super preferredLayoutAttributesFittingAttributes:layoutAttributes] copy];
    CGSize newSize = [self systemLayoutSizeFittingSize:CGSizeMake(FIXED_WIDTH,layoutAttributes.size) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
    CGRect newFrame = attr.frame;
    newFrame.size.height = size.height;
    attr.frame = newFrame;
    return attr;
}

당연히 레이아웃을 다음과 같이 올바르게 설정해야합니다.

UICollectionViewFlowLayout *flowLayout = (UICollectionViewFlowLayout *) self.collectionView.collectionViewLayout;
flowLayout.estimatedItemSize = CGSizeMake(FIXED_WIDTH, estimatedHeight)];

여기에 일정한 너비의 셀을 사용하고 동적 유형을 지원 하는 Github에 넣은 것이 있으므로 시스템 글꼴 크기가 변경되면 셀의 높이가 업데이트됩니다.


답변

예, 프로그래밍 방식으로 자동 레이아웃을 사용하고 스토리 보드 또는 xib에서 제약 조건을 설정하여 수행 할 수 있습니다. 너비 크기를 일정하게 유지하고 높이를 다음보다 크거나 같게 설정하려면 제약 조건을 추가해야합니다.

http://www.thinkandbuild.it/learn-to-love-auto-layout-programmatically/

http://www.cocoanetics.com/2013/08/variable-sized-items-in-uicollectionview/

희망이 도움이되고 문제를 해결합니다.


답변