자동 레이아웃이있는 UICollectionView 자체 크기 조정 셀
UICollectionViewCells
자동 레이아웃을 사용하여 자체 크기 조정을 시도하고 있지만 셀 크기를 내용에 맞게 조정할 수는 없습니다. 셀의 contentView 안에있는 내용에서 셀 크기가 어떻게 업데이트되는지 이해하는 데 어려움을 겪고 있습니다.
내가 시도한 설정은 다음과 같습니다.
- 사용자
UICollectionViewCell
A를UITextView
그있는 contentView한다. - 에 대한 스크롤
UITextView
이 비활성화되었습니다. - contentView의 가로 제약 조건은 "H : | [_textView (320)]"입니다. 즉,
UITextView
명시 적 너비가 320 인 셀의 왼쪽에 고정됩니다. - contentView의 수직 제약은 "V : | -0-[_ textView]"입니다. 즉
UITextView
, 셀 상단에 고정됩니다. - 는
UITextView
상수로 설정 높이 제약이UITextView
보고서는 텍스트에 맞게됩니다.
셀 배경이 빨간색으로 UITextView
설정되고 배경이 파란색으로 설정된 모습은 다음과 같습니다 .
나는 GitHub 에서 놀고있는 프로젝트를 여기 에 넣었다 .
스위프트 5 업데이트
preferredLayoutAttributesFittingAttributes
이름 변경 preferredLayoutAttributesFitting
및 자동 크기 조정 사용
스위프트 4 업데이트
systemLayoutSizeFittingSize
로 개명 systemLayoutSizeFitting
iOS 9 용으로 업데이트
iOS 9에서 GitHub 솔루션이 중단 된 것을보고 마침내 문제를 완전히 조사 할 시간을 얻었습니다. 자체 크기 조정 셀에 대한 서로 다른 구성의 몇 가지 예를 포함하도록 저장소를 업데이트했습니다. 내 결론은 자기 사이징 셀은 이론적으로는 훌륭하지만 실제로는 지저분하다는 것입니다. 자체 크기 조정 셀을 진행할 때주의 할 사항.
TL; DR
내 GitHub 프로젝트를 확인하십시오
자체 크기 조정 셀은 플로우 레이아웃에서만 지원되므로 사용중인 것이 맞는지 확인하십시오.
자체 크기 조정 셀이 작동하려면 설정해야 할 두 가지가 있습니다.
1. 설정 estimatedItemSize
에UICollectionViewFlowLayout
estimatedItemSize
속성 을 설정하면 흐름 레이아웃이 실제로 동적이 됩니다.
self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
2. 셀 서브 클래스 크기 조정 지원 추가
이것은 2 가지 맛으로 나옵니다. 의 자동 레이아웃 또는 맞춤 재정의 preferredLayoutAttributesFittingAttributes
.
자동 레이아웃으로 셀 생성 및 구성
셀의 제약 조건을 구성하는 것에 대한 훌륭한 SO 게시물 이 있기 때문에 이에 대해 자세히 설명하지 않습니다 . 것을 그냥 조심 엑스 코드 (6) 파산 당신이 아이폰 OS 7 지원하는 경우 아이폰 OS 7 그래서 함께 잔뜩, 당신은 autoresizingMask 셀의 경계 때와 같이 설정되어있는 contentView의 경계 셀의있는 contentView에와 있다는 설정되어 있는지 확인 같은 물건을 수행해야합니다 셀이로드됩니다 (예 :) awakeFromNib
.
알아야 할 사항은 셀이 테이블 뷰 셀보다 더 심각하게 제한되어야한다는 것입니다. 예를 들어 너비를 동적으로 만들려면 셀에 높이 제한이 필요합니다. 마찬가지로, 높이를 동적으로하려면 셀에 대한 너비 제한이 필요합니다.
preferredLayoutAttributesFittingAttributes
커스텀 셀에서 구현
이 기능이 호출되면 뷰가 이미 컨텐츠로 구성되었습니다 (예 : cellForItem
호출). 제약 조건이 적절하게 설정되었다고 가정하면 다음과 같이 구현할 수 있습니다.
//forces the system to do one layout pass
var isHeightCalculated: Bool = false
override func preferredLayoutAttributesFitting(_ layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes {
//Exhibit A - We need to cache our calculation to prevent a crash.
if !isHeightCalculated {
setNeedsLayout()
layoutIfNeeded()
let size = contentView.systemLayoutSizeFitting(layoutAttributes.size)
var newFrame = layoutAttributes.frame
newFrame.size.width = CGFloat(ceilf(Float(size.width)))
layoutAttributes.frame = newFrame
isHeightCalculated = true
}
return layoutAttributes
}
참고 iOS 9에서는주의하지 않으면 구현에서 충돌이 발생할 수있는 동작이 약간 변경되었습니다 (자세한 내용은 여기 참조 ). 구현할 때 preferredLayoutAttributesFittingAttributes
레이아웃 속성의 프레임 만 한 번만 변경해야합니다. 이 작업을 수행하지 않으면 레이아웃이 구현을 무기한 호출하고 결국 충돌합니다. 한 가지 해결책은 셀에서 계산 된 크기를 캐시하고 셀을 재사용하거나 isHeightCalculated
속성으로 수행 한 내용을 변경할 때마다 무효화하는 것입니다 .
레이아웃 경험
이 시점에서 collectionView에 '기능적인'동적 셀이 있어야합니다. 아직 테스트 중에 충분한 기본 솔루션을 찾지 못 했으므로 언제든지 의견을 말하십시오. 여전히 UITableView
역동적 인 사이징 IMHO와의 싸움에서이기는 것 같습니다 .
경고
프로토 타입 셀을 사용하여 추정 된 항목 크기 를 계산하는 경우 XIB가 크기 클래스를 사용 하면 중단됩니다 . 그 이유는 XIB에서 셀을로드 할 때 크기 클래스가로 구성되기 때문입니다 Undefined
. iOS 7에서는 크기 클래스가 장치 (iPad = Regular-Any, iPhone = Compact-Any)를 기반으로로드되기 때문에 iOS 8 이상에서만 손상됩니다. XIB를로드하지 않고 추정 된 항목 크기를 설정하거나 XIB에서 셀을로드하고 collectionView에 추가하고 (traitCollection을 설정 함) 레이아웃을 수행 한 다음 슈퍼 뷰에서 제거 할 수 있습니다. 또는 셀이 traitCollection
게터를 재정의 하고 적절한 특성을 반환 하도록 할 수도 있습니다 . 그것은 당신에게 달려 있습니다.
내가 놓친 것이 있으면 알려주세요. 도움이 되길 바랍니다. 행운을 빕니다.
Daniel Galasko의 답변에 대한 몇 가지 주요 변경 사항으로 모든 문제가 해결되었습니다. 불행히도, 나는 직접적으로 (아직) 의견을 말할만큼 충분한 평판을 얻지 못했습니다.
1 단계에서 자동 레이아웃을 사용하는 경우 단일 상위 UIView를 셀에 추가하기 만하면됩니다. 셀 내부의 모든 것은 부모의 하위 뷰 여야합니다. 그것은 내 모든 문제에 답했다. Xcode는 UITableViewCells에 이것을 자동으로 추가하지만 UICollectionViewCells에는 그렇지 않습니다. 문서 에 따르면 :
셀 모양을 구성하려면 dataView 속성의보기에 데이터 항목의 내용을 하위보기로 표시하는 데 필요한보기를 추가하십시오. 셀 자체에 하위 뷰를 직접 추가하지 마십시오.
그런 다음 3 단계를 완전히 건너 뜁니다. 필요하지 않습니다.
iOS10에는 UICollectionViewFlowLayout.automaticSize
(이전 UICollectionViewFlowLayoutAutomaticSize
) 이라는 새로운 상수가 있으므로 대신 :
self.flowLayout.estimatedItemSize = CGSize(width: 100, height: 100)
이것을 사용할 수 있습니다 :
self.flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
컬렉션 뷰의 셀에 일정한 wid가있는 경우 특히 성능이 향상됩니다.
흐름 레이아웃 액세스 :
override func viewDidLoad() {
super.viewDidLoad()
if let flowLayout = collectionView?.collectionViewLayout as? UICollectionViewFlowLayout {
flowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
}
}
iOS 10 이상에서는 매우 간단한 2 단계 프로세스입니다.
모든 셀 내용이 단일 UIView (또는 UIStackView와 같은 UIView의 하위 항목) 내에 배치되어 자동 레이아웃을 많이 단순화해야합니다. UITableViewCells의 크기를 동적으로 조정하는 것처럼 전체 뷰 계층에는 가장 바깥 쪽 컨테이너에서 가장 안쪽 뷰까지 제약 조건이 구성되어 있어야합니다. UICollectionViewCell과 즉각적인 childview 사이의 제약 조건이 포함됩니다.
UICollectionView의 flowlayout에 자동 크기 조정을 지시하십시오.
yourFlowLayout.estimatedItemSize = UICollectionViewFlowLayout.automaticSize
viewDidLoad ()에 flowLayout 추가
override func viewDidLoad() { super.viewDidLoad() if let flowLayout = infoCollection.collectionViewLayout as? UICollectionViewFlowLayout { flowLayout.estimatedItemSize = CGSize(width: 1, height:1) } }
또한 UIView를 셀의 mainContainer로 설정하고 셀에 필요한 모든보기를 추가하십시오.
자세한 내용은이 멋진 튜토리얼을 참조하십시오 .iOS 9 및 10에서 자동 레이아웃을 사용하여 자동 크기 조정 셀이있는 UICollectionView
일정 시간 동안이 문제를 해결 한 후 스크롤을 비활성화하지 않으면 UITextViews에서 크기 조정이 작동하지 않는 것으로 나타났습니다.
let textView = UITextView()
textView.scrollEnabled = false
컬렉션 뷰의 동적 셀 높이를 수행했습니다. 여기 git hub repo가 있습니다.
그리고 preferredLayoutAttributesFittingAttributes가 두 번 이상 호출 된 이유를 찾아보십시오. 실제로는 3 회 이상 호출됩니다.
첫 번째 preferredLayoutAttributesFittingAttributes :
(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa405c290e0> index path: (<NSIndexPath: 0xc000000000000016>
{length = 2, path = 0 - 0}); frame = (15 12; 384 57.5);
(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 0);
layoutAttributes.frame.size.height는 현재 상태 57.5 입니다.
두 번째 preferredLayoutAttributesFittingAttributes :
(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa405c16370> index path: (<NSIndexPath: 0xc000000000000016>
{length = 2, path = 0 - 0}); frame = (15 12; 384 534.5);
(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 0);
예상대로 셀 프레임 높이가 534.5 로 변경되었습니다 . 그러나 컬렉션 뷰의 높이는 여전히 0입니다.
3 번째 preferredLayoutAttributesFittingAttributes :
(lldb) po layoutAttributes
<UICollectionViewLayoutAttributes: 0x7fa403d516a0> index path: (<NSIndexPath: 0xc000000000000016>
{length = 2, path = 0 - 0}); frame = (15 12; 384 534.5);
(lldb) po self.collectionView
<UICollectionView: 0x7fa40606c800; frame = (0 57.6667; 384 477);
컬렉션 뷰 높이가 0에서 477로 변경된 것을 볼 수 있습니다 .
동작은 스크롤 처리와 비슷합니다.
1. Before self-sizing cell
2. Validated self-sizing cell again after other cells recalculated.
3. Did changed self-sizing cell
처음에는이 방법이 한 번만 호출된다고 생각했습니다. 그래서 나는 다음과 같이 코딩했다 :
CGRect frame = layoutAttributes.frame;
frame.size.height = frame.size.height + self.collectionView.contentSize.height;
UICollectionViewLayoutAttributes* newAttributes = [layoutAttributes copy];
newAttributes.frame = frame;
return newAttributes;
이 줄 :
frame.size.height = frame.size.height + self.collectionView.contentSize.height;
시스템 호출 무한 루프 및 앱 충돌이 발생합니다.
크기가 변경되면 모든 셀의 위치 (예 : 프레임)가 더 이상 변경되지 않을 때까지 모든 셀의 preferredLayoutAttributesFittingAttributes를 반복해서 검증합니다.
UICollectionViewDelegateFlowLayout 메소드를 구현하는 경우 :
- (CGSize)collectionView:(UICollectionView*)collectionView layout:(UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath*)indexPath
When you call collectionview performBatchUpdates:completion:
, the size height will use sizeForItemAtIndexPath
instead of preferredLayoutAttributesFittingAttributes
.
The rendering process of performBatchUpdates:completion
will go through the method preferredLayoutAttributesFittingAttributes
but it ignores your changes.
To whomever it may help,
I had that nasty crash if estimatedItemSize
was set. Even if I returned 0 in numberOfItemsInSection
. Therefore, the cells themselves and their auto-layout were not the cause of the crash... The collectionView just crashed, even when empty, just because estimatedItemSize
was set for self-sizing.
In my case I reorganized my project, from a controller containing a collectionView to a collectionViewController, and it worked.
Go figure.
The example method above does not compile. Here is a corrected version (but untested as to whether or not it works.)
override func preferredLayoutAttributesFittingAttributes(layoutAttributes: UICollectionViewLayoutAttributes) -> UICollectionViewLayoutAttributes
{
let attr: UICollectionViewLayoutAttributes = layoutAttributes.copy() as! UICollectionViewLayoutAttributes
var newFrame = attr.frame
self.frame = newFrame
self.setNeedsLayout()
self.layoutIfNeeded()
let desiredHeight: CGFloat = self.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
newFrame.size.height = desiredHeight
attr.frame = newFrame
return attr
}
Update more information:
If you use
flowLayout.estimatedItemSize
, suggest use iOS8.3 later version. Before iOS8.3, it will crash[super layoutAttributesForElementsInRect:rect];
. The error message is*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
Second, in iOS8.x version,
flowLayout.estimatedItemSize
will cause different section inset setting did not work. i.e. function:(UIEdgeInsets)collectionView:layout:insetForSectionAtIndex:
.
For anyone who tried everything without luck, this is the only thing that got it working for me. For the multiline labels inside cell, try adding this magic line:
label.preferredMaxLayoutWidth = 200
More info: here
Cheers!
In addition to above answers,
Just make sure you set estimatedItemSize property of UICollectionViewFlowLayout to some size and do not implement sizeForItem:atIndexPath delegate method.
That's it.
I tried using estimatedItemSize
but there were a bunch of bugs when inserting and deleting cells if the estimatedItemSize
was not exactly equal to the cell's height. i stopped setting estimatedItemSize
and implemented dynamic cell's by using a prototype cell. here's how that's done:
create this protocol:
protocol SizeableCollectionViewCell {
func fittedSize(forConstrainedSize size: CGSize)->CGSize
}
implement this protocol in your custom UICollectionViewCell
:
class YourCustomCollectionViewCell: UICollectionViewCell, SizeableCollectionViewCell {
@IBOutlet private var mTitle: UILabel!
@IBOutlet private var mDescription: UILabel!
@IBOutlet private var mContentView: UIView!
@IBOutlet private var mTitleTopConstraint: NSLayoutConstraint!
@IBOutlet private var mDesciptionBottomConstraint: NSLayoutConstraint!
func fittedSize(forConstrainedSize size: CGSize)->CGSize {
let fittedSize: CGSize!
//if height is greatest value, then it's dynamic, so it must be calculated
if size.height == CGFLoat.greatestFiniteMagnitude {
var height: CGFloat = 0
/*now here's where you want to add all the heights up of your views.
apple provides a method called sizeThatFits(size:), but it's not
implemented by default; except for some concrete subclasses such
as UILabel, UIButton, etc. search to see if the classes you use implement
it. here's how it would be used:
*/
height += mTitle.sizeThatFits(size).height
height += mDescription.sizeThatFits(size).height
height += mCustomView.sizeThatFits(size).height //you'll have to implement this in your custom view
//anything that takes up height in the cell has to be included, including top/bottom margin constraints
height += mTitleTopConstraint.constant
height += mDescriptionBottomConstraint.constant
fittedSize = CGSize(width: size.width, height: height)
}
//else width is greatest value, if not, you did something wrong
else {
//do the same thing that's done for height but with width, remember to include leading/trailing margins in calculations
}
return fittedSize
}
}
now make your controller conform to UICollectionViewDelegateFlowLayout
, and in it, have this field:
class YourViewController: UIViewController, UICollectionViewDelegateFlowLayout {
private var mCustomCellPrototype = UINib(nibName: <name of the nib file for your custom collectionviewcell>, bundle: nil).instantiate(withOwner: nil, options: nil).first as! SizeableCollectionViewCell
}
it will be used as a prototype cell to bind data to and then determine how that data affected the dimension that you want to be dynamic
finally, the UICollectionViewDelegateFlowLayout's
collectionView(:layout:sizeForItemAt:)
has to be implemented:
class YourViewController: UIViewController, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource {
private var mDataSource: [CustomModel]
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath)->CGSize {
//bind the prototype cell with the data that corresponds to this index path
mCustomCellPrototype.bind(model: mDataSource[indexPath.row]) //this is the same method you would use to reconfigure the cells that you dequeue in collectionView(:cellForItemAt:). i'm calling it bind
//define the dimension you want constrained
let width = UIScreen.main.bounds.size.width - 20 //the width you want your cells to be
let height = CGFloat.greatestFiniteMagnitude //height has the greatest finite magnitude, so in this code, that means it will be dynamic
let constrainedSize = CGSize(width: width, height: height)
//determine the size the cell will be given this data and return it
return mCustomCellPrototype.fittedSize(forConstrainedSize: constrainedSize)
}
}
and that's it. Returning the cell's size in collectionView(:layout:sizeForItemAt:)
in this way preventing me from having to use estimatedItemSize
, and inserting and deleting cells works perfectly.
참고 URL : https://stackoverflow.com/questions/25895311/uicollectionview-self-sizing-cells-with-auto-layout
'Programing' 카테고리의 다른 글
CMake 파일에 링커 또는 컴파일 플래그를 어떻게 추가합니까? (0) | 2020.05.16 |
---|---|
{ " (0) | 2020.05.16 |
Visual Studio에서 설치된 TypeScript 버전을 어디에서 찾을 수 있습니까? (0) | 2020.05.16 |
비동기 함수를 호출하는 동안 mocha 테스트에서 시간 초과 오류를 피하는 방법 : 시간 초과 2000ms 초과 (0) | 2020.05.16 |
MYSQL 업데이트 명령문 내부 조인 테이블 (0) | 2020.05.16 |