SwiftUI metninin bir NSTableView hücresinde satırlara kaydırılması için barındırma görünümüne belirli bir genişlik verin, yüksekliğini Auto Layout ile hücreye bağlayın ve otomatik satır yüksekliklerini etkinleştirin. Bu kılavuz, bu düzenlemeyle küçük bir Mac etiket listesi oluşturuyor.
Görev belirli: kendi uygulamanızda farklı uzunluklarda yapay etiketler görüntüleyin ve kullanılabilir genişlik değiştikçe satırların içeriği gösterecek kadar uzamasını sağlayın. Örnek macOS 13 veya sonraki sürümleri hedefler ve görünüm tabanlı bir tablo kullanır.
SwiftUI ve NSTableView boyutlandırmayı nasıl paylaşır?
Üç katman rol oynar. Tablo, hücrenin dikdörtgen alanını ayırır. Auto Layout, hücreyi içine yerleştirilmiş NSHostingView öğesine bağlar. SwiftUI, etiketin kendisine önerilen alana nasıl sığacağını belirler. Apple, NSHostingView öğesini düzen sırasında SwiftUI içerik boyutu tercihlerini AppKit'e bildiren köprü olarak tanımlar. NSHostingView
Metni satırlara kaydırmak için yükseklik anlamlı olmadan önce genişlik belirlenmelidir. Etiket geniş bir sütunda tek satıra, dar bir sütunda ise birkaç satıra sığabilir. Barındırma görünümünün ilk çerçevesini ayarlamak kalıcı bir tablo hücresi düzeni sağlamaz: Apple, tablonun hücre temsilcisinin döndürdüğü görünümün çerçevesini ayarladığını belgeliyor. tableView(_:viewFor:row:)
Bu nedenle aşağıdaki uygulama tek bir boyutlandırma düzenini izler: tablo yatay alanı sağlar, barındırma görünümü yatay kenar boşluklarıyla hücrenin kenarlarına sabitlenir ve SwiftUI etiketi, önerilen genişliği kabul ederken ideal dikey boyutunu korur. usesAutomaticRowHeights etkinleştirildiğinde tablo, Auto Layout kullanarak satır yüksekliklerini hesaplar. usesAutomaticRowHeights
intrinsicContentSize ve fittingSize değerlerini birbirinin yerine kullanılabilir ölçümler olarak değerlendirmeyin. Apple, fittingSize değerini görünümün kendi kısıtlarını ve alt görünümlerinin kısıtlarını karşılayan en küçük boyut olarak tanımlar. Dolayısıyla, amaçlanan genişlik kısıtları olmadan bu değeri sorgulamak, hücrenin mevcut sütun genişliğinde ne kadar yüksek olması gerektiği sorusunu yanıtlamaz. fittingSize
macOS 13 ve sonraki sürümler için eksiksiz bir etiket tablosu oluşturun
Xcode'da SwiftUI arayüzlü ve dağıtım hedefi macOS 13 veya üzeri olan bir macOS App projesi oluşturun. Oluşturulan uygulama bildirimini aşağıdaki ilk dosyayla değiştirin, ardından diğer iki Swift dosyasını aynı uygulama hedefine ekleyin. Bu üç dosya birlikte eksiksiz bir program oluşturur; yalnızca bir @main bildirimi bırakın.
macOS 13 gereksinimi, NSHostingView.sizingOptions özelliğinin açıkça kullanılmasından kaynaklanır. Barındırma görünümü macOS 10.15'ten, otomatik tablo satır yükseklikleri ise macOS 10.13'ten itibaren kullanılabilir. Daha eski bir dağıtım hedefi, sizingOptions çevresinde kullanılabilirlik denetimi yapılmasını veya bu özelleştirmeyi dışarıda bırakan bir uygulama sürümünü gerektirir. NSHostingView, sizingOptions, usesAutomaticRowHeights
Uygulamayı ve yapay örnek verileri oluşturun
İlk dosya olan LabelSizingApp.swift, üç etiket ve son etiketi değiştiren bir düğme sağlar. Böylece hem ilk düzeni hem de içerik güncellemesini denetleyebilirsiniz.
import SwiftUI
@main
struct LabelSizingApp: App {
@State private var expanded = false
private var labels: [String] {
[
"Amber tile",
"Blue tile with a longer descriptive label that can wrap.",
expanded
? "Green tile with several descriptive words. This longer label makes it easier to inspect wrapping when the window becomes narrower."
: "Green tile"
]
}
var body: some Scene {
WindowGroup {
VStack(alignment: .leading, spacing: 12) {
Button("Toggle longer label") {
expanded.toggle()
}
LabelTable(labels: labels)
}
.padding()
.frame(minWidth: 300, minHeight: 240)
}
}
}
Barındırma görünümünü hücreye bağlayın
HostedLabelCell.swift dosyasında SwiftUI içeriği kullanılabilir genişliği kabul eder ve ideal yüksekliğini talep eder. Eksen belirten fixedSize değiştiricisi önemlidir: yatay ekseni sabitlemek, metinden ideal genişliğini korumasını ister ve metnin üst görünümünün sınırlarını aşmasına yol açabilir. fixedSize(horizontal:vertical:)
import AppKit
import SwiftUI
struct RowLabel: View {
let text: String
var body: some View {
Text(verbatim: text)
.font(.system(size: 14))
.fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.vertical, 8)
}
}
@MainActor
final class HostedLabelCell: NSTableCellView {
private let host = NSHostingView(
rootView: RowLabel(text: "")
)
override init(frame frameRect: NSRect) {
super.init(frame: frameRect)
host.translatesAutoresizingMaskIntoConstraints = false
host.sizingOptions = [.intrinsicContentSize]
addSubview(host)
NSLayoutConstraint.activate([
host.leadingAnchor.constraint(
equalTo: leadingAnchor, constant: 12
),
host.trailingAnchor.constraint(
equalTo: trailingAnchor, constant: -12
),
host.topAnchor.constraint(equalTo: topAnchor),
host.bottomAnchor.constraint(equalTo: bottomAnchor)
])
}
required init?(coder: NSCoder) {
fatalError("This sample creates cells programmatically.")
}
func configure(text: String) {
host.rootView = RowLabel(text: text)
}
}
Dört kenar kısıtı, barındırma görünümü ile hücre arasında bağlantı kurar. Gömülü barındırma görünümünde autoresizing-mask dönüşümünü devre dışı bırakmak, Apple'ın dinamik ayarlama gerektiren kısıtlar kullanan görünümler için önerdiği gibi, açık kısıtların görünümü yönetmesini sağlar. Dış hücrenin konumlandırılmasından tablo sorumludur. translatesAutoresizingMaskIntoConstraints
Burada sizingOptions, barındırma görünümünün minimum ve maksimum boyut kısıtlarını talep etmeden içerik boyutuna göre doğal boyutlandırmayı korur. Apple bunların ayrı ayrı seçilebilen davranışlar olduğunu belgeliyor; özelliğin boş bir seçenek kümesine ayarlanması, içerikten türetilen bu kısıtların hiçbirini oluşturmaz. Doğal içerik boyutlandırmasını korumak, içerik odaklı bu hücre için bir uygulama tercihidir; her barındırma görünümü için geçerli evrensel bir ayar değildir. sizingOptions
Otomatik satır yüksekliklerini etkinleştirin ve yeniden kullanılan hücreleri yapılandırın
Kaydırma görünümünü, tabloyu ve temsilciyi oluşturmak için LabelTable.swift dosyasını ekleyin. Program, elle satır yüksekliği temsilcisi uygulamadan bilerek otomatik boyutlandırmayı kullanır.
import AppKit
import SwiftUI
@MainActor
struct LabelTable: NSViewRepresentable {
let labels: [String]
func makeCoordinator() -> Coordinator {
Coordinator(labels: labels)
}
func makeNSView(context: Context) -> NSScrollView {
let table = NSTableView()
let column = NSTableColumn(
identifier: Coordinator.cellID
)
column.title = "Label"
column.width = 360
column.minWidth = 120
column.resizingMask = .autoresizingMask
table.addTableColumn(column)
table.headerView = nil
table.columnAutoresizingStyle = .lastColumnOnlyAutoresizingStyle
table.intercellSpacing = NSSize(width: 0, height: 0)
table.usesAutomaticRowHeights = true
table.dataSource = context.coordinator
table.delegate = context.coordinator
let scrollView = NSScrollView()
scrollView.hasVerticalScroller = true
scrollView.documentView = table
return scrollView
}
func updateNSView(
_ scrollView: NSScrollView,
context: Context
) {
guard let table = scrollView.documentView as? NSTableView,
context.coordinator.labels != labels else {
return
}
context.coordinator.labels = labels
table.reloadData()
}
@MainActor
final class Coordinator: NSObject,
NSTableViewDataSource, NSTableViewDelegate {
static let cellID = NSUserInterfaceItemIdentifier("LabelCell")
var labels: [String]
init(labels: [String]) {
self.labels = labels
super.init()
}
func numberOfRows(in tableView: NSTableView) -> Int {
labels.count
}
func tableView(
_ tableView: NSTableView,
viewFor tableColumn: NSTableColumn?,
row: Int
) -> NSView? {
let cell = (
tableView.makeView(
withIdentifier: Self.cellID,
owner: self
) as? HostedLabelCell
) ?? HostedLabelCell(frame: .zero)
cell.identifier = Self.cellID
cell.configure(text: labels[row])
return cell
}
}
}
Temsilci, hücre oluşturmadan önce yeniden kullanım havuzundan görünüm almayı dener ve ardından her seferinde geçerli etiketi atar. Bu, Apple'ın hücre görünümü oluşturma için önerdiği sırayı izler. makeView(withIdentifier:owner:) yeniden kullanılan bir görünüm döndürebilir veya eşleşen bir görünüm yoksa nil döndürebilir. tableView(_:viewFor:row:), makeView(withIdentifier:owner:)
Örnek, yalnızca girdi dizisi değiştiğinde tabloyu yeniden yükler. Üç satır için tüm tabloyu yeniden yüklemek anlaşılması kolay bir yöntemdir; ancak Apple, bu işlemin görünür satır ve hücre görünümlerini bırakıp yeniden aldığını belgeliyor. Daha büyük bir uygulamada, uygunsa daha dar kapsamlı bir güncelleme yöntemi seçin. AppKit görünüm oluşturma ve güncellemelerini ana aktörde tutun; Swift, MainActor öğesinin ana dağıtım kuyruğuna eşdeğer bir yürütücü kullandığını belgeliyor. reloadData(), MainActor
Kırpılmayı veya aşırı satır yüksekliğini nasıl teşhis edebilirsiniz?
Önce genişliği, ardından yüksekliğin nasıl aktarıldığını, son olarak güncellemeleri inceleyin. Bu sıra, düzen bağımlılıklarını izler ve birbiriyle ilgisiz birkaç ayarı aynı anda değiştirmekten kaçınmaya yardımcı olur.
| Belirti | İlk kontrol | Yararlı düzeltme |
|---|---|---|
| Uzun metin hücrenin dışına yatay olarak taşıyor | Yatay ideal boyutlandırma etkin mi? | Bu satırlara kaydırılan etiket için fixedSize(horizontal: false, vertical: true) kullanın. |
| Metin satırlara kayıyor ama son satırları kırpılıyor | Barındırma görünümü hücrenin hem üst hem alt kenarına bağlı mı? | Üst ve alt kısıtları geri yükleyin ve çakışan sabit yükseklikleri kaldırın. |
| Tüm satırlar aynı yükseklikte kalıyor | Otomatik boyutlandırma etkin mi, elle yükseklik uygulaması var mı? | Tablo yapılandırmasını denetleyin ve bilinçli olarak tek bir boyutlandırma yöntemi kullanın. |
| Kısa metin gereksiz yere yüksek bir hücrede görünüyor | İçerikte sabit yükseklik veya fazladan dikey boşluk var mı? | Tabloyu ayarlamadan önce SwiftUI hiyerarşisini ve dolgu değerlerini inceleyin. |
| Değiştirilen etiket eski içeriği gösteriyor | Model değiştikten sonra hücre yeniden yapılandırılıyor mu? | Görüntülenen kök görünümü güncelleyin veya etkilenen içeriği yeniden yükleyin. |
İncelemeyi somutlaştırmak için bir genişlik bütçesi kullanın. Hücre örneğin 320 punto genişliğindeyse, örnekteki 12'şer puntoluk iki yatay iç boşluk barındırma görünümüne 296 punto bırakır. Dikey dolgu toplam 16 punto ekler. Gerekli kalan yükseklik gerçek metin düzenine bağlıdır; bu sayılar belirli bir etiketin kaç satır tutacağını öngörmez.
Genişleyen SwiftUI çerçevesi, kullanılabilir yatay alan içinde başlangıç kenarına hizalama sağlar. Sınırsız fiziksel genişlik atamaz: Apple'ın çerçeve belgeleri, çerçevenin üst görünümünden gelen önerilere katıldığını ve belirtilen boyut kısıtlarını uyguladığını açıklar. frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:)
Örneği uzun etiketle hem geniş hem dar pencere boyutunda çalıştırın, ardından kısa etikete geri dönmek için düğmeye basın. Tüm metnin görünür kaldığını ve satırın yeniden kısalabildiğini kontrol edin. Bunlar, desteklediğiniz macOS sürümlerinde gerçekleştirmeniz gereken kabul kontrolleridir. Kod, alınan API belgelerine göre incelendi; bu ortamda Swift derleyicisi veya macOS çalışma zamanı bulunmadığından derlenip çalıştırılmadı.
Satır yüksekliklerini elle hesaplarsanız ne değişir?
Elle boyutlandırma ayrı bir uygulama yoludur. Buna ihtiyacınız varsa, yükseklik temsilcisinin dışında amaçlanan içerik genişliği için yükseklikleri hesaplayın ve temsilcinin hazırlanan sonucu döndürmesini sağlayın. Yararlı bir önbellek anahtarı öğeyi, kullanılabilir genişliği ve yazı tipi veya dolgu gibi düzeni değiştiren sunum girdilerini içerir.
Apple, elle döndürülen satır yüksekliğinin sıfırdan büyük olmasını ve hücreler arası boşluğu içermemesini şart koşar. Belgeleri ayrıca tableView(_:heightOfRow:) içinde hücre veya satır görünümlerinin alınmasını yasaklar ve özyinelemeli tablo düzenini tetikleyebilecek geometri sorgularına karşı uyarır. Bu nedenle, bu geri çağrıdan görünür bir hücreyi ölçmek elle uygulama için uygun bir temel değildir. tableView(_:heightOfRow:)
Hazırlanan yükseklikler değiştiğinde tabloya noteHeightOfRows(withIndexesChanged:) ile bildirim gönderin. Apple, bu temsilci yöntemi uygulanmışsa tablonun temsilcinin yüksekliklerini kullanarak hemen yeniden döşendiğini belgeliyor. Sütun genişliği değişiklikleri de genişliğe bağlı ölçümlerin geçersiz kılınmasını gerektirir; columnDidResizeNotification bu olay için belgelenen bildirimdir. Otomatik yaklaşımı elle bir önbellekle değiştirirseniz bunları göz önünde bulundurmanız gerekir. noteHeightOfRows(withIndexesChanged:), columnDidResizeNotification
SwiftUI etiketini TextKit ile ölçmek gerekir mi?
TextKit, uygulamanız metni gerçekten bir TextKit kapsayıcısı üzerinden düzenliyorsa yararlıdır. Belgelenen ölçümünün kapsamı SwiftUI hücresinin tamamından daha dardır: NSLayoutManager.usedRect(for:), belirtilen kapsayıcıda zaten düzenlenmiş gliflerin sınırlarını bildirir; kendisi glif üretmez veya düzenleme yapmaz. Düzen henüz tamamlanmadıysa bu ölçümü okumadan önce ensureLayout(for:) çağrısını yapın. usedRect(for:), ensureLayout(for:)
Kapsayıcı yapılandırmasını da hesaba katın. Apple, her satır parçasının iki ucunda varsayılan lineFragmentPadding değerinin 5 punto olduğunu ve bunun kullanılabilir düzen genişliğini etkilediğini belgeliyor. Ayrıca bu özelliğin metin kenar boşluklarını ifade etmek için tasarlanmadığını belirtiyor. lineFragmentPadding
Bunun pratik sonucu, ayrı yapılandırılmış bir TextKit ölçümünün tek başına bu SwiftUI hiyerarşisinin yüksekliğini belirleyememesidir. Hücrede SwiftUI düzen davranışı ve dolgu da bulunur. Bu etiket tablosunda boyutlandırma yolunu barındırma görünümü ve Auto Layout üzerinden sürdürün; ardından uygulamanızın desteklediği genişliklerde satır kaydırmayı, içerik değişikliklerini ve küçülmeyi doğrulayın.
Kaynaklar
- NSHostingView — Apple
- sizingOptions — Apple
- usesAutomaticRowHeights — Apple
- tableView(_:viewFor:row:) — Apple
- fittingSize — Apple
- fixedSize(horizontal:vertical:) — Apple
- translatesAutoresizingMaskIntoConstraints — Apple
- makeView(withIdentifier:owner:) — Apple
- reloadData() — Apple
- MainActor — Apple
- frame(minWidth:idealWidth:maxWidth:minHeight:idealHeight:maxHeight:alignment:) — Apple
- tableView(_:heightOfRow:) — Apple
- noteHeightOfRows(withIndexesChanged:) — Apple
- columnDidResizeNotification — Apple
- usedRect(for:) — Apple
- ensureLayout(for:) — Apple
- lineFragmentPadding — Apple