Untuk mengukur label dalam app Mac anda tanpa meregangkannya, lampirkan onGeometryChange pada paparan yang ingin anda periksa. Untuk sasaran penggunaan yang lebih lama, letakkan GeometryReader di latar belakangnya. Kedudukan pengubah menentukan lebar yang anda baca.

Panduan ini mengukur label sintetik, termasuk ruang dalamannya, sambil memastikan susun atur sekelilingnya tidak bergantung pada hasil tersebut. Panduan ini juga menerangkan cara membezakan lebar semasa label daripada lebar idealnya dan lebar bekasnya.

Mengapa pembaca geometri menyebabkan susun atur mengembang?

Susun atur SwiftUI melibatkan cadangan dan respons: induk mencadangkan ruang, lalu anaknya melaporkan saiz yang dikira. Menambah bingkai memperkenalkan satu lagi paparan dalam hierarki itu, jadi label dan bingkainya boleh mempunyai lebar yang berbeza. Apple menerangkan proses ini dalam Menyusun atur paparan ringkas.

GeometryReader ialah bekas dengan saiz pilihan yang fleksibel. Oleh itu, membungkus label ringkas dengannya mengubah peserta susun atur yang dipersembahkan kepada induk. Anda sedang mengukur bekas pembaca itu, yang boleh menggunakan ruang jauh lebih besar daripada keperluan teks. Lihat dokumentasi GeometryReader Apple.

Untuk pemerhatian, lampirkan ukuran pada paparan sedia ada. Apabila pembaca latar belakang diperlukan, kedudukannya penting: Apple mendokumenkan bahawa latar belakang menerima saiz paparan yang dilampirkan padanya. Ini membolehkan label menentukan kawasan pengukuran. Lihat Menambahkan latar belakang pada paparan anda.

Sebelum memilih pelaksanaan, tentukan soalan yang perlu dijawab oleh antara muka anda:

Soalan anda Pendekatan yang sesuai
Berapakah lebar label ini dalam susun atur semasanya? Perhatikan geometri label.
Berapakah lebar lencana berlapik di sekeliling label? Perhatikan selepas ruang dalaman ditambah.
Berapakah lebar baris yang menampungnya? Perhatikan baris atau bingkainya.
Berapakah lebar yang dikehendaki oleh keseluruhan teks? Siasat saiz ideal secara berasingan.
Persembahan manakah antara beberapa pilihan yang muat? Pertimbangkan ViewThatFits.
Bagaimanakah beberapa anak patut diukur dan diletakkan bersama? Pertimbangkan Layout tersuai.

Perbezaan ini mengelakkan kesilapan lazim: menganggap lebar label yang dikekang dan mungkin dipotong sebagai ruang yang diperlukan untuk memaparkan keseluruhan rentetan.

Ukur lebar semasa pada macOS 13 dan versi lebih baharu

Dokumentasi semasa Apple menyenaraikan lebihan tindakan nilai tunggal onGeometryChange(for:of:action:) sebagai tersedia pada macOS 13 dan versi lebih baharu. Ia menukarkan proksi geometri kepada nilai yang boleh dibandingkan dan memanggil tindakan apabila nilai itu berubah. Gunakan SDK yang menyertakan API ini, dan kekalkan tindakan satu parameter yang ditunjukkan di sini. Lihat onGeometryChange(for:of:action:).

Cipta projek app macOS dalam Xcode menggunakan SwiftUI dan sasaran penggunaan macOS 13 atau versi lebih baharu. Gantikan pengisytiharan app dan paparan kandungan yang dijana dengan program lengkap ini, dengan hanya satu pengisytiharan app @main dalam sasaran. Contoh ini hanya menggunakan label sintetik setempat.

import SwiftUI
import Foundation

@main
struct WidthDemoApp: App {
    var body: some Scene {
        WindowGroup {
            WidthDemoView()
        }
    }
}

struct WidthDemoView: View {
    @State private var measuredWidth: CGFloat = 0
    @State private var offeredWidth: Double = 260
    @State private var showLongLabel = false

    private var label: String {
        showLongLabel
            ? "Sample collection of blue circles"
            : "Blue circles"
    }

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text(label)
                .font(.system(size: 16))
                .lineLimit(1)
                .padding(.horizontal, 12)
                .padding(.vertical, 8)
                .background(Color.blue.opacity(0.12))
                .onGeometryChange(for: CGFloat.self) { proxy in
                    proxy.size.width
                } action: { newWidth in
                    measuredWidth = newWidth
                }
                .frame(
                    width: CGFloat(offeredWidth),
                    alignment: .leading
                )

            Text(String(
                format: "Measured badge: %.1f pt",
                Double(measuredWidth)
            ))
            .monospacedDigit()
            .frame(width: 340, alignment: .leading)

            Slider(value: $offeredWidth, in: 120...340)

            Button("Change sample label") {
                showLongLabel.toggle()
            }
        }
        .frame(width: 360, alignment: .leading)
        .padding(20)
    }
}

Pemerhati membaca proxy.size.width; Apple mentakrifkan GeometryProxy.size sebagai saiz paparan bekas. Di sini, paparan yang diperhatikan merangkumi teks dan ruang dalamannya, manakala bingkai selepasnya memberikan cadangan lebar yang dikawal secara berasingan. Lihat GeometryProxy.size.

Ukuran itu mengemas kini paparan bacaan yang berasingan. Ia tidak menetapkan lebar lencana yang diukur. Kawasan demo yang tetap dan lebar paparan bacaan memudahkan pemeriksaan hasil tanpa membiarkan teks diagnostik mengubah saiz tindanan sekeliling.

Memulangkan CGFloat sahaja juga memastikan pemerhatian tertumpu pada lebar: perubahan pada ketinggian sahaja tidak mengubah nilai yang ditukarkan. Apple mengesyorkan agar geometri ditukarkan kepada data khusus yang anda perlukan kerana geometri boleh kerap berubah. Lihat onGeometryChange(for:of:action:).

Contoh ini disemak berdasarkan pengisytiharan API yang diperoleh. Contoh ini tidak dikompil atau dijalankan di sini kerana persekitaran yang tersedia ialah Linux tanpa rantaian alat Swift; bina dan periksa antara muka dalam Xcode pada Mac.

Di manakah pengubah ukuran patut diletakkan?

Baca rantaian pengubah sebagai turutan paparan yang saling membungkus. Ukuran perlu diletakkan sejurus selepas pengubah terakhir yang mempengaruhi saiz dan yang ingin anda sertakan. Penjelasan susun atur Apple menerangkan cara bingkai membungkus paparan dan ruang dalaman mengubah ruang yang tersedia untuk kandungannya. Lihat Menyusun atur paparan ringkas.

Lokasi ukuran dalam contoh Perkara yang diperhatikan
Sebelum kedua-dua pengubah ruang dalaman Lebar susun atur semasa paparan teks
Selepas kedua-dua pengubah ruang dalaman Lebar semasa lencana berlapik
Selepas bingkai lebar eksplisit Lebar bingkai luar
Pada VStack sekeliling Lebar tindanan

Sebagai pengiraan ilustrasi, katakan paparan teks melaporkan 96 mata di bawah cadangan semasanya. Menambah 12 mata pada setiap sisi mendatar menghasilkan lebar berlapik sebanyak 120 mata. Bingkai luar selebar 260 mata ialah ukuran lain yang berbeza. Angka ini menggambarkan sempadan pengubah; ia bukan hasil ukuran untuk rentetan contoh.

Untuk menyiasat nilai yang tidak dijangka, letakkan sempadan buat sementara pada kedudukan yang sama dalam rantaian pengubah dengan pemerhati. Bandingkan sempadan itu dengan sasaran yang dimaksudkan. Pembaca yang dilampirkan pada keseluruhan baris boleh melaporkan lebar baris dengan betul walaupun label yang kelihatan hanya menggunakan sebahagian kecil daripadanya.

Gunakan pembaca latar belakang untuk sasaran macOS 12

Untuk sasaran penggunaan macOS 12, gunakan GeometryReader latar belakang untuk menerbitkan lebar melalui keutamaan. API latar belakang penutupan di hujung yang digunakan di bawah tersedia mulai macOS 12. Apple mengesyorkan latar belakang apabila paparan yang diubah suai patut menguasai susun atur. Lihat background(alignment:content:).

Ini ialah program alternatif yang lengkap. Gantikan contoh sebelumnya dengannya dan bukannya menambah pengisytiharan app kedua.

import SwiftUI
import Foundation

private struct BadgeWidthKey: PreferenceKey {
    static var defaultValue: CGFloat { 0 }

    static func reduce(
        value: inout CGFloat,
        nextValue: () -> CGFloat
    ) {
        value = nextValue()
    }
}

@main
struct LegacyWidthDemoApp: App {
    var body: some Scene {
        WindowGroup {
            LegacyWidthDemoView()
        }
    }
}

struct LegacyWidthDemoView: View {
    @State private var measuredWidth: CGFloat = 0
    @State private var showLongLabel = false

    var body: some View {
        VStack(alignment: .leading, spacing: 16) {
            Text(showLongLabel ? "Blue circles collection" : "Blue circles")
                .font(.system(size: 16))
                .lineLimit(1)
                .padding(.horizontal, 12)
                .padding(.vertical, 8)
                .background(Color.blue.opacity(0.12))
                .background {
                    GeometryReader { proxy in
                        Color.clear.preference(
                            key: BadgeWidthKey.self,
                            value: proxy.size.width
                        )
                    }
                }
                .onPreferenceChange(BadgeWidthKey.self) { newWidth in
                    measuredWidth = newWidth
                }

            Text(String(
                format: "Measured badge: %.1f pt",
                Double(measuredWidth)
            ))
            .frame(width: 340, alignment: .leading)

            Button("Change sample label") {
                showLongLabel.toggle()
            }
        }
        .frame(width: 360, alignment: .leading)
        .padding(20)
    }
}

PreferenceKey membawa nilai ke arah leluhur, dan onPreferenceChange menerima perubahan pada nilai itu. Pembaca memancarkan ukuran dan bukannya menetapkan keadaan dalam penutupan kandungannya. Lihat dokumentasi PreferenceKey dan onPreferenceChange(_:perform:) Apple.

Pengecil ini sengaja mengandaikan hanya satu lencana yang memancarkan nilai. Menggunakan semula kunci yang sama untuk beberapa anak akan menggabungkan nilainya, dan pelaksanaan ini mengekalkan nilai terakhir yang dikecilkan. Untuk label yang dijejak secara berasingan, reka bentuk koleksi ukuran berkunci dan bukannya menggunakan satu nilai skalar untuk mewakili beberapa paparan.

Bagaimanakah anda boleh mengesahkan ukuran kekal bebas?

Gunakan program pertama sebagai urutan diagnostik ringkas:

  1. Mulakan dengan Blue circles dan lebar ditawarkan yang lapang. Bandingkan lencana berwarna dengan paparan bacaan.
  2. Seret peluncur. Bingkai luar berubah secara berasingan; lencana boleh kekal padat selagi ruang mencukupi.
  3. Pilih Change sample label, kemudian kurangkan lebar ditawarkan. Periksa lencana semasa yang dikekang dan bukannya mengharapkan lebar ideal rentetan penuh.
  4. Alihkan pemerhati ke bawah bingkai eksplisit. Kini ia sepatutnya melaporkan lebar bingkai itu.
  5. Letakkan semula pemerhati di atas bingkai sebelum menggunakan ukuran lencana di tempat lain.

Jauhkan lebar yang diperhatikan daripada formula yang menentukan lebar paparan yang sama. Contohnya, menetapkan bingkainya kepada lebar yang diperhatikan ditambah ruang dalaman boleh berulang kali memasukkan hasil yang lebih besar kembali ke dalam susun atur. Ini berpunca terus daripada kebergantungan yang anda wujudkan: pemerhatian mengubah keadaan, keadaan mengubah saiz, dan saiz baharu menghasilkan pemerhatian lain.

Jika tugas sebenar anda ialah memilih antara label penuh dengan alternatif ringkas, ViewThatFits pada macOS 13 dan versi lebih baharu boleh memilih anak pertama yang saiz idealnya muat pada paksi yang dikekang. Ini mungkin menghapuskan keperluan untuk menyimpan lebar sama sekali. Lihat ViewThatFits.

Jika beberapa anak perlu diukur dan diletakkan bersama, gunakan Layout tersuai pada macOS 13 dan versi lebih baharu. Kaedah pengukuran dan peletakannya membolehkan bekas menyoal subpaparan sebelum meletakkannya, lalu mengekalkan keputusan itu dalam proses susun atur. Lihat Layout.

Bolehkah saiz ideal atau metrik teks AppKit menjawab soalan yang sama?

Gunakan .fixedSize(horizontal: true, vertical: false) hanya apabila anda bermaksud mengekalkan lebar ideal teks. Apple mendokumenkan bahawa ini boleh menyebabkan teks melebihi sempadan induknya. Menambahkannya untuk mendapatkan ukuran akan mengubah tingkah laku saiz paparan yang dipaparkan, jadi ia tidak mengekalkan susun atur terkekang yang asal. Lihat fixedSize(horizontal:vertical:).

Kiraan aksara juga tidak sesuai sebagai ukuran lebar. String.count Swift mengira nilai Character yang diwakili oleh gugusan grafem lanjutan; ia tidak memulangkan dimensi tipografi. Mendarab kiraan itu dengan anggaran lebar aksara tidak mengukur paparan SwiftUI. Lihat Strings and Characters.

Untuk tugas lukisan AppKit, NSAttributedString.size() memulangkan saiz yang diperlukan untuk melukis rentetan beratribut itu, yang mungkin merangkumi dimensi pecahan. Apple menasihatkan supaya membundarkan hasil itu ke atas apabila menggunakannya untuk menetapkan saiz paparan. Ini menjawab soalan tentang lukisan teks; ia tidak merangkumi ruang dalaman paparan SwiftUI atau bingkai yang mengelilinginya. Lihat size().

Untuk susun atur TextKit 1 sedia ada, NSLayoutManager.usedRect(for:) melaporkan kawasan yang diduduki oleh glif yang sedang disusun atur dalam bekas teksnya. Ia tidak menjana glif atau melakukan susun atur dengan sendirinya; panggil ensureLayout(for:) apabila susun atur perlu diselesaikan terlebih dahulu. API ini mengukur bekas teks yang dikonfigurasikan itu, jadi asingkan hasilnya daripada geometri SwiftUI. Lihat usedRect(for:) dan ensureLayout(for:).

Untuk label yang sudah dipaparkan dalam antara muka Mac SwiftUI anda, perhatikan geometrinya pada sempadan yang diperlukan, pastikan paparan bacaannya bebas, dan uji perubahan pada kandungan serta ruang yang tersedia. Ini menghasilkan lebar yang berguna sambil mengekalkan susun atur yang ingin anda periksa.

Sumber