Yan kontrolleri en-boy oranlı bir SwiftUI önizlemesiyle eşleştirmek için kullanılabilir pencere alanına sığdırılmış önizleme boyutunu hesaplayın, ardından kontrollere aynı yüksekliği verin. Bu macOS örneği, sentetik bir önizlemeyi orantılı tutar ve taşan kontrolleri kaydırılabilir hâle getirir.

Görev, sol tarafta önizleme ve sağda sabit genişlikli bir kontrol paneli bulunan, boyutu değiştirilebilir bir masaüstü düzenleyicisidir. Burada “önizleme”, çalışan uygulamanızın içindeki içeriği ifade eder. Amaç, pencerenin boyutu değiştikçe iki panelin üst ve alt kenarlarını hizalamaktır.

Kontroller hangi yüksekliği izlemeli?

Önce eşleştirmek istediğiniz dikdörtgeni tanımlayın. En-boy oranına göre sığdırılmış bir önizleme, çevresindeki kapsayıcıdan daha az yer kaplayabilir. Kontroller kapsayıcının yüksekliğini izlerse her iki görünüm de doğru düzenlenmiş olsa bile görünür önizlemenin altına taşabilir.

Apple, en-boy oranını genişliğin yüksekliğe bölümü olarak tanımlar. Boyutu değiştirilebilir içeriklerde aspectRatio(_:contentMode:) ve .fit, içeriği önerilen alana sığdırırken bu oranı korur. Belgelerdeki elips örneği, sığdırılmış şeklin ve onu çevreleyen kare çerçevenin neden farklı boyutlara sahip olabileceğini gösterir. Apple’ın en-boy oranı belgeleri.

Bu uygulama, sığdırılmış dikdörtgeni açıkça tanımlar. Kontroller için sabit bir genişlik ve paneller arasında bir boşluk ayırır, ardından önizlemeyi kalan genişliğe ve kullanılabilir yüksekliğe sığdırır. Her iki panele de hesaplanan önizleme yüksekliği verilir.

Bu, bu örneğe özgü bir düzen ilkesidir: kontrollerin görüntü alanı yüksekliğini önizleme belirler. Uzun kontrol listesi, önizlemeyi büyütmek yerine bu görüntü alanında kaydırılır. Apple, ScrollView bileşenini kaydırılabilir bir bölge içinde, temel içeriğin hareket ettirilebilir bir bölümünü gösteren yapı olarak belgeler. Apple’ın kaydırma görünümü belgeleri.

Sığdırılmış dikdörtgeni bir kez hesaplayın

W ve H dış kenar boşluklarından sonraki kullanılabilir içerik boyutları, C kontrollerin genişliği, G boşluk ve R önizlemenin genişlik-yükseklik oranı olsun. Hesaplama şöyledir:

remainingWidth = max(0, W - C - G)
previewWidth = min(remainingWidth, H * R)
previewHeight = previewWidth / R
controlsHeight = previewHeight

İlk sınır, önizlemenin kontrollerin yatay alanını kullanmasını önler. İkinci sınır, geniş bir pencerenin kullanılabilir alandan daha yüksek bir önizleme oluşturmasını engeller. Yüksekliği seçilen genişlikten hesaplamak, her iki durumda da oranı korur.

Örneğin, içerik alanının kenar boşluklarından önce 960 × 600 nokta olduğunu varsayalım. Her kenarda 16 nokta kenar boşluğu, 220 nokta genişliğinde bir kontrol paneli, 16 nokta boşluk ve 16:9 önizlemeyle kullanılabilir alan 928 × 568 nokta olur. Önizleme için kalan genişlik 692 noktadır ve önizleme yüksekliği 389,25 nokta olur. Böylece kontrol görüntü alanı 220 × 389,25 nokta olur.

Bunlar örnek amaçlı girdilerdir; ölçülmüş uygulama sonuçları değildir. Ayrıca kontrolleri kullanılabilir alanın tamamı olan 568 noktayla eşleştirmenin neden yanlış hizalamaya yol açacağını da gösterirler.

Düzen boyutları için CGFloat, oran içinse pozitif ve sonlu bir değer kullanın. Swift’te let ratio: CGFloat = 16.0 / 9.0, kayan noktalı hesaplama niyetini açıkça belirtir. Uygulamanız daha sonra yapılandırılabilir oranları kabul ederse bölme işleminden önce bunları doğrulayın.

Eksiksiz macOS örneğini oluşturun

Xcode’da bir macOS SwiftUI uygulaması oluşturun ve dağıtım hedefini macOS 13 veya daha yenisine ayarlayın. Oluşturulan uygulama ve içerik görünümü bildirimlerini bu tek dosyayla değiştirin; hedefte yalnızca bir @main bildirimi bırakın. Örnek için varlık veya harici paket gerekmez.

Dağıtım gereksinimi, burada kullanılan pencere boyutlandırma API’sinden kaynaklanır. Sahne, Apple’ın içerik boyutuna göre pencere minimumunu belirlediğini ve maksimum boyut uygulamadığını açıkladığı .contentMinSize değerini kullanır. Apple’ın pencere yeniden boyutlandırma belgeleri, Apple’ın içerik minimum boyutu belgeleri.

import SwiftUI

@main
struct MatchedPreviewApp: App {
    var body: some Scene {
        WindowGroup {
            PreviewEditor()
                .frame(minWidth: 640, minHeight: 360)
        }
        .windowResizability(.contentMinSize)
    }
}

struct PreviewEditor: View {
    @State private var intensity: Double = 0.5
    @State private var showsLabel = true

    private let ratio: CGFloat = 16.0 / 9.0
    private let controlsWidth: CGFloat = 220
    private let gap: CGFloat = 16

    var body: some View {
        GeometryReader { geometry in
            let availableHeight = max(0, geometry.size.height)
            let remainingWidth = max(
                0,
                geometry.size.width - controlsWidth - gap
            )
            let previewWidth = min(
                remainingWidth,
                availableHeight * ratio
            )
            let previewHeight = previewWidth / ratio

            HStack(alignment: .top, spacing: gap) {
                RoundedRectangle(cornerRadius: 12)
                    .fill(Color.blue.opacity(0.2 + intensity * 0.7))
                    .aspectRatio(ratio, contentMode: .fit)
                    .overlay {
                        Text("Sample composition")
                            .font(.headline)
                            .opacity(showsLabel ? 1 : 0)
                    }
                    .frame(
                        width: previewWidth,
                        height: previewHeight
                    )

                ScrollView(.vertical) {
                    VStack(alignment: .leading, spacing: 16) {
                        Text("Preview controls")
                            .font(.headline)

                        Toggle("Show label", isOn: $showsLabel)

                        VStack(alignment: .leading, spacing: 8) {
                            Text("Color intensity")
                            Slider(value: $intensity, in: 0...1)
                        }

                        ForEach(1...8, id: \.self) { index in
                            Text("Sample setting \(index)")
                                .font(.caption)
                        }
                    }
                    .padding(16)
                    .frame(maxWidth: .infinity, alignment: .leading)
                }
                .frame(
                    width: controlsWidth,
                    height: previewHeight
                )
                .background(Color.gray.opacity(0.08))
            }
            .frame(
                width: geometry.size.width,
                height: geometry.size.height,
                alignment: .center
            )
        }
        .padding(16)
    }
}

GeometryReader, düzenleyiciye ayrılan bölgenin boyutunu sağlar. Apple, bunu kendi boyutuna ve koordinat alanına göre içerik tanımlayan, tercih edilen boyutu esnek olan bir yapı olarak açıklar. Kenar boşluğunu okuyucunun dışına yerleştirmek, hesaplamanın içeriğe ayrılan alanı kullanmasını sağlar. Apple’ın geometry-reader belgeleri.

İki sabit çerçeve ortak yüksekliği belirler. Apple, frame(width:height:alignment:) değerini belirtilen boyutlara sahip görünmez bir çerçeve oluşturan öğe olarak belgeler. Kontrollerin arka planı bu çerçeveden sonra gelir; böylece kısa bir listenin altındaki boşluk da dâhil olmak üzere kontrol görüntü alanının tamamını kaplar. Apple’ın sabit çerçeve belgeleri.

Son çerçeve, satırın tamamını okuyucunun içinde ortalar. İki panelden hiçbirini büyütmez. Bu nedenle, kontroller önizlemeyle eşleşmeyi sürdürürken uzun bir pencerede satırın üstünde ve altında kullanılmayan alan kalabilir.

Boyutlandırma sırasında durum ve yaşam döngüsü davranışını koruyun

Kullanıcının düzenleyebildiği değerleri hesaplanan geometriden ayrı tutun. Örnekte intensity ve showsLabel durum değerleridir; önizleme genişliği ve yüksekliği ise mevcut kullanılabilir boyuttan türetilen yerel değerlerdir.

Apple, durumu SwiftUI tarafından yönetilen ve değişiklikleri görünüm hiyerarşisinin bağımlı kısımlarını güncelleyen depolama olarak açıklar. Bu, kaydırıcı ve açma-kapama düğmesi değerlerini onlara sahip olan düzenleyicide tutmayı destekler. Apple’ın durum belgeleri.

Düzen hesabında durum ataması, görünüm yaşam döngüsü geri çağrısı, eşzamansız iş veya ölçüm geri bildirimi yoktur. Boyut değişikliği, aynı görünüm yapısının kullandığı boyutları değiştirir. Etiket görünür kalır ve düğme değiştiğinde opaklığı değişir.

Bunu mevcut bir düzenleyiciye uyarlarken model sahipliğini ve bağlama türlerini koruyun. Boyutlandırma ilkesini önizleme ve kontroller çevresinde uygulayın; model oluşturmayı geometri hesabına taşımayın. Bu örnekteki aritmetik yalnızca kullanılabilir boyutları ve seçilen oranı gerektirir.

Boyutlandırmayı, taşmayı ve değiştirici sırasını kontrol edin

Hesaplamayı kontrol etmek için aşağıdaki boyutları kullanın. Bunlar pencerenin dış çerçevesini değil, örneğin dış kenar boşluklarından önceki içerik alanını tanımlar. Beklenen değerler iki ondalık basamağa yuvarlanmıştır.

İçerik alanı Önizleme boyutu Kontrol görüntü alanı
960 × 600 nokta 692 × 389,25 220 × 389,25
640 × 360 nokta 372 × 209,25 220 × 209,25
1200 × 400 nokta 654,22 × 368 220 × 368
700 × 900 nokta 432 × 243 220 × 243

Aritmetik, sığdırılmış önizlemenin kullanılabilir yüksekliği aşmadığını ve her iki panelle boşluğun yatay olarak alana sığdığını doğrulayan iddialar içeren küçük bir Python hesabıyla kontrol edildi. Araştırma ortamında Swift ve Xcode bulunmadığından, SwiftUI programının tamamı incelendi ancak derlenmedi veya çalıştırılmadı.

Xcode’da uygulamayı çalıştırın ve pencereyi geniş, kısa ve uzun biçimlere getirecek şekilde yeniden boyutlandırın. Panel kenarlarının hizalı kaldığını, içerik görüntü alanı aştığında kontrollerin kaydırıldığını ve boyutlandırma sırasında kaydırıcı ile açma-kapama düğmesinin değerlerini koruduğunu doğrulayın.

Örneği uyarladıktan sonra hizalama hâlâ farklıysa şu koşulları inceleyin:

  • Ek panel kenar boşluğu: Yalnızca bir panelin sabit çerçevesinin dışına uygulanan kenar boşluğu, o panelin toplam alanını büyütür. Kontrollerin kenar boşluğunu gösterildiği gibi kaydırma görünümünün içinde tutun.
  • Daha büyük bir önizleme sarmalayıcısı: Önizlemenin çevresine daha büyük bir çerçeve eklerseniz kontrollerin bu sarmalayıcıyla mı yoksa sığdırılmış içerikle mi eşleşeceğine karar verin. Buradaki hesaplama sığdırılmış dikdörtgeni eşleştirir.
  • Taşan özel içerik: Bir çerçeve düzen boyutlarını belirler ancak dışına çizilen içeriği otomatik olarak gizlemez. Apple, dikdörtgen biçiminde kırpma için .clipped() öğesini belgeler; önizlemenin kasıtlı taşan kısmını gizlemek istediğinizde kullanın. Tüm ayarlara erişilebilmesi için kontrolleri kaydırılabilir tutun. Apple’ın kırpma belgeleri.
  • Yetersiz ana makine alanı: Örnek, içerik minimum boyutuna dayanır. Daha küçük bir ana makine içine yerleştirilirse sabit kontroller genişliği önizleme için kullanışlı alan bırakmayabilir. Bu ana makinede daha dar bir panel, dikey düzen veya açıkça belirtilmiş bir minimum boyut seçin.

Bunun yerine ne zaman özel bir düzen kullanmalısınız?

Bu yaklaşım, sınırları belirlenmiş bir pencere alanı ve bilinen bir en-boy oranı kullanan düzenleyiciler için uygundur. Boyutlandırma ilkesi açıktır ve önizlemenin içeriğini ölçmeye bağlı değildir.

Yeniden kullanılabilir bileşeninizin tercih edilen boyutunu alt öğelerinden belirlemesi gerekiyorsa özel bir Layout kullanmayı değerlendirin. Apple’ın belgelenmiş sözleşmesi, birleşik boyutun sizeThatFits(proposal:subviews:cache:) üzerinden bildirilmesini, alt öğelerin placeSubviews(in:proposal:subviews:cache:) üzerinden konumlandırılmasından ayrı tutar. Alt görünüm vekilleri, ölçüm ve yerleştirme işlemlerini sağlar. Apple’ın düzen belgeleri.

Burada gösterilen sınırları belirlenmiş masaüstü düzenleyicisi için ortak bir sığdırılmış dikdörtgen yeterlidir: kontroller için yatay alan ayırın, önizlemeyi kullanılabilir her iki boyuta göre sığdırın ve yüksekliğini kontrol görüntü alanına verin. Böylece kontrollerin içeriği kaydırılabilir kalırken net bir boyutlandırma kuralı elde edersiniz.

Kaynaklar