วิธีการปรับขนาด superview เพื่อให้พอดีกับการแสดงย่อยทั้งหมดด้วย autolayout


142

ความเข้าใจของฉันเกี่ยวกับการชำระอัตโนมัติคือการใช้ขนาดของการกำกับดูแลและยึดตามข้อ จำกัด และขนาดที่แท้จริงที่จะคำนวณตำแหน่งของการสัมภาษณ์

มีวิธีย้อนกลับกระบวนการนี้หรือไม่? ฉันต้องการปรับขนาด superview บนพื้นฐานของข้อ จำกัด และขนาดที่แท้จริง วิธีที่ง่ายที่สุดในการบรรลุเป้าหมายนี้คืออะไร?

ฉันได้ดูการออกแบบใน Xcode UITableViewซึ่งผมใช้เป็นส่วนหัวสำหรับ มุมมองนี้มีป้ายกำกับและปุ่ม ขนาดของฉลากจะแตกต่างกันไปตามข้อมูล ขึ้นอยู่กับข้อ จำกัด ของฉลากกดปุ่มลงได้สำเร็จหรือหากมีข้อ จำกัด ระหว่างปุ่มและด้านล่างของการกำกับดูแลฉลากจะถูกบีบอัด

ฉันพบคำถามที่คล้ายกันสองสามข้อ แต่พวกเขาไม่มีคำตอบที่ดีและง่าย


28
คุณควรเลือกหนึ่งในคำตอบด้านล่างเพื่อให้แน่ใจว่า Tom Swifts ตอบคำถามของคุณ ผู้โพสต์ทั้งหมดใช้เวลาเป็นจำนวนมากเพื่อช่วยคุณในตอนนี้คุณควรทำส่วนของคุณและเลือกคำตอบที่คุณชอบที่สุด
David H

คำตอบ:


149

API ที่ถูกต้องในการใช้งานจะUIView systemLayoutSizeFittingSize:ผ่านอย่างใดอย่างหนึ่งหรือUILayoutFittingCompressedSizeUILayoutFittingExpandedSize

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

มีข้อควรพิจารณาเพิ่มเติมหากคุณมี UILabel หนึ่งรายการขึ้นไปในมุมมองของคุณที่เป็นหลายบรรทัด สำหรับสิ่งเหล่านี้มีความจำเป็นที่preferredMaxLayoutWidthจะต้องตั้งค่าคุณสมบัติอย่างถูกต้องเพื่อให้ฉลากมีความถูกต้องintrinsicContentSizeซึ่งจะใช้ในการsystemLayoutSizeFittingSize'sคำนวณ

แก้ไข: ตามคำขอเพิ่มตัวอย่างของการคำนวณความสูงสำหรับเซลล์มุมมองตาราง

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

อย่างที่ฉันได้กล่าวไว้ข้างต้นถ้าคุณใช้ multiline UILabelคุณจำเป็นต้องทำการซิงค์preferredMaxLayoutWidthกับความกว้างของฉลาก ฉันใช้UILabelคลาสย่อยที่กำหนดเองเพื่อทำสิ่งนี้:

@implementation TSLabel

- (void) layoutSubviews
{
    [super layoutSubviews];

    if ( self.numberOfLines == 0 )
    {
        if ( self.preferredMaxLayoutWidth != self.frame.size.width )
        {
            self.preferredMaxLayoutWidth = self.frame.size.width;
            [self setNeedsUpdateConstraints];
        }
    }
}

- (CGSize) intrinsicContentSize
{
    CGSize s = [super intrinsicContentSize];

    if ( self.numberOfLines == 0 )
    {
        // found out that sometimes intrinsicContentSize is 1pt too short!
        s.height += 1;
    }

    return s;
}

@end

นี่คือคลาสย่อย UITableViewController ที่วางแผนไว้ซึ่งแสดงให้เห็นถึง heightForRowAtIndexPath:

#import "TSTableViewController.h"
#import "TSTableViewCell.h"

@implementation TSTableViewController

- (NSString*) cellText
{
    return @"Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
}

#pragma mark - Table view data source

- (NSInteger) numberOfSectionsInTableView: (UITableView *) tableView
{
    return 1;
}

- (NSInteger) tableView: (UITableView *)tableView numberOfRowsInSection: (NSInteger) section
{
    return 1;
}

- (CGFloat) tableView: (UITableView *) tableView heightForRowAtIndexPath: (NSIndexPath *) indexPath
{
    static TSTableViewCell *sizingCell;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{

        sizingCell = (TSTableViewCell*)[tableView dequeueReusableCellWithIdentifier: @"TSTableViewCell"];
    });

    // configure the cell
    sizingCell.text = self.cellText;

    // force layout
    [sizingCell setNeedsLayout];
    [sizingCell layoutIfNeeded];

    // get the fitting size
    CGSize s = [sizingCell.contentView systemLayoutSizeFittingSize: UILayoutFittingCompressedSize];
    NSLog( @"fittingSize: %@", NSStringFromCGSize( s ));

    return s.height;
}

- (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath
{
    TSTableViewCell *cell = (TSTableViewCell*)[tableView dequeueReusableCellWithIdentifier: @"TSTableViewCell" ];

    cell.text = self.cellText;

    return cell;
}

@end

เซลล์ที่กำหนดเองง่าย ๆ :

#import "TSTableViewCell.h"
#import "TSLabel.h"

@implementation TSTableViewCell
{
    IBOutlet TSLabel* _label;
}

- (void) setText: (NSString *) text
{
    _label.text = text;
}

@end

และนี่คือภาพของข้อ จำกัด ที่กำหนดไว้ในกระดานเรื่องราว โปรดทราบว่าไม่มีข้อจำกัดความสูง / ความกว้างบนฉลาก - สิ่งเหล่านี้อนุมานจากฉลากintrinsicContentSize:

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


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

@rdelmar - แน่นอน ฉันเพิ่มตัวอย่างในคำตอบแล้ว
TomSwift

7
สิ่งนี้ไม่ได้ผลสำหรับฉันจนกว่าฉันจะเพิ่มข้อ จำกัด แนวตั้งสุดท้ายจากด้านล่างของเซลล์ไปที่ด้านล่างของมุมมองย่อยต่ำสุดในเซลล์ ดูเหมือนว่าข้อ จำกัด ในแนวตั้งจะต้องมีการเว้นวรรคแนวตั้งด้านบนและล่างระหว่างเซลล์และเนื้อหาเพื่อให้การคำนวณความสูงของเซลล์สำเร็จ
Eric Baker

1
ฉันต้องการเพียงเคล็ดลับเพิ่มเติมเพื่อให้ทำงานได้ และในที่สุดทิปของ @EricBaker ก็ถูกจับ ขอบคุณสำหรับการแบ่งปันผู้ชายคนนั้น ฉันมีขนาดที่ไม่เป็นศูนย์ในขณะนี้กับsizingCellหรือcontentViewขนาด
MkVal

1
ให้กับผู้ที่มีปัญหาไม่ได้รับความสูงที่เหมาะสมให้แน่ใจว่าคุณsizingCell's ความกว้างตรงกับtableViews กว้าง
MkVal

30

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

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

//
//  ViewController.m
//  AutoLayoutDynamicVerticalContainerHeight
//

#import "ViewController.h"

@interface ViewController ()
@property (strong, nonatomic) UIView *contentView;
@property (strong, nonatomic) UILabel *myLabel;
@property (strong, nonatomic) UILabel *myOtherLabel;
@end

@implementation ViewController

- (void)viewDidLoad
{
    // INVOKE SUPER
    [super viewDidLoad];

    // INIT ALL REQUIRED UI ELEMENTS
    self.contentView = [[UIView alloc] init];
    self.myLabel = [[UILabel alloc] init];
    self.myOtherLabel = [[UILabel alloc] init];
    NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(_contentView, _myLabel, _myOtherLabel);

    // TURN AUTO LAYOUT ON FOR EACH ONE OF THEM
    self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
    self.myLabel.translatesAutoresizingMaskIntoConstraints = NO;
    self.myOtherLabel.translatesAutoresizingMaskIntoConstraints = NO;

    // ESTABLISH VIEW HIERARCHY
    [self.view addSubview:self.contentView]; // View adds content view
    [self.contentView addSubview:self.myLabel]; // Content view adds my label (and all other UI... what's added here drives the container height (and width))
    [self.contentView addSubview:self.myOtherLabel];

    // LAYOUT

    // Layout CONTENT VIEW (Pinned to left, top. Note, it expects to get its vertical height (and horizontal width) dynamically based on whatever is placed within).
    // Note, if you don't want horizontal width to be driven by content, just pin left AND right to superview.
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|[_contentView]" options:0 metrics:0 views:viewsDictionary]]; // Only pinned to left, no horizontal width yet
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|[_contentView]" options:0 metrics:0 views:viewsDictionary]]; // Only pinned to top, no vertical height yet

    /* WHATEVER WE ADD NEXT NEEDS TO EXPLICITLY "PUSH OUT ON" THE CONTAINING CONTENT VIEW SO THAT OUR CONTENT DYNAMICALLY DETERMINES THE SIZE OF THE CONTAINING VIEW */
    // ^To me this is what's weird... but okay once you understand...

    // Layout MY LABEL (Anchor to upper left with default margin, width and height are dynamic based on text, font, etc (i.e. UILabel has an intrinsicContentSize))
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_myLabel]" options:0 metrics:0 views:viewsDictionary]];
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[_myLabel]" options:0 metrics:0 views:viewsDictionary]];

    // Layout MY OTHER LABEL (Anchored by vertical space to the sibling label that comes before it)
    // Note, this is the view that we are choosing to use to drive the height (and width) of our container...

    // The LAST "|" character is KEY, it's what drives the WIDTH of contentView (red color)
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[_myOtherLabel]-|" options:0 metrics:0 views:viewsDictionary]];

    // Again, the LAST "|" character is KEY, it's what drives the HEIGHT of contentView (red color)
    [self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[_myLabel]-[_myOtherLabel]-|" options:0 metrics:0 views:viewsDictionary]];

    // COLOR VIEWS
    self.view.backgroundColor = [UIColor purpleColor];
    self.contentView.backgroundColor = [UIColor redColor];
    self.myLabel.backgroundColor = [UIColor orangeColor];
    self.myOtherLabel.backgroundColor = [UIColor greenColor];

    // CONFIGURE VIEWS

    // Configure MY LABEL
    self.myLabel.text = @"HELLO WORLD\nLine 2\nLine 3, yo";
    self.myLabel.numberOfLines = 0; // Let it flow

    // Configure MY OTHER LABEL
    self.myOtherLabel.text = @"My OTHER label... This\nis the UI element I'm\narbitrarily choosing\nto drive the width and height\nof the container (the red view)";
    self.myOtherLabel.numberOfLines = 0;
    self.myOtherLabel.font = [UIFont systemFontOfSize:21];
}

@end

วิธีการปรับขนาด superview ให้พอดีกับการแสดงย่อยทั้งหมดด้วย autolayout.png


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

นี่คือเงิน! หนึ่งในตัวอย่าง VFL ที่กระชับกว่าที่ฉันเคยเห็น
Evan R

นี่คือสิ่งที่ฉันกำลังมองหา (ขอบคุณ @John) ดังนั้นฉันได้โพสต์เวอร์ชั่น Swift ที่นี่
James

1
"คุณต้องเลือกมุมมองย่อยที่จะผลักดันความสูงและ / หรือความกว้างของคอนเทนเนอร์ ... โดยทั่วไปสิ่งที่องค์ประกอบ UI ที่คุณวางไว้ที่มุมขวาล่างของ UI โดยรวมของคุณ" .. ฉันใช้ PureLayout และนี่คือกุญแจสำคัญสำหรับฉัน สำหรับมุมมองพาเรนต์หนึ่งมุมมองสำหรับเด็กหนึ่งมุมมองของเด็กนั่งอยู่ที่มุมขวาของเด็กที่ปักหมุดที่มุมล่างขวา ขอบคุณ!
Steven Elliott

1
การเลือกหนึ่งมุมมองเพื่อขับเคลื่อนความกว้างนั้นเป็นสิ่งที่ฉันไม่สามารถทำได้ บางครั้งมุมมองย่อยหนึ่งกว้างกว่าบางครั้งมุมมองย่อยอีกมุมหนึ่งก็กว้างกว่า ความคิดใด ๆ สำหรับกรณีนี้?
Henning

3

คุณสามารถทำได้โดยการสร้างข้อ จำกัด และเชื่อมต่อผ่านตัวสร้างส่วนต่อประสาน

ดูคำอธิบาย: Auto_Layout_Constraints_in_Interface_Builder

raywenderlich จุดเริ่มต้นอัตโนมัติเค้าโครง

AutolayoutPG บทความข้อ จำกัด พื้นฐาน

@interface ViewController : UIViewController {
    IBOutlet NSLayoutConstraint *leadingSpaceConstraint;
    IBOutlet NSLayoutConstraint *topSpaceConstraint;
}
@property (weak, nonatomic) IBOutlet NSLayoutConstraint *leadingSpaceConstraint;

เชื่อมต่อข้อ จำกัด ทางออกนี้ด้วยมุมมองย่อยของคุณ จำกัด หรือเชื่อมต่อมุมมองพิเศษ จำกัด ด้วยและตั้งค่าตามความต้องการของคุณเช่นนี้

 self.leadingSpaceConstraint.constant = 10.0;//whatever you want to assign

ฉันหวังว่านี่จะทำให้ชัดเจน


ดังนั้นจึงเป็นไปได้ที่จะกำหนดค่าข้อ จำกัด ที่สร้างขึ้นใน XCode จากซอร์สโค้ด แต่คำถามก็คือวิธีการกำหนดค่าให้ปรับขนาด superview
DAK

ใช่ @DAK โปรดตรวจสอบให้แน่ใจเกี่ยวกับรูปแบบการกำกับ วางข้อ จำกัด บน superview และสร้างความสูงเช่นกันดังนั้นเมื่อใดก็ตามที่ subview ของคุณเพิ่มขึ้นมันจะเพิ่มความสูงของข้อ จำกัด ของ superview โดยอัตโนมัติ
chandan

มันก็ใช้ได้กับฉันเช่นกัน ช่วยให้ฉันมีเซลล์ขนาดคงที่ (ความสูง 60px) ดังนั้นเมื่อมุมมองโหลดฉันตั้ง IBOutlet ความสูงของฉันให้เป็น 60 * x โดยที่ x คือจำนวนเซลล์ ท้ายที่สุดคุณอาจต้องการสิ่งนี้ในมุมมองเลื่อนเพื่อให้คุณเห็นทุกสิ่ง
Sandy Chapman

-1

ซึ่งสามารถทำได้สำหรับปกติsubviewภายในที่มีขนาดใหญ่แต่ก็ไม่สามารถทำงานได้โดยอัตโนมัติUIView headerViewsความสูงของheaderViewจะถูกกำหนดโดยสิ่งที่ส่งกลับโดยtableView:heightForHeaderInSection:เพื่อให้คุณมีการคำนวณheightบนพื้นฐานของheightของUILabelพื้นที่บวกสำหรับUIButtonและใด ๆpaddingที่คุณต้องการ คุณต้องทำสิ่งนี้:

-(CGFloat)tableView:(UITableView *)tableView 
          heightForHeaderInSection:(NSInteger)section {
    NSString *s = self.headeString[indexPath.section];
    CGSize size = [s sizeWithFont:[UIFont systemFontOfSize:17] 
                constrainedToSize:CGSizeMake(281, CGFLOAT_MAX)
                    lineBreakMode:NSLineBreakByWordWrapping];
    return size.height + 60;
}

นี่headerStringคือสตริงที่คุณต้องการเติมUILabelและหมายเลข 281 เป็นwidthของUILabel(ตามการตั้งค่าInterface Builder)


น่าเสียดายที่นี่ใช้งานไม่ได้ “ ขนาดเพื่อให้พอดีกับเนื้อหา” บน superview จะลบข้อ จำกัด ที่เชื่อมโยงปุ่มด้านล่างของปุ่มนั้นไปยัง superview ตามที่คุณคาดการณ์ไว้ เมื่อรันไทม์ฉลากจะถูกปรับขนาดและกดปุ่มลง แต่ผู้ควบคุมไม่ได้ปรับขนาดโดยอัตโนมัติ
DAK

@DAK ขออภัยปัญหาคือมุมมองของคุณเป็นส่วนหัวสำหรับมุมมองตาราง ฉันเข้าใจสถานการณ์ของคุณผิด ฉันคิดว่ามุมมองที่ล้อมรอบปุ่มและป้ายกำกับอยู่ในมุมมองอื่น แต่ดูเหมือนว่าคุณใช้เป็นส่วนหัว (แทนที่จะเป็นมุมมองภายในส่วนหัว) ดังนั้นคำตอบดั้งเดิมของฉันจะไม่ทำงาน ฉันเปลี่ยนคำตอบเป็นสิ่งที่ฉันคิดว่าควรจะได้ผล
rdelmar

นี่คล้ายกับการใช้งานปัจจุบันของฉัน แต่ฉันหวังว่าจะมีวิธีที่ดีกว่า ฉันต้องการหลีกเลี่ยงการอัปเดตรหัสนี้ทุกครั้งเมื่อฉันเปลี่ยนส่วนหัว
DAK

@ Dak, ขออภัยฉันไม่คิดว่าจะมีวิธีที่ดีกว่านั่นเป็นเพียงวิธีการดูตารางการทำงาน
rdelmar

โปรดดูคำตอบของฉัน "วิธีที่ดีกว่า" คือการใช้ UIView systemLayoutSizeFittingSize: ที่กล่าวว่าการแสดงอัตโนมัติเต็มรูปแบบในมุมมองหรือเซลล์เพียงเพื่อให้ได้ความสูงที่ต้องการในมุมมองตารางนั้นค่อนข้างแพง ฉันทำเพื่อเซลล์ที่ซับซ้อน แต่บางทีอาจเป็นทางเลือกที่ดีกว่าสำหรับวิธีแก้ปัญหาที่ง่ายกว่า
TomSwift
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.