UITableViewCell
내에서 자동 레이아웃을 사용하여 각 셀의 콘텐츠와 하위 보기가 행 높이(자체/자동)를 결정하면서 부드러운 스크롤 성능을 유지하도록 하는 방법은 무엇입니까?
질문자 :smileyborg
TL;DR: 독서를 좋아하지 않습니까? GitHub의 샘플 프로젝트로 바로 이동합니다.
- iOS 8 샘플 프로젝트 - iOS 8 필요
- iOS 7 샘플 프로젝트 - iOS 7 이상에서 작동
개념 설명
아래의 처음 2단계는 개발 중인 iOS 버전에 관계없이 적용할 수 있습니다.
1. 제약 조건 설정 및 추가
UITableViewCell
하위 클래스에서 셀의 하위 보기가 셀의 contentView 가장자리에 고정되도록 제약 조건을 추가합니다(가장 중요하게는 위쪽 및 아래쪽 가장자리). 참고: 하위 보기를 셀 자체에 고정하지 마십시오. 셀의 contentView
에만! 각 하위 보기에 대한 세로 차원의 콘텐츠 압축 저항 및 콘텐츠 포옹 제약 조건이 추가한 우선 순위가 더 높은 제약 조건에 의해 무시되지 않도록 하여 이러한 하위 보기의 고유 콘텐츠 크기가 테이블 보기 셀의 콘텐츠 보기 높이를 구동하도록 합니다. (어 ? 여기를 클릭하세요. )
아이디어는 셀의 하위 보기가 셀의 콘텐츠 보기에 수직으로 연결되어 "압력을 가할 수 있고" 콘텐츠 보기가 그에 맞게 확장되도록 하는 것입니다. 몇 개의 하위 보기가 있는 예제 셀을 사용하여 제약 조건 중 일부 (전부는 아님)가 어떻게 생겼는지 시각적으로 보여줍니다.
위의 예제 셀에서 여러 줄로 된 본문 레이블에 더 많은 텍스트가 추가되면 텍스트에 맞게 세로로 커져야 하므로 셀의 높이가 효과적으로 커집니다. (물론 이것이 제대로 작동하려면 제약 조건을 올바르게 설정해야 합니다!)
제약 조건을 올바르게 설정하는 것은 자동 레이아웃에서 동적 셀 높이를 얻는 데 있어 확실히 가장 어렵고 가장 중요한 부분입니다. 여기서 실수를 하면 다른 모든 것이 작동하지 않을 수 있으므로 시간을 들여야 합니다! 어떤 제약 조건이 어디에 추가되는지 정확히 알고 있고 문제가 발생할 때 디버그하기가 훨씬 쉽기 때문에 코드에서 제약 조건을 설정하는 것이 좋습니다. 코드에 제약 조건을 추가하는 것은 레이아웃 앵커를 사용하거나 GitHub에서 사용할 수 있는 환상적인 오픈 소스 API 중 하나를 사용하는 Interface Builder만큼 쉽고 훨씬 강력할 수 있습니다.
- 코드에 제약 조건을 추가하는 경우 UITableViewCell 하위 클래스
updateConstraints
메서드 내에서 이 작업을 한 번 수행해야 합니다.updateConstraints
는 두 번 이상 호출될 수 있으므로 동일한 제약 조건을 두 번 이상 추가하지 않으려면didSetupConstraints
와 같은 부울 속성 검사updateConstraints
내에서 제약 조건 추가 코드를 래핑해야 합니다. 제약 조건 추가 코드를 한 번 실행). 반면에 기존 제약 조건을 업데이트하는 코드가 있는 경우(예: 일부 제약 조건에constant
updateConstraints
didSetupConstraints
검사 외부에 배치하여 메서드가 호출될 때마다 실행할 수 있도록 합니다.
2. 고유한 테이블 보기 셀 재사용 식별자 결정
셀의 모든 고유한 제약 조건 세트에 대해 고유한 셀 재사용 식별자를 사용합니다. 즉, 셀에 둘 이상의 고유 레이아웃이 있는 경우 각 고유 레이아웃은 고유한 재사용 식별자를 받아야 합니다. (새로운 재사용 식별자를 사용해야 한다는 좋은 힌트는 셀 변형에 다른 수의 하위 보기가 있거나 하위 보기가 고유한 방식으로 정렬되어 있는 경우입니다.)
예를 들어, 각 셀에 이메일 메시지를 표시하는 경우 제목만 있는 메시지, 제목과 본문이 있는 메시지, 제목과 사진이 첨부된 메시지, 제목이 있는 메시지의 4가지 고유한 레이아웃이 있을 수 있습니다. 본체, 사진첨부. 각 레이아웃에는 이를 달성하는 데 필요한 완전히 다른 제약 조건이 있으므로 일단 셀이 초기화되고 이러한 셀 유형 중 하나에 대한 제약 조건이 추가되면 셀은 해당 셀 유형에 고유한 재사용 식별자를 가져와야 합니다. 즉, 재사용을 위해 셀을 큐에서 빼는 경우 제약 조건이 이미 추가되어 해당 셀 유형에 사용할 준비가 되었습니다.
고유 콘텐츠 크기의 차이로 인해 동일한 제약 조건(유형)을 가진 셀의 높이가 여전히 다를 수 있습니다! 콘텐츠의 크기가 다르기 때문에 근본적으로 다른 레이아웃(다른 제약 조건)과 계산된 뷰 프레임(동일한 제약 조건에서 해결됨)을 혼동하지 마십시오.
- 완전히 다른 제약 조건 집합이 있는 셀을 동일한 재사용 풀에 추가하지 마십시오(즉, 동일한 재사용 식별자 사용). 그런 다음 각 대기열에서 제거한 후 이전 제약 조건을 제거하고 처음부터 새로운 제약 조건을 설정하려고 시도하지 마십시오. 내부 Auto Layout 엔진은 제약 조건의 대규모 변경을 처리하도록 설계되지 않았으며 엄청난 성능 문제를 보게 됩니다.
iOS 8의 경우 - 셀 자체 크기 조정
3. 행 높이 추정 활성화
자체 크기 조정 테이블 보기 셀을 사용하려면 테이블 보기의 rowHeight 속성을 UITableViewAutomaticDimension으로 설정해야 합니다. 또한 EstimatedRowHeight 속성에 값을 할당해야 합니다. 이 두 속성이 모두 설정되면 시스템은 자동 레이아웃을 사용하여 행의 실제 높이를 계산합니다.
Apple: 자체 크기 조정 테이블 보기 셀로 작업하기
iOS 8을 통해 Apple은 iOS 8 이전에 사용자가 구현해야 했던 많은 작업을 내부화했습니다. 자체 크기 조정 셀 메커니즘이 작동하도록 하려면 먼저 rowHeight
속성을 다음으로 설정해야 합니다. 상수 UITableView.automaticDimension
. estimatedRowHeight
된 RowHeight 속성을 0이 아닌 값으로 설정하여 행 높이 추정을 활성화하기만 하면 됩니다. 예를 들면 다음과 같습니다.
self.tableView.rowHeight = UITableView.automaticDimension; self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is
이것이 하는 일은 아직 화면에 표시되지 않은 셀의 행 높이에 대한 임시 추정치/자리 표시자를 테이블 보기에 제공하는 것입니다. 그런 다음 이러한 셀이 화면에서 스크롤될 때 실제 행 높이가 계산됩니다. 각 행의 실제 높이를 결정하기 위해 테이블 뷰는 콘텐츠 뷰의 알려진 고정 너비(테이블 뷰의 너비에서 섹션과 같은 추가 항목을 뺀 값을 기반으로 함)를 기반으로 contentView
인덱스 또는 액세서리 보기) 및 셀의 콘텐츠 보기 및 하위 보기에 추가한 자동 레이아웃 제약 조건. 이 실제 셀 높이가 결정되면 행의 이전 예상 높이가 새로운 실제 높이로 업데이트됩니다(필요에 따라 테이블 보기의 contentSize/contentOffset 조정이 이루어집니다).
일반적으로 말해서 제공한 추정치는 매우 정확할 필요는 없습니다. 이는 테이블 보기에서 스크롤 표시기의 크기를 올바르게 조정하는 데만 사용되며 테이블 보기는 화면에서 셀을 스크롤합니다. 테이블 보기( viewDidLoad
또는 이와 유사한 것)의 estimatedRowHeight
된RowHeight 속성을 "평균" 행 높이인 상수 값으로 설정해야 합니다. 행 높이에 극도의 가변성이 있고(예: 크기 순서가 다름) 스크롤할 때 스크롤 표시기가 "점프"하는 것을 발견한 경우에만 tableView:estimatedHeightForRowAtIndexPath:
각 행.
iOS 7 지원(자동 셀 크기 조정 직접 구현)
3. 레이아웃 통과 및 셀 높이 얻기
먼저 높이 계산에 엄격하게 사용되는 각 재사용 식별자에 대해 하나의 인스턴스인 테이블 뷰 셀의 오프스크린 인스턴스를 인스턴스화합니다. (오프스크린은 셀 참조가 뷰 컨트롤러의 속성/ivar에 저장되고 tableView:cellForRowAtIndexPath:
에서 반환되지 않음을 의미합니다. 테이블 뷰가 실제로 화면에 렌더링되도록 합니다.) 다음으로 셀은 정확한 내용(예: 텍스트, 이미지 등) 테이블 보기에 표시되는 경우 유지됩니다.
그런 다음 셀이 해당 하위 뷰를 즉시 레이아웃하도록 강제 실행한 다음 UITableViewCell
의 contentView
systemLayoutSizeFittingSize:
메서드를 사용하여 필요한 셀 높이를 확인합니다. UILayoutFittingCompressedSize
를 사용하여 셀의 모든 내용을 맞추는 데 필요한 가장 작은 크기를 가져옵니다. tableView:heightForRowAtIndexPath:
대리자 메서드에서 높이를 반환할 수 있습니다.
4. 예상 행 높이 사용
tableView:heightForRowAtIndexPath:
가 각 행에서 호출되기 때문에 테이블 뷰를 처음 로드할 때 자동 레이아웃 제약 조건 해결을 수행하면 메인 스레드가 빠르게 중단될 수 있습니다. 첫 번째 로드 시(스크롤 표시기의 크기를 계산하기 위해).
iOS 7부터 테이블 뷰 estimatedRowHeight
속성을 사용할 수 있습니다(절대 사용해야 합니다). 이것이 하는 일은 아직 화면에 표시되지 않은 셀의 행 높이에 대한 임시 추정치/자리 표시자를 테이블 보기에 제공하는 것입니다. 그런 다음 이러한 셀이 화면에서 스크롤될 때 실제 행 높이가 계산되고( tableView:heightForRowAtIndexPath:
호출) 예상 높이가 실제 높이로 업데이트됩니다.
일반적으로 말해서 제공한 추정치는 매우 정확할 필요는 없습니다. 이는 테이블 보기에서 스크롤 표시기의 크기를 올바르게 조정하는 데만 사용되며 테이블 보기는 화면에서 셀을 스크롤합니다. 테이블 보기( viewDidLoad
또는 이와 유사한 것)의 estimatedRowHeight
된RowHeight 속성을 "평균" 행 높이인 상수 값으로 설정해야 합니다. 행 높이에 극도의 가변성이 있고(예: 크기 순서가 다름) 스크롤할 때 스크롤 표시기가 "점프"하는 것을 발견한 경우에만 tableView:estimatedHeightForRowAtIndexPath:
각 행.
5. (필요한 경우) 행 높이 캐싱 추가
tableView:heightForRowAtIndexPath:
에서 제약 조건 해결을 수행할 때 성능이 허용할 수 없을 정도로 느리다는 것을 발견하면 불행히도 셀 높이에 대해 일부 캐싱을 구현해야 합니다. (이것은 Apple 엔지니어가 제안한 접근 방식입니다.) 일반적인 아이디어는 Autolayout 엔진이 처음으로 제약 조건을 해결하도록 한 다음 해당 셀에 대해 계산된 높이를 캐시하고 해당 셀 높이에 대한 모든 향후 요청에 대해 캐시된 값을 사용하는 것입니다. 물론 트릭은 셀의 높이를 변경할 수 있는 일이 발생할 때 셀에 대해 캐시된 높이를 지우도록 하는 것입니다. 주로 해당 셀의 내용이 변경되거나 다른 중요한 이벤트가 발생할 때(예: 사용자가 조정 동적 유형 텍스트 크기 슬라이더).
iOS 7 일반 샘플 코드(수많은 주석 포함)
- (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; } }
샘플 프로젝트
- iOS 8 샘플 프로젝트 - iOS 8 필요
- iOS 7 샘플 프로젝트 - iOS 7 이상에서 작동
이러한 프로젝트는 UILabels의 동적 콘텐츠를 포함하는 테이블 뷰 셀로 인해 가변 행 높이가 있는 테이블 뷰의 완전히 작동하는 예입니다.
자마린(C#/.NET)
당신이 자 마린을 사용하는 경우,이 체크 아웃 샘플 프로젝트 에 의해 함께 넣어 @KentBoogaart .
Community Wiki
위의 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)
잊지 마세요.
전체 예제 코드는 여기에 있습니다 .
William Hu
가변 높이 UITableViewCell의 신속한 예
Swift 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면에 고정합니다. 그런 다음 테이블 보기 셀을 테이블 보기로 끌어다 놓습니다. 그리고 프로토타입 셀로 레이블을 드래그합니다. 자동 레이아웃을 사용하여 테이블 보기 셀의 콘텐츠 보기의 네 가장자리에 레이블을 고정합니다.
중요 사항:
- 자동 레이아웃은 위에서 언급한 중요한 두 줄의 코드와 함께 작동합니다. 자동 레이아웃을 사용하지 않으면 작동하지 않습니다.
기타 IB 설정
사용자 정의 클래스 이름 및 식별자
테이블 보기 셀을 선택하고 사용자 정의 클래스를 MyCustomCell
(우리가 추가한 Swift 파일의 클래스 이름)로 설정합니다. 또한 Identifier를 cell
(위 코드 cellReuseIdentifier
에 사용한 것과 동일한 문자열)로 설정합니다.
레이블에 대한 제로 라인
레이블에서 줄 수를 0
이것은 여러 줄을 의미하며 레이블이 내용에 따라 자체적으로 크기를 조정할 수 있도록 합니다.
콘센트 연결
- 스토리보드의 테이블 보기에서
ViewController
tableView
변수로 드래그를 제어합니다. -
MyCustomCell
클래스myCellLabel
변수에 대한 프로토타입 셀의 레이블에 대해 동일한 작업을 수행합니다.
완성 된
지금 프로젝트를 실행하고 다양한 높이의 셀을 얻을 수 있어야 합니다.
노트
- 이 예는 iOS 8 이상에서만 작동합니다. 여전히 iOS 7을 지원해야 하는 경우에는 작동하지 않습니다.
- 향후 프로젝트의 사용자 지정 셀에는 하나 이상의 레이블이 있을 수 있습니다. 자동 레이아웃이 사용할 올바른 높이를 결정할 수 있도록 모든 것이 올바르게 고정되었는지 확인하십시오. 수직 압축 저항과 포옹을 사용해야 할 수도 있습니다. 이에 대한 자세한 내용은 이 기사를 참조하십시오.
선행 및 후행(왼쪽 및 오른쪽) 가장자리를 고정하지 않는 경우 줄 바꿈 시점을 알 수 있도록
preferredMaxLayoutWidth
예를 들어 위 프로젝트의 레이블에 Center Horizontally 제약 조건을 추가한 경우 선행 및 후행 가장자리를 고정하지 않고 다음 줄을tableView:cellForRowAtIndexPath
메서드에 추가해야 합니다.cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width
또한보십시오
Suragch
@smileyborg의 iOS7 솔루션을 카테고리로 래핑했습니다.
@smileyborg의 이 영리한 솔루션을 UICollectionViewCell+AutoLayoutDynamicHeightCalculation
범주로 래핑하기로 결정했습니다.
카테고리는 또한 @wildmonkey의 답변에 설명된 문제를 수정합니다(nib 및 systemLayoutSizeFittingSize:
CGRectZero
반환).
캐싱을 고려하지 않지만 지금 당장은 필요에 맞습니다. 자유롭게 복사, 붙여넣기 및 해킹하십시오.
UICollectionViewCell+AutoLayoutDynamicHeightCalculation.h
#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
UICollectionViewCell+AutoLayoutDynamicHeightCalculation.m
#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에서는 이 재즈를 할 필요가 없지만 지금은 있습니다!
Adam Waite
내 솔루션은 다음과 같습니다.
뷰를 로드하기 전에 테이블 뷰에 estimatedHeight
TableView
를 알려야 합니다. 그렇지 않으면 예상대로 작동할 수 없습니다.
오브젝티브-C
- (void)viewWillAppear:(BOOL)animated { _messageField.delegate = self; _tableView.estimatedRowHeight = 65.0; _tableView.rowHeight = UITableViewAutomaticDimension; }
스위프트 4.2로 업데이트
override func viewWillAppear(_ animated: Bool) { tableView.rowHeight = UITableView.automaticDimension tableView.estimatedRowHeight = 65.0 }
Eddwin Paz
@smileyborg가 제안한 솔루션은 거의 완벽합니다. 사용자 정의 셀이 있고 동적 높이가 있는 UILabel
을 원하는 경우 AutoLayout이 활성화된 systemLayoutSizeFittingSize 메소드는 모든 셀 제약 조건을 셀에서 해당 contentView로 이동하지 않는 CGSizeZero
반환합니다(여기에서 @TomSwift가 제안한 대로 크기 조정 방법 autolayout이 있는 모든 하위 보기에 맞게 superview를 사용하시겠습니까? ).
그렇게 하려면 사용자 정의 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 답변을 이것과 혼합하면 작동합니다.
wildmonkey
답변으로 게시하기 위해 방금 만난 중요한 문제가 있습니다.
@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);
(재사용을 위해 이 특정 셀을 캐싱했지만 이는 관련이 없습니다.)
Bob Spryn
사람들이 여전히 이것으로 문제를 겪고 있는 경우. 저는 동적 셀 높이에 대한 자동 레이아웃을 활용하는 UITableViews와 함께 자동 레이아웃을 사용하는 방법과 이를 보다 추상적이고 쉽게 구현하는 데 도움이 되는 오픈 소스 구성 요소에 대한 빠른 블로그 게시물을 작성했습니다. https://github.com/Raizlabs/RZCellSizeManager
Alex Rouse
(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에 오류/경고가 표시되더라도). 콘솔 로그에서 런타임 시 AutoLayout 오류가 발생하지 않는지 확인하십시오.
사실 IB에서 항상 행 높이를 업데이트하려는 시도는 매우 성가시고 때로는 불가능에 가깝습니다(소수 값 때문에).
성가신 IB 경고/오류를 방지하려면 관련 보기를 선택하고 Size Inspector
에서 Ambiguity
Verify Position Only
선택합니다.
Xcode 8.x / Xcode 9.x는 (때때로) Xcode 7.x와 다르게 작동하지만 여전히 잘못되었습니다. 예를 들어 compression resistance priority
/ hugging priority
가 필수(1000)로 설정된 경우에도 Interface Builder는 레이블 주위에 맞게 셀 높이 크기를 조정하는 대신 셀에 맞게 레이블을 늘리거나 자를 수 있습니다. 그리고 그러한 경우 AutoLayout 경고나 오류를 표시하지 않을 수도 있습니다. 또는 때로는 위에서 설명한 Xcode 7.x가 수행한 작업을 정확히 수행합니다.
Nikolay Suvandzhiev
셀의 레이아웃이 좋은 한.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath]; return [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height; }
업데이트: iOS 8에 도입된 동적 크기 조정을 사용해야 합니다.
cvb
행 높이 및 예상 행 높이에 대한 자동 치수를 설정하려면 자동 치수가 셀/행 높이 레이아웃에 적용되도록 다음 단계를 확인하십시오.
- tableview dataSource 및 위임 할당 및 구현
- rowHeight 및 EstimatedRowHeight에
UITableViewAutomaticDimension
할당 - 대리자/dataSource 메서드 구현(예:
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의 레이블 인스턴스의 경우
- 줄 수 설정 = 0(& 줄 바꿈 모드 = 꼬리 자르기)
- 상위 뷰/셀 컨테이너와 관련된 모든 제약 조건(위쪽, 아래쪽, 오른쪽 왼쪽)을 설정합니다.
- Optional : 데이터가 없더라도 최소한의 수직 영역을 레이블로 덮고 싶다면 레이블의 최소 높이를 설정합니다.
참고 : 콘텐츠 크기에 따라 조정해야 하는 동적 길이의 레이블(UIElements)이 두 개 이상인 경우: 더 높은 우선순위로 확장/압축하려는 레이블에 대해 '콘텐츠 포옹 및 압축 저항 우선 순위'를 조정합니다.
Krunal
@Bob-Spryn 처럼 나는 이것을 답변으로 게시하고 있을 만큼 충분히 중요한 문제를 만났습니다.
나는 잠시 동안 @smileyborg의 대답에 어려움을 겪었습니다. [YourTableViewCellClass alloc] init]
셀을 인스턴스화할 때 UILabels
, UIButtons
등)가 있는 IB의 프로토타입 셀을 정의한 경우 다른 모든 것을 인스턴스화하지 않는다는 것입니다. 그렇게 하는 코드를 작성하지 않은 한 해당 셀 내의 요소. initWithStyle
과 비슷한 경험을 했다.)
스토리보드가 모든 추가 요소를 인스턴스화하도록 하려면 [tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"]
사용하여 셀을 얻습니다( [tableView dequeueReusableCellWithIdentifier:forIndexPath:]
아니라 흥미로운 문제가 발생합니다.) 이 작업을 수행할 때 정의한 모든 요소 IB가 인스턴스화됩니다.
nickb
스토리보드 자동 레이아웃 문제를 해결하는 좋은 방법:
- (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; }
Mohnasm
또 다른 "해결책": 이 모든 좌절감을 건너뛰고 대신 UIScrollView를 사용하여 UITableView와 모양과 느낌이 동일한 결과를 얻으십시오.
스마일리보그가 제안한 것과 같은 것을 구축하기 위해 문자 그대로 총 20시간 이상을 투자하고 App Store 릴리스의 세 가지 버전에 걸쳐 실패한 후 고통스러운 "해결책"이었습니다.
내 생각에 iOS 7 지원이 정말로 필요한 경우(저희에게는 필수입니다) 기술이 너무 취약하여 노력을 기울이지 않을 것입니다. 그리고 그 UITableView는 고급 행 편집 기능을 사용하지 않거나 1000개 이상의 "행"을 지원해야 하는 경우가 아니면 일반적으로 완전히 과잉입니다(저희 앱에서는 실제로 20개 행을 넘지 않음).
추가된 보너스는 코드가 UITableView와 함께 제공되는 모든 델리게이트 쓰레기에 비해 엄청나게 단순해진다는 것입니다. 보기에 우아하고 관리하기 쉬운 viewOnLoad의 단일 코드 루프입니다.
이를 수행하는 방법에 대한 몇 가지 팁은 다음과 같습니다.
Storyboard 또는 nib 파일을 사용하여 ViewController 및 관련 루트 보기를 생성합니다.
UIScrollView를 루트 뷰로 드래그하십시오.
UIScrollView가 전체 루트 뷰를 채우도록 최상위 뷰에 상단, 하단, 왼쪽 및 오른쪽 제약 조건을 추가합니다.
UIScrollView 내부에 UIView를 추가하고 "컨테이너"라고 부릅니다. UIScrollView(상위)에 위쪽, 아래쪽, 왼쪽 및 오른쪽 제약 조건을 추가합니다. 핵심 트릭: UIScrollView와 UIView를 연결하기 위해 "동일 너비" 제약 조건도 추가합니다.
참고: "스크롤 보기에 모호한 스크롤 가능한 콘텐츠 높이가 있습니다"라는 오류가 발생하고 컨테이너 UIView의 높이가 0픽셀이어야 합니다. 앱이 실행 중일 때는 두 오류 모두 문제가 되지 않습니다.
각 "셀"에 대해 nib 파일과 컨트롤러를 만듭니다. UITableViewCell이 아닌 UIView를 사용하십시오.
루트 ViewController에서 기본적으로 모든 "행"을 컨테이너 UIView에 추가하고 왼쪽 및 오른쪽 가장자리를 컨테이너 보기에, 위쪽 가장자리를 컨테이너 보기 위쪽(첫 번째 항목의 경우) 또는 이전 항목에 연결하는 제약 조건을 프로그래밍 방식으로 추가합니다. 셀. 그런 다음 최종 셀을 컨테이너 바닥에 연결합니다.
우리에게 각 "행"은 nib 파일에 있습니다. 따라서 코드는 다음과 같습니다.
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)) } }
지금까지 이 접근 방식에서 찾은 유일한 "문제"는 UITableView에 스크롤할 때 보기 상단에 "플로팅" 섹션 헤더라는 멋진 기능이 있다는 것입니다. 위의 솔루션은 더 많은 프로그래밍을 추가하지 않는 한 그렇게 하지 않을 것이지만 우리의 특별한 경우 이 기능은 100% 필수가 아니었고 아무도 그것이 없어졌을 때 눈치채지 못했습니다.
셀 사이에 구분선이 필요한 경우 구분선처럼 보이는 사용자 지정 "셀" 하단에 1픽셀 높이의 UIView를 추가하기만 하면 됩니다.
새로 고침 컨트롤이 작동하도록 "바운스" 및 "수직으로 바운스"를 켜서 테이블뷰처럼 보이게 해야 합니다.
이 솔루션과 달리 전체 화면을 채우지 않는 경우 TableView는 콘텐츠 아래에 일부 빈 행과 구분선을 표시합니다. 그러나 개인적으로, 나는 그 빈 행이 어쨌든 거기에 없다면 선호합니다. 가변 셀 높이를 사용하면 어쨌든 거기에 빈 행을 갖는 것이 항상 "버그"처럼 보였습니다.
다른 프로그래머가 자신의 앱에서 Table View를 사용하여 알아내려고 20시간 이상을 낭비하기 전에 내 게시물을 읽기를 바랍니다. :)
alpsystems.com
Abo3atef
동적 보기(코드별 설정 보기 및 제약 조건)를 사용해야 했고 preferredMaxLayoutWidth 레이블의 너비를 0으로 설정하려고 할 때 셀 높이가 잘못되었습니다.
그런 다음 추가했습니다.
[cell layoutSubviews];
실행하기 전에
[cell setNeedsUpdateConstraints]; [cell updateConstraintsIfNeeded];
그 레이블의 너비가 예상대로이고 동적 높이가 올바르게 계산된 후.
Ruslan Mansurov
하위 뷰가 있는 셀이 있고 하위 뷰 + 패딩을 포함할 수 있을 만큼 셀의 높이가 높아야 한다고 가정해 보겠습니다.
1) 하위 뷰의 하단 제약 조건을 cell.contentView에서 원하는 패딩을 뺀 것과 동일하게 설정합니다. cell 또는 cell.contentView 자체에 제약 조건을 설정하지 마십시오.
2) tableView의 rowHeight
속성 또는 tableView:heightForRowAtIndexPath:
를 UITableViewAutomaticDimension
합니다.
3) tableView의 estimatedRowHeight
속성이나 tableView:estimatedHeightForRowAtIndexPath:
를 높이를 가장 잘 추측할 수 있도록 설정합니다.
그게 다야
Vadoff
프로그래밍 방식으로 레이아웃을 수행하는 경우 Swift에서 앵커를 사용하여 iOS 10에 대해 고려해야 할 사항은 다음과 같습니다.
세 가지 규칙/단계가 있습니다.
NUMBER 1: viewDidLoad에서 tableview의 이 두 속성을 설정합니다. 첫 번째는 tableview에 셀의 동적 크기를 예상하도록 지시하고, 두 번째는 앱이 스크롤 막대 표시기의 크기를 계산하도록 하므로 도움이 됩니다. 성능.
tableView.rowHeight = UITableViewAutomaticDimension tableView.estimatedRowHeight = 100
NUMBER 2: 이것은 뷰가 아닌 셀의 contentView에 하위 뷰를 추가해야 하는 것이 중요합니다. 또한 layoutsmarginguide를 사용하여 하위 뷰를 상단 및 하단에 고정해야 합니다. 이것은 수행 방법의 실제 예입니다.
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 { }
그렇게 하면 구현을 재정의하게 됩니다.
tableview의 동적 셀에 대해 이 3가지 규칙을 따르십시오.
다음은 작동하는 구현입니다. https://github.com/jamesrochabrun/MinimalViewController
James Rochabrun
긴 문자열이 있는 경우. 예를 들어 줄 바꿈 이 없는 것. 그러면 몇 가지 문제가 발생할 수 있습니다.
"주장된" 수정 사항은 허용된 답변과 몇 가지 다른 답변으로 언급됩니다. 추가하기만 하면 됩니다.
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
때문에.
이를 더 잘 처리하려면 먼저 0단계를 유지한 다음 1-6단계를 주석 처리하십시오. setupLayout()
그대로 두십시오. 행동을 관찰하십시오.
그런 다음 step1의 주석을 제거하고 관찰하십시오.
그런 다음 step2의 주석을 제거하고 관찰하십시오.
6단계의 주석을 모두 제거하고 동작을 관찰할 때까지 이 작업을 수행합니다.
이 모든 것에서 어떤 결론을 내릴 수 있습니까? 어떤 요소가 contenSize
변경할 수 있습니까?
- 텍스트 길이: 더 긴 텍스트가 있으면 내장 콘텐츠 크기의 너비 가 증가합니다.
- 줄 바꿈:
\n
을 추가하면 내장 콘텐츠 크기의 너비가 모든 줄의 최대 너비가 됩니다. 한 줄에 25자, 다른 줄에 2자, 다른 줄에 21자가 있는 경우 너비는 25자를 기준으로 계산됩니다. - 허용되는 줄 수:
numberOfLines
를0
설정해야 합니다. 그렇지 않으면 여러 줄을 사용할 수 없습니다. 귀하의numberOfLines
는 귀하의 internalContentSize의 높이를 조정합니다. 조정: 텍스트를 기반으로 내장 콘텐츠 크기의 너비가
200
이고 높이가100
이지만 너비를 레이블의 컨테이너로 제한하고 싶었다고 상상해 보세요. 무엇을 하시겠습니까? 해결책은 원하는 너비로 설정하는 것입니다.preferredMaxLayoutWidth
를130
으로 설정하면 새로운 내장 콘텐츠 크기(intrinsicContentSize)의 너비가 대략130
됩니다. 더 많은 줄이 필요하기 때문에100
이상이 될 것입니다. 즉, 제약 조건이 올바르게 설정되어 있으면 이것을 전혀 사용할 필요가 없습니다! 이에 대한 자세한 내용은 이 답변 과 해당 설명을 참조하십시오. 당신은 사용할 필요가preferredMaxLayoutWidth
하나는 말할 수있다 "는 초과하지 않는 텍스트 줄 바꿈하지 않는에서와 너비 / 높이 제한하는 제약 조건이없는 경우preferredMaxLayoutWidth
".numberOfLines
를0
설정하면 100% 확신할 수 있습니다. 긴 이야기 짧은 여기에 사용을 권장하는 대부분의 답변이 잘못되었습니다! 당신은 그것을 필요로하지 않습니다. 그것이 필요하다는 것은 제약 조건이 올바르게 설정되지 않았거나 제약 조건이 없다는 표시입니다.글꼴 크기: 또한 fontSize를 늘리면 내장 콘텐츠 크기의 높이 가 증가합니다. 내 코드에 표시하지 않았습니다. 직접 시도해 볼 수 있습니다.
따라서 tableViewCell 예제로 돌아가십시오.
다음 작업만 하면 됩니다.
-
numberOfLines
를0
설정 - 레이블을 여백/가장자리에 올바르게 제한
-
preferredMaxLayoutWidth
를 설정할 필요가 없습니다.
Honey
제 경우에는 서버에서 가져오고 너비와 높이가 될 수 있는 이미지로 사용자 지정 셀을 만들어야 합니다. 그리고 동적 크기(너비 및 높이 모두)를 가진 두 개의 UILabel
나는 자동 레이아웃과 프로그래밍 방식으로 내 대답에서 동일한 것을 달성했습니다.
기본적으로 @smileyBorg 답변 위는 도움이되었지만 systemLayoutSizeFittingSize는 저에게 효과가 없었습니다. 내 접근 방식에서 :
1. 자동 행 높이 계산 속성을 사용하지 않습니다. 2. 예상 높이를 사용하지 않음 3. 불필요한 updateConstraints가 필요하지 않습니다. 4. 자동 기본 최대 레이아웃 너비를 사용하지 않습니다. 5. systemLayoutSizeFittingSize 를 사용하지 않습니다(사용해야 하지만 작동하지 않아야 합니다. 내부적으로 무엇을 하는지 모릅니다). 대신 내 메소드 -(float)getViewHeight가 작동하고 내부적으로 무엇을 하는지 알고 있습니다.
maddy
제 경우 패딩은 sectionHeader 및 sectionFooter 높이 때문이었습니다. 스토리보드에서 최소 1로 변경할 수 있었습니다. 따라서 viewDidLoad 메서드에서 다음을 수행합니다.
tableView.sectionHeaderHeight = 0 tableView.sectionFooterHeight = 0
Rashid
rowHeight
및 estimatedRowHeight
의 2개 값으로 어리석은 시도와 오류를 수행했으며 디버깅 통찰력을 제공할 수 있다고 생각했습니다.
둘 다 설정하거나 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
Honey
@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:는 테이블뷰의 너비입니다.
railwayparade
이 두 기능을 viewcontroller에 추가하기만 하면 문제가 해결됩니다. 여기서 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 }
Parth Barot
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) }
Manee ios
셀 높이가 내용에 따라 동적인 경우 셀이 렌더링되기 전에 정확하게 계산한 다음 높이 값을 반환해야 합니다. 쉬운 방법은 컨트롤러가 테이블 셀 높이 대리자 메서드에서 호출할 테이블 뷰 셀 코드에서 계산 메서드를 정의하는 것입니다. 높이가 테이블이나 화면의 너비에 의존하는 경우 실제 셀 프레임 너비 (기본값은 320)를 계산하는 것을 잊지 마십시오. 즉, 테이블 셀 높이 대리자 메서드에서 cell.frame을 사용하여 먼저 셀 너비를 수정한 다음 셀에 정의된 counting height 메서드를 호출하여 적절한 값을 가져와 반환합니다 .
추신. 셀 개체를 생성하는 코드는 호출할 다른 테이블 뷰 셀 대리자 메서드에 대해 다른 메서드에서 정의할 수 있습니다.
Shrdi
UITableView.automaticDimension
은 Interface Builder를 통해 설정할 수 있습니다.
Xcode > 스토리보드 > 크기 검사기
테이블 보기 셀 > 행 높이 > 자동
pkamb
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 } }
djdance
출처 : http:www.stackoverflow.com/questions/18746929/using-auto-layout-in-uitableview-for-dynamic-cell-layouts-variable-row-heights
'etc. > StackOverFlow' 카테고리의 다른 글
Python에서 파일 이름에서 확장자 추출 (0) | 2022.02.15 |
---|---|
LF는 git에서 CRLF로 대체됩니다. - 그게 무엇이며 중요합니까? [복제하다] (0) | 2022.02.08 |
pull 중 변경 사항을 위해 Git 병합 충돌 해결 (0) | 2022.02.08 |
더 이상 원격에서 추적 분기 제거 (0) | 2022.02.08 |
목록에서 상속하지 않는 이유<T> ? (0) | 2022.02.08 |