Autolayout - ขนาดที่แท้จริงของ UIButton ไม่รวมชื่อเรื่อง


196

หากฉันมีการจัดเรียง UIButton โดยใช้การชำระอัตโนมัติโดยอัตโนมัติขนาดของมันจะปรับให้เข้ากับเนื้อหาของมัน

ถ้าฉันตั้งรูปภาพเป็นbutton.imageขนาด instrinsic อีกครั้งดูเหมือนว่าจะคำนึงถึงเรื่องนี้

อย่างไรก็ตามถ้าฉันปรับแต่งtitleEdgeInsetsปุ่มการจัดวางไม่ได้ทำเพื่อสิ่งนี้และจะตัดทอนชื่อปุ่มแทน

ฉันจะมั่นใจได้อย่างไรว่าความกว้างที่แท้จริงของปุ่มนั้นเป็นสิ่งที่ใส่เข้าไป?

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

แก้ไข:

ฉันใช้สิ่งต่อไปนี้:

[self.backButton setTitleEdgeInsets:UIEdgeInsetsMake(0, 5, 0, 0)];

เป้าหมายคือการเพิ่มการแยกระหว่างรูปภาพและข้อความ


3
คุณยื่นแบบนี้เป็นเรดาร์หรือไม่? ดูเหมือนว่าจะเป็นข้อบกพร่องในการคำนวณขนาดที่แท้จริงของ UIButton
Ryan Poolos

1
ฉันพร้อมที่จะยื่นเรดาร์ แต่จริงๆแล้วนี่น่าจะเป็นพฤติกรรมที่คาดหวัง นี่เป็นเอกสารเกี่ยวกับ* EdgeInsets ของ UIButtonคุณสมบัติ : "สิ่งที่ใส่เข้าไปที่คุณระบุจะถูกนำไปใช้กับรูปแบบชื่อเรื่องหลังจากรูปสี่เหลี่ยมผืนผ้านั้นมีขนาดที่พอดีกับข้อความของปุ่มดังนั้นค่าที่ใส่เข้าไปในเชิงบวกอาจจริง ๆ คลิปข้อความชื่อ [... ] ปุ่มไม่ใช้คุณสมบัตินี้เพื่อกำหนด intrinsicContentSize และ sizeThatFits :. "
Guillaume Algis

7
@ GuillaumeAlgis ฉันจะโต้แย้งว่าแม้ว่านี่จะเป็นพฤติกรรมที่ระบุไว้ แต่ก็ไม่ได้เป็นอย่างที่ทุกคนคาดหวังว่าจะเกิดขึ้นเมื่อใช้ autolayout ฉันได้ยื่นข้อบกพร่องและจะสนับสนุนให้ผู้อื่นยื่นข้อเสนอเช่นกัน
เมมมอน

หากคุณสามารถลิงก์ไปยังข้อผิดพลาดเรดาร์ที่นี่เราสามารถคลิกและ +1 มันได้หรือไม่
gprasant

1
จากtitleEdgeInsetเอกสาร: The insets you specify are applied to the title rectangle after that rectangle has been sized to fit the button’s text. Thus, positive inset values may actually clip the title text. ดังนั้นโดยการเพิ่มสิ่งที่ใส่เข้าไปคุณกำลังบังคับให้ปุ่มเพื่อคลิปข้อความอย่างแน่นอน
Marco Pappalardo

คำตอบ:


192

คุณสามารถแก้ปัญหานี้ได้โดยไม่ต้องแทนที่วิธีการใด ๆ หรือตั้งค่าข้อจำกัดความกว้างตามอำเภอใจ คุณสามารถทำได้ใน Interface Builder ดังต่อไปนี้

  • ความกว้างของปุ่มที่แท้จริงนั้นมาจากความกว้างของชื่อเรื่องบวกกับความกว้างของไอคอนบวกกับขอบของเนื้อหาด้านซ้ายและขวา

  • หากปุ่มมีทั้งรูปภาพและข้อความแสดงว่าพวกเขาอยู่ตรงกลางเป็นกลุ่มโดยไม่มีช่องว่างภายใน

  • หากคุณเพิ่มสิ่งที่ใส่เข้าไปทางซ้ายเนื้อหาจะถูกคำนวณโดยสัมพันธ์กับข้อความไม่ใช่ไอคอน + ข้อความ

  • หากคุณตั้งค่าภาพด้านซ้ายเป็นลบภาพจะถูกดึงออกไปทางซ้าย แต่ความกว้างของปุ่มโดยรวมไม่ได้รับผลกระทบ

  • หากคุณตั้งค่าสิ่งที่ใส่เข้าไปทางซ้ายของภาพเชิงลบเค้าโครงจริงจะใช้ครึ่งหนึ่งของค่านั้น ดังนั้นในการรับสิ่งที่ใส่เข้าไป -20 จุดซ้ายคุณต้องใช้ค่าสิ่งที่เหลือ -40 จุดซ้ายในตัวสร้างส่วนต่อประสาน

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

ค่าตัวอย่างบางส่วน:

// Produces a button with the layout:
// |-20-icon-10-text-20-|
// AutoLayout intrinsic width works as you'd desire.
button.contentEdgeInsets = UIEdgeInsetsMake(10, 30, 10, 20)
button.imageEdgeInsets = UIEdgeInsetsMake(0, -20, 0, 0)

เหตุใดเค้าโครงจริงจึงใช้ครึ่งหนึ่งของค่าสิ่งที่ใส่เข้าไปทางซ้าย ฉันพบปัญหาเดียวกัน!
Tony Lin

1
มันเป็นเรื่องที่ดีที่มีวิธีแก้ปัญหา แต่ฉันหวังว่านี้ไม่ได้ใช้เพื่อแสดงให้เห็นถึงพฤติกรรมแปลก ๆ UIButtonของ
funct7

205

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

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

ปรับปรุง : Xcode 7 มีข้อบกพร่องที่คุณไม่สามารถป้อนค่าลบในRightฟิลด์สิ่งที่ใส่เข้าไป แต่คุณสามารถใช้ตัวควบคุม stepper ติดกับมันเพื่อลดค่า (ขอบคุณสจวร์ต)

การทำเช่นนี้จะเพิ่มระยะห่าง 8pt ระหว่างภาพและชื่อและจะเพิ่มความกว้างที่แท้จริงของปุ่มด้วยจำนวนเดียวกัน แบบนี้:

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


2
ใช้ contentEdgeInsets (ซึ่งไม่ใช่บั๊กซี) เพื่อให้การชำระอัตโนมัติเพื่อเพิ่มความกว้างของปุ่ม และย้ายฉลากไปยังพื้นที่ว่างทางด้านขวา วิธีที่ชาญฉลาดในการแก้ไขข้อบกพร่องของสิ่งที่ใส่ขอบของหัวเรื่อง
ugur

7
เคล็ดลับนี้ใช้งานไม่ได้อีกต่อไป เครื่องมือสร้างอินเตอร์เฟสไม่ยอมรับค่าลบในRightฟิลด์อีกต่อไป
Joris Mans

7
@JorisMans คุณไม่สามารถพิมพ์ค่าลบใน แต่ก็ใช้งานได้สำหรับฉันโดยใช้ตัวควบคุม stepper ทางด้านขวาของฟิลด์ข้อความเพื่อก้าวลงไปที่ค่าลบที่ต้องการ ... ไปคิด!
สจวร์ต

3
นี่ควรเป็นคำตอบแรกทำไมมันลงที่นี่? ฉันได้พยายามอีก 5 ก่อนที่จะหานี้ ...
ลอร์ด Zsolt

2
ฉันทำถูกต้องของเนื้อหาที่ใส่เข้าไป 16 เพื่อจัดข้อความให้อยู่กึ่งกลางใน UIButton
coolcool1994

96

เหตุใดจึงไม่แทนที่intrinsicContentSizeวิธีการบน UIView ตัวอย่างเช่น:

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

    return CGSizeMake(s.width + self.titleEdgeInsets.left + self.titleEdgeInsets.right,
                      s.height + self.titleEdgeInsets.top + self.titleEdgeInsets.bottom);
}

สิ่งนี้ควรบอกระบบ autolayout ว่าควรเพิ่มขนาดของปุ่มเพื่ออนุญาตการแทรกและแสดงข้อความเต็ม ฉันไม่ได้อยู่ที่คอมพิวเตอร์ของตัวเองดังนั้นฉันจึงไม่ได้ทดสอบสิ่งนี้


1
ปุ่มไม่ควรจะถูกแทนที่เท่าที่ฉันรู้ ปัญหาคือทุกประเภทของปุ่มถูกนำไปใช้โดยคลาสย่อยที่แตกต่างกัน
Sulthan

2
intrinsicContentSizeเป็นวิธีการใน UIView ไม่ใช่ UIButton ดังนั้นคุณจะไม่สับสนในวิธีการของ UIButton Apple ไม่คิดว่าจะมีปัญหา: "การเอาชนะวิธีนี้ทำให้มุมมองที่กำหนดเองสามารถสื่อสารกับระบบเลย์เอาต์ขนาดที่ต้องการให้เป็นไปตามเนื้อหาของมัน" และ OP ไม่ได้พูดอะไรเกี่ยวกับปุ่มต่าง ๆ เพียงปุ่มเดียว
Maarten

1
มันใช้งานได้จริงและเป็นทางออกที่ฉันไปด้วย intrinsicContentSizeแน่นอนเป็นวิธีการใน UIView และ UIButton เป็น subclass ของ UIView ดังนั้นแน่นอนคุณสามารถแทนที่วิธีนี้ ไม่มีอะไรในเอกสารของ Apple ที่บอกว่าคุณไม่ควรทำ เพียงสร้างคลาสย่อย UIButton โดยใช้วิธีการแทนที่ของ Maarten และเปลี่ยน UIButton ของคุณใน Interface Builder ให้เป็นประเภท YourUIButtonSubclass และจะทำงานได้อย่างสมบูรณ์แบบ
n8tr

37
ดูเหมือนว่าฉันintrinsicContentSizeสำหรับ UIButton ควรเพิ่มใน titleEdgeInsets ฉันจะยื่นข้อผิดพลาดกับ Apple
progrmr

6
ฉันเห็นด้วยและเหมือนกันสำหรับ imageEdgeInsets
Ricardo Sanchez-Saez

87

คุณไม่ได้ระบุวิธีการตั้งค่า insets ดังนั้นฉันเดาว่าคุณกำลังใช้ titleEdgeInsets เพราะฉันเห็นเอฟเฟกต์แบบเดียวกันกับที่คุณได้รับ หากฉันใช้ contentEdgeInsets แทนจะทำงานได้อย่างถูกต้อง

- (IBAction)ChangeTitle:(UIButton *)sender {
    self.button.contentEdgeInsets = UIEdgeInsetsMake(0,20,0,20);
    [self.button setTitle:@"Long Long Title" forState:UIControlStateNormal];
}

แน่นอนฉันใช้ titleEdgeInsets ฉันต้องการให้ระยะห่างชื่อจากภาพไม่ใช่ภาพจากขอบของปุ่ม บางทีฉันควรใช้รูปที่มีช่องว่างภายในหรือไม่? ดูเหมือนว่าแฮ็ค
Ben Packard

มันทำงานได้อย่างสมบูรณ์เมื่อรวมกับการชำระอัตโนมัติขอขอบคุณ!
Cal S

3
นี่เป็นทางออกที่ดีกว่าเพราะจะทำสิ่งที่คุณต้องการโดยไม่ต้องแตะต้อง intrinsicContentSize
RyJ

28
นี่ไม่ได้ตอบคำถามเมื่อใช้ภาพและจำเป็นต้องปรับสิ่งที่ใส่เข้าไประหว่างภาพและชื่อ!
Brody Robertson

24

และสำหรับสวิฟต์ก็ทำงานอย่างนี้:

extension UIButton {
    override open var intrinsicContentSize: CGSize {
        let intrinsicContentSize = super.intrinsicContentSize

        let adjustedWidth = intrinsicContentSize.width + titleEdgeInsets.left + titleEdgeInsets.right
        let adjustedHeight = intrinsicContentSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom

        return CGSize(width: adjustedWidth, height: adjustedHeight)
    }
}

รักเธออย่างรวดเร็ว


1
แม้ว่าคุณไม่ควรทำเช่นนั้นจะดีกว่า subclass ในกรณีนี้เนื่องจากเอกสารของ Apple ระบุไว้อย่างชัดเจนว่าขนาดที่แท้จริงไม่รวม titleEdgeInsets ในการคำนวณและโดยใช้ส่วนขยายที่คุณละเมิดไม่ใช่เพียงแค่ความคาดหวังของ Apple แต่นักพัฒนาอื่น ๆ ทั้งหมดที่อ่าน เอกสาร
ไซเรน

18

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

UIButton* myButton = [[UIButton alloc] init];
// setup some autolayout constraints here
myButton.titleEdgeInsets = UIEdgeInsetsMake(-desiredBottomPadding,
                                            -desiredRightPadding,
                                            -desiredTopPadding,
                                            -desiredLeftPadding);

เมื่อรวมกับข้อ จำกัด การกดปุ่มอัตโนมัติที่ถูกต้องคุณจะพบกับปุ่มปรับขนาดอัตโนมัติที่มีภาพและข้อความ! เห็นด้านล่างด้วยการdesiredLeftPaddingตั้งค่าเป็น 10

ปุ่มพร้อมรูปภาพและข้อความสั้น

ปุ่มพร้อมรูปภาพและข้อความยาว

คุณจะเห็นได้ว่ากรอบจริงของปุ่มนั้นไม่ได้ล้อมรอบฉลาก (เนื่องจากป้ายเลื่อนไปทางขวา 10 จุดนอกขอบเขต) แต่เราได้ระยะห่างระหว่างจุดกับข้อความถึง 10 จุด


1
นี่เป็นวิธีแก้ปัญหาที่ฉันใช้เนื่องจากไม่จำเป็นต้องใช้คลาสย่อย จะไม่ทำงานหากปุ่มของคุณมีพื้นหลัง แต่โดยปกติจะไม่เกิดปัญหากับ iOS 7
José Manuel Sánchez

สิ่งนี้จะทำงานกับภาพพื้นหลังหากคุณตั้งค่าออฟเซ็ตเนื้อหาของปุ่ม (ค่าบวก> = ชื่อแทรก)
Ben Flynn

9

สำหรับSwift 3ตามคำตอบของpegpeg :

extension UIButton {

    override open var intrinsicContentSize: CGSize {

        let intrinsicContentSize = super.intrinsicContentSize

        let adjustedWidth = intrinsicContentSize.width + titleEdgeInsets.left + titleEdgeInsets.right
        let adjustedHeight = intrinsicContentSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom

        return CGSize(width: adjustedWidth, height: adjustedHeight)

    }

}

สวัสดี. ฉันต้องการใช้ปุ่มส่วนขยายที่กำหนดเองใน interfacebuilder ช่วยด้วย
kemdo

6

ทั้งหมดที่กล่าวมาไม่สามารถใช้งานกับiOS 9+สิ่งที่ฉันทำคือ:

  • เพิ่มข้อจำกัดความกว้าง (สำหรับความกว้างต่ำสุดเมื่อปุ่มไม่มีข้อความใด ๆ ปุ่มจะปรับขนาดอัตโนมัติหากมีข้อความ)
  • ตั้งค่าความสัมพันธ์กับมากกว่าหรือเท่ากับ

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

ตอนนี้เพื่อเพิ่มเส้นขอบรอบ ๆ ปุ่มเพียงใช้วิธีการ:

button.contentEdgeInsets = UIEdgeInsetsMake(0,20,0,20);

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

เพราะคุณกำหนดความกว้างขั้นต่ำ แนวคิดทั้งหมดเกี่ยวกับการชำระอัตโนมัติจะทำเสร็จแล้วโดยไม่ตั้งค่าความกว้าง (น้อยที่สุด) อย่างชัดเจน
Joris Mans

มันไม่ได้เกี่ยวกับความกว้างคุณสามารถตั้งค่าความกว้างถึง 1 ถ้าคุณต้องการ แต่ความต้องการ autolayout ที่จะรู้ว่าความกว้างสามารถเท่ากันหรืออื่น ๆ อีกมากมาย ฉันปรับปรุงคำตอบของฉัน
Oritm

คุณไม่จำเป็นต้องมีข้อ จำกัด ด้านความกว้างเลย contentEdgeInset คือคีย์โครงร่างอัตโนมัติจากนั้นใช้ขนาดนั้นสำหรับเนื้อหาภายใน
Chris Conover

5

ฉันต้องการเพิ่มช่องว่าง 5pt ระหว่างไอคอน UIButton และป้ายกำกับ นี่คือวิธีที่ฉันได้รับ:

UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeCustom];
// more button config etc
infoButton.contentEdgeInsets = UIEdgeInsetsMake(0, 0, 0, 5);
infoButton.titleEdgeInsets = UIEdgeInsetsMake(0, 5, 0, -5);

วิธี contentEdgeInsets, titleEdgeInsets และ imageEdgeInsets เกี่ยวข้องกันต้องให้เล็กน้อยและรับจากแต่ละสิ่งที่ใส่เข้าไป ดังนั้นหากคุณเพิ่มสิ่งที่ใส่เข้าไปทางด้านซ้ายของชื่อคุณจะต้องเพิ่มสิ่งที่ใส่เข้าไปทางด้านขวาและเพิ่มช่องว่าง (ผ่านทางส่วนที่เป็นบวก) ทางด้านขวาของเนื้อหา

โดยการเพิ่มส่วนแทรกเนื้อหาที่ถูกต้องเพื่อให้ตรงกับการเปลี่ยนแปลงของชื่อแทรกข้อความของฉันไม่ได้อยู่นอกขอบเขตของปุ่ม


3

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

ภาพหน้าจอของเครื่องมือสร้างอินเตอร์เฟส


1
ใช่เป็นคำตอบนี้จะอธิบายถึงเหตุผลที่มันทำงานเป็นเพราะคุณกำลังปรับขอบ: เนื้อหาที่นี่แทนขอบ: ชื่อหรือขอบ: ภาพ
smileyborg

1

วิธีแก้ปัญหาที่ฉันใช้คือการเพิ่มข้อ จำกัด ความกว้างของปุ่ม หลังจากที่ตั้งค่าข้อความของคุณแล้วให้อัปเดตข้อ จำกัด ความกว้างดังนี้:

self.buttonWidthConstraint.constant = self.shareButton.intrinsicContentSize.width + 8;

ที่ 8 คือสิ่งที่คุณใส่เข้าไป


buttonWidthConstraint คืออะไร
Alexey Golikov


1
นี่ไม่ใช่โซลูชันที่ยอดเยี่ยมเพราะหากขนาดเนื้อหาที่แท้จริงของปุ่มเปลี่ยนคุณจะต้องอัปเดตconstantข้อ จำกัดด้วยตนเองเป็นค่าใหม่ ... และรู้ว่าเมื่อใดขนาดของเนื้อหาที่แท้จริงของปุ่มเปลี่ยนเป็นเรื่องยาก โดยไม่ต้อง subclassing ปุ่ม
smileyborg

Ayup ฉันไม่ใช้วิธีนี้อีกต่อไป ประหลาดใจว่ามันสมควรได้รับการลงคะแนน แต่¯_ (ツ) _ / ¯
Bob Spryn

การโทรไปยังsetNeedsUpdateConstraintsสามารถ "ด้วยตนเอง" ได้หลังจากอัปเดตชื่อปุ่มหรือรูปภาพ จากนั้นคุณสามารถแทนที่updateConstraintsและคำนวณbuttonWidthConstraintค่าคงที่ใหม่ได้จากที่นั่น นี่ไม่ใช่วิธีการที่ดีที่สุด แต่ก็ใช้ได้ดีพอสำหรับฉัน YMMV;)
Olivier

1

การเรียก sizeToFit () ทำให้แน่ใจว่า contentEdgeInset มีผล

extension UIButton {

   open override func draw(_ rect: CGRect) { 
       self.contentEdgeInsets = UIEdgeInsets(top: 10, left: 40, bottom: 10, right: 40)
       self.sizeToFit()
   }
}

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