UITableViewCell
부드러운 스크롤 성능을 유지하면서 각 셀의 내용과 하위 뷰가 행 높이 (자체 / 자동)를 결정하도록 테이블보기 에서 s 내에서 자동 레이아웃을 사용하는 방법은 무엇입니까?
UITableViewCell
부드러운 스크롤 성능을 유지하면서 각 셀의 내용과 하위 뷰가 행 높이 (자체 / 자동)를 결정하도록 테이블보기 에서 s 내에서 자동 레이아웃을 사용하는 방법은 무엇입니까?
답변:
TL; DR : 독서를 좋아하지 않습니까? GitHub의 샘플 프로젝트로 바로 이동하십시오.
아래의 첫 2 단계는 개발중인 iOS 버전에 관계없이 적용 할 수 있습니다.
당신의에서 UITableViewCell
셀의 파단이 셀의 가장자리에 고정 가장자리 가질 수 있도록 서브 클래스 제약 조건을 추가 있는 contentView (가장 중요한 위쪽과 아래쪽 가장자리를). 참고 : 하위 뷰를 셀 자체에 고정하지 마십시오. 셀에만 contentView
! 각 하위 뷰의 세로 차원에서 콘텐츠 압축 저항 및 콘텐츠 껴안기 제약 조건이 추가 한 높은 우선 순위 제약 조건으로 재정의되지 않도록 하여 이러한 하위 뷰의 고유 한 콘텐츠 크기로 인해 테이블 뷰 셀의 콘텐츠 뷰 높이를 높일 수 있습니다. ( 허? 여기를 클릭하십시오. )
이 아이디어는 셀의 서브 뷰를 셀의 컨텐츠 뷰에 수직으로 연결하여 압력을 가해 컨텐츠 뷰를 확장 할 수 있도록하는 것입니다. 몇 가지 서브 뷰가있는 예제 셀을 사용하면 다음과 같은 제약 조건 중 일부 (모두가 아님!) 가 어떻게 보이는지 시각적으로 보여줍니다 .
위의 예제 셀에서 여러 줄 본문 레이블에 더 많은 텍스트가 추가됨에 따라 텍스트에 맞게 세로로 자라야 셀의 높이를 효과적으로 증가시킬 수 있습니다. (물론, 이것이 제대로 작동하려면 제약 조건을 가져와야합니다!)
자동 레이아웃을 사용하여 동적 셀 높이를 얻는 데있어 제약 조건을 올바르게 얻는 것이 가장 어렵고 중요한 부분 입니다. 여기서 실수를하면 다른 모든 것이 작동하지 않을 수 있으므로 시간을 내십시오! 코드에 제약 조건을 설정하는 것이 좋습니다. 어떤 제약 조건이 어디에 추가되는지 정확히 알고 있기 때문에 문제가 발생했을 때 디버깅하기가 훨씬 쉽습니다. 코드에 제약 조건을 추가하는 것은 레이아웃 앵커 또는 GitHub에서 사용할 수있는 환상적인 오픈 소스 API 중 하나를 사용하여 Interface Builder보다 쉽고 강력합니다.
updateConstraints
UITableViewCell 하위 클래스 의 메서드 내에서이 작업을 한 번 수행해야합니다 . 참고 updateConstraints
이렇게 한 번 이상 같은 제약 조건을 추가 피하기 위해 한 번 이상 호출 할 수는 확실히 내에서 제약 추가 코드를 포장 할 수 있도록 updateConstraints
같은 부울 속성에 대한 검사에 didSetupConstraints
당신이 당신의 제약 조건을 실행 한 후 YES로 설정한다 ( -코드를 한 번 추가). 반면, 기존 제약 조건 (예 : constant
일부 제약 조건 의 속성 조정)을 업데이트하는 코드가있는 경우 에는 메소드를 호출 할 때마다 실행될 수 있도록 updateConstraints
검사 외부에 배치하십시오 didSetupConstraints
.셀의 모든 고유 제한 조건 세트마다 고유 한 셀 재사용 ID를 사용하십시오. 즉, 셀에 둘 이상의 고유 한 레이아웃이있는 경우 각 고유 한 레이아웃에는 고유 한 재사용 식별자가 있어야합니다. (새 재사용 식별자를 사용해야한다는 힌트는 셀 변형에 다른 수의 하위 뷰가 있거나 하위 뷰가 다른 방식으로 정렬 된 경우입니다.)
예를 들어, 각 셀에 이메일 메시지를 표시하는 경우 제목이있는 메시지, 제목과 본문이있는 메시지, 제목과 사진이 첨부 된 메시지, 제목이있는 메시지, 신체 및 사진 첨부. 각 레이아웃에는이를 달성하는 데 필요한 완전히 다른 제약 조건이 있으므로 셀이 초기화되고 이러한 셀 유형 중 하나에 대한 제약 조건이 추가되면 셀은 해당 셀 유형에 고유 한 재사용 식별자를 가져와야합니다. 이는 재사용을 위해 셀을 큐에서 제거 할 때 제한 조건이 이미 추가되었으며 해당 셀 유형으로 이동할 준비가되었음을 의미합니다.
고유 내용 크기의 차이로 인해 동일한 구속 조건 (유형)을 가진 셀의 높이는 여전히 다를 수 있습니다! 콘텐츠의 크기가 다르기 때문에 기본적으로 다른 레이아웃 (다른 제약 조건)을 다른 계산 된 뷰 프레임 (동일한 제약 조건에서 해결됨)과 혼동하지 마십시오.
자체 크기 조정 테이블보기 셀을 사용하려면 테이블보기의 rowHeight 특성을 UITableViewAutomaticDimension으로 설정해야합니다. expectedRowHeight 속성에도 값을 할당해야합니다. 이 두 속성이 설정되면 시스템은 자동 레이아웃을 사용하여 행의 실제 높이를 계산합니다
Apple : 자체 크기 조정 테이블 뷰 셀에 대한 작업
iOS 8을 사용하면 Apple은 이전에 iOS 8 이전에 구현해야했던 많은 작업을 내재화했습니다. 자체 크기 조정 셀 메커니즘이 작동하려면 먼저 rowHeight
테이블보기 의 속성을 상수로 설정해야합니다 UITableViewAutomaticDimension
. 그런 다음 테이블 뷰의 estimatedRowHeight
속성을 0이 아닌 값 으로 설정하여 행 높이 추정을 활성화하면됩니다 .
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is
이것이 수행하는 작업은 아직 화면에 표시되지 않은 셀의 행 높이에 대한 임시 견적 / 자리 표시자를 테이블보기에 제공하는 것입니다. 그런 다음이 셀이 화면에서 스크롤 되려고하면 실제 행 높이가 계산됩니다. 각 행의 실제 높이를 결정하기 위해 테이블 뷰는 각 셀 contentView
에 컨텐츠 뷰의 알려진 고정 너비 (테이블 뷰의 너비를 기준으로 섹션 인덱스와 같은 추가 항목을 뺀 것)를 기준으로 필요한 높이를 자동으로 묻습니다. 또는 액세서리보기) 및 셀의 내용보기 및 하위보기에 추가 한 자동 레이아웃 제약 조건. 이 실제 셀 높이가 결정되면 행의 이전 예상 높이가 새로운 실제 높이로 업데이트됩니다 (그리고 테이블 뷰의 contentSize / contentOffset에 대한 조정은 필요에 따라 이루어집니다).
일반적으로 제공하는 견적은 매우 정확할 필요는 없습니다. 테이블보기에서 스크롤 표시기의 크기를 올바르게 조정하는 데만 사용되며, 테이블보기는 스크롤 표시기를 조정하여 잘못된 추정치에 대한 조정을 잘 수행합니다. 화면에서 셀을 스크롤하십시오. estimatedRowHeight
테이블보기 ( viewDidLoad
또는 유사한) 의 속성을 "평균"행 높이 인 상수 값으로 설정해야합니다. 행 높이가 극단적으로 변동하는 경우 (예를 들어, 크기 순서에 따라 다름) tableView:estimatedHeightForRowAtIndexPath:
각 행에 대해 더 정확한 추정값을 리턴하는 데 필요한 최소 계산을 수행하도록 구현해야하는 경우 스크롤 표시기 "점프"가 나타납니다 .
먼저, 높이 계산에 엄격하게 사용되는 테이블 뷰 셀의 오프 스크린 인스턴스 ( 각 재사용 식별자마다 하나의 인스턴스)를 인스턴스화하십시오 . (스크린은 셀 참조가 뷰 컨트롤러의 속성 / ivar에 저장되고 tableView:cellForRowAtIndexPath:
테이블 뷰가 실제로 화면에서 렌더링되도록 반환되지 않음 을 의미합니다.) 그런 다음 셀은 정확한 내용 (예 : 텍스트, 이미지 등)으로 구성되어야합니다. 테이블 뷰에 표시 될 경우 보류됩니다.
즉시 파단 레이아웃에 그런 다음 셀을 강제하고 사용 systemLayoutSizeFittingSize:
상의 방법 UITableViewCell
의 contentView
셀의 필요한 높이가 무엇인지 알아. UILayoutFittingCompressedSize
셀의 모든 내용에 맞는 가장 작은 크기를 얻는 데 사용하십시오 . 그런 다음 tableView:heightForRowAtIndexPath:
델리게이트 메소드 에서 높이를 리턴 할 수 있습니다 .
테이블 뷰에 12 개 이상의 행이있는 경우 자동 레이아웃 제약 조건 해결을 수행하면 테이블 뷰를 처음로드 할 때 처음로드 할 때 tableView:heightForRowAtIndexPath:
마다 매 행마다 호출되는 것처럼 메인 스레드가 빠르게 멈출 수 있습니다 ( 스크롤 표시기의 크기를 계산하기 위해).
iOS 7부터는 estimatedRowHeight
테이블 뷰 에서 속성을 사용할 수 있으며 반드시 사용해야합니다 . 이것이 수행하는 작업은 아직 화면에 표시되지 않은 셀의 행 높이에 대한 임시 견적 / 자리 표시자를 테이블보기에 제공하는 것입니다. 그런 다음이 셀이 화면에서 스크롤 되려고 할 때 실제 행 높이가 계산되고 (호출 tableView:heightForRowAtIndexPath:
) 실제 예상 높이가 업데이트됩니다.
일반적으로 제공하는 견적은 매우 정확할 필요는 없습니다. 테이블보기에서 스크롤 표시기의 크기를 올바르게 조정하는 데만 사용되며, 테이블보기는 스크롤 표시기를 조정하여 잘못된 추정치에 대한 조정을 잘 수행합니다. 화면에서 셀을 스크롤하십시오. estimatedRowHeight
테이블보기 ( viewDidLoad
또는 유사한) 의 속성을 "평균"행 높이 인 상수 값으로 설정해야합니다. 행 높이가 극단적으로 변동하는 경우 (예를 들어, 크기 순서에 따라 다름) tableView:estimatedHeightForRowAtIndexPath:
각 행에 대해 더 정확한 추정값을 리턴하는 데 필요한 최소 계산을 수행하도록 구현해야하는 경우 스크롤 표시기 "점프"가 나타납니다 .
위의 모든 작업을 수행했지만에서 제약 조건 해결을 수행 할 때 성능이 허용 할 수 없을 정도로 느리다는 것을 알게되면 tableView:heightForRowAtIndexPath:
불행히도 셀 높이에 대해 일부 캐싱을 구현해야합니다. (이것은 Apple 엔지니어가 제안한 방법입니다.) 일반적인 아이디어는 Autolayout 엔진이 처음으로 제약 조건을 해결하도록 한 다음 해당 셀의 계산 된 높이를 캐시하고 해당 셀 높이의 모든 향후 요청에 대해 캐시 된 값을 사용하는 것입니다. 당연히 셀 높이가 변경 될 수있는 상황이 발생할 때 셀의 캐시 된 높이를 지우는 것이 중요합니다. 주로 셀 내용이 변경되거나 다른 중요한 이벤트가 발생할 때 (사용자 조정과 같은 경우) 동적 유형 텍스트 크기 슬라이더).
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Determine which reuse identifier should be used for the cell at this
// index path, depending on the particular layout required (you may have
// just one, or may have many).
NSString *reuseIdentifier = ...;
// Dequeue a cell for the reuse identifier.
// Note that this method will init and return a new cell if there isn't
// one available in the reuse pool, so either way after this line of
// code you will have a cell with the correct constraints ready to go.
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
// Configure the cell with content for the given indexPath, for example:
// cell.textLabel.text = someTextForThisCell;
// ...
// Make sure the constraints have been set up for this cell, since it
// may have just been created from scratch. Use the following lines,
// assuming you are setting up constraints from within the cell's
// updateConstraints method:
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
// If you are using multi-line UILabels, don't forget that the
// preferredMaxLayoutWidth needs to be set correctly. Do it at this
// point if you are NOT doing it within the UITableViewCell subclass
// -[layoutSubviews] method. For example:
// cell.multiLineLabel.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds);
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Determine which reuse identifier should be used for the cell at this
// index path.
NSString *reuseIdentifier = ...;
// Use a dictionary of offscreen cells to get a cell for the reuse
// identifier, creating a cell and storing it in the dictionary if one
// hasn't already been added for the reuse identifier. WARNING: Don't
// call the table view's dequeueReusableCellWithIdentifier: method here
// because this will result in a memory leak as the cell is created but
// never returned from the tableView:cellForRowAtIndexPath: method!
UITableViewCell *cell = [self.offscreenCells objectForKey:reuseIdentifier];
if (!cell) {
cell = [[YourTableViewCellClass alloc] init];
[self.offscreenCells setObject:cell forKey:reuseIdentifier];
}
// Configure the cell with content for the given indexPath, for example:
// cell.textLabel.text = someTextForThisCell;
// ...
// Make sure the constraints have been set up for this cell, since it
// may have just been created from scratch. Use the following lines,
// assuming you are setting up constraints from within the cell's
// updateConstraints method:
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
// Set the width of the cell to match the width of the table view. This
// is important so that we'll get the correct cell height for different
// table view widths if the cell's height depends on its width (due to
// multi-line UILabels word wrapping, etc). We don't need to do this
// above in -[tableView:cellForRowAtIndexPath] because it happens
// automatically when the cell is used in the table view. Also note,
// the final width of the cell may not be the width of the table view in
// some cases, for example when a section index is displayed along
// the right side of the table view. You must account for the reduced
// cell width.
cell.bounds = CGRectMake(0.0, 0.0, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds));
// Do the layout pass on the cell, which will calculate the frames for
// all the views based on the constraints. (Note that you must set the
// preferredMaxLayoutWidth on multiline UILabels inside the
// -[layoutSubviews] method of the UITableViewCell subclass, or do it
// manually at this point before the below 2 lines!)
[cell setNeedsLayout];
[cell layoutIfNeeded];
// Get the actual height required for the cell's contentView
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
// Add an extra point to the height to account for the cell separator,
// which is added between the bottom of the cell's contentView and the
// bottom of the table view cell.
height += 1.0;
return height;
}
// NOTE: Set the table view's estimatedRowHeight property instead of
// implementing the below method, UNLESS you have extreme variability in
// your row heights and you notice the scroll indicator "jumping"
// as you scroll.
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Do the minimal calculations required to be able to return an
// estimated row height that's within an order of magnitude of the
// actual height. For example:
if ([self isTallCellAtIndexPath:indexPath]) {
return 350.0;
} else {
return 40.0;
}
}
이 프로젝트는 UILabels에 동적 내용을 포함하는 테이블 뷰 셀로 인해 가변 행 높이를 갖는 테이블 뷰의 완전한 작동 예입니다.
당신이 자 마린을 사용하는 경우,이 체크 아웃 샘플 프로젝트 에 의해 함께 넣어 @KentBoogaart .
heightForRowAtIndexPath
하고 셀을 유지 한 후 다음 cellForRowAtIndexPath
에 호출 하면 누출을 피할 수 있습니다 .
iOS8
구현이 여전히 iOS9
and에 권장 iOS10
됩니까, 아니면이 답변이 게시 된 이후에 새로운 접근법이 있습니까?
위의 iOS 8의 경우 정말 간단합니다.
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.estimatedRowHeight = 80
self.tableView.rowHeight = UITableView.automaticDimension
}
또는
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableView.automaticDimension
}
그러나 iOS 7의 경우 키는 자동 레이아웃 후 높이를 계산합니다.
func calculateHeightForConfiguredSizingCell(cell: GSTableViewCell) -> CGFloat {
cell.setNeedsLayout()
cell.layoutIfNeeded()
let height = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingExpandedSize).height + 1.0
return height
}
중대한
여러 줄 레이블 경우, 설정 잊지 마세요 numberOfLines
합니다 0
.
잊지 마세요 label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)
전체 예제 코드는 여기에 있습니다 .
스위프트 3 업데이트
William Hu의 Swift 답변은 훌륭하지만 처음으로 무언가를 배울 때 간단하지만 자세한 단계를 수행하는 데 도움이됩니다. 아래 예제는 UITableView
다양한 셀 높이 로 만드는 법을 배우는 동안 테스트 프로젝트 입니다. Swift의 기본 UITableView 예제를 기반으로했습니다 .
완성 된 프로젝트는 다음과 같아야합니다.
단일 뷰 응용 프로그램 일 수 있습니다.
새 Swift 파일을 프로젝트에 추가하십시오. 이름을 MyCustomCell로 지정하십시오. 이 클래스는 스토리 보드에서 셀에 추가 한보기에 대한 아울렛을 보유합니다. 이 기본 예제에서는 각 셀에 하나의 레이블 만 있습니다.
import UIKit
class MyCustomCell: UITableViewCell {
@IBOutlet weak var myCellLabel: UILabel!
}
나중에이 콘센트를 연결하겠습니다.
ViewController.swift를 열고 다음 내용이 있는지 확인하십시오.
import UIKit
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// These strings will be the data for the table view cells
let animals: [String] = [
"Ten horses: horse horse horse horse horse horse horse horse horse horse ",
"Three cows: cow, cow, cow",
"One camel: camel",
"Ninety-nine sheep: sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep baaaa sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep sheep",
"Thirty goats: goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat goat "]
// Don't forget to enter this in IB also
let cellReuseIdentifier = "cell"
@IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// delegate and data source
tableView.delegate = self
tableView.dataSource = self
// Along with auto layout, these are the keys for enabling variable cell height
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
}
// number of rows in table view
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.animals.count
}
// create a cell for each table view row
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:MyCustomCell = self.tableView.dequeueReusableCell(withIdentifier: cellReuseIdentifier) as! MyCustomCell
cell.myCellLabel.text = self.animals[indexPath.row]
return cell
}
// method to run when table view cell is tapped
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You tapped cell number \(indexPath.row).")
}
}
중요 사항:
가변 셀 높이를 가능하게하는 것은 다음 두 줄의 코드 (자동 레이아웃과 함께)입니다.
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
뷰 컨트롤러에 테이블 뷰를 추가하고 자동 레이아웃을 사용하여 4면에 고정하십시오. 그런 다음 테이블 뷰 셀을 테이블 뷰로 드래그하십시오. 프로토 타입 셀로 레이블을 드래그하십시오. 자동 레이아웃을 사용하여 레이블을 테이블 뷰 셀 컨텐츠 뷰의 네 모서리에 고정하십시오.
중요 사항:
커스텀 클래스 이름과 식별자
테이블 뷰 셀을 선택하고 사용자 정의 클래스를 MyCustomCell
(추가 한 Swift 파일의 클래스 이름)으로 설정하십시오. 또한 식별자를 위의 코드에서 cell
사용한 것과 동일한 문자열 로 설정하십시오 cellReuseIdentifier
.
라벨 제로 라인
0
레이블에서 줄 수를 설정하십시오 . 이것은 여러 줄을 의미하며 레이블이 내용에 따라 크기를 조정할 수 있습니다.
콘센트를 연결
tableView
변수로 드래그를 제어 ViewController
합니다.myCellLabel
에 대해 MyCustomCell
클래스 의 변수에 대해서도 동일하게 수행하십시오 .이제 프로젝트를 실행하고 다양한 높이의 셀을 얻을 수 있어야합니다.
선행 및 후행 (왼쪽 및 오른쪽) 모서리를 고정하지 않는 경우 preferredMaxLayoutWidth
줄 바꿈시기를 알 수 있도록 레이블을 설정해야 할 수도 있습니다 . 예를 들어 선행 및 후행 가장자리를 고정하지 않고 위의 프로젝트 레이블에 Center Horizontally 구속 조건을 추가 한 경우이 선을 tableView:cellForRowAtIndexPath
메소드에 추가해야합니다 .
cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
preferredMaxLayoutWidth
셀에 대해 설정하는 것이 더 좋지 contentSize
않습니까? 그렇게하면 accessoryView가 있거나 편집을 위해 스 와이프하면 여전히 고려됩니까?
@smileyborg의이 영리한 솔루션을 UICollectionViewCell+AutoLayoutDynamicHeightCalculation
범주 로 묶기로 결정했습니다 .
이 카테고리는 또한 @wildmonkey의 답변에 설명 된 문제를 수정합니다 (펜촉에서 셀로드 및 systemLayoutSizeFittingSize:
반환 CGRectZero
)
캐싱을 고려하지는 않지만 현재 내 요구에 적합합니다. 자유롭게 복사하여 붙여 넣고 해킹하십시오.
#import <UIKit/UIKit.h>
typedef void (^UICollectionViewCellAutoLayoutRenderBlock)(void);
/**
* A category on UICollectionViewCell to aid calculating dynamic heights based on AutoLayout contraints.
*
* Many thanks to @smileyborg and @wildmonkey
*
* @see stackoverflow.com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights
*/
@interface UICollectionViewCell (AutoLayoutDynamicHeightCalculation)
/**
* Grab an instance of the receiving type to use in order to calculate AutoLayout contraint driven dynamic height. The method pulls the cell from a nib file and moves any Interface Builder defined contrainsts to the content view.
*
* @param name Name of the nib file.
*
* @return collection view cell for using to calculate content based height
*/
+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name;
/**
* Returns the height of the receiver after rendering with your model data and applying an AutoLayout pass
*
* @param block Render the model data to your UI elements in this block
*
* @return Calculated constraint derived height
*/
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width;
/**
* Directly calls `heightAfterAutoLayoutPassAndRenderingWithBlock:collectionViewWidth` assuming a collection view width spanning the [UIScreen mainScreen] bounds
*/
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block;
@end
#import "UICollectionViewCell+AutoLayout.h"
@implementation UICollectionViewCell (AutoLayout)
#pragma mark Dummy Cell Generator
+ (instancetype)heightCalculationCellFromNibWithName:(NSString *)name
{
UICollectionViewCell *heightCalculationCell = [[[NSBundle mainBundle] loadNibNamed:name owner:self options:nil] lastObject];
[heightCalculationCell moveInterfaceBuilderLayoutConstraintsToContentView];
return heightCalculationCell;
}
#pragma mark Moving Constraints
- (void)moveInterfaceBuilderLayoutConstraintsToContentView
{
[self.constraints enumerateObjectsUsingBlock:^(NSLayoutConstraint *constraint, NSUInteger idx, BOOL *stop) {
[self removeConstraint:constraint];
id firstItem = constraint.firstItem == self ? self.contentView : constraint.firstItem;
id secondItem = constraint.secondItem == self ? self.contentView : constraint.secondItem;
[self.contentView addConstraint:[NSLayoutConstraint constraintWithItem:firstItem
attribute:constraint.firstAttribute
relatedBy:constraint.relation
toItem:secondItem
attribute:constraint.secondAttribute
multiplier:constraint.multiplier
constant:constraint.constant]];
}];
}
#pragma mark Height
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block
{
return [self heightAfterAutoLayoutPassAndRenderingWithBlock:block
collectionViewWidth:CGRectGetWidth([[UIScreen mainScreen] bounds])];
}
- (CGFloat)heightAfterAutoLayoutPassAndRenderingWithBlock:(UICollectionViewCellAutoLayoutRenderBlock)block collectionViewWidth:(CGFloat)width
{
NSParameterAssert(block);
block();
[self setNeedsUpdateConstraints];
[self updateConstraintsIfNeeded];
self.bounds = CGRectMake(0.0f, 0.0f, width, CGRectGetHeight(self.bounds));
[self setNeedsLayout];
[self layoutIfNeeded];
CGSize calculatedSize = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return calculatedSize.height;
}
@end
- (CGSize)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout sizeForItemAtIndexPath:(NSIndexPath *)indexPath
{
MYSweetCell *cell = [MYSweetCell heightCalculationCellFromNibWithName:NSStringFromClass([MYSweetCell class])];
CGFloat height = [cell heightAfterAutoLayoutPassAndRenderingWithBlock:^{
[(id<MYSweetCellRenderProtocol>)cell renderWithModel:someModel];
}];
return CGSizeMake(CGRectGetWidth(self.collectionView.bounds), height);
}
고맙게도 iOS8에서이 재즈를 수행 할 필요는 없지만 지금은 존재합니다!
[YourCell new]
를 사용하고 더미로 사용할 수 있어야합니다 . 제약 조건 코드 작성 코드가 인스턴스에서 시작되고 프로그래밍 방식으로 레이아웃 패스를 트리거하는 한 잘 진행해야합니다.
UICollectionViews
도 잘 작동한다는 것을 알게 되었습니다.
내 해결책은 다음과 같습니다.
당신은 말할 필요가 가보기를로드하기 전에. 그렇지 않으면 예상대로 작동 할 수 없습니다.TableView
estimatedHeight
목표 -C
- (void)viewWillAppear:(BOOL)animated {
_messageField.delegate = self;
_tableView.estimatedRowHeight = 65.0;
_tableView.rowHeight = UITableViewAutomaticDimension;
}
Swift 4.2로 업데이트
override func viewWillAppear(_ animated: Bool) {
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 65.0
}
estimatedRowHeight
행마다 변하는 경우 어떻게해야 합니까? 초과 또는 미달해야합니까? 내가 사용하는 높이의 최소 또는 최대를 사용 tableView
합니까?
estimatedRowHeight
. 스크롤 막대의 크기에 영향을 미치며 실제 셀의 높이에는 영향을 미치지 않습니다. 선택한 높이에서 굵게 표시 : 삽입 / 삭제 애니메이션에 영향을줍니다.
@smileyborg가 제안한 솔루션은 거의 완벽합니다. 사용자 정의 셀이 있고 UILabel
동적 높이가있는 하나 이상을 원한다면 AutoLayout이 활성화 된 systemLayoutSizeFittingSize 메소드는 CGSizeZero
셀의 모든 제약 조건을 셀에서 contentView로 이동하지 않는 한 (@TomSwift에서 제안한 것처럼 슈퍼 뷰의 크기를 조정하는 방법) 자동 레이아웃과 함께 모든 하위 뷰에 적합합니까? ).
이렇게하려면 사용자 정의 UITableViewCell 구현에 다음 코드를 삽입해야합니다 (@Adrian 덕분에).
- (void)awakeFromNib{
[super awakeFromNib];
for (NSLayoutConstraint *cellConstraint in self.constraints) {
[self removeConstraint:cellConstraint];
id firstItem = cellConstraint.firstItem == self ? self.contentView : cellConstraint.firstItem;
id seccondItem = cellConstraint.secondItem == self ? self.contentView : cellConstraint.secondItem;
NSLayoutConstraint *contentViewConstraint =
[NSLayoutConstraint constraintWithItem:firstItem
attribute:cellConstraint.firstAttribute
relatedBy:cellConstraint.relation
toItem:seccondItem
attribute:cellConstraint.secondAttribute
multiplier:cellConstraint.multiplier
constant:cellConstraint.constant];
[self.contentView addConstraint:contentViewConstraint];
}
}
@ smileyborg 답변을 혼합하면 작동합니다.
systemLayoutSizeFittingSize
셀이 아닌 contentView에서 호출해야 함
대답으로 게시하기 위해 방금 뛰어 들었을 정도로 중요한 문제입니다.
@smileyborg의 대답은 대부분 정확합니다. 그러나 layoutSubviews
사용자 정의 셀 클래스 의 메소드에 코드를 설정 한 경우 (예 :)를 설정하면 preferredMaxLayoutWidth
이 코드로 실행되지 않습니다.
[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];
그것은 잠시 동안 나를 혼란시켰다. 그런 다음 contentView
셀 자체가 아닌 에 대한 layoutSubviews 만 트리거하기 때문에 깨달았습니다 .
내 작업 코드는 다음과 같습니다.
TCAnswerDetailAppSummaryCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailAppSummaryCell"];
[cell configureWithThirdPartyObject:self.app];
[cell layoutIfNeeded];
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height;
새 셀을 만드는 경우 setNeedsLayout
이미 설정되어 있기 때문에 전화를 걸 필요가 없습니다 . 셀에 대한 참조를 저장하는 경우이를 호출해야합니다. 어느 쪽이든 그것은 아무것도 아프지 않아야합니다.
같은 것을 설정하는 셀 서브 클래스를 사용하는 경우 또 다른 팁 preferredMaxLayoutWidth
. @smileyborg가 언급했듯이 "테이블 뷰 셀의 너비는 아직 테이블 뷰의 너비에 고정되어 있지 않습니다". 이것은 뷰 컨트롤러가 아닌 서브 클래스에서 작업을 수행하는 경우 문제가됩니다. 그러나 테이블 너비를 사용하여이 시점에서 셀 프레임을 간단히 설정할 수 있습니다.
예를 들어 높이 계산에서 :
self.summaryCell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailDefaultSummaryCell"];
CGRect oldFrame = self.summaryCell.frame;
self.summaryCell.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, self.tableView.frame.size.width, oldFrame.size.height);
(이 재사용을 위해이 특정 셀을 캐시하지만, 관련이 없습니다.)
사람들이 여전히이 문제를 겪고있는 경우. UITableViews와 함께 자동 레이아웃을 사용하는 방법과 동적 셀 높이 를위한 자동 레이아웃 및 오픈 소스 구성 요소를 활용하여이를보다 추상적이고 구현하기 쉽게 만드는 방법 에 대한 간단한 블로그 게시물을 작성했습니다 . https://github.com/Raizlabs/RZCellSizeManager
셀의 레이아웃이 좋은 한.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];
return [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
}
업데이트 : iOS 8에 도입 된 동적 크기 조정을 사용해야합니다.
tableView:cellForRowAtIndexPath:
해도 괜찮 tableView:heightForRowAtIndexPath:
습니까?
systemLayoutSizeFittingSize:
에서 tableView:cellForRowAtIndexPath:
다음 그 결과를 캐시와의 것을 사용 tableView:heightForRowAtIndexPath:
제한이 설정은 물론 제대로만큼 그만큼 잘 작동합니다!
(Xcode 8.x / Xcode 9.x 하단에서 읽음)
혼동의 원인이 될 수있는 Xcode 7.x의 다음 문제에주의하십시오.
Interface Builder는 자동 크기 조정 셀 설정을 올바르게 처리하지 않습니다. 제약 조건이 절대적으로 유효하더라도 IB는 여전히 불만을 제기하고 혼란스러운 제안과 오류를 제공합니다. 그 이유는 IB가 제약 조건에 따라 행 높이를 변경하지 않기 때문입니다 (셀이 내용에 맞도록). 대신, 행의 높이가 고정 유지하고 당신이 당신의 제약, 변경 제안을 시작 하면 무시를 .
예를 들어 모든 것을 올바르게 설정하고 경고, 오류, 모든 작업을 설정했다고 가정 해보십시오.
이제 글꼴 크기를 변경하면 (이 예제에서는 설명 레이블 글꼴 크기를 17.0에서 18.0로 변경합니다)
글꼴 크기가 증가했기 때문에 레이블은 이제 3 행을 차지하려고합니다 (2 행을 차지하기 전에).
Interface Builder가 예상대로 작동하면 새 레이블 높이를 수용하도록 셀 높이의 크기가 조정됩니다. 그러나 실제로 발생하는 것은 IB가 빨간색 자동 레이아웃 오류 아이콘을 표시하고 포옹 / 압축 우선 순위를 수정하도록 제안하는 것입니다.
이 경고를 무시해야합니다. 대신 할 수있는 일은 행 높이를 수동으로 변경하는 것입니다 (셀> 크기 검사기> 행 높이 선택).
빨간색 화살표 오류가 사라질 때까지 한 번에 한 번의 클릭 (위 / 아래 스테퍼 사용) 으로이 높이를 변경했습니다! (실제로 노란색 경고가 표시됩니다.이 시점에서 계속 '업데이트 프레임'을 수행하면 모두 작동합니다).
* Interface Builder에서 이러한 빨간색 오류나 노란색 경고를 실제로 해결할 필요는 없습니다. 런타임에 모든 것이 제대로 작동합니다 (IB에 오류 / 경고가 표시되는 경우에도). 콘솔 로그에서 런타임에 자동 레이아웃 오류가 발생하지 않는지 확인하십시오.
실제로 IB에서 행 높이를 항상 업데이트하려고 시도하는 것은 매우 성가 시며 때로는 분수 값 때문에 불가능에 가깝습니다.
성가신 IB 경고 / 오류를 방지하기 위해 관련된보기를 선택하고Size Inspector
대한 속성을 Ambiguity
선택Verify Position Only
Xcode 8.x / Xcode 9.x는 (때로는) Xcode 7.x와 다르게 작동하지만 여전히 잘못 작동하는 것 같습니다. 예를 들어compression resistance priority
/ hugging priority
가 필수 (1000)로 설정된 인터페이스 빌더는 레이블에 맞게 셀 높이를 조정하는 대신 셀에 맞게 레이블을 늘리거나자를 수 있습니다. 이러한 경우 자동 레이아웃 경고 나 오류가 표시되지 않을 수도 있습니다. 또는 때로는 위에서 설명한 Xcode 7.x가했던 것과 정확히 일치합니다.
행 높이 및 예상 행 높이에 대한 자동 치수를 설정하려면 다음 단계를 수행하여 셀 / 행 높이 레이아웃에 자동 치수를 적용하십시오.
UITableViewAutomaticDimension
rowHeight 및 추정 된 RowHeight에 할당heightForRowAt
, 값 UITableViewAutomaticDimension
을 반환 )-
목표 C :
// in ViewController.h
#import <UIKit/UIKit.h>
@interface ViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
@property IBOutlet UITableView * table;
@end
// in ViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
self.table.dataSource = self;
self.table.delegate = self;
self.table.rowHeight = UITableViewAutomaticDimension;
self.table.estimatedRowHeight = UITableViewAutomaticDimension;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return UITableViewAutomaticDimension;
}
빠른:
@IBOutlet weak var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Don't forget to set dataSource and delegate for table
table.dataSource = self
table.delegate = self
// Set automatic dimensions for row height
// Swift 4.2 onwards
table.rowHeight = UITableView.automaticDimension
table.estimatedRowHeight = UITableView.automaticDimension
// Swift 4.1 and below
table.rowHeight = UITableViewAutomaticDimension
table.estimatedRowHeight = UITableViewAutomaticDimension
}
// UITableViewAutomaticDimension calculates height of label contents/text
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// Swift 4.2 onwards
return UITableView.automaticDimension
// Swift 4.1 and below
return UITableViewAutomaticDimension
}
UITableviewCell의 레이블 인스턴스
참고 : 동적 길이의 레이블 (UIElements)이 둘 이상인 경우 컨텐츠 크기에 따라 조정해야합니다. 우선 순위가 더 높은 확장 / 압축하려는 레이블에 대해 '콘텐츠 포옹 및 압축 저항 우선 순위 조정'.
tableView: heightForRow
데이터 소스 를 구현하지 않고 나를 위해 일하고 있습니다.
tableView: heightForRow
. (iOS 10- tableView: heightForRow
필요)
tableView: heightForRow
..if (indexPath.row == 0) { return 100} else { return UITableView.automaticDimension }
@ Bob-Spryn 과 마찬가지로 나는 이것을 대답으로 게시 할만 큼 중요한 문제에 부딪쳤다 .
@smileyborg의 답변으로 잠시 고생했습니다 . 추가 요소 (와 IB의 프로토 타입 셀을 정의 한 경우 내가 우연히 있다는 잡았다입니다 UILabels
, UIButtons
당신은 [와 셀 인스턴스화 할 때 IB에서, 등) [YourTableViewCellClass alloc] init]
당신이했습니다 않는 한 그 셀 내의 다른 모든 요소를 인스턴스화 할 것 이를 위해 작성된 코드. (나는 비슷한 경험을했다.initWithStyle
.)
스토리 보드에서 모든 추가 요소를 인스턴스화하려면 셀을 얻습니다 [tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"]
( [tableView dequeueReusableCellWithIdentifier:forIndexPath:]
이것은 흥미로운 문제를 유발 하지 않습니다 ). 이렇게하면 IB에서 정의한 모든 요소가 인스턴스화됩니다.
스토리 보드 자동 레이아웃으로 문제를 해결하는 좋은 방법 :
- (CGFloat)heightForImageCellAtIndexPath:(NSIndexPath *)indexPath {
static RWImageCell *sizingCell = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sizingCell = [self.tableView dequeueReusableCellWithIdentifier:RWImageCellIdentifier];
});
[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];
CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height;
}
또 다른 "솔루션":이 모든 좌절을 건너 뛰고 대신 UIScrollView를 사용하여 UITableView와 동일한 모양과 느낌의 결과를 얻습니다.
스마일 보그가 제안한 것과 몇 달에 걸친 실패와 3 개월간 App Store 릴리스와 같은 것을 만들기 위해 문자 그대로 20 시간이 넘는 매우 실망스러운 시간을 투자 한 후에 그것은 고통스러운 "솔루션"이었습니다.
필자는 iOS 7 지원이 실제로 필요한 경우 (필수 사항) 기술이 너무 취해서 머리카락을 뽑아 내야한다는 것입니다. 그리고 UITableView는 고급 행 편집 기능 중 일부를 사용하지 않거나 실제로 1000 개 이상의 "행"을 지원 해야하는 경우가 아니라면 일반적으로 완전 과잉입니다 (우리의 응용 프로그램에서는 실제로는 20 행을 넘지 않습니다).
추가 된 보너스는 코드가 UITableView와 함께 제공되는 모든 대리자 쓰레기에 비해 미친 듯이 단순하다는 것입니다. 우아하고 관리하기 쉬운 viewOnLoad의 단일 코드 루프입니다.
이를 수행하는 방법에 대한 몇 가지 팁이 있습니다.
스토리 보드 또는 nib 파일을 사용하여 ViewController 및 연관된 루트보기를 작성하십시오.
UIScrollView 위로 루트 뷰로 드래그하십시오.
UIScrollView가 전체 루트 뷰를 채우도록 제약 조건을 맨 위, 맨 아래, 왼쪽 및 오른쪽 제약 조건을 최상위 뷰에 추가합니다.
UIScrollView 내에 UIView를 추가하고 "컨테이너"라고합니다. UIScrollView (상위)에 위쪽, 아래쪽, 왼쪽 및 오른쪽 제약 조건을 추가하십시오. KEY TRICK : UIScrollView와 UIView를 연결하기 위해 "균등 너비"제한 조건을 추가하십시오.
참고 : "스크롤보기에 스크롤 가능한 내용 높이가 모호합니다"라는 오류가 표시되고 컨테이너 UIView의 높이가 0 픽셀이어야합니다. 앱이 실행될 때 오류가 중요하지 않은 것 같습니다.
각 "셀"에 대한 펜촉 파일과 컨트롤러를 만듭니다. UITableViewCell이 아닌 UIView를 사용하십시오.
루트 ViewController에서 기본적으로 모든 "행"을 컨테이너 UIView에 추가하고 왼쪽 및 오른쪽 가장자리를 컨테이너보기에 연결하고 제약 조건을 위쪽을 컨테이너보기 상단 (첫 번째 항목의 경우) 또는 이전 항목에 연결합니다. 세포. 그런 다음 최종 셀을 컨테이너 바닥에 연결하십시오.
우리를 위해, 각 "행"은 펜촉 파일에 있습니다. 따라서 코드는 다음과 같습니다.
class YourRootViewController {
@IBOutlet var container: UIView! //container mentioned in step 4
override func viewDidLoad() {
super.viewDidLoad()
var lastView: UIView?
for data in yourDataSource {
var cell = YourCellController(nibName: "YourCellNibName", bundle: nil)
UITools.addViewToTop(container, child: cell.view, sibling: lastView)
lastView = cell.view
//Insert code here to populate your cell
}
if(lastView != nil) {
container.addConstraint(NSLayoutConstraint(
item: lastView!,
attribute: NSLayoutAttribute.Bottom,
relatedBy: NSLayoutRelation.Equal,
toItem: container,
attribute: NSLayoutAttribute.Bottom,
multiplier: 1,
constant: 0))
}
///Add a refresh control, if you want - it seems to work fine in our app:
var refreshControl = UIRefreshControl()
container.addSubview(refreshControl!)
}
}
UITools.addViewToTop의 코드는 다음과 같습니다.
class UITools {
///Add child to container, full width of the container and directly under sibling (or container if sibling nil):
class func addViewToTop(container: UIView, child: UIView, sibling: UIView? = nil)
{
child.setTranslatesAutoresizingMaskIntoConstraints(false)
container.addSubview(child)
//Set left and right constraints so fills full horz width:
container.addConstraint(NSLayoutConstraint(
item: child,
attribute: NSLayoutAttribute.Leading,
relatedBy: NSLayoutRelation.Equal,
toItem: container,
attribute: NSLayoutAttribute.Left,
multiplier: 1,
constant: 0))
container.addConstraint(NSLayoutConstraint(
item: child,
attribute: NSLayoutAttribute.Trailing,
relatedBy: NSLayoutRelation.Equal,
toItem: container,
attribute: NSLayoutAttribute.Right,
multiplier: 1,
constant: 0))
//Set vertical position from last item (or for first, from the superview):
container.addConstraint(NSLayoutConstraint(
item: child,
attribute: NSLayoutAttribute.Top,
relatedBy: NSLayoutRelation.Equal,
toItem: sibling == nil ? container : sibling,
attribute: sibling == nil ? NSLayoutAttribute.Top : NSLayoutAttribute.Bottom,
multiplier: 1,
constant: 0))
}
}
지금까지이 방법으로 찾은 유일한 "gotcha"는 UITableView에 스크롤 할 때 뷰 상단에 "부동"섹션 헤더 기능이 있다는 것입니다. 위의 솔루션은 더 많은 프로그래밍을 추가하지 않는 한 그렇게하지는 않지만 특정 경우에는이 기능이 100 % 필수 요소는 아니며 사라질 때 아무도 눈치 채지 못했습니다.
셀 사이에 디바이더를 원한다면 사용자 정의 "셀"의 맨 아래에 디바이더처럼 보이는 1 픽셀 높이의 UIView를 추가하십시오.
새로 고침 컨트롤이 작동하도록하려면 "바운스"및 "수직 바운스"를 설정해야하므로 테이블 뷰처럼 보입니다.
TableView는이 솔루션이 제공하지 않는 전체 화면을 채우지 않으면 콘텐츠 아래에 빈 행과 구분선을 표시합니다. 그러나 개인적으로, 나는 그 빈 행이 어쨌든 존재하지 않는 것을 선호합니다-가변 셀 높이로 인해 항상 빈 행을 갖기 위해 항상 "버기"처럼 보였습니다.
다음은 다른 프로그래머가 자신의 앱에서 테이블보기로 알아 내려고 20 시간 이상을 낭비하기 전에 내 게시물을 읽기를 바랍니다. :)
서브 뷰가있는 셀이 있고 셀의 높이가 서브 뷰 + 패딩을 포함 할만큼 충분히 높기를 원한다고 가정 해 봅시다.
1) 하위 뷰의 하단 제약 조건을 cell.contentView에서 원하는 패딩을 뺀 값과 동일하게 설정하십시오. 셀 또는 cell.contentView 자체에 제한 조건을 설정하지 마십시오.
2) tableView의 rowHeight
속성 또는 tableView:heightForRowAtIndexPath:
을 설정하십시오 UITableViewAutomaticDimension
.
3) tableView의 estimatedRowHeight
속성 또는 tableView:estimatedHeightForRowAtIndexPath:
높이를 가장 잘 추측하도록 설정하십시오 .
그게 다야.
프로그래밍 방식으로 레이아웃을 수행하는 경우 Swift에서 앵커를 사용하여 iOS 10에서 고려해야 할 사항이 있습니다.
세 가지 규칙 / 단계가 있습니다
NUMBER 1 : viewDidLoad에서 tableview 의이 두 속성을 설정하십시오. 첫 번째는 셀에서 동적 크기를 예상 해야하는 tableview에 알리고 두 번째는 앱이 스크롤 막대 표시기의 크기를 계산하도록하는 것입니다. 공연.
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100
NUMBER 2 : 이것은 뷰가 아닌 셀의 contentView에 서브 뷰를 추가해야하며 레이아웃 여백 안내선을 사용하여 서브 뷰를 상단과 하단에 고정하는 것이 중요합니다.
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setUpViews()
}
private func setUpViews() {
contentView.addSubview(movieImageView)
contentView.addSubview(descriptionLabel)
let marginGuide = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
movieImageView.heightAnchor.constraint(equalToConstant: 80),
movieImageView.widthAnchor.constraint(equalToConstant: 80),
movieImageView.leftAnchor.constraint(equalTo: marginGuide.leftAnchor),
movieImageView.topAnchor.constraint(equalTo: marginGuide.topAnchor, constant: 20),
descriptionLabel.leftAnchor.constraint(equalTo: movieImageView.rightAnchor, constant: 15),
descriptionLabel.rightAnchor.constraint(equalTo: marginGuide.rightAnchor),
descriptionLabel.bottomAnchor.constraint(equalTo: marginGuide.bottomAnchor, constant: -15),
descriptionLabel.topAnchor.constraint(equalTo: movieImageView.topAnchor)
])
}
서브 뷰를 추가하고 레이아웃을 수행하는 메소드를 작성하고 init 메소드에서 호출하십시오.
3 번 : 방법을 부르지 마십시오 :
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
}
그렇게하면 구현을 재정의합니다.
테이블 뷰에서 동적 셀에 대해이 3 가지 규칙을 따르십시오.
다음은 작동중인 구현입니다 https://github.com/jamesrochabrun/MinimalViewController
긴 줄 이 있다면 . 예를 들어 줄 바꿈 이없는 것. 그런 다음 몇 가지 문제가 발생할 수 있습니다.
"답변 된"수정 사항은 승인 된 답변과 다른 답변으로 언급됩니다. 당신은 추가해야합니다
cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
내가 찾을 수 Suragh의 대답은 가장 완벽하고 간결 따라서 혼동하지.
비록 이러한 변경이 필요한 이유를 설명하지는 않지만 . 그걸하자.
다음 코드를 프로젝트에 놓으십시오.
import UIKit
class ViewController: UIViewController {
lazy var label : UILabel = {
let lbl = UILabel()
lbl.translatesAutoresizingMaskIntoConstraints = false
lbl.backgroundColor = .red
lbl.textColor = .black
return lbl
}()
override func viewDidLoad() {
super.viewDidLoad()
// step0: (0.0, 0.0)
print("empty Text intrinsicContentSize: \(label.intrinsicContentSize)")
// ----------
// step1: (29.0, 20.5)
label.text = "hiiiii"
print("hiiiii intrinsicContentSize: \(label.intrinsicContentSize)")
// ----------
// step2: (328.0, 20.5)
label.text = "translatesAutoresizingMaskIntoConstraints"
print("1 translate intrinsicContentSize: \(label.intrinsicContentSize)")
// ----------
// step3: (992.0, 20.5)
label.text = "translatesAutoresizingMaskIntoConstraints translatesAutoresizingMaskIntoConstraints translatesAutoresizingMaskIntoConstraints"
print("3 translate intrinsicContentSize: \(label.intrinsicContentSize)")
// ----------
// step4: (328.0, 20.5)
label.text = "translatesAutoresizingMaskIntoConstraints\ntranslatesAutoresizingMaskIntoConstraints\ntranslatesAutoresizingMaskIntoConstraints"
print("3 translate w/ line breaks (but the line breaks get ignored, because numberOfLines is defaulted to `1` and it will force it all to fit into one line! intrinsicContentSize: \(label.intrinsicContentSize)")
// ----------
// step5: (328.0, 61.0)
label.numberOfLines = 0
print("3 translate w/ line breaks and '0' numberOfLines intrinsicContentSize: \(label.intrinsicContentSize)")
// ----------
// step6: (98.5, 243.5)
label.preferredMaxLayoutWidth = 100
print("3 translate w/ line breaks | '0' numberOfLines | preferredMaxLayoutWidth: 100 intrinsicContentSize: \(label.intrinsicContentSize)")
setupLayout()
}
func setupLayout(){
view.addSubview(label)
label.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
label.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
}
}
크기 제한을 추가하지 않았습니다 . centerX, centerY 제약 조건 만 추가했습니다. 그러나 여전히 레이블의 크기가 올바르게 설정됩니다. 왜?
때문에 contentSize
.
이를 더 잘 처리하려면 먼저 step0을 유지 한 다음 1-6 단계를 주석 처리하십시오. 하자 setupLayout()
숙박. 행동을 관찰하십시오.
그런 다음 1 단계 주석을 해제하고 관찰하십시오.
그런 다음 2 단계 주석을 해제하고 관찰하십시오.
6 단계를 모두 주석 해제하고 해당 동작을 관찰 할 때까지이 작업을 수행하십시오.
contenSize
있습니까?\n
하면 intrinsicContentSize의 너비가 모든 줄의 최대 너비가됩니다. 한 줄에 25자가 있고 다른 줄에 2자가 있고 다른 줄에 21자가 있으면 너비는 25자를 기준으로 계산됩니다.numberOfLines
를 0
그렇지 않으면 여러 줄이 없도록로 설정해야합니다 . 당신 numberOfLines
의 intrinsicContentSize의 높이를 조정합니다조정 : 텍스트를 기준으로 intrinsicContentSize의 너비는 200
높이 100
였지만 너비는 레이블의 컨테이너로 제한하려고 한다고 상상해보십시오 . 해결책은 원하는 너비로 설정하는 것입니다. 이렇게 설정 preferredMaxLayoutWidth
하면 130
새 intrinsicContentSize의 너비가 대략 큽니다 130
. 100
더 많은 선이 필요하기 때문에 높이는 분명히 더 큽니다 . 제약 조건이 올바르게 설정되어 있으면 이것을 사용할 필요가 없습니다! 이에 대한 자세한 내용은 이 답변 과 해당 의견 을 참조하십시오 . preferredMaxLayoutWidth
너비 / 높이를 제한하는 제약 조건이없는 경우 에만 "텍스트를 초과하지 않는 한 텍스트를 줄 바꿈하지 마십시오.preferredMaxLayoutWidth
. "하지만 100 % 확신을 가지고 당신은 설정하면 / 주요 후행과 numberOfLines
에 0
당신에게있는 거 좋아!그것을 사용하는 것이 좋습니다 긴 이야기 짧은 대부분의 대답은 잘못입니다! 필요하지 않습니다. 제약 조건이 올바르게 설정되지 않았거나 제약 조건이 없다는 표시가 필요합니다.
글꼴 크기 : fontSize를 늘리면 intrinsicContentSize의 높이 가 증가합니다. 나는 그것을 내 코드에서 보여주지 않았다. 직접 시도해 볼 수 있습니다.
다시 tableViewCell 예제로 돌아가십시오.
당신이해야 할 일은 :
numberOfLines
에를0
preferredMaxLayoutWidth
.제 경우에는 서버에서 오는 이미지로 사용자 정의 셀을 만들어야하며 너비와 높이가 가능합니다. 그리고 동적 크기 (너비 및 높이)를 가진 두 개의 UILabels
자동 레이아웃과 프로그래밍 방식으로 내 대답에서 동일한 결과를 얻었습니다.
기본적으로 위의 @smileyBorg 답변이 도움이되었지만 systemLayoutSizeFittingSize는 저에게 효과적이지 않았습니다.
1. 자동 행 높이 계산 속성을 사용하지 않습니다. 2. 추정 높이를 사용하지 않음 3. 불필요한 업데이트가 필요 없음 자동 자동 최대 레이아웃 너비를 사용하지 마십시오. 5. systemLayoutSizeFittingSize 를 사용하지 마십시오 (사용해야하지만 작동하지 않아야합니다. 내부에서 수행중인 작업을 모르겠습니다). 대신 내 방법-(float) getViewHeight가 작동하고 내부적으로 수행중인 작업을 알고 있습니다.
난 그냥의 2 개 값 멍청한 시도와 오류했던 rowHeight
과를 estimatedRowHeight
하고 그냥 몇 가지 디버깅 통찰력을 제공 할 수 있습니다 생각 :
둘 다 설정하거나 OR 만 설정 estimatedRowHeight
하면 원하는 동작을 얻을 수 있습니다.
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1.00001 // MUST be greater than 1
정확한 견적을 얻으려면 최선을 다하는 것이 좋지만 최종 결과는 다르지 않습니다. 성능에만 영향을 미칩니다.
rowHeight 만 설정하면 다음과 같이하십시오.
tableView.rowHeight = UITableViewAutomaticDimension
최종 결과는 바람직하지 않습니다.
estimatedRowHeight
를 1 이하로 설정하면 에 관계없이 충돌 이 발생 합니다 rowHeight
.
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1
다음 오류 메시지와 함께 충돌했습니다.
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'table view row height
must not be negative - provided height for index path (<NSIndexPath:
0xc000000000000016> {length = 2, path = 0 - 0}) is -1.000000'
...some other lines...
libc++abi.dylib: terminating with uncaught exception of type
NSException
@smileyborg의 허용되는 답변과 관련하여
[cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]
제약 조건이 모호한 경우 신뢰할 수 없습니다. 아래 UIView의 도우미 범주를 사용하여 레이아웃 엔진이 한 방향으로 높이를 계산하도록하는 것이 좋습니다.
-(CGFloat)systemLayoutHeightForWidth:(CGFloat)w{
[self setNeedsLayout];
[self layoutIfNeeded];
CGSize size = [self systemLayoutSizeFittingSize:CGSizeMake(w, 1) withHorizontalFittingPriority:UILayoutPriorityRequired verticalFittingPriority:UILayoutPriorityFittingSizeLevel];
CGFloat h = size.height;
return h;
}
여기서 w :는 테이블 뷰의 너비입니다.
뷰 컨트롤러에이 두 기능을 추가하기 만하면 문제가 해결됩니다. 여기서 list는 모든 행의 문자열을 포함하는 문자열 배열입니다.
func tableView(_ tableView: UITableView,
estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
tableView.rowHeight = self.calculateHeight(inString: list[indexPath.row])
return (tableView.rowHeight)
}
func calculateHeight(inString:String) -> CGFloat
{
let messageString = input.text
let attributes : [NSAttributedStringKey : Any] = [NSAttributedStringKey(rawValue: NSAttributedStringKey.font.rawValue) : UIFont.systemFont(ofSize: 15.0)]
let attributedString : NSAttributedString = NSAttributedString(string: messageString!, attributes: attributes)
let rect : CGRect = attributedString.boundingRect(with: CGSize(width: 222.0, height: CGFloat.greatestFiniteMagnitude), options: .usesLineFragmentOrigin, context: nil)
let requredSize:CGRect = rect
return requredSize.height
}
swift 4
@IBOutlet weak var tableViewHeightConstraint: NSLayoutConstraint!
@IBOutlet weak var tableView: UITableView!
private var context = 1
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.addObserver(self, forKeyPath: "contentSize", options: [.new,.prior], context: &context)
}
// Added observer to adjust tableview height based on the content
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
if context == &self.context{
if let size = change?[NSKeyValueChangeKey.newKey] as? CGSize{
print("-----")
print(size.height)
tableViewHeightConstraint.constant = size.height + 50
}
}
}
//Remove observer
deinit {
NotificationCenter.default.removeObserver(self)
}
내용에 따라 셀 높이가 동적이면 셀을 정확하게 계산 한 다음 셀이 렌더링되기 전에 높이 값을 반환해야합니다. 쉬운 방법은 컨트롤러가 테이블 셀 높이 대리자 메서드를 호출 할 수 있도록 테이블 뷰 셀 코드에서 계산 방법을 정의하는 것입니다. 높이가 테이블 또는 화면의 너비에 의존하는 경우 실제 셀 프레임 너비 (기본값은 320) 를 계산하는 것을 잊지 마십시오 . 즉, 테이블 셀 높이 대리자 메소드에서 cell.frame을 사용하여 셀 너비를 먼저 수정 한 다음 셀에 정의 된 계수 높이 메소드를 호출하여 적절한 값을 가져 와서 리턴하십시오. .
추신. 셀 오브젝트를 생성하기위한 코드는 다른 테이블 뷰 셀 대리자 메소드가 호출 할 다른 메소드에서 정의 될 수 있습니다.
Swift의 또 다른 iOs7 + iOs8 솔루션
var cell2height:CGFloat=44
override func viewDidLoad() {
super.viewDidLoad()
theTable.rowHeight = UITableViewAutomaticDimension
theTable.estimatedRowHeight = 44.0;
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("myTableViewCell", forIndexPath: indexPath) as! myTableViewCell
cell2height=cell.contentView.height
return cell
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if #available(iOS 8.0, *) {
return UITableViewAutomaticDimension
} else {
return cell2height
}
}
cellForRowAtIndexPath
않습니다.이 시점에서 셀이 아직 배치되지 않았습니다.