Autolayout - ukuran intrinsik UIButton tidak termasuk insets judul

196

Jika saya memiliki UIButton yang diatur menggunakan autolayout, ukurannya menyesuaikan dengan baik agar sesuai dengan isinya.

Jika saya menetapkan gambar sebagai button.image, ukuran instrinsik lagi tampaknya bertanggung jawab untuk ini.

Namun, jika saya mengubah titleEdgeInsetstombol, tata letak tidak memperhitungkan ini dan malah memotong judul tombol.

Bagaimana saya bisa memastikan bahwa lebar intrinsik tombol menyumbang inset?

masukkan deskripsi gambar di sini

Edit:

Saya menggunakan yang berikut ini:

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

Tujuannya adalah untuk menambahkan beberapa pemisahan antara gambar dan teks.

Ben Packard
sumber
3
Apakah Anda mengajukan ini sebagai radar? Tampaknya memang ada bug dalam perhitungan ukuran intrinsik UIButton.
Ryan Poolos
1
Saya sudah siap untuk mengajukan radar, tetapi ini sebenarnya merupakan perilaku yang diharapkan. Ini didokumentasikan pada properti UIButton's * EdgeInsets : "Insets yang Anda tentukan diterapkan pada judul persegi setelah ukuran persegi itu telah disesuaikan dengan teks tombol. Dengan demikian, nilai inset positif sebenarnya dapat klip teks judul. [...] The tombol tidak menggunakan properti ini untuk menentukan intrinsicContentSize dan sizeThatFits :. "
Guillaume Algis
7
@GuillaumeAlgis Saya berpendapat bahwa meskipun ini adalah perilaku yang dinyatakan, itu tidak sama sekali apa yang diharapkan terjadi ketika menggunakan AutoLayout. Saya telah mengajukan bug dan akan mendorong orang lain untuk mengajukan satu juga.
memmons
Jika Anda dapat menautkan ke bug radar di sini, dapatkah kami mengekliknya dan memberi +1 padanya?
gprasant
1
dari titleEdgeInsetdokumentasi: 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. Jadi dengan menambahkan inset Anda memaksa tombol untuk klip teks pastinya
Marco Pappalardo

Jawaban:

192

Anda dapat menyelesaikan ini tanpa harus menimpa metode apa pun atau menetapkan batasan lebar sewenang-wenang. Anda bisa melakukan semuanya di Interface Builder sebagai berikut.

  • Lebar tombol intrinsik berasal dari lebar judul ditambah lebar ikon ditambah insets tepi konten kiri dan kanan .

  • Jika sebuah tombol memiliki gambar dan teks, mereka dipusatkan sebagai grup, tanpa padding di antaranya.

  • Jika Anda menambahkan inset konten kiri, itu dihitung relatif terhadap teks, bukan ikon teks +.

  • Jika Anda menetapkan inset gambar kiri negatif, gambar ditarik ke kiri tetapi lebar tombol keseluruhan tidak terpengaruh.

  • Jika Anda menetapkan inset gambar kiri negatif, tata letak yang sebenarnya menggunakan setengah dari nilai itu. Jadi untuk mendapatkan inset kiri -20 poin, Anda harus menggunakan nilai inset kiri -40 di Interface Builder.

Jadi, Anda memberikan inset konten kiri yang cukup besar untuk menciptakan ruang untuk inset kiri yang diinginkan dan lapisan dalam di antara ikon dan teks, lalu menggeser ikon ke kiri dengan menggandakan jumlah padding yang Anda inginkan antara ikon dan teks. Hasilnya adalah tombol dengan inset konten kiri dan kanan yang sama, dan pasangan teks dan ikon yang dipusatkan sebagai grup, dengan jumlah padding di antaranya.

Beberapa contoh nilai:

// 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)
jaredsinclair
sumber
mengapa tata letak yang sebenarnya menggunakan setengah dari nilai inset kiri negatif ?? Saya mengalami masalah yang sama!
Tony Lin
1
Sangat bagus bahwa ada solusinya, tapi saya harap ini tidak digunakan untuk membenarkan perilaku aneh UIButton.
funct7
205

Anda bisa membuatnya berfungsi di Interface Builder (tanpa menulis kode apa pun), dengan menggunakan kombinasi Judul dan Konten Insets negatif dan positif.

masukkan deskripsi gambar di sini

Pembaruan : Xcode 7 memiliki bug di mana Anda tidak dapat memasukkan nilai negatif di Rightbidang Inset, tetapi Anda dapat menggunakan kontrol stepper di sebelahnya untuk mengurangi nilai. (Terima kasih Stuart)

Melakukan ini akan menambah jarak 8pt antara gambar dan judul dan akan menambah lebar intrinsik tombol dengan jumlah yang sama. Seperti ini:

masukkan deskripsi gambar di sini

n.Buat remake
sumber
2
Menggunakan contentEdgeInsets (yang bukan buggy) untuk membiarkan pembayaran otomatis menambah lebar tombol. Dan pindahkan label ke ruang kosong di sebelah kanan. Cara cerdik untuk mengatasi bug inset tepi judul.
ugur
7
Trik ini tidak lagi berfungsi. Pembuat antarmuka tidak lagi menerima nilai negatif di Rightlapangan.
Joris Mans
7
@ JorisMans Anda tidak dapat mengetikkan nilai negatif, tetapi itu berhasil bagi saya dengan menggunakan kontrol stepper di sebelah kanan bidang teks untuk mundur ke nilai negatif yang diperlukan ... lihat gambar!
Stuart
3
Ini harus menjadi jawaban pertama, mengapa di sini? Saya sudah mencoba 5 lainnya sebelum menemukan ini ...
Lord Zsolt
2
Saya membuat Right of content inset 16 untuk memusatkan teks di UIButton
coolcool1994
96

Mengapa tidak mengganti intrinsicContentSizemetode pada UIView? Sebagai contoh:

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

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

Ini akan memberi tahu sistem autolayout bahwa ia harus menambah ukuran tombol untuk memungkinkan insets dan menampilkan teks lengkap. Saya tidak di komputer saya sendiri, jadi saya belum menguji ini.

Maarten
sumber
1
Tombol tidak boleh ditimpa sejauh yang saya tahu. Masalahnya adalah bahwa setiap jenis tombol diimplementasikan oleh subkelas yang berbeda.
Sulthan
2
intrinsicContentSizeadalah metode pada UIView, bukan UIButton, jadi Anda tidak akan mengotak-atik metode UIButton apa pun. Apple tidak berpikir itu masalah: "Mengganti metode ini memungkinkan tampilan khusus untuk berkomunikasi dengan sistem tata letak ukuran apa yang ingin didasarkan pada kontennya." Dan OP tidak mengatakan apa-apa tentang tombol yang berbeda, hanya satu.
Maarten
1
Ini pasti bekerja dan merupakan solusi yang saya gunakan. intrinsicContentSizememang metode di UIView dan UIButton adalah subkelas dari UIView sehingga tentu saja Anda dapat mengganti metode ini; tidak ada dalam dokumen Apple yang mengatakan bahwa Anda tidak boleh. Cukup buat subkelas UIButton menggunakan metode yang diganti Maarten dan ubah UIButton Anda di Interface Builder menjadi tipe YourUIButtonSubclass dan itu akan bekerja dengan sempurna.
n8tr
37
Menurut saya intrinsicContentSizeuntuk UIButton harus menambahkan titleEdgeInsets, saya akan mengajukan bug dengan Apple.
program
6
Saya setuju, dan hal yang sama untuk imageEdgeInsets.
Ricardo Sanchez-Saez
87

Anda belum menentukan bagaimana Anda mengatur insets, jadi saya menduga Anda menggunakan titleEdgeInsets karena saya melihat efek yang sama dengan yang Anda dapatkan. Jika saya menggunakan contentEdgeInsets sebagai gantinya berfungsi dengan baik.

- (IBAction)ChangeTitle:(UIButton *)sender {
    self.button.contentEdgeInsets = UIEdgeInsetsMake(0,20,0,20);
    [self.button setTitle:@"Long Long Title" forState:UIControlStateNormal];
}
rdelmar
sumber
Saya memang menggunakan titleEdgeInsets. Saya perlu menjauhkan judul dari gambar, bukan gambar dari tepi tombol. Mungkin saya harus menggunakan gambar dengan padding di dalamnya? Tampaknya berantakan.
Ben Packard
Ini berfungsi sempurna dalam kombinasi dengan pembayaran otomatis, terima kasih!
Cal S
3
Ini adalah solusi yang lebih baik, karena ia melakukan apa yang Anda inginkan tanpa menyentuh intrinsicContentSize.
RyJ
28
Ini TIDAK menjawab pertanyaan saat menggunakan gambar dan perlu menyesuaikan inset antara gambar dan judul!
Brody Robertson
23

Dan untuk Swift bekerja ini:

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

Love U Swift

pasak
sumber
1
Meskipun Anda tidak seharusnya melakukannya, lebih baik untuk subklas dalam hal ini karena Apple docs secara eksplisit menyatakan bahwa ukuran intrinsik tidak termasuk titleEdgeInsets dalam perhitungannya dan dengan menggunakan ekstensi Anda melanggar tidak hanya harapan Apple tetapi semua pengembang lain yang membaca dokumen.
Sirene
18

Utas ini agak lama, tetapi saya sendiri menabrak ini dan dapat menyelesaikannya dengan menggunakan inset negatif. Misalnya, gantikan nilai padding yang Anda inginkan di sini:

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

Dikombinasikan dengan batasan autolayout yang tepat, Anda akan mendapatkan tombol ubah ukuran otomatis yang berisi gambar dan teks! Terlihat di bawah dengan desiredLeftPaddingset ke 10.

Tombol dengan gambar dan teks pendek

Tombol dengan gambar dan teks panjang

Anda dapat melihat bahwa bingkai tombol yang sebenarnya tidak mencakup label (karena label digeser 10 poin ke kanan, di luar batas), tetapi kami telah mencapai 10 poin bantalan antara teks dan gambar.

Brian Gerstle
sumber
1
Ini adalah solusi yang saya gunakan karena tidak memerlukan subclassing. Tidak akan berfungsi jika tombol Anda memiliki latar belakang, tapi itu biasanya bukan masalah dengan iOS 7
José Manuel Sánchez
Ini akan bekerja dengan gambar latar belakang jika Anda juga mengatur offset konten tombol (nilai positif> = judul inset).
Ben Flynn
9

Untuk Swift 3 berdasarkan jawaban pasak :

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)

    }

}
TeoK
sumber
Halo. saya ingin menggunakan tombol ekstensi kustom di interfacebuilder. tolong bantu
kemdo
6

Semua di atas tidak berfungsi untuk iOS 9+ , yang saya lakukan adalah:

  • Tambahkan batasan lebar (untuk lebar minimum ketika tombol tidak memiliki teks. Tombol akan skala otomatis jika teks disediakan)
  • atur hubungannya dengan Greater Than atau Equal

masukkan deskripsi gambar di sini

Sekarang untuk menambahkan perbatasan di sekitar tombol cukup gunakan metode:

button.contentEdgeInsets = UIEdgeInsetsMake(0,20,0,20);
Oritm
sumber
Kenapa tidak? Secara otomatis skala dengan konten, Anda hanya perlu mengatur lebar minimum (yang bisa lebih kecil dari yang akan ditampilkan teks)
Oritm
Karena Anda menetapkan lebar minimum. Seluruh gagasan autolayout dilakukan tanpa menetapkan lebar eksplisit (minimal).
Joris Mans
Ini bukan tentang lebar, Anda dapat mengatur lebar ke 1 jika Anda mau, tetapi autolayout perlu tahu bahwa lebarnya bisa sama atau lebih . Saya memperbarui jawaban saya
Oritm
Anda tidak memerlukan batasan lebar sama sekali, contentEdgeInset adalah kuncinya, tata letak otomatis kemudian menggunakannya untuk ukuran konten intrinsik.
Chris Conover
5

Saya ingin menambahkan ruang 5pt antara ikon UIButton saya dan labelnya. Beginilah cara saya mencapainya:

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

Cara contentEdgeInsets, titleEdgeInsets, dan imageEdgeInsets saling berhubungan membutuhkan sedikit memberi dan menerima dari setiap inset. Jadi, jika Anda menambahkan beberapa insets ke kiri judul Anda harus menambahkan inset negatif di sebelah kanan dan memberikan lebih banyak ruang (melalui inset positif) di konten kanan.

Dengan menambahkan inset konten yang tepat agar sesuai dengan pergeseran judul inset, teks saya tidak melampaui batas tombol.

orj
sumber
3

Opsi ini juga tersedia di pembuat antarmuka. Lihat Inset. Saya mengatur kiri dan kanan ke 3. Bekerja seperti pesona.

Tangkapan layar pembangun antarmuka

Zeiteisen
sumber
1
Ya, seperti jawaban ini menjelaskan, alasannya adalah karena Anda menyesuaikan Edge: Konten di sini, bukan Edge: Judul atau Edge: Gambar .
smileyborg
1

Solusi yang saya gunakan adalah menambahkan batasan lebar pada tombol. Kemudian di suatu tempat di inisialisasi, setelah teks Anda diatur, perbarui batasan lebar seperti:

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

Di mana 8 adalah apa pun inset Anda.

Bob Spryn
sumber
Apa itu buttonWidthConstraint?
Alexey Golikov
@AlexeyGolikov An NSLayoutConstraint - developer.apple.com/library/mac/documentation/AppKit/Reference/…
1in9ui5t
1
Ini bukan solusi yang bagus, karena jika ukuran konten intrinsik tombol berubah, Anda harus memperbarui constantbatasan secara manual ke nilai baru ... dan mengetahui kapan ukuran konten intrinsik perubahan tombol sulit. tanpa subklas tombol.
smileyborg
Ayup. Saya tidak menggunakan metode ini lagi. Terkejut itu layak mendapat suara turun tapi ¯_ (ツ) _ / ¯
Bob Spryn
Panggilan ke setNeedsUpdateConstraintsdapat "secara manual" dilakukan setelah memperbarui judul tombol atau gambar. Anda kemudian dapat mengesampingkan updateConstraintsdan menghitung ulang buttonWidthConstraintkonstanta dari sana. Ini belum tentu merupakan pendekatan terbaik tetapi itu bekerja cukup baik untuk saya. YMMV;)
Olivier
1

memanggil sizeToFit () memastikan bahwa contentEdgeInset diberlakukan

extension UIButton {

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