การจัดแนวข้อความและรูปภาพบน UIButton ด้วย imageEdgeInsets และ titleEdgeInsets


250

ฉันต้องการวางไอคอนด้านซ้ายของข้อความสองบรรทัดเพื่อให้มีพื้นที่ว่างประมาณ 2-3 พิกเซลระหว่างรูปภาพและจุดเริ่มต้นของข้อความ ตัวควบคุมอยู่ตรงกลางในแนวนอน (ตั้งค่าผ่านเครื่องมือสร้างส่วนติดต่อ)

ปุ่มจะคล้ายกับสิ่งนี้:

|                  |
|[Image] Add To    |
|        Favorites |

ฉันกำลังพยายามกำหนดค่านี้ด้วย contentEdgeInset, imageEdgeInsets และ titleEdgeInsets เพื่อประโยชน์ ฉันเข้าใจว่าค่าลบขยายขอบขณะที่ค่าบวกย่อตัวเพื่อย้ายเข้าใกล้ศูนย์กลาง

ฉันเหนื่อย:

[button setTitleEdgeInsets:UIEdgeInsetsMake(0, -image.size.width, 0, 0)];
[button setImageEdgeInsets:UIEdgeInsetsMake(0, button.titleLabel.bounds.size.width, 0, 0)];

แต่มันไม่แสดงอย่างถูกต้อง ฉันได้รับการปรับแต่งค่า แต่จากการพูด -5 ถึง -10 บนค่าสิ่งที่ใส่เข้าไปทางซ้ายดูเหมือนจะไม่ย้ายไปในลักษณะที่คาดไว้ -10 จะดักฟังข้อความไปทางซ้ายดังนั้นฉันคาดว่า -5 จะวิ่งไปทางซ้ายครึ่งทางจากด้านซ้าย แต่ไม่ได้

ตรรกะที่อยู่เบื้องหลังสิ่งที่ใส่เข้าไปคืออะไร? ฉันไม่คุ้นเคยกับตำแหน่งรูปภาพและคำศัพท์ที่เกี่ยวข้อง

ฉันใช้คำถาม SO นี้เป็นข้อมูลอ้างอิง แต่มีบางอย่างเกี่ยวกับค่านิยมของฉันที่ไม่ถูกต้อง UIButton: วิธีจัดรูปภาพและข้อความให้อยู่กึ่งกลางโดยใช้ imageEdgeInsets และ titleEdgeInsets

คำตอบ:


392

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

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

CGFloat spacing = 10; // the amount of spacing to appear between image and title
tabBtn.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, spacing);
tabBtn.titleEdgeInsets = UIEdgeInsetsMake(0, spacing, 0, 0);

ฉันยังเปลี่ยนเป็นหมวดหมู่สำหรับ UIButton ดังนั้นมันจะใช้งานง่าย:

UIButton + Position.h

@interface UIButton(ImageTitleCentering)

-(void) centerButtonAndImageWithSpacing:(CGFloat)spacing;

@end

UIButton + Position.m

@implementation UIButton(ImageTitleCentering)

-(void) centerButtonAndImageWithSpacing:(CGFloat)spacing {
    self.imageEdgeInsets = UIEdgeInsetsMake(0, 0, 0, spacing);
    self.titleEdgeInsets = UIEdgeInsetsMake(0, spacing, 0, 0);
}

@end

ดังนั้นตอนนี้สิ่งที่ฉันต้องทำคือ:

[button centerButtonAndImageWithSpacing:10];

และฉันได้สิ่งที่ฉันต้องการทุกครั้ง ไม่ยุ่งกับขอบแทรกด้วยตนเองอีกต่อไป

แก้ไข: การสลับรูปภาพและข้อความ

ตอบกลับไปที่ @Javal ในความคิดเห็น

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

[self.view layoutIfNeeded];
CGFloat flippedSpacing = -(desiredSpacing + button.currentImage.size.width + button.titleLabel.frame.size.width);
[button centerButtonAndImageWithSpacing:flippedSpacing];

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


หากฉันมีชื่อเรื่องที่แตกต่างกันสำหรับเรื่องปกติและไฮไลต์ฉันจะจัดตำแหน่งใหม่ได้อย่างไรเมื่อผู้ใช้ไฮไลต์และยกเลิกการเน้นปุ่ม
user102008

@ user102008 ชื่อที่แตกต่างกันโดยสิ้นเชิง? หรือเพียงแค่สีที่ต่างกัน การวางตำแหน่งไม่ควรเปลี่ยนถ้าคุณกำลังใช้หรือแม้กระทั่ง[UIButton setTitleColor:forState:] [UIButton setTitle:forState:]
Kekoa

5
คุณจะแก้ไข [ขนาดปุ่มเพื่อฟิต] ได้อย่างไรเพื่อให้มีขนาดที่เหมาะสม?
RonLugge

@RonLugge ฉันไม่แน่ใจว่าทำไมคุณถึงต้องการ sizeToFit ดังนั้นฉันจึงไม่สามารถตอบคำถามของคุณได้ มันทำงานได้ดีสำหรับฉันโดยไม่ต้องใช้ sizeToFit
Kekoa

ฉันต้องการ sizeToFit เพราะปุ่ม / ข้อความเป็นแบบไดนามิกดังนั้นฉันจึงต้องปรับขนาดปุ่มให้พอดีกับฉลาก (ที่ผู้ใช้กำหนด) ปัญหาคือมันไม่ได้ชดเชยพื้นที่เพิ่ม ฉันปิดทับมันและเพิ่มความกว้างของเฟรมด้วยตนเอง 10
RonLugge

397

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

คำตอบของ Keko นั้นยอดเยี่ยม แต่เนื่องจาก RonLugge กล่าวถึงมันสามารถทำให้ปุ่มไม่เคารพอีกต่อไปsizeToFitหรือที่สำคัญกว่านั้นสามารถทำให้ปุ่มเพื่อคลิปเนื้อหาเมื่อมันมีขนาดที่แท้จริง อ๊ะ!

ก่อนอื่น

คำอธิบายสั้น ๆ เกี่ยวกับวิธีที่ฉันเชื่อimageEdgeInsetsและtitleEdgeInsetsทำงาน:

เอกสารสำหรับimageEdgeInsetsมีดังต่อไปนี้จะพูดในส่วน:

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

ฉันเชื่อว่าเอกสารนี้เขียนขึ้นโดยนึกว่าปุ่มนั้นไม่มีชื่อ แต่เป็นภาพ มันทำให้รู้สึกถึงความคิดในวิธีนี้มากขึ้นและประพฤติตามUIEdgeInsetsปกติ โดยทั่วไปกรอบของภาพ (หรือชื่อเรื่องด้วยtitleEdgeInsets) จะถูกย้ายเข้าด้านในสำหรับส่วนที่เป็นบวกและด้านนอกสำหรับส่วนที่เป็นลบ

ตกลงอะไร

ฉันไปถึงที่นั่น! นี่คือสิ่งที่คุณมีตามค่าเริ่มต้นการตั้งค่ารูปภาพและชื่อ (เส้นขอบปุ่มเป็นสีเขียวเพื่อแสดงว่ามันอยู่ที่ไหน):

ภาพเริ่มต้น;  ไม่มีช่องว่างระหว่างชื่อเรื่องและรูปภาพ

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

@implementation UIButton(ImageTitleCentering)

- (void)centerButtonAndImageWithSpacing:(CGFloat)spacing {
    CGFloat insetAmount = spacing / 2.0;
    self.imageEdgeInsets = UIEdgeInsetsMake(0, -insetAmount, 0, insetAmount);
    self.titleEdgeInsets = UIEdgeInsetsMake(0, insetAmount, 0, -insetAmount);
}

@end

แต่เดี๋ยวก่อนคุณพูดเมื่อฉันทำอย่างนั้นฉันจะได้สิ่งนี้:

การเว้นวรรคดี แต่รูปภาพและชื่อเรื่องอยู่นอกกรอบของมุมมอง

โอ้ใช่! ฉันลืมเอกสารเตือนฉันเกี่ยวกับเรื่องนี้ พวกเขากล่าวว่าในส่วน:

คุณสมบัตินี้ใช้สำหรับวางตำแหน่งภาพในระหว่างการจัดวางเท่านั้น ปุ่มไม่ได้ใช้คุณสมบัตินี้ในการตรวจสอบและintrinsicContentSizesizeThatFits:

แต่มีเป็นcontentEdgeInsetsคุณสมบัติที่สามารถช่วยและที่ เอกสารสำหรับการพูดในส่วนที่:

ปุ่มที่ใช้คุณสมบัตินี้ในการตรวจสอบและintrinsicContentSizesizeThatFits:

นั่นฟังดูดี ดังนั้นเรามาปรับแต่งหมวดหมู่อีกครั้ง:

@implementation UIButton(ImageTitleCentering)

- (void)centerButtonAndImageWithSpacing:(CGFloat)spacing {
    CGFloat insetAmount = spacing / 2.0;
    self.imageEdgeInsets = UIEdgeInsetsMake(0, -insetAmount, 0, insetAmount);
    self.titleEdgeInsets = UIEdgeInsetsMake(0, insetAmount, 0, -insetAmount);
    self.contentEdgeInsets = UIEdgeInsetsMake(0, insetAmount, 0, insetAmount);
}

@end

แล้วคุณจะได้อะไร

ระยะห่างและเฟรมถูกต้องแล้ว

ดูเหมือนจะเป็นผู้ชนะสำหรับฉัน


ทำงานในสวิฟท์และไม่อยากคิดอะไรเลยเหรอ? นี่คือส่วนขยายรุ่นสุดท้ายใน Swift:

extension UIButton {
    func centerTextAndImage(spacing: CGFloat) {
        let insetAmount = spacing / 2
        imageEdgeInsets = UIEdgeInsets(top: 0, left: -insetAmount, bottom: 0, right: insetAmount)
        titleEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: -insetAmount)
        contentEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: insetAmount)
    }
}

25
ช่างเป็นคำตอบที่ยอดเยี่ยม! ใช่สายไปงานปาร์ตี้ไม่กี่ปี แต่สิ่งนี้แก้ไขปัญหาของintrinsicContentSizeความไม่ถูกต้องซึ่งเป็นสิ่งสำคัญมากในวันที่จัดเลย์เอาต์อัตโนมัติเหล่านี้นับตั้งแต่ได้รับคำตอบดั้งเดิม
Acey

2
และถ้าคุณต้องการระยะห่างเท่ากันระหว่างปุ่มด้านนอกกับภาพและป้ายกำกับให้เพิ่มspacingค่าของ self.contentEdgeInsets ในแต่ละค่าทั้งสี่ดังนี้:self.contentEdgeInsets = UIEdgeInsetsMake(spacing, spacing + insetAmount, spacing, spacing + insetAmount);
Erik van der Neut

1
คำตอบที่ดีน่าเสียดายที่มันไม่ได้ผลค่อนข้างดีเมื่อจัดแนวภาพให้ถูกต้อง
jlpiedrahita

2
คำตอบที่สมบูรณ์แบบเกือบ! สิ่งเดียวที่ขาดหายไปคือสิ่งที่แทรกของรูปภาพและหัวเรื่องควรจะต้องกลับด้านเมื่อทำงานบนอินเตอร์เฟซจากขวาไปซ้าย
jeeeyul

วิธีการตั้งค่าอัตราส่วนภาพเป็น 1 ต่อ 1
Yestay Muratov


38

นอกจากนี้หากคุณต้องการทำสิ่งที่คล้ายกัน

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

คุณต้องการ

1. ตั้งแนวนอนและแนวตั้งสำหรับปุ่มเพื่อ

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

  1. ค้นหาค่าที่ต้องการทั้งหมดและตั้งค่า UIImageEdgeInsets

            CGSize buttonSize = button.frame.size;
            NSString *buttonTitle = button.titleLabel.text;
            CGSize titleSize = [buttonTitle sizeWithAttributes:@{ NSFontAttributeName : [UIFont camFontZonaProBoldWithSize:12.f] }];
            UIImage *buttonImage = button.imageView.image;
            CGSize buttonImageSize = buttonImage.size;
    
            CGFloat offsetBetweenImageAndText = 10; //vertical space between image and text
    
            [button setImageEdgeInsets:UIEdgeInsetsMake((buttonSize.height - (titleSize.height + buttonImageSize.height)) / 2 - offsetBetweenImageAndText,
                                                        (buttonSize.width - buttonImageSize.width) / 2,
                                                        0,0)];                
            [button setTitleEdgeInsets:UIEdgeInsetsMake((buttonSize.height - (titleSize.height + buttonImageSize.height)) / 2 + buttonImageSize.height + offsetBetweenImageAndText,
                                                        titleSize.width + [button imageEdgeInsets].left > buttonSize.width ? -buttonImage.size.width  +  (buttonSize.width - titleSize.width) / 2 : (buttonSize.width - titleSize.width) / 2 - buttonImage.size.width,
                                                        0,0)];

ปุ่มนี้จะจัดเรียงหัวเรื่องและรูปภาพของคุณบนปุ่ม

นอกจากนี้โปรดทราบการปรับปรุงนี้ในแต่ละ relayout


รวดเร็ว

import UIKit

extension UIButton {
    // MARK: - UIButton+Aligment

    func alignContentVerticallyByCenter(offset:CGFloat = 10) {
        let buttonSize = frame.size

        if let titleLabel = titleLabel,
            let imageView = imageView {

            if let buttonTitle = titleLabel.text,
                let image = imageView.image {
                let titleString:NSString = NSString(string: buttonTitle)
                let titleSize = titleString.sizeWithAttributes([
                    NSFontAttributeName : titleLabel.font
                    ])
                let buttonImageSize = image.size

                let topImageOffset = (buttonSize.height - (titleSize.height + buttonImageSize.height + offset)) / 2
                let leftImageOffset = (buttonSize.width - buttonImageSize.width) / 2
                imageEdgeInsets = UIEdgeInsetsMake(topImageOffset,
                                                   leftImageOffset,
                                                   0,0)

                let titleTopOffset = topImageOffset + offset + buttonImageSize.height
                let leftTitleOffset = (buttonSize.width - titleSize.width) / 2 - image.size.width

                titleEdgeInsets = UIEdgeInsetsMake(titleTopOffset,
                                                   leftTitleOffset,
                                                   0,0)
            }
        }
    }
}

29

คุณสามารถหลีกเลี่ยงปัญหามากโดยใช้สิ่งนี้ -

myButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;   
myButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;

วิธีนี้จะจัดแนวเนื้อหาทั้งหมดของคุณโดยอัตโนมัติไปทางซ้าย (หรือทุกที่ที่คุณต้องการ)

สวิฟท์ 3:

myButton.contentHorizontalAlignment = UIControlContentHorizontalAlignment.left;   
myButton.contentVerticalAlignment = UIControlContentVerticalAlignment.center;

myButton.contentVerticalAlignment = UIControlContentVerticalAlignment.center; // Typo แก้ไข
Naishta

25

ในXcode 8.0คุณสามารถทำได้โดยเปลี่ยนinsetsขนาดตัวตรวจสอบ

เลือก UIButton -> Attribute Inspector -> ไปที่ขนาดผู้ตรวจสอบและแก้ไขเนื้อหารูปภาพและชื่อเรื่อง

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

และถ้าคุณต้องการเปลี่ยนภาพทางด้านขวาคุณสามารถเปลี่ยนคุณสมบัติความหมายเป็นForce Right-to-leftในตัวตรวจสอบคุณสมบัติ

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


ในรูปปุ่มกรณีของฉันไม่เคยย้ายไปทางด้านขวาของข้อความด้วย xcode 10? คุณช่วยได้ไหม
Satish Mavani

สวัสดี Satish นี่ยังทำงานได้ดีกับ xcode 10. หวังว่าคุณจะตั้งค่าภาพไม่ใช่ภาพพื้นหลังและคุณยังสามารถเปลี่ยนภาพแมลงโดยใช้ตัวตรวจสอบขนาด
Sahil

18

ฉันไปปาร์ตี้สายเล็ก ๆ น้อย ๆ เช่นนี้ แต่ฉันคิดว่าฉันมีสิ่งที่มีประโยชน์ในการเพิ่ม: o)

ฉันสร้างUIButtonคลาสย่อยที่มีวัตถุประสงค์เพื่อให้สามารถเลือกตำแหน่งที่ภาพของปุ่มเป็นเลย์เอาต์ได้ทั้งแนวตั้งหรือแนวนอน

หมายความว่าคุณสามารถสร้างปุ่มประเภทนี้ได้: ปุ่มชนิดต่าง ๆ

นี่คือรายละเอียดเกี่ยวกับวิธีสร้างปุ่มเหล่านี้ด้วยคลาสของฉัน:

func makeButton (imageVerticalAlignment:LayoutableButton.VerticalAlignment, imageHorizontalAlignment:LayoutableButton.HorizontalAlignment, title:String) -> LayoutableButton {
    let button = LayoutableButton ()

    button.imageVerticalAlignment = imageVerticalAlignment
    button.imageHorizontalAlignment = imageHorizontalAlignment

    button.setTitle(title, for: .normal)

    // add image, border, ...

    return button
}

let button1 = makeButton(imageVerticalAlignment: .center, imageHorizontalAlignment: .left, title: "button1")
let button2 = makeButton(imageVerticalAlignment: .center, imageHorizontalAlignment: .right, title: "button2")
let button3 = makeButton(imageVerticalAlignment: .top, imageHorizontalAlignment: .center, title: "button3")
let button4 = makeButton(imageVerticalAlignment: .bottom, imageHorizontalAlignment: .center, title: "button4")
let button5 = makeButton(imageVerticalAlignment: .bottom, imageHorizontalAlignment: .center, title: "button5")
button5.contentEdgeInsets = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10)

ต้องการทำเช่นนั้นฉันเพิ่ม 2 คุณลักษณะ: และimageVerticalAlignment imageHorizontalAlignmentแน่นอนถ้าปุ่มของคุณมีเพียงรูปภาพหรือชื่อ ... อย่าใช้คลาสนี้เลย!

ฉันยังเพิ่มแอตทริบิวต์ชื่อimageToTitleSpacingที่ให้คุณปรับช่องว่างระหว่างชื่อเรื่องและรูปภาพ

ชั้นนี้พยายามที่ดีที่สุดของเขาจะเข้ากันได้ถ้าคุณต้องการที่จะใช้imageEdgeInsets, titleEdgeInsetsและcontentEdgeInsetsตรงหรือใน combinaison กับคุณลักษณะรูปแบบใหม่

ตามที่ @ravron อธิบายเราฉันพยายามอย่างดีที่สุดเพื่อทำให้ขอบเนื้อหาของปุ่มถูกต้อง (อย่างที่คุณเห็นด้วยเส้นขอบสีแดง)

คุณยังสามารถใช้ในเครื่องมือสร้างส่วนต่อประสาน:

  1. สร้าง UIButton
  2. เปลี่ยนคลาสปุ่ม
  3. ปรับแอททริบิวต์ที่สามารถจัดวางได้โดยใช้ "กึ่งกลาง", "ด้านบน", "ด้านล่าง", "ซ้าย" หรือ "ขวา" คุณสมบัติของปุ่ม

นี่คือรหัส ( ส่วนสำคัญ ):

@IBDesignable
class LayoutableButton: UIButton {

    enum VerticalAlignment : String {
        case center, top, bottom, unset
    }


    enum HorizontalAlignment : String {
        case center, left, right, unset
    }


    @IBInspectable
    var imageToTitleSpacing: CGFloat = 8.0 {
        didSet {
            setNeedsLayout()
        }
    }


    var imageVerticalAlignment: VerticalAlignment = .unset {
        didSet {
            setNeedsLayout()
        }
    }

    var imageHorizontalAlignment: HorizontalAlignment = .unset {
        didSet {
            setNeedsLayout()
        }
    }

    @available(*, unavailable, message: "This property is reserved for Interface Builder. Use 'imageVerticalAlignment' instead.")
    @IBInspectable
    var imageVerticalAlignmentName: String {
        get {
            return imageVerticalAlignment.rawValue
        }
        set {
            if let value = VerticalAlignment(rawValue: newValue) {
                imageVerticalAlignment = value
            } else {
                imageVerticalAlignment = .unset
            }
        }
    }

    @available(*, unavailable, message: "This property is reserved for Interface Builder. Use 'imageHorizontalAlignment' instead.")
    @IBInspectable
    var imageHorizontalAlignmentName: String {
        get {
            return imageHorizontalAlignment.rawValue
        }
        set {
            if let value = HorizontalAlignment(rawValue: newValue) {
                imageHorizontalAlignment = value
            } else {
                imageHorizontalAlignment = .unset
            }
        }
    }

    var extraContentEdgeInsets:UIEdgeInsets = UIEdgeInsets.zero

    override var contentEdgeInsets: UIEdgeInsets {
        get {
            return super.contentEdgeInsets
        }
        set {
            super.contentEdgeInsets = newValue
            self.extraContentEdgeInsets = newValue
        }
    }

    var extraImageEdgeInsets:UIEdgeInsets = UIEdgeInsets.zero

    override var imageEdgeInsets: UIEdgeInsets {
        get {
            return super.imageEdgeInsets
        }
        set {
            super.imageEdgeInsets = newValue
            self.extraImageEdgeInsets = newValue
        }
    }

    var extraTitleEdgeInsets:UIEdgeInsets = UIEdgeInsets.zero

    override var titleEdgeInsets: UIEdgeInsets {
        get {
            return super.titleEdgeInsets
        }
        set {
            super.titleEdgeInsets = newValue
            self.extraTitleEdgeInsets = newValue
        }
    }

    //Needed to avoid IB crash during autolayout
    override init(frame: CGRect) {
        super.init(frame: frame)
    }


    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)

        self.imageEdgeInsets = super.imageEdgeInsets
        self.titleEdgeInsets = super.titleEdgeInsets
        self.contentEdgeInsets = super.contentEdgeInsets
    }

    override func layoutSubviews() {
        if let imageSize = self.imageView?.image?.size,
            let font = self.titleLabel?.font,
            let textSize = self.titleLabel?.attributedText?.size() ?? self.titleLabel?.text?.size(attributes: [NSFontAttributeName: font]) {

            var _imageEdgeInsets = UIEdgeInsets.zero
            var _titleEdgeInsets = UIEdgeInsets.zero
            var _contentEdgeInsets = UIEdgeInsets.zero

            let halfImageToTitleSpacing = imageToTitleSpacing / 2.0

            switch imageVerticalAlignment {
            case .bottom:
                _imageEdgeInsets.top = (textSize.height + imageToTitleSpacing) / 2.0
                _imageEdgeInsets.bottom = (-textSize.height - imageToTitleSpacing) / 2.0
                _titleEdgeInsets.top = (-imageSize.height - imageToTitleSpacing) / 2.0
                _titleEdgeInsets.bottom = (imageSize.height + imageToTitleSpacing) / 2.0
                _contentEdgeInsets.top = (min (imageSize.height, textSize.height) + imageToTitleSpacing) / 2.0
                _contentEdgeInsets.bottom = (min (imageSize.height, textSize.height) + imageToTitleSpacing) / 2.0
                //only works with contentVerticalAlignment = .center
                contentVerticalAlignment = .center
            case .top:
                _imageEdgeInsets.top = (-textSize.height - imageToTitleSpacing) / 2.0
                _imageEdgeInsets.bottom = (textSize.height + imageToTitleSpacing) / 2.0
                _titleEdgeInsets.top = (imageSize.height + imageToTitleSpacing) / 2.0
                _titleEdgeInsets.bottom = (-imageSize.height - imageToTitleSpacing) / 2.0
                _contentEdgeInsets.top = (min (imageSize.height, textSize.height) + imageToTitleSpacing) / 2.0
                _contentEdgeInsets.bottom = (min (imageSize.height, textSize.height) + imageToTitleSpacing) / 2.0
                //only works with contentVerticalAlignment = .center
                contentVerticalAlignment = .center
            case .center:
                //only works with contentVerticalAlignment = .center
                contentVerticalAlignment = .center
                break
            case .unset:
                break
            }

            switch imageHorizontalAlignment {
            case .left:
                _imageEdgeInsets.left = -halfImageToTitleSpacing
                _imageEdgeInsets.right = halfImageToTitleSpacing
                _titleEdgeInsets.left = halfImageToTitleSpacing
                _titleEdgeInsets.right = -halfImageToTitleSpacing
                _contentEdgeInsets.left = halfImageToTitleSpacing
                _contentEdgeInsets.right = halfImageToTitleSpacing
            case .right:
                _imageEdgeInsets.left = textSize.width + halfImageToTitleSpacing
                _imageEdgeInsets.right = -textSize.width - halfImageToTitleSpacing
                _titleEdgeInsets.left = -imageSize.width - halfImageToTitleSpacing
                _titleEdgeInsets.right = imageSize.width + halfImageToTitleSpacing
                _contentEdgeInsets.left = halfImageToTitleSpacing
                _contentEdgeInsets.right = halfImageToTitleSpacing
            case .center:
                _imageEdgeInsets.left = textSize.width / 2.0
                _imageEdgeInsets.right = -textSize.width / 2.0
                _titleEdgeInsets.left = -imageSize.width / 2.0
                _titleEdgeInsets.right = imageSize.width / 2.0
                _contentEdgeInsets.left = -((imageSize.width + textSize.width) - max (imageSize.width, textSize.width)) / 2.0
                _contentEdgeInsets.right = -((imageSize.width + textSize.width) - max (imageSize.width, textSize.width)) / 2.0
            case .unset:
                break
            }

            _contentEdgeInsets.top += extraContentEdgeInsets.top
            _contentEdgeInsets.bottom += extraContentEdgeInsets.bottom
            _contentEdgeInsets.left += extraContentEdgeInsets.left
            _contentEdgeInsets.right += extraContentEdgeInsets.right

            _imageEdgeInsets.top += extraImageEdgeInsets.top
            _imageEdgeInsets.bottom += extraImageEdgeInsets.bottom
            _imageEdgeInsets.left += extraImageEdgeInsets.left
            _imageEdgeInsets.right += extraImageEdgeInsets.right

            _titleEdgeInsets.top += extraTitleEdgeInsets.top
            _titleEdgeInsets.bottom += extraTitleEdgeInsets.bottom
            _titleEdgeInsets.left += extraTitleEdgeInsets.left
            _titleEdgeInsets.right += extraTitleEdgeInsets.right

            super.imageEdgeInsets = _imageEdgeInsets
            super.titleEdgeInsets = _titleEdgeInsets
            super.contentEdgeInsets = _contentEdgeInsets

        } else {
            super.imageEdgeInsets = extraImageEdgeInsets
            super.titleEdgeInsets = extraTitleEdgeInsets
            super.contentEdgeInsets = extraContentEdgeInsets
        }

        super.layoutSubviews()
    }
}

1
ฉันแก้ไขบางสิ่งเพื่อไม่ให้แตก IB ด้วยerror: IB Designables: Failed to update auto layout status: The agent crashed, gist.github.com/nebiros/ecf69ff9cb90568edde071386c6c4ddb
nebiros

@nebiros คุณสามารถอธิบายสิ่งที่ผิดและวิธีการแก้ไขได้ไหม
gbitaudeau

@gbitaudeau เมื่อฉันคัดลอกและวางสคริปต์ฉันได้รับข้อผิดพลาดerror: IB Designables: Failed to update auto layout status: The agent crashedเนื่องจากinit(frame: CGRect)ไม่ได้ถูกแทนที่ฉันยังเพิ่ม@availableคำอธิบายประกอบ ... คุณสามารถdiff -Naurถ้าคุณต้องการ ;-)
nebiros

9

ส่วนเพิ่มเติมของ Riley Avron ตอบการเปลี่ยนแปลงสถานที่ของบัญชี:

extension UIButton {
    func centerTextAndImage(spacing: CGFloat) {
        let insetAmount = spacing / 2
        let writingDirection = UIApplication.sharedApplication().userInterfaceLayoutDirection
        let factor: CGFloat = writingDirection == .LeftToRight ? 1 : -1

        self.imageEdgeInsets = UIEdgeInsets(top: 0, left: -insetAmount*factor, bottom: 0, right: insetAmount*factor)
        self.titleEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount*factor, bottom: 0, right: -insetAmount*factor)
        self.contentEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: insetAmount)
    }
}

6

Swift 4.x

extension UIButton {
    func centerTextAndImage(spacing: CGFloat) {
        let insetAmount = spacing / 2
        let writingDirection = UIApplication.shared.userInterfaceLayoutDirection
        let factor: CGFloat = writingDirection == .leftToRight ? 1 : -1

        self.imageEdgeInsets = UIEdgeInsets(top: 0, left: -insetAmount*factor, bottom: 0, right: insetAmount*factor)
        self.titleEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount*factor, bottom: 0, right: -insetAmount*factor)
        self.contentEdgeInsets = UIEdgeInsets(top: 0, left: insetAmount, bottom: 0, right: insetAmount)
    }
}

การใช้งาน :

button.centerTextAndImage(spacing: 10.0)

จะใช้กับขนาดภาพ custiom ได้อย่างไร?
midhun p

2

ฉันเขียนโค้ด bewlow มันทำงานได้ดีในรุ่นผลิตภัณฑ์ Supprot Swift 4.2 +

extension UIButton{
 enum ImageTitleRelativeLocation {
    case imageUpTitleDown
    case imageDownTitleUp
    case imageLeftTitleRight
    case imageRightTitleLeft
}
 func centerContentRelativeLocation(_ relativeLocation: 
                                      ImageTitleRelativeLocation,
                                   spacing: CGFloat = 0) {
    assert(contentVerticalAlignment == .center,
           "only works with contentVerticalAlignment = .center !!!")

    guard (title(for: .normal) != nil) || (attributedTitle(for: .normal) != nil) else {
        assert(false, "TITLE IS NIL! SET TITTLE FIRST!")
        return
    }

    guard let imageSize = self.currentImage?.size else {
        assert(false, "IMGAGE IS NIL! SET IMAGE FIRST!!!")
        return
    }
    guard let titleSize = titleLabel?
        .systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) else {
            assert(false, "TITLELABEL IS NIL!")
            return
    }

    let horizontalResistent: CGFloat
    // extend contenArea in case of title is shrink
    if frame.width < titleSize.width + imageSize.width {
        horizontalResistent = titleSize.width + imageSize.width - frame.width
        print("horizontalResistent", horizontalResistent)
    } else {
        horizontalResistent = 0
    }

    var adjustImageEdgeInsets: UIEdgeInsets = .zero
    var adjustTitleEdgeInsets: UIEdgeInsets = .zero
    var adjustContentEdgeInsets: UIEdgeInsets = .zero

    let verticalImageAbsOffset = abs((titleSize.height + spacing) / 2)
    let verticalTitleAbsOffset = abs((imageSize.height + spacing) / 2)

    switch relativeLocation {
    case .imageUpTitleDown:

        adjustImageEdgeInsets.top = -verticalImageAbsOffset
        adjustImageEdgeInsets.bottom = verticalImageAbsOffset
        adjustImageEdgeInsets.left = titleSize.width / 2 + horizontalResistent / 2
        adjustImageEdgeInsets.right = -titleSize.width / 2 - horizontalResistent / 2

        adjustTitleEdgeInsets.top = verticalTitleAbsOffset
        adjustTitleEdgeInsets.bottom = -verticalTitleAbsOffset
        adjustTitleEdgeInsets.left = -imageSize.width / 2 + horizontalResistent / 2
        adjustTitleEdgeInsets.right = imageSize.width / 2 - horizontalResistent / 2

        adjustContentEdgeInsets.top = spacing
        adjustContentEdgeInsets.bottom = spacing
        adjustContentEdgeInsets.left = -horizontalResistent
        adjustContentEdgeInsets.right = -horizontalResistent
    case .imageDownTitleUp:
        adjustImageEdgeInsets.top = verticalImageAbsOffset
        adjustImageEdgeInsets.bottom = -verticalImageAbsOffset
        adjustImageEdgeInsets.left = titleSize.width / 2 + horizontalResistent / 2
        adjustImageEdgeInsets.right = -titleSize.width / 2 - horizontalResistent / 2

        adjustTitleEdgeInsets.top = -verticalTitleAbsOffset
        adjustTitleEdgeInsets.bottom = verticalTitleAbsOffset
        adjustTitleEdgeInsets.left = -imageSize.width / 2 + horizontalResistent / 2
        adjustTitleEdgeInsets.right = imageSize.width / 2 - horizontalResistent / 2

        adjustContentEdgeInsets.top = spacing
        adjustContentEdgeInsets.bottom = spacing
        adjustContentEdgeInsets.left = -horizontalResistent
        adjustContentEdgeInsets.right = -horizontalResistent
    case .imageLeftTitleRight:
        adjustImageEdgeInsets.left = -spacing / 2
        adjustImageEdgeInsets.right = spacing / 2

        adjustTitleEdgeInsets.left = spacing / 2
        adjustTitleEdgeInsets.right = -spacing / 2

        adjustContentEdgeInsets.left = spacing
        adjustContentEdgeInsets.right = spacing
    case .imageRightTitleLeft:
        adjustImageEdgeInsets.left = titleSize.width + spacing / 2
        adjustImageEdgeInsets.right = -titleSize.width - spacing / 2

        adjustTitleEdgeInsets.left = -imageSize.width - spacing / 2
        adjustTitleEdgeInsets.right = imageSize.width + spacing / 2

        adjustContentEdgeInsets.left = spacing
        adjustContentEdgeInsets.right = spacing
    }

    imageEdgeInsets = adjustImageEdgeInsets
    titleEdgeInsets = adjustTitleEdgeInsets
    contentEdgeInsets = adjustContentEdgeInsets

    setNeedsLayout()
}
}

1

นี่คือตัวอย่างง่ายๆของการใช้ imageEdgeInsets วิธีนี้จะทำให้ปุ่ม 30x30 มีพื้นที่ที่ไม่ใหญ่มาก 10 พิกเซลที่ใหญ่กว่า (50x50)

    var expandHittableAreaAmt : CGFloat = 10
    var buttonWidth : CGFloat = 30
    var button = UIButton.buttonWithType(UIButtonType.Custom) as UIButton
    button.frame = CGRectMake(0, 0, buttonWidth+expandHittableAreaAmt, buttonWidth+expandHittableAreaAmt)
    button.imageEdgeInsets = UIEdgeInsetsMake(expandHittableAreaAmt, expandHittableAreaAmt, expandHittableAreaAmt, expandHittableAreaAmt)
    button.setImage(UIImage(named: "buttonImage"), forState: .Normal)
    button.addTarget(self, action: "didTouchButton:", forControlEvents:.TouchUpInside)

0

วิธีที่สวยงามใน Swift 3 และดีกว่าที่จะเข้าใจ:

override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
    let leftMargin:CGFloat = 40
    let imgWidth:CGFloat = 24
    let imgHeight:CGFloat = 24
    return CGRect(x: leftMargin, y: (contentRect.size.height-imgHeight) * 0.5, width: imgWidth, height: imgHeight)
}

override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
    let leftMargin:CGFloat = 80
    let rightMargin:CGFloat = 80
    return CGRect(x: leftMargin, y: 0, width: contentRect.size.width-leftMargin-rightMargin, height: contentRect.size.height)
}
override func backgroundRect(forBounds bounds: CGRect) -> CGRect {
    let leftMargin:CGFloat = 10
    let rightMargin:CGFloat = 10
    let topMargin:CGFloat = 10
    let bottomMargin:CGFloat = 10
    return CGRect(x: leftMargin, y: topMargin, width: bounds.size.width-leftMargin-rightMargin, height: bounds.size.height-topMargin-bottomMargin)
}
override func contentRect(forBounds bounds: CGRect) -> CGRect {
    let leftMargin:CGFloat = 5
    let rightMargin:CGFloat = 5
    let topMargin:CGFloat = 5
    let bottomMargin:CGFloat = 5
    return CGRect(x: leftMargin, y: topMargin, width: bounds.size.width-leftMargin-rightMargin, height: bounds.size.height-topMargin-bottomMargin)
}

-1

โซลูชันรุ่น swift 4.2 จะเป็นดังต่อไปนี้:

let spacing: CGFloat = 10 // the amount of spacing to appear between image and title
self.button?.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: spacing)
self.button?.titleEdgeInsets = UIEdgeInsets(top: 0, left: spacing, bottom: 0, right: 0)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.