TL: DR:ไม่ชอบอ่านใช่ไหม ข้ามไปที่ตัวอย่างโครงการใน GitHub:
คำอธิบายแนวคิด
2 ขั้นตอนแรกด้านล่างสามารถใช้งานได้โดยไม่คำนึงว่าคุณกำลังพัฒนา iOS เวอร์ชันใด
1. ตั้งค่าและเพิ่มข้อ จำกัด
ในUITableViewCell
คลาสย่อยของคุณเพิ่มข้อ จำกัด เพื่อให้มุมมองย่อยของเซลล์ยึดกับขอบของcontentViewของเซลล์(ที่สำคัญที่สุดคือขอบด้านบนและล่าง) หมายเหตุ: ไม่ต้องตรึงผู้ชมไว้ที่เซลล์ เฉพาะกับเซลล์contentView
! ให้ขนาดเนื้อหาที่แท้จริงของการชมย่อยเหล่านี้เพิ่มความสูงของมุมมองเนื้อหาของเซลล์มุมมองตารางโดยตรวจสอบให้แน่ใจว่าความต้านทานการบีบอัดเนื้อหาและข้อ จำกัด การกอดเนื้อหาในมิติแนวตั้งสำหรับแต่ละมุมมองย่อยจะไม่ถูกแทนที่ ( หืม? คลิกที่นี่ )
โปรดจำไว้ว่าแนวคิดคือให้มีการเชื่อมโยงการดูย่อยของเซลล์ในแนวตั้งกับมุมมองเนื้อหาของเซลล์เพื่อให้พวกเขาสามารถ "ออกแรงกดดัน" และทำให้มุมมองเนื้อหาขยายให้พอดี การใช้เซลล์ตัวอย่างที่มีการดูสักเล็กน้อยนี่เป็นภาพประกอบที่แสดงให้เห็นว่าข้อ จำกัด ของคุณบางอย่าง (ไม่ใช่ทั้งหมด!)จะต้องเป็นอย่างไร:
คุณสามารถจินตนาการได้ว่าเมื่อมีการเพิ่มข้อความลงในฉลากเนื้อหาแบบหลายบรรทัดในเซลล์ตัวอย่างด้านบนจะต้องเติบโตในแนวตั้งเพื่อให้พอดีกับข้อความซึ่งจะบังคับให้เซลล์เติบโตอย่างมีประสิทธิภาพ (แน่นอนคุณต้องได้รับข้อ จำกัด เพื่อที่จะทำงานได้อย่างถูกต้อง!)
การทำให้ข้อ จำกัด ของคุณถูกต้องนั้นเป็นส่วนที่ยากที่สุดและสำคัญที่สุดในการสร้างความสูงของเซลล์แบบไดนามิกที่ทำงานกับเค้าโครงอัตโนมัติ หากคุณทำผิดพลาดที่นี่มันสามารถป้องกันไม่ให้ทุกสิ่งทุกอย่างทำงาน - ดังนั้นใช้เวลาของคุณ! ฉันแนะนำให้ตั้งค่าข้อ จำกัด ของคุณในโค้ดเพราะคุณรู้ว่าข้อ จำกัด ใดที่ถูกเพิ่มตรงไหนและมันง่ายกว่าที่จะทำการดีบั๊กเมื่อเกิดข้อผิดพลาด การเพิ่มข้อ จำกัด ในโค้ดนั้นทำได้ง่ายและมีประสิทธิภาพมากกว่า Interface Builder โดยใช้ตัวยึดเลย์เอาต์หรือ API โอเพนซอร์สที่ยอดเยี่ยมที่มีอยู่ใน GitHub
- หากคุณกำลังเพิ่มข้อ จำกัด ในโค้ดคุณควรทำสิ่งนี้เพียงครั้งเดียวจากภายใน
updateConstraints
เมธอดของคลาสย่อย UITableViewCell ของคุณ โปรดทราบว่าupdateConstraints
อาจมีการเรียกมากกว่าหนึ่งครั้งดังนั้นเพื่อหลีกเลี่ยงการเพิ่มข้อ จำกัด เดียวกันมากกว่าหนึ่งครั้งตรวจสอบให้แน่ใจว่าได้ห่อโค้ดเพิ่มข้อ จำกัด ของคุณไว้updateConstraints
ในการตรวจสอบคุณสมบัติบูลีนเช่นdidSetupConstraints
(ซึ่งคุณตั้งค่าเป็น YES หลังจากคุณเรียกใช้ข้อ จำกัด - ใส่รหัสอีกครั้ง) ในทางกลับกันถ้าคุณมีรหัสที่อัพเดตข้อ จำกัด ที่มีอยู่ (เช่นการปรับconstant
คุณสมบัติในข้อ จำกัด บางอย่าง) ให้วางสิ่งนี้ไว้updateConstraints
นอกการตรวจสอบเพื่อdidSetupConstraints
ให้สามารถเรียกใช้ทุกครั้งที่มีการเรียกเมธอด
2. กำหนดตัวระบุการใช้ซ้ำของเซลล์ในมุมมองตาราง
สำหรับข้อ จำกัด ที่ไม่ซ้ำกันทุกชุดในเซลล์ให้ใช้ตัวระบุการใช้เซลล์ซ้ำที่ไม่ซ้ำกัน กล่าวอีกนัยหนึ่งถ้าเซลล์ของคุณมีเค้าโครงที่ไม่ซ้ำกันมากกว่าหนึ่งเค้าโครงแต่ละรูปแบบที่ไม่ซ้ำกันควรได้รับตัวระบุการใช้ซ้ำ (คำใบ้ที่ดีที่คุณต้องใช้ตัวระบุการใช้ซ้ำใหม่คือเมื่อชุดตัวเลือกเซลล์ของคุณมีจำนวนการดูย่อยที่แตกต่างกันหรือการจัดเรียงในลักษณะที่แตกต่างกัน)
ตัวอย่างเช่นหากคุณแสดงข้อความอีเมลในแต่ละเซลล์คุณอาจมีเค้าโครงที่ไม่ซ้ำกัน 4 แบบ: ข้อความที่มีเพียงหัวเรื่อง, ข้อความที่มีหัวเรื่องและเนื้อหา, ข้อความที่มีหัวเรื่องและสิ่งที่แนบรูปถ่ายและข้อความที่มีหัวเรื่อง ร่างกายและสิ่งที่แนบภาพ แต่ละเลย์เอาต์มีข้อ จำกัด ที่แตกต่างกันโดยสิ้นเชิงเพื่อให้บรรลุผลดังนั้นเมื่อเริ่มต้นเซลล์และมีการเพิ่มข้อ จำกัด สำหรับหนึ่งในประเภทเซลล์เหล่านี้เซลล์ควรได้รับตัวระบุการใช้ซ้ำที่ไม่ซ้ำกับประเภทเซลล์ ซึ่งหมายความว่าเมื่อคุณ dequeue เซลล์เพื่อนำมาใช้ใหม่ข้อ จำกัด ได้ถูกเพิ่มและพร้อมที่จะไปสำหรับเซลล์ประเภทนั้น
โปรดทราบว่าเนื่องจากความแตกต่างของขนาดเนื้อหาที่แท้จริงเซลล์ที่มีข้อ จำกัด เดียวกัน (ประเภท) อาจยังมีความสูงที่แตกต่างกัน! อย่าสับสนเค้าโครงที่แตกต่างกันโดยพื้นฐาน (ข้อ จำกัด ที่แตกต่างกัน) กับกรอบมุมมองที่คำนวณได้ที่แตกต่างกัน (แก้ไขจากข้อ จำกัด ที่เหมือนกัน) เนื่องจากเนื้อหามีขนาดแตกต่างกัน
- อย่าเพิ่มเซลล์ที่มีข้อ จำกัด ที่แตกต่างกันโดยสิ้นเชิงกับกลุ่มการใช้ซ้ำ (เช่นใช้ตัวระบุการใช้ซ้ำเดิม) จากนั้นพยายามลบข้อ จำกัด เก่าและตั้งค่าข้อ จำกัด ใหม่จากการลบหลังจากแต่ละ dequeue เอ็นจิ้นเลย์เอาต์อัตโนมัติภายในไม่ได้ออกแบบมาเพื่อจัดการกับการเปลี่ยนแปลงขนาดใหญ่ในข้อ จำกัด และคุณจะเห็นปัญหาประสิทธิภาพการทำงานจำนวนมาก
สำหรับ iOS 8 - เซลล์ที่ปรับขนาดด้วยตนเอง
3. เปิดใช้การประมาณความสูงของแถว
ในการเปิดใช้งานเซลล์มุมมองตารางที่ปรับขนาดเองคุณต้องตั้งค่าคุณสมบัติ rowHeight ของมุมมองตารางเป็น UITableViewAutomaticDimension คุณต้องกำหนดค่าให้กับคุณสมบัติโดยประมาณแถวความสูง ทันทีที่ตั้งค่าคุณสมบัติเหล่านี้ทั้งสองระบบจะใช้เค้าโครงอัตโนมัติเพื่อคำนวณความสูงที่แท้จริงของแถว
Apple: การทำงานกับเซลล์มุมมองตารางที่ปรับขนาดเอง
ด้วย iOS 8 แอปเปิลได้ปรับใช้งานส่วนใหญ่ที่คุณต้องดำเนินการก่อนหน้านี้ใน iOS 8 เพื่อให้กลไกการปรับขนาดเซลล์ทำงานได้เองคุณต้องตั้งค่าrowHeight
คุณสมบัติบนมุมมองตารางเป็นค่าคงที่ก่อนUITableViewAutomaticDimension
. จากนั้นคุณเพียงแค่ต้องเปิดใช้งานการประมาณความสูงของแถวโดยการตั้งค่าestimatedRowHeight
คุณสมบัติของมุมมองตารางเป็นค่าที่ไม่ใช่ศูนย์ตัวอย่างเช่น
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44.0; // set to whatever your "average" cell height is
สิ่งนี้จะให้มุมมองตารางด้วยตัวประมาณ / ตัวยึดชั่วคราวสำหรับความสูงของแถวของเซลล์ที่ยังไม่ปรากฏบนหน้าจอ จากนั้นเมื่อเซลล์เหล่านี้กำลังจะเลื่อนบนหน้าจอความสูงของแถวที่แท้จริงจะถูกคำนวณ ในการกำหนดความสูงที่แท้จริงสำหรับแต่ละแถวมุมมองตารางจะถามแต่ละเซลล์ว่าความสูงนั้นcontentView
ต้องขึ้นอยู่กับความกว้างคงที่ที่ทราบของมุมมองเนื้อหา (ซึ่งขึ้นอยู่กับความกว้างของมุมมองตารางลบสิ่งเพิ่มเติมเช่นดัชนีส่วน หรือมุมมองอุปกรณ์เสริม) และข้อ จำกัด เลย์เอาต์อัตโนมัติที่คุณเพิ่มลงในมุมมองเนื้อหาและการแสดงตัวอย่าง เมื่อมีการกำหนดความสูงของเซลล์จริงแล้วความสูงโดยประมาณเดิมของแถวจะได้รับการอัปเดตด้วยความสูงจริงใหม่ (และการปรับเปลี่ยนใด ๆ กับ contentSize / contentOffset ของมุมมองตารางจะถูกสร้างขึ้นตามที่คุณต้องการ)
โดยทั่วไปการประมาณการที่คุณระบุไม่จำเป็นต้องแม่นยำมากนัก - มันใช้เพื่อกำหนดขนาดตัวบ่งชี้การเลื่อนในมุมมองตารางอย่างถูกต้องเท่านั้นและมุมมองตารางจะทำงานได้ดีในการปรับตัวบ่งชี้การเลื่อนสำหรับการประมาณการที่ไม่ถูกต้อง เลื่อนเซลล์บนหน้าจอ คุณควรตั้งค่าestimatedRowHeight
คุณสมบัติในมุมมองตาราง (ในviewDidLoad
หรือคล้ายกัน) เป็นค่าคงที่ที่ความสูงของแถว "เฉลี่ย" เฉพาะในกรณีที่ความสูงของแถวของคุณมีความแปรปรวนมาก (เช่นแตกต่างกันตามลำดับของขนาด) และคุณสังเกตเห็นตัวบ่งชี้การเลื่อน "กระโดด" ในขณะที่คุณเลื่อนหากคุณไม่สนใจที่tableView:estimatedHeightForRowAtIndexPath:
จะใช้การคำนวณขั้นต่ำที่ต้องการ
สำหรับการสนับสนุน iOS 7 (การปรับขนาดเซลล์อัตโนมัติด้วยตนเอง)
3. ทำเลย์เอาท์ผ่านและรับความสูงของเซลล์
ขั้นแรกให้สร้างอินสแตนซ์นอกจอของเซลล์มุมมองตารางหนึ่งอินสแตนซ์สำหรับตัวระบุการใช้ซ้ำแต่ละครั้งซึ่งจะใช้สำหรับการคำนวณความสูงอย่างเคร่งครัด (หน้าจอหมายถึงการอ้างอิงเซลล์จะถูกเก็บไว้ในคุณสมบัติ / ivar บนตัวควบคุมมุมมองและไม่เคยส่งคืนจากtableView:cellForRowAtIndexPath:
เพื่อให้มุมมองตารางแสดงผลบนหน้าจอจริง) ถัดไปเซลล์จะต้องกำหนดค่าด้วยเนื้อหาที่แน่นอน (เช่นข้อความรูปภาพ ฯลฯ ) มันจะถือถ้ามันจะถูกแสดงในมุมมองตาราง
จากนั้นบังคับให้เซลล์จัดเค้าโครงหน้าย่อยของมันทันทีจากนั้นใช้systemLayoutSizeFittingSize:
วิธีการUITableViewCell
ของcontentView
เพื่อค้นหาความสูงที่ต้องการของเซลล์ ใช้UILayoutFittingCompressedSize
เพื่อให้ได้ขนาดที่เล็กที่สุดเพื่อให้พอดีกับเนื้อหาทั้งหมดของเซลล์ ความสูงสามารถกลับมาจากtableView:heightForRowAtIndexPath:
วิธีการมอบหมาย
4. ใช้ความสูงแถวโดยประมาณ
หากมุมมองตารางของคุณมีแถวมากกว่าสิบสองแถวคุณจะพบว่าการแก้ไขข้อ จำกัด เลย์เอาต์อัตโนมัติสามารถทำให้เธรดหลักหายไปอย่างรวดเร็วเมื่อโหลดมุมมองตารางครั้งแรกตามที่tableView:heightForRowAtIndexPath:
ถูกเรียกในแต่ละแถวเมื่อโหลดครั้งแรก ( เพื่อคำนวณขนาดของตัวบ่งชี้การเลื่อน)
ตั้งแต่ iOS 7 คุณสามารถ (และควรอย่างยิ่ง) ใช้estimatedRowHeight
คุณสมบัติบนมุมมองตาราง สิ่งนี้จะให้มุมมองตารางด้วยตัวประมาณ / ตัวยึดชั่วคราวสำหรับความสูงของแถวของเซลล์ที่ยังไม่ปรากฏบนหน้าจอ จากนั้นเมื่อเซลล์เหล่านี้กำลังจะเลื่อนบนหน้าจอความสูงของแถวที่แท้จริงจะถูกคำนวณ (โดยการโทรtableView:heightForRowAtIndexPath:
) และความสูงโดยประมาณที่อัปเดตด้วยเซลล์จริง
โดยทั่วไปการประมาณการที่คุณระบุไม่จำเป็นต้องแม่นยำมากนัก - มันใช้เพื่อกำหนดขนาดตัวบ่งชี้การเลื่อนในมุมมองตารางอย่างถูกต้องเท่านั้นและมุมมองตารางจะทำงานได้ดีในการปรับตัวบ่งชี้การเลื่อนสำหรับการประมาณการที่ไม่ถูกต้อง เลื่อนเซลล์บนหน้าจอ คุณควรตั้งค่าestimatedRowHeight
คุณสมบัติในมุมมองตาราง (ในviewDidLoad
หรือคล้ายกัน) เป็นค่าคงที่ที่ความสูงของแถว "เฉลี่ย" เฉพาะในกรณีที่ความสูงของแถวของคุณมีความแปรปรวนมาก (เช่นแตกต่างกันตามลำดับของขนาด) และคุณสังเกตเห็นตัวบ่งชี้การเลื่อน "กระโดด" ในขณะที่คุณเลื่อนหากคุณไม่สนใจที่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;
}
}
ตัวอย่างโครงการ
โครงการเหล่านี้เป็นตัวอย่างการทำงานอย่างสมบูรณ์ของมุมมองตารางที่มีความสูงของแถวตัวแปรเนื่องจากเซลล์มุมมองตารางที่มีเนื้อหาแบบไดนามิกใน UILabels
ซามาริน (C # /. NET)
หากคุณกำลังใช้ Xamarin ตรวจสอบนี้โครงการตัวอย่างใส่กันโดย@KentBoogaart