ใช้เค้าโครงอัตโนมัติใน UITableView สำหรับเค้าโครงเซลล์แบบไดนามิกและความสูงของแถวตัวแปร


1501

คุณจะใช้การจัดวางอัตโนมัติUITableViewCellในมุมมองตารางเพื่อให้เนื้อหาและการแสดงผลย่อยของแต่ละเซลล์กำหนดความสูงของแถว (ตัวเอง / โดยอัตโนมัติ) อย่างไรในขณะที่ยังคงประสิทธิภาพการเลื่อนที่ราบรื่น


นี่คือตัวอย่างโค้ดใน swift 2.3 github.com/dpakthakur/DynamicCellHeight
Deepak Thakur

เพื่อให้เข้าใจคำตอบส่วนใหญ่เกี่ยวกับการมี UILabel หลายระดับคุณต้องมีความเข้าใจอย่างเต็มที่เกี่ยวกับวิธีการcontentSizeและการpreferredMaxLayoutWidthทำงาน ดูที่นี่ ดังที่กล่าวไว้หากคุณตั้งค่าข้อ จำกัด อย่างถูกต้องคุณไม่จำเป็นต้องใช้preferredMaxLayoutWidthและในความเป็นจริงสามารถสร้างผลลัพธ์ที่ไม่คาดคิด
ฮันนี่

คำตอบ:


2387

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


1
ขณะนี้ใช้งานได้ดีฉันพบว่าประมาณขนาดที่ต้องการเล็กน้อย (อาจเป็นเพราะปัญหาการปัดเศษหลายครั้ง) และฉันต้องเพิ่มจุดสองสามจุดให้สูงสุดท้ายเพื่อให้ข้อความทั้งหมดของฉันพอดีกับฉลาก
DBD

5
@ Alex311 น่าสนใจมากขอบคุณที่ให้ตัวอย่างนี้ ฉันทำการทดสอบเล็กน้อยที่ส่วนท้ายของฉันและเขียนความคิดเห็นที่นี่: github.com/Alex311/TableCellWithAutoLayout/commit/
......

3
ฉันขอแนะนำให้แคชเซลล์สำหรับตัวระบุการใช้ซ้ำแต่ละเซลล์อย่างจริงจัง ทุกครั้งที่คุณ dequeue สำหรับความสูงที่คุณกำลังเอาเซลล์ออกจากคิวที่ไม่ได้ถูกเพิ่มกลับ การแคชสามารถลดจำนวนครั้งที่ initializer สำหรับเซลล์ตารางของคุณได้อย่างมีนัยสำคัญ ด้วยเหตุผลบางอย่างเมื่อฉันไม่ได้แคชจำนวนของหน่วยความจำที่ใช้เพิ่มขึ้นเรื่อย ๆ เนื่องจากฉันจะเลื่อน
Alex311

1
กระดานเรื่องราวจริงๆแล้ว ฉันไม่คิดว่าใช้งานได้กับเซลล์ต้นแบบ (อย่างน้อยก็ไม่มีการสร้าง VC ขึ้นใหม่ทั้งหมด) อาจเป็นไปได้ที่จะหลีกเลี่ยงการรั่วไหลโดยสิ้นเชิงโดยการ dequeuing ในheightForRowAtIndexPathทำให้เซลล์และกลับมาในครั้งต่อไปที่cellForRowAtIndexPathเรียกว่า
nschum

2
คือiOS8การดำเนินงานยังคงแนะนำสำหรับการiOS9และiOS10หรือวิธีการใหม่ที่นั่นตั้งแต่คำตอบนี้ได้รับการตีพิมพ์?
koen

175

สำหรับ 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
}

สิ่งสำคัญ

  • ถ้าป้ายหลายบรรทัดอย่าลืมตั้งค่าการnumberOfLines0

  • อย่าลืม label.preferredMaxLayoutWidth = CGRectGetWidth(tableView.bounds)

รหัสตัวอย่างเต็มรูปแบบที่นี่


7
ฉันคิดว่าเราไม่จำเป็นต้องใช้ฟังก์ชั่น heightForRowAtIndexPath ในกรณี
OS8

@eddwinpaz บันทึกคำตอบอื่นเป็นสิ่งสำคัญที่ข้อ จำกัด ที่ใช้ในการล็อคความสูงของแถวไม่รวมระยะขอบ
ริชาร์ด

1
+1 สำหรับ "หากป้ายกำกับหลายบรรทัดอย่าลืมตั้งค่า numberOfLines เป็น 0" สิ่งนี้ทำให้เซลล์ของฉันไม่ได้มีขนาดแบบไดนามิก
Ian-Fogelman

เรียกฉันว่าเป็นคนที่คลั่งไคล้การควบคุม แต่ถึงแม้ในยุคของ iOS 11 ฉันยังคงไม่ชอบใช้ UITableViewAutomaticDimension เคยมีประสบการณ์เลวร้ายในอดีต ด้วยเหตุนี้ฉันจึงมักใช้โซลูชัน iOS7 ที่ระบุไว้ที่นี่ บันทึกของเจ้าชายวิลเลี่ยมจะไม่ลืมฉลากนำหน้า MaxLayout ความกว้างที่นี่ช่วยฉัน
Brian Sachetta

เมื่อเราเลื่อนฉลากเริ่มปรากฏในหลายบรรทัดและความสูงของแถวก็ไม่เพิ่มขึ้นอีกด้วย
Karanveer Singh

93

ตัวอย่างที่รวดเร็วของความสูงของตัวแปร UITableViewCell

อัปเดตสำหรับ Swift 3

คำตอบอย่างรวดเร็วของ William Hu นั้นดี แต่มันช่วยให้ฉันมีขั้นตอนที่เรียบง่าย แต่มีรายละเอียดเมื่อเรียนรู้ที่จะทำอะไรเป็นครั้งแรก ตัวอย่างด้านล่างคือโครงการทดสอบของฉันในขณะที่เรียนรู้ที่จะสร้างUITableViewเซลล์ที่มีความสูงของตัวแปร ผมตามมันในตัวอย่างนี้ 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

ตั้งค่ากระดานเรื่องราว

เพิ่มมุมมองตารางไปยังตัวควบคุมมุมมองของคุณและใช้เค้าโครงอัตโนมัติเพื่อตรึงมุมทั้งสี่ด้าน จากนั้นลากเซลล์มุมมองตารางไปยังมุมมองตาราง และบนเซลล์ต้นแบบให้ลากป้ายกำกับ ใช้เลย์เอาต์อัตโนมัติเพื่อปักป้ายกำกับไว้ที่ขอบสี่ของมุมมองเนื้อหาของ Table View Cell

ป้อนคำอธิบายรูปภาพที่นี่

โน๊ตสำคัญ:

  • เลย์เอาต์อัตโนมัติทำงานร่วมกับโค้ดสองบรรทัดที่สำคัญที่ฉันกล่าวถึงข้างต้น หากคุณไม่ได้ใช้การจัดวางอัตโนมัติมันจะไม่ทำงาน

การตั้งค่า IB อื่น ๆ

ชื่อคลาสแบบกำหนดเองและตัวระบุ

เลือกเซลล์มุมมองตารางและตั้งค่าคลาสที่กำหนดเองเป็นMyCustomCell(ชื่อของคลาสในไฟล์ Swift ที่เราเพิ่ม) ตั้งค่าตัวระบุให้เป็นcell(สตริงเดียวกันกับที่เราใช้สำหรับcellReuseIdentifierในรหัสด้านบน

ป้อนคำอธิบายรูปภาพที่นี่

เส้นศูนย์สำหรับฉลาก

กำหนดจำนวนบรรทัด0ในฉลากของคุณ ซึ่งหมายความว่าหลายบรรทัดและอนุญาตให้ฉลากปรับขนาดตัวเองตามเนื้อหา

ป้อนคำอธิบายรูปภาพที่นี่

เชื่อมต่อ Outlets

  • ควบคุมการลากจากมุมมองตารางในกระดานเรื่องราวไปยังtableViewตัวแปรในViewControllerรหัส
  • ทำเช่นเดียวกันสำหรับฉลากในเซลล์ต้นแบบของคุณกับmyCellLabelตัวแปรในMyCustomCellคลาส

เสร็จ

คุณควรจะสามารถเรียกใช้โครงการของคุณตอนนี้และรับเซลล์ที่มีความสูงของตัวแปรได้

หมายเหตุ

  • ตัวอย่างนี้ใช้งานได้กับ iOS 8 และหลังจากนั้น หากคุณยังต้องการสนับสนุน iOS 7 อยู่สิ่งนี้จะไม่ทำงานสำหรับคุณ
  • เซลล์ที่กำหนดเองของคุณเองในโครงการในอนาคตของคุณอาจมีมากกว่าหนึ่งป้ายกำกับ ตรวจสอบให้แน่ใจว่าคุณได้รับทุกอย่างที่ถูกต้องเพื่อให้การจัดวางอัตโนมัติสามารถกำหนดความสูงที่ถูกต้องที่จะใช้ คุณอาจต้องใช้ความต้านทานแรงอัดและการกอดแบบแนวตั้ง ดูบทความนี้สำหรับข้อมูลเพิ่มเติมเกี่ยวกับที่
  • หากคุณไม่ปักหมุดนำหน้าและขอบ (ซ้ายและขวา) คุณอาจต้องตั้งค่าฉลากpreferredMaxLayoutWidthเพื่อให้ทราบว่าเมื่อใดที่ต้องวางบรรทัด ตัวอย่างเช่นหากคุณเพิ่มข้อ จำกัด ของศูนย์ตามแนวนอนลงในป้ายกำกับในโครงการด้านบนแทนที่จะปักหมุดนำหน้าและขอบท้ายคุณจะต้องเพิ่มบรรทัดนี้ในtableView:cellForRowAtIndexPathวิธีการ:

     cell.myCellLabel.preferredMaxLayoutWidth = tableView.bounds.width

ดูสิ่งนี้ด้วย


จริง ๆ แล้วมันไม่ดีไปกว่าการตั้งค่า preferredMaxLayoutWidthกับเซลล์contentSizeหรือไม่ ด้วยวิธีนี้ถ้าคุณมีอุปกรณ์เสริม ViewView หรือถูก swiped เพื่อแก้ไขมันจะถูกนำมาพิจารณาด้วยหรือไม่
ฮันนี่

@ เงินคุณอาจจะพูดถูก ฉันยังไม่ได้ติดตาม iOS เป็นเวลาประมาณหนึ่งปีแล้วและฉันก็ยังมีแรงเกินไปที่จะตอบคุณในตอนนี้
Suragch

65

ฉันห่อโซลูชัน iOS7 ของ @ smileyborg ไว้ในหมวดหมู่หนึ่งแล้ว

ฉันตัดสินใจที่จะปิดโซลูชันที่ฉลาดนี้โดย @smileyborg เป็นUICollectionViewCell+AutoLayoutDynamicHeightCalculationหมวดหมู่

หมวดหมู่ยังแก้ไขปัญหาที่ระบุไว้ในคำตอบของ @ wildmonkey (โหลดเซลล์จากปลายปากกาและ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 แต่ตอนนี้มันมีมาแล้ว!


2
คุณควรจะใช้: [YourCell new]และใช้มันเป็นหุ่นจำลอง ตราบใดที่โค้ดการสร้างโค้ด จำกัด ถูกใช้ในอินสแตนซ์ของคุณและคุณทริกเกอร์การส่งเลย์เอาต์โดยทางโปรแกรมคุณควรไปได้ดี
Adam Waite

1
ขอบคุณ! วิธีนี้ใช้ได้ผล หมวดหมู่ของคุณยอดเยี่ยม มันทำให้ฉันรู้ว่าเทคนิคนี้ใช้ได้กับUICollectionViewsเช่นกัน
Ricardo Sanchez-Saez

2
คุณจะทำสิ่งนี้โดยใช้เซลล์ต้นแบบที่กำหนดไว้ในกระดานเรื่องราวได้อย่างไร
เดวิดพอตเตอร์

57

นี่คือทางออกของฉัน:

คุณจำเป็นต้องบอกก่อนที่มันจะโหลดมุมมอง มิฉะนั้นจะไม่สามารถทำงานได้อย่างที่คาดไว้TableViewestimatedHeight

Objective-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
}

2
การตั้งค่าการชำระอัตโนมัติอย่างถูกต้องพร้อมกับการเพิ่มรหัสนี้ใน viewDidLoad ทำให้เคล็ดลับ
บรูซ

1
แต่estimatedRowHeightจะเป็นเช่นไรหากเกิดการเปลี่ยนแปลงทีละแถว ฉันควรสูงหรือต่ำกว่าประมาณการ ใช้ความสูงขั้นต่ำหรือสูงสุดที่ฉันใช้tableViewใช่หรือไม่
Janos

1
@ Jánosนี่คือจุดของแถวความสูง เมื่อต้องการทำสิ่งนี้ให้เป็นไปตามที่คาดไว้คุณต้องใช้ข้อ จำกัด โดยไม่มีระยะขอบและจัดแนวให้กับ TableViewCell วัตถุ และฉันคิดว่าคุณกำลังใช้ UITextView ดังนั้นคุณยังต้องลบ autoscroll = false ไม่เช่นนั้นจะรักษาความสูงและความสูงสัมพัทธ์จะไม่ทำตามที่คาดไว้
Eddwin Paz

1
นั่นคือทางออกที่แข็งแกร่งที่สุด ไม่ต้องกังวลเลยestimatedRowHeightส่วนใหญ่จะมีผลกับขนาดของแถบเลื่อนไม่สูงของเซลล์จริง กล้าหาญในระดับความสูงที่คุณเลือก: มันจะส่งผลต่อภาพเคลื่อนไหวการแทรก / การลบ
SwiftArchitect

นี่คือตัวอย่างโค้ดใน swift 2.3 github.com/dpakthakur/DynamicCellHeight
Deepak Thakur

47

โซลูชันที่เสนอโดย @smileyborg เกือบจะสมบูรณ์แบบ ถ้าคุณมีมือถือที่กำหนดเองและคุณต้องการหนึ่งหรือมากกว่าUILabelมีความสูงแบบไดนามิกแล้วsystemLayoutSizeFittingSizeวิธีการรวมกับ autolayout เปิดการใช้งานผลตอบแทนCGSizeZeroจนกว่าคุณจะย้ายทุกข้อ จำกัด ของมือถือของคุณจากเซลล์เพื่อ contentView (ตามที่แนะนำโดย @TomSwift ที่นี่วิธีการปรับขนาด 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 กับสิ่งนี้ควรใช้งานได้


systemLayoutSizeFittingSizeต้องมีการเรียกบน contentView ไม่ใช่เซลล์
onmyway133

25

gotcha สำคัญพอฉันเพิ่งวิ่งเข้าไปโพสต์เป็นคำตอบ

คำตอบของ @ smileyborg นั้นถูกต้องเป็นส่วนใหญ่ อย่างไรก็ตามหากคุณมีรหัสใด ๆ ในlayoutSubviewsวิธีการเรียนเซลล์ที่กำหนดเองของคุณเช่นการตั้งค่าpreferredMaxLayoutWidthแล้วมันจะไม่ทำงานด้วยรหัสนี้:

[cell.contentView setNeedsLayout];
[cell.contentView layoutIfNeeded];

มันทำให้ฉันสับสนในช่วงเวลาหนึ่ง จากนั้นฉันก็รู้ว่าเป็นเพราะสิ่งเหล่านั้นเป็นเพียงการเรียกใช้เลย์เอาต์การแสดงความคิดเห็นบนcontentViewไม่ใช่เซลล์เอง

รหัสการทำงานของฉันมีลักษณะเช่นนี้:

TCAnswerDetailAppSummaryCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TCAnswerDetailAppSummaryCell"];
[cell configureWithThirdPartyObject:self.app];
[cell layoutIfNeeded];
CGFloat height = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return height;

โปรดทราบว่าหากคุณกำลังสร้างเซลล์ใหม่ฉันค่อนข้างแน่ใจว่าคุณไม่จำเป็นต้องโทรsetNeedsLayoutตามที่ควรจะตั้งไว้แล้ว ในกรณีที่คุณบันทึกการอ้างอิงไปยังเซลล์คุณอาจเรียกมันว่า ไม่ว่าจะด้วยวิธีใดก็ไม่ควรทำร้ายอะไร

เคล็ดลับก็ถ้าคุณกำลังใช้มือถือ subclasses 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);

(ฉันบังเอิญแคชเซลล์นี้เพื่อนำกลับมาใช้ใหม่ แต่ไม่เกี่ยวข้อง)


21

ในกรณีที่ผู้คนยังคงมีปัญหากับสิ่งนี้ ฉันเขียนโพสต์บล็อกด่วนเกี่ยวกับการใช้ Autolayout กับ UITableViews Leveraging Autolayout สำหรับ Dynamic Cell Heightsรวมถึงส่วนประกอบโอเพ่นซอร์สเพื่อช่วยทำให้สิ่งนี้เป็นนามธรรมและง่ายต่อการนำไปใช้ https://github.com/Raizlabs/RZCellSizeManager


19

ตราบใดที่เลย์เอาต์ในมือถือของคุณดี

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [self tableView:tableView cellForRowAtIndexPath:indexPath];

    return [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
}

อัปเดต: คุณควรใช้การปรับขนาดแบบไดนามิกที่แนะนำใน iOS 8


นี้คือการทำงานสำหรับฉันใน iOS7 ก็คือตอนนี้ตกลงที่จะเรียกtableView:cellForRowAtIndexPath:ในtableView:heightForRowAtIndexPath:ตอนนี้หรือไม่
มด

ตกลงดังนั้นนี่ใช้งานไม่ได้ แต่เมื่อฉันโทรsystemLayoutSizeFittingSize:เข้าtableView:cellForRowAtIndexPath:และแคชผลลัพธ์แล้วใช้tableView:heightForRowAtIndexPath:มันในการทำงานได้ดีตราบใดที่ข้อ จำกัด มีการติดตั้งอย่างถูกต้องแน่นอน!
มด

ใช้งานได้เฉพาะถ้าคุณใช้ dequeueReusableCellWithIdentifier: แทน dequeueReusableCellWithIdentifier: forIndexPath:
Antoine

1
ฉันไม่คิดว่าจะเรียก tableView: cellForRowAtIndexPath: เป็นวิธีที่ดีโดยตรง
Itachi

19

(สำหรับ Xcode 8.x / Xcode 9.x อ่านที่ด้านล่าง)

ระวังปัญหาต่อไปนี้ใน Xcode 7.x ซึ่งอาจเป็นสาเหตุของความสับสน:

ตัวสร้างส่วนต่อประสานไม่ได้จัดการการตั้งค่าขนาดเซลล์อัตโนมัติอย่างถูกต้อง แม้ว่าข้อ จำกัด ของคุณจะถูกต้องแน่นอน IB จะยังคงบ่นและให้คำแนะนำและข้อผิดพลาดที่ทำให้คุณสับสน เหตุผลก็คือ IB ไม่ต้องการเปลี่ยนความสูงของแถวเนื่องจากข้อ จำกัด ของคุณกำหนดไว้ (เพื่อให้เซลล์พอดีกับเนื้อหาของคุณ) แต่มันช่วยให้ความสูงของแถวที่คงที่และเริ่มแนะนำให้คุณเปลี่ยนข้อ จำกัด ของคุณซึ่งคุณควรละเลย

ตัวอย่างเช่นสมมติว่าคุณตั้งค่าทุกอย่างเรียบร้อยไม่มีคำเตือนไม่มีข้อผิดพลาดใช้งานได้ทั้งหมด

ป้อนคำอธิบายรูปภาพที่นี่

ตอนนี้ถ้าคุณเปลี่ยนขนาดตัวอักษร (ในตัวอย่างนี้ฉันกำลังเปลี่ยนขนาดตัวอักษรคำอธิบายจาก 17.0 เป็น 18.0)

ป้อนคำอธิบายรูปภาพที่นี่

เนื่องจากขนาดตัวอักษรเพิ่มขึ้นตอนนี้ป้ายกำกับจึงต้องการครอบครอง 3 แถว (ก่อนหน้านั้นมี 2 แถว)

หากตัวสร้างส่วนต่อประสานทำงานได้ตามที่คาดหวังจะปรับขนาดความสูงของเซลล์เพื่อรองรับความสูงของป้ายกำกับใหม่ อย่างไรก็ตามสิ่งที่เกิดขึ้นจริงคือ 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ถูกตั้งค่าเป็นต้องการ (1,000) ตัวสร้างส่วนต่อประสานอาจยืดหรือคลิปฉลากให้พอดีกับเซลล์ (แทนที่จะปรับขนาดความสูงของเซลล์ให้พอดีกับฉลาก) และในกรณีเช่นนี้อาจไม่แสดงคำเตือนหรือข้อผิดพลาด AutoLayout ใด ๆ หรือบางครั้งมันทำสิ่งที่ Xcode 7.x ทำอย่างที่อธิบายไว้ข้างต้น


hi เป็นไปได้หรือไม่ที่จะให้ความสูงแบบไดนามิกสำหรับเซลล์ที่มี tableview กับเซลล์ที่มีเนื้อหาของเซลล์แบบไดนามิก
Jignesh B

18

ในการตั้งค่ามิติอัตโนมัติสำหรับความสูงของแถว & ความสูงของแถวโดยประมาณให้ทำตามขั้นตอนต่อไปนี้ทำมิติอัตโนมัติให้มีประสิทธิภาพสำหรับโครงร่างความสูงของเซลล์ / แถว

  • กำหนดและใช้ tableview dataSource และผู้รับมอบสิทธิ์
  • กำหนดให้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 (& โหมดแบ่งบรรทัด = ตัดปลาย)
  • ตั้งค่าข้อ จำกัด ทั้งหมด (ด้านบน, ด้านล่าง, ซ้ายขวา) ตามการกำกับดูแล / คอนเทนเนอร์ของเซลล์
  • ทางเลือก : ตั้งค่าความสูงต่ำสุดสำหรับป้ายกำกับหากคุณต้องการพื้นที่แนวตั้งขั้นต่ำที่ครอบคลุมโดยป้ายกำกับแม้ว่าจะไม่มีข้อมูลก็ตาม

ป้อนคำอธิบายรูปภาพที่นี่

หมายเหตุ : หากคุณมีป้ายกำกับมากกว่าหนึ่งรายการ (UIElements) ที่มีความยาวแบบไดนามิกซึ่งควรปรับตามขนาดเนื้อหา: ปรับ 'ลำดับความสำคัญของเนื้อหาและการบีบอัดความต้านทานการบีบอัด' สำหรับป้ายกำกับที่คุณต้องการขยาย / บีบอัด


1
ขอบคุณดูเหมือนโซลูชันที่ชัดเจนและเรียบง่าย แต่ฉันมีปัญหาฉันไม่ได้ใช้ป้ายกำกับ แต่เป็นข้อความแสดงข้อความดังนั้นฉันจึงต้องการความสูงของแถวเพื่อเพิ่มเมื่อมีการเพิ่มข้อมูล ปัญหาของฉันคือส่งข้อมูลไปที่ heightForRowAt ฉันสามารถวัดความสูงของการเปลี่ยนข้อความตัวอักษรได้ แต่ตอนนี้ต้องเปลี่ยนความสูงของแถว ขอขอบคุณความช่วยเหลือบางอย่าง
Jeremy Andrews

@ JeremyAndrews แน่นอนว่าจะช่วยคุณได้ ถามคำถามด้วยซอร์สโค้ดที่คุณได้ลองและรายละเอียดเกี่ยวกับปัญหา
Krunal

มันทำงานได้สำหรับฉันโดยไม่ต้องใช้tableView: heightForRowแหล่งข้อมูล
Hemang

@Hemang - ตั้งแต่ iOS ของคุณ 11 + tableView: heightForRowมันทำงานได้โดยไม่ต้อง (สำหรับ iOS 10- tableView: heightForRowต้องใช้)
Krunal

1
@Hemang - โซลูชันขึ้นอยู่กับคำจำกัดความของแบบสอบถามที่แน่นอน ที่นี่ฉันมีวิธีแก้ปัญหาทั่วไปทั่วไปสำหรับการสืบค้นของคุณ ใส่เงื่อนไขไว้ข้างในtableView: heightForRow..if (indexPath.row == 0) { return 100} else { return UITableView.automaticDimension }
Krunal

16

เช่น@ Bob-Sprynฉันพบ gotcha ที่สำคัญพอที่ฉันโพสต์สิ่งนี้เป็นคำตอบ

ฉันต่อสู้กับคำตอบของ @ smileyborg มาระยะหนึ่งแล้ว gotcha ที่ฉันพบคือถ้าคุณกำหนดเซลล์ต้นแบบของคุณใน IB ด้วยองค์ประกอบเพิ่มเติม ( UILabels, UIButtonsฯลฯ ) ใน IB เมื่อคุณยกตัวอย่างเซลล์ด้วย [ [YourTableViewCellClass alloc] init]มันจะไม่ยกตัวอย่างองค์ประกอบอื่น ๆ ทั้งหมดในเซลล์นั้นเว้นแต่คุณจะ เขียนโค้ดเพื่อทำสิ่งนั้น (ฉันมีประสบการณ์คล้ายกันกับinitWithStyle)

หากต้องการให้กระดานเรื่องราวยกตัวอย่างองค์ประกอบเพิ่มเติมทั้งหมดให้กับมือถือของคุณด้วย[tableView dequeueReusableCellWithIdentifier:@"DoseNeeded"](ไม่[tableView dequeueReusableCellWithIdentifier:forIndexPath:]เช่นนี้จะทำให้เกิดปัญหาที่น่าสนใจ) เมื่อคุณทำเช่นนี้องค์ประกอบทั้งหมดที่คุณกำหนดไว้ใน IB จะได้รับการยกตัวอย่าง


15

ตารางความสูงของเซลล์มุมมองแบบไดนามิกและเค้าโครงอัตโนมัติ

วิธีที่ดีในการแก้ไขปัญหาด้วยเค้าโครงอัตโนมัติ:

- (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;
}

1
สิ่งนี้ได้รับการครอบคลุมอย่างกว้างขวางในคำตอบที่ยอมรับสำหรับคำถามนี้
smileyborg

2
ใช่ฉันรู้ ... แต่ฉันไม่ต้องการใช้ PureLayout และ 'เคล็ดลับ' dispatch_once ช่วยฉันได้มากในการทำงานโดยใช้สตอรี่บอร์ดเท่านั้น
Mohnasm


13

"โซลูชัน" อื่น: ข้ามความยุ่งยากทั้งหมดนี้และใช้ UIScrollView แทนเพื่อรับผลลัพธ์ที่มีลักษณะและความรู้สึกเหมือนกับ UITableView

นั่นเป็น "การแก้ปัญหา" ที่เจ็บปวดสำหรับฉันหลังจากใช้เวลา 20 ชั่วโมงในการสร้างสิ่งที่น่าผิดหวังและแนะนำให้ฉันลองและสร้างความล้มเหลวในช่วงหลายเดือนและรุ่น App Store สามรุ่น

สิ่งที่ฉันต้องทำคือถ้าคุณต้องการการสนับสนุน iOS 7 (สำหรับเรามันเป็นเรื่องสำคัญ) จากนั้นเทคโนโลยีนั้นเปราะเกินไปและคุณจะต้องถอนผมออกไป และโดยทั่วไปแล้ว UITableView จะสมบูรณ์เกินไปเว้นแต่คุณจะใช้คุณสมบัติการแก้ไขแถวขั้นสูงบางส่วนและ / หรือต้องการการสนับสนุน "แถว" มากกว่า 1,000 แถว (ในแอปของเรามันมีมากกว่า 20 แถวจริง)

โบนัสเพิ่มเติมคือรหัสนั้นเรียบง่ายเมามันเมื่อเทียบกับอึตัวแทนทั้งหมดและกลับไปกลับมาที่มาพร้อมกับ UITableView มันเป็นโค้ดเดียวใน viewOnLoad ที่ดูดีและจัดการได้ง่าย

นี่คือเคล็ดลับเกี่ยวกับวิธีการทำ:

  1. ใช้ Storyboard หรือไฟล์ปลายปากกาสร้าง ViewController และรูทมุมมองที่เกี่ยวข้อง

  2. ลากผ่าน UIScrollView ไปยังมุมมองรูตของคุณ

  3. เพิ่มข้อ จำกัด ด้านบนด้านล่างด้านซ้ายและด้านขวาลงในมุมมองระดับบนสุดเพื่อให้ UIScrollView เติมเต็มมุมมองรูททั้งหมด

  4. เพิ่ม UIView ภายใน UIScrollView และเรียกมันว่า "คอนเทนเนอร์" เพิ่มข้อ จำกัด ด้านบนด้านล่างซ้ายและขวาให้กับ UIScrollView (พาเรนต์) KEY TRICK: เพิ่มข้อ จำกัด "ความกว้างเท่ากับ" เพื่อเชื่อมโยง UIScrollView และ UIView

    หมายเหตุ: คุณจะได้รับข้อผิดพลาด "มุมมองการเลื่อนมีความสูงเนื้อหาที่เลื่อนได้ที่ไม่ชัดเจน" และ UIView คอนเทนเนอร์ของคุณควรมีความสูง 0 พิกเซล ดูเหมือนว่าจะไม่มีข้อผิดพลาดเกิดขึ้นเมื่อแอปทำงาน

  5. สร้างไฟล์และปลายปากกาสำหรับแต่ละ "เซลล์" ของคุณ ใช้ UIView ไม่ใช่ UITableViewCell

  6. ใน ViewController รูทของคุณคุณต้องเพิ่ม "row" ทั้งหมดลงใน 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 มีคุณลักษณะที่ดีของส่วนหัว "floating" ที่ด้านบนของมุมมองขณะที่คุณเลื่อน วิธีการแก้ปัญหาข้างต้นจะไม่ทำเช่นนั้นเว้นแต่คุณจะเพิ่มการเขียนโปรแกรมเพิ่มเติม แต่สำหรับกรณีของเราคุณสมบัตินี้ไม่จำเป็น 100% และไม่มีใครสังเกตเห็นเมื่อมันหายไป

หากคุณต้องการตัวแบ่งระหว่างเซลล์ของคุณเพียงแค่เพิ่ม UIView สูง 1 พิกเซลที่ด้านล่างของ "เซลล์" ที่กำหนดเองซึ่งดูเหมือนว่าตัวแบ่ง

อย่าลืมเปิดใช้ "การตีกลับ" และ "การตีกลับในแนวตั้ง" เพื่อให้การควบคุมการรีเฟรชทำงานและดูเหมือนว่าจะเป็นมุมมองบนโต๊ะ

TableView จะแสดงแถวและตัวแบ่งที่ว่างเปล่าบางส่วนภายใต้เนื้อหาของคุณหากไม่เต็มหน้าจอโดยที่วิธีนี้ไม่ได้แก้ปัญหา แต่โดยส่วนตัวแล้วฉันชอบถ้าแถวที่ว่างเหล่านั้นไม่ได้อยู่ที่นั่น - ด้วยความสูงของเซลล์ที่แปรผันมันก็มักจะ "บั๊กกี้" สำหรับฉันเสมอ

นี่คือความหวังว่าโปรแกรมเมอร์คนอื่นอ่านโพสต์ของฉันก่อนที่จะเสียเวลามากกว่า 20 ชั่วโมงในการพยายามคิดออกด้วย Table View ในแอปของพวกเขาเอง :)


11

ฉันต้องใช้มุมมองแบบไดนามิก (มุมมองการตั้งค่าและข้อ จำกัด ตามรหัส) และเมื่อฉันต้องการตั้งค่าความกว้างของป้ายกำกับที่ต้องการ MaxLayoutWidth คือ 0 ดังนั้นฉันจึงมีความสูงของเซลล์ผิด

จากนั้นฉันก็เพิ่ม

[cell layoutSubviews];

ก่อนดำเนินการ

[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];

หลังจากความกว้างของป้ายกำกับนั้นเป็นไปตามที่คาดไว้และความสูงแบบไดนามิกคำนวณได้ถูกต้อง


9

สมมติว่าคุณมีเซลล์ที่มีมุมมองย่อยและคุณต้องการให้ความสูงของเซลล์สูงพอที่จะครอบคลุมส่วนย่อย + padding

1) กำหนดข้อ จำกัด ด้านล่างของมุมมองย่อยเท่ากับ cell.contentView ลบด้วยการขยายที่คุณต้องการ อย่าตั้งข้อ จำกัด ในเซลล์หรือ cell.contentView เอง

2) ชุดทั้ง tableView ของrowHeightทรัพย์สินหรือการtableView:heightForRowAtIndexPath:UITableViewAutomaticDimension

3) ตั้งค่าestimatedRowHeightคุณสมบัติของ tableView หรือtableView:estimatedHeightForRowAtIndexPath:คาดเดาความสูงได้ดีที่สุด

แค่นั้นแหละ.


7

หากคุณจัดเลย์เอาต์ทางโปรแกรมนี่คือสิ่งที่ต้องพิจารณาสำหรับ iOS 10 โดยใช้แองเคอร์ใน Swift

มีสามกฎ / ขั้นตอน

หมายเลข 1: ตั้งค่าคุณสมบัติสองอย่างนี้ของ tableview บน viewDidLoad อันแรกบอกกับ tableview ที่ควรคาดหวังว่าขนาดไดนามิกในเซลล์ของพวกเขาส่วนที่สองเป็นเพียงเพื่อให้แอปคำนวณขนาดของตัวบ่งชี้แถบเลื่อนดังนั้นมันช่วย ประสิทธิภาพ.

    tableView.rowHeight = UITableViewAutomaticDimension
    tableView.estimatedRowHeight = 100

หมายเลข 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)

        ])
}

สร้างวิธีการที่จะเพิ่มมุมมองย่อยและดำเนินการกับเค้าโครงเรียกมันในวิธีการเริ่มต้น

หมายเลข 3: อย่าโทรหาวิธี:

  override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    }

หากคุณทำเช่นนั้นคุณจะแทนที่การใช้งานของคุณ

ปฏิบัติตาม 3 กฎนี้สำหรับเซลล์แบบไดนามิกใน tableviews

นี่คือการดำเนินการที่ใช้งานได้ https://github.com/jamesrochabrun/MinimalViewController


ใน Swift 5 UITableViewAutomaticDimension เปลี่ยนชื่อเป็น UITableView.automaticDimension
James Rochabrun

4

หากคุณมีสายยาว เช่นที่ไม่มีการแบ่งบรรทัด จากนั้นคุณอาจพบปัญหาบางอย่าง

การแก้ไข "ที่ถูกกล่าวหา" ถูกกล่าวถึงโดยคำตอบที่ยอมรับและคำตอบอื่น ๆ คุณเพียงแค่ต้องเพิ่ม

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()อยู่ สังเกตพฤติกรรม

จากนั้นไม่ใส่เครื่องหมายข้อคิดเห็นขั้นตอนที่ 1 และสังเกต

จากนั้นไม่ใส่เครื่องหมายข้อคิดเห็นขั้นที่ 2 และสังเกต

ทำสิ่งนี้จนกว่าคุณจะไม่ใส่เครื่องหมายข้อคิดเห็นทั้ง 6 ขั้นตอนและสังเกตพฤติกรรมของพวกเขา

อะไรคือข้อสรุปจากทั้งหมดนี้? ปัจจัยใดบ้างที่สามารถเปลี่ยนแปลงได้contenSize?

  1. ความยาวข้อความ:หากคุณมีข้อความที่ยาวขึ้นความกว้างของ intrinsicContentSize ของคุณจะเพิ่มขึ้น
  2. ตัวแบ่งบรรทัด:หากคุณเพิ่ม\nความกว้างของ intrinsicContentSize จะเป็นความกว้างสูงสุดของทุกบรรทัด หากหนึ่งบรรทัดมี 25 ตัวอักษรอีกบรรทัดหนึ่งมี 2 ตัวอักษรและอีกบรรทัดมี 21 ตัวอักษรความกว้างของคุณจะถูกคำนวณตามตัวอักษร 25 ตัว
  3. จำนวนของสายได้รับอนุญาต:คุณต้องตั้งค่าnumberOfLinesการ0มิฉะนั้นคุณจะไม่ต้องหลายบรรทัด คุณnumberOfLinesจะปรับความสูงของ intrinsicContentSize ของคุณ
  4. ทำการปรับ:ลองนึกภาพตามข้อความของคุณความกว้างของ intrinsicContentSize คือ200และความสูง100แต่คุณต้องการ จำกัด ความกว้างให้กับคอนเทนเนอร์ของป้ายกำกับคุณจะทำอะไร การแก้ปัญหาคือการตั้งค่าความกว้างที่ต้องการ คุณทำอย่างนั้นโดยการตั้งค่าpreferredMaxLayoutWidthไป130แล้ว intrinsicContentSize 130ใหม่ของคุณจะมีความกว้างของประมาณ ความสูงจะเห็นได้ชัดมากกว่า100เพราะคุณต้องการเส้นมากขึ้น ที่ถูกกล่าวว่าหากข้อ จำกัด ของคุณตั้งไว้อย่างถูกต้องแล้วคุณไม่จำเป็นต้องใช้สิ่งนี้เลย! สำหรับข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้โปรดดูคำตอบและความคิดเห็น คุณจะต้องใช้เฉพาะในpreferredMaxLayoutWidthกรณีที่คุณไม่มีข้อ จำกัด ในการจำกัดความกว้าง / ความสูงดังที่อาจกล่าวได้ว่าpreferredMaxLayoutWidth"แต่ด้วยความมั่นใจ 100% ถ้าคุณตั้งค่าการนำหน้า / ท้ายและnumberOfLinesเป็น0คุณทำได้ดี!เรื่องสั้นสั้น ๆ คำตอบส่วนใหญ่ที่นี่ซึ่งแนะนำให้ใช้มันผิด! คุณไม่ต้องการมัน ต้องการเป็นสัญญาณว่าข้อ จำกัด ของคุณไม่ได้ตั้งอย่างถูกต้องหรือว่าคุณไม่มีข้อ จำกัด

  5. ขนาดตัวอักษร:โปรดทราบว่าหากคุณเพิ่มขนาดตัวอักษรของคุณความสูงของ intrinsicContentSize จะเพิ่มขึ้น ฉันไม่ได้แสดงในรหัสของฉัน คุณสามารถลองด้วยตัวคุณเอง

ดังนั้นกลับไปที่ตัวอย่าง tableViewCell ของคุณ:

สิ่งที่คุณต้องทำคือ:

  • ตั้งค่าnumberOfLinesการ0
  • จำกัด ฉลากให้ถูกต้องจนถึงขอบ / ขอบ
  • preferredMaxLayoutWidthไม่จำเป็นต้องมีชุดไม่เป็น

1

ในกรณีของฉันฉันต้องสร้างเซลล์ที่กำหนดเองด้วยภาพที่มาจากเซิร์ฟเวอร์และสามารถมีความกว้างและความสูงได้ และสอง UILabels ที่มีขนาดไดนามิก (ทั้งความกว้างและความสูง)

ฉันประสบความสำเร็จเหมือนกันที่นี่ในคำตอบของฉันกับ autolayout และโดยทางโปรแกรม:

โดยทั่วไปเหนือ @smileyBorg ตอบช่วย แต่ systemLayoutSizeFittingSize ไม่เคยทำงานให้ฉันในแนวทางของฉัน:

1. ไม่มีการใช้คุณสมบัติการคำนวณความสูงของแถวอัตโนมัติ 2. ไม่มีการใช้ความสูงโดยประมาณ 3. ไม่จำเป็นต้องมีการอัพเดตที่ไม่จำเป็นข้อ จำกัด 4. ไม่มีการใช้ความกว้างสูงสุดที่ต้องการโดยอัตโนมัติ 5. ไม่มีการใช้systemLayoutSizeFittingSize (ควรใช้ แต่ไม่ได้ผลสำหรับฉันฉันไม่รู้ว่ามันทำอะไรภายใน) แต่แทนที่จะใช้วิธีของฉัน - (ลอย) getViewHeight ทำงานและฉันรู้ว่ามันทำอะไรภายใน

เป็นไปได้หรือไม่ที่จะมีความสูงแตกต่างกันในเซลล์ UITableView เมื่อฉันใช้วิธีการแสดงเซลล์ที่แตกต่างกันหลายวิธี?


1

ในกรณีของฉันการแพ็ดดิ้งเป็นเพราะส่วนหัวและตัวเลื่อนส่วนสูงที่กระดานเรื่องราวอนุญาตให้ฉันเปลี่ยนเป็นขั้นต่ำ 1 ดังนั้นในเมธอด viewDidLoad:

tableView.sectionHeaderHeight = 0
tableView.sectionFooterHeight = 0

1

ฉันเพิ่งลองใบ้และผิดพลาดกับค่า 2 rowHeightและestimatedRowHeightและเพียงแค่คิดว่ามันอาจให้บางความเข้าใจการแก้จุดบกพร่อง:

หากคุณตั้งค่าเหล่านี้ทั้งสองหรือเพียงตั้งค่าestimatedRowHeightคุณจะได้รับพฤติกรรมที่ต้องการ:

tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 1.00001 // MUST be greater than 1

ขอแนะนำให้คุณทำอย่างดีที่สุดเพื่อให้ได้ค่าประมาณที่ถูกต้อง แต่ผลลัพธ์สุดท้ายนั้นไม่แตกต่างกัน มันจะส่งผลกระทบต่อประสิทธิภาพการทำงานของคุณ

ป้อนคำอธิบายรูปภาพที่นี่


หากคุณตั้งค่า rowHeight เท่านั้นให้ทำ:

tableView.rowHeight = UITableViewAutomaticDimension

ผลลัพธ์สุดท้ายของคุณจะไม่เป็นที่ต้องการ:

ป้อนคำอธิบายรูปภาพที่นี่


ถ้าคุณตั้งค่าestimatedRowHeight1 หรือขนาดเล็กแล้วคุณจะผิดพลาด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

1

ฉันได้พบคำตอบที่ยอมรับโดย @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: คือความกว้างของ tableview


0

เพียงเพิ่มทั้งสองฟังก์ชั่นลงในวิวเวอร์คอนโทรลเลอร์ของคุณมันจะช่วยแก้ปัญหาของคุณ ที่นี่รายการเป็นอาร์เรย์สตริงที่มีสตริงของคุณทุกแถว

 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
}

-1
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)

    }

-1

หากความสูงของเซลล์นั้นเป็นแบบไดนามิกโดยเนื้อหาคุณควรนับอย่างแม่นยำแล้วส่งคืนค่าความสูงก่อนที่จะแสดงผลเซลล์ วิธีง่าย ๆ คือการกำหนดวิธีการนับในรหัสเซลล์มุมมองตารางสำหรับตัวควบคุมเพื่อเรียกที่วิธีการมอบหมายความสูงของเซลล์ตาราง อย่าลืมนับความกว้างเฟรมจริงของเซลล์ (ค่าเริ่มต้นคือ 320) หากความสูงนั้นขึ้นอยู่กับความกว้างของตารางหรือหน้าจอ นั่นคือในเซลล์ความสูงของตารางวิธีผู้รับมอบสิทธิ์ใช้ cell.frame เพื่อแก้ไขความกว้างของเซลล์แรกแล้วโทรวิธีการนับความสูงที่กำหนดไว้ในมือถือจะได้รับค่าที่เหมาะสมและส่งกลับมา

PS รหัสสำหรับการสร้างวัตถุเซลล์สามารถกำหนดในวิธีอื่นสำหรับวิธีการมอบหมายเซลล์มุมมองตารางที่แตกต่างกันในการโทร


-3

UITableView.automaticDimension สามารถตั้งค่าผ่านตัวสร้างส่วนต่อประสาน:

Xcode> สตอรี่บอร์ด> ตัวตรวจสอบขนาด

เซลล์มุมมองตาราง> ความสูงของแถว> อัตโนมัติ

สารวัตรขนาด


-4

อีกโซลูชัน iOs7 + iOs8 ใน Swift

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
    }
}

หมายเหตุ: systemLayoutSizeFittingSize ไม่ทำงานในกรณีของฉัน
djdance

ความสูงของเซลล์ไม่ถูกต้องcellForRowAtIndexPathเซลล์ยังไม่ได้วางในจุดนี้
Andrii Chernenko

ใน iOs7 เป็นค่าคงที่เช่นทำงานได้ คุณสามารถตั้งค่าในนอก cellForRowAtIndexPath หากคุณต้องการ
djdance
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.