Mac uygulamanızda iç içe genişletmeleri canlandırmak için DisclosureGroup ve animasyonlu bir genişletme bağlamasıyla başlayın. Genişleyen yüksekliği hassas biçimde denetlemeniz gerekiyorsa kaydırma görünümünde özel bir düzen kullanın; yalnızca içerik geçişi bu geometriyi tanımlamaz.
Bu öğretici belirli bir görevi ele alır: İçeriği görünür kılmak ve aşağıdaki öğelerin değişen yüksekliğe göre hareket etmesini sağlamak için küçük bir öğe hiyerarşisini genişletmek. Örneklerde yapay etiketler kullanılır ve işlemler tümüyle uygulamanın kendi penceresinde gerçekleşir.
İçerik neden görünür biçimde genişlemeden soluyor?
Sonucu incelerken üç şeyi birbirinden ayırın: genişletme durumu, eklenen içeriğin görünümü ve kapsayıcının ayırdığı alan. Apple; withAnimation, değere dayalı animasyon değiştiricileri ve animasyonlu bağlamaları, durum değişikliklerini canlandırma yöntemleri olarak belgeliyor. Geçişleri ise görünümler hiyerarşiye girerken veya hiyerarşiden çıkarken kullanılan efektler olarak ayrı biçimde belgeliyor. Animasyonlar
Opaklık geçişi saydamlığı değiştirir. Hareket geçişi, giren veya çıkan görünümün konumunu bir kenara göre değiştirir. Bu API’lerin hiçbiri tek başına, çevresindeki bir listenin satır yüksekliğini nasıl aradeğerlemesi gerektiğini belirtmez. Bu nedenle hareket geçişi eklemek, görünür giriş efektini değiştirebilir ancak istediğiniz belirli düzen davranışını sağlamayabilir. move(edge:)
Bunu belirli bir dahili uygulamanın kanıtı olarak değil, tanılama ayrımı olarak değerlendirin: Ayrıntılar solarken sonraki satır birden atlıyorsa kapsayıcının geometrisini, ayrıntıların geçişinden ayrı olarak inceleyin. Belgelenmiş List API’si, isteğe bağlı seçim özelliği olan bir satır kapsayıcısını açıklar; her iç içe yerleşim ve liste stili için belirli bir genişletme eğrisi vaat etmez. Liste
Yerel iç içe açılır gruplarla başlayın
Geleneksel bir Mac hiyerarşisi için kendi açılır kontrolünüzü uygulamadan önce DisclosureGroup kullanın. Genişletme durumunu bir Boolean bağlamasıyla sunar ve Apple’ın belgeleri bir açılır grubun diğerinin içinde kullanılmasını açıkça gösterir. API, macOS 11 ve sonraki sürümlerde kullanılabilir. DisclosureGroup
SwiftUI kullanarak Xcode’da bir macOS uygulama projesi oluşturun. Oluşturulan uygulama bildirimini aşağıdaki tam programla değiştirin ve hedefte yalnızca bir @main bildirimi bulundurun. Örnek içeriğin ve iki genişletme durumunun tümünü içerir.
import SwiftUI
@main
struct NestedDisclosureDemoApp: App {
var body: some Scene {
WindowGroup {
NativeHierarchyView()
.frame(minWidth: 360, minHeight: 280)
}
}
}
struct NativeHierarchyView: View {
@Environment(\.accessibilityReduceMotion) private var reduceMotion
@State private var collectionExpanded = false
@State private var shapesExpanded = false
private var expansionAnimation: Animation? {
reduceMotion ? nil : .easeInOut(duration: 0.25)
}
var body: some View {
List {
DisclosureGroup(
"Collection",
isExpanded: $collectionExpanded.animation(expansionAnimation)
) {
DisclosureGroup(
"Shapes",
isExpanded: $shapesExpanded.animation(expansionAnimation)
) {
Text("Circle")
Text("Square")
}
Text("Palette")
}
Text("Following item")
}
}
}
Bağlamanın animation yöntemi, değeri değiştiğinde animasyon isteğinde bulunur. Collection öğesine tıklayın, Shapes öğesini genişletin, ardından Collection öğesini daraltıp yeniden açın. Ayrı ayrı saklanan durum, üst öğesi kapalıyken Shapes öğesinin genişletilmiş olup olmadığını örneğin hatırlamasını sağlar. Bu, geçerli görünüm için bellekte tutulan bir durumdur; uygulama başlatmaları arasında kalıcı değildir. Animasyonlu bağlamalar, Apple’ın belgelediği animasyon başlatma noktalarından biridir. Animasyonlar
Örnek, açıklama amacıyla çeyrek saniyelik bir yumuşatma eğrisi kullanır. Sistem Reduce Motion tercihi etkin olduğunda nil değerini geçirir. Apple bu tercihi accessibilityReduceMotion üzerinden sunar ve değer true olduğunda büyük animasyonlardan kaçınılmasını önerir. accessibilityReduceMotion
Yerel sürümü, uygulamanızın desteklediği her macOS sürümünde ve liste stilinde değerlendirin. Davranışı ihtiyacınızı karşılıyorsa bu sürümü kullanın. Yalnızca açılır görünümün ayarlanması gerekiyorsa DisclosureGroupStyle, macOS 13 ve sonraki sürümlerde desteklenen bir özelleştirme noktası sağlar. Özel bir stil yine görünümleri birleştirir; belgeleri, çevresindeki listenin satır animasyonunu istediğiniz gibi denetleme olanağı vaat etmez. DisclosureGroupStyle
Kapsayıcıyı gereken sonuca göre seçin
Daha fazla değiştirici eklemeden önce bu karar yardımını kullanın. Yerel satır etkileşimi ile açıkça denetlenen bir açılımı birbirinden ayırır.
| Gereken sonuç | Başlangıç noktası | Doğrulanacaklar |
|---|---|---|
| Liste seçimi sunan geleneksel genişletilebilir satırlar | List ve DisclosureGroup |
Gerçek macOS sürümündeki ve liste stilindeki genişletme davranışı |
| Farklı açılır etiket veya kontrol görünümü | DisclosureGroupStyle |
Çevresindeki kapsayıcının yine de istenen geometriyi sağlayıp sağlamadığı |
| Küçük bir hiyerarşi için hassas biçimde tanımlanmış açılım yüksekliği | ScrollView, VStack ve animasyonlanabilir bir Layout |
İç içe yeniden boyutlandırma, kırpma ve kontrol davranışı |
| Sütunları ve temsilcileri olan mevcut bir AppKit anahat görünümü | NSOutlineView |
Kendi genişletme API’leri ve veri kaynağı davranışı |
Aşağıdaki özel yaklaşım, kaydırma görünümünün içinde bilerek bir yığın kullanır. Kendi açılır düğmelerini ve geometrisini sağlar; bu nedenle arayüzünüzün ihtiyaç duyduğu liste seçimini veya satır komutlarını da sizin uygulamanız gerekir. Apple, seçimi List özelliği olarak belgeliyor; kapsayıcıyı değiştirmek bu başlangıç noktasını değiştirir. Liste
Görünen yüksekliği özel bir düzenle canlandırın
macOS 13 ve sonraki sürümlerde Layout protokolü, kapsayıcının boyutunu hesaplamasına ve alt görünümlerini yerleştirmesine olanak tanır. Ayrıca Animatable protokolünden miras alır. Bunlar, yalnızca ekleme efektlerine güvenmek yerine genişletme miktarını aradeğerlenen bir sayıya dönüştürmek için belgelenmiş yapı taşlarıdır. Layout
Aşağıdaki özgün uygulama, her açılım için bir alt görünüm yığını kullanır. Bu yığını kullanılabilir genişlikte ve belirtilmemiş bir yükseklikle ölçer, ardından ölçülen yüksekliğin bir kesrini bildirir. Alt görünüm, görünür bölge değişirken üstte konumlandırılmış olarak kalır. Apple, hem bir alt görünümü bir öneriyle ölçmeyi hem de eşleşen bir öneriyle yerleştirmeyi belgeliyor. sizeThatFits(_:), place(at:anchor:proposal:)
Önceki örneğin yerine bu alternatif tam programı kullanın. Dağıtım hedefini macOS 13 veya sonraki bir sürüme ayarlayın. Kod, belgelenmiş API’lere göre incelendi; bu ortamda Swift çalıştırılabilir dosyası veya macOS çalışma zamanı bulunmadığından derlenmedi ya da görsel olarak sınanmadı.
import SwiftUI
@main
struct RevealDemoApp: App {
var body: some Scene {
WindowGroup {
ScrollView {
VStack(alignment: .leading, spacing: 0) {
RevealGroup("Collection") {
RevealGroup("Shapes") {
Text("Circle")
Text("Square")
}
Text("Palette")
}
Text("Following item")
.padding(.vertical, 8)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding()
}
.frame(minWidth: 360, minHeight: 280)
}
}
}
struct RevealGroup<Content: View>: View {
let title: String
let content: Content
@State private var expanded = false
@Environment(\.accessibilityReduceMotion) private var reduceMotion
init(_ title: String, @ViewBuilder content: () -> Content) {
self.title = title
self.content = content()
}
var body: some View {
VStack(alignment: .leading, spacing: 0) {
Button {
withAnimation(
reduceMotion ? nil : .easeInOut(duration: 0.25)
) {
expanded.toggle()
}
} label: {
HStack {
Image(systemName: "chevron.right")
.rotationEffect(.degrees(expanded ? 90 : 0))
.accessibilityHidden(true)
Text(title)
Spacer(minLength: 0)
}
.padding(.vertical, 6)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.accessibilityValue(expanded ? "Expanded" : "Collapsed")
RevealLayout(progress: expanded ? 1 : 0) {
VStack(alignment: .leading, spacing: 8) {
content
}
.padding(.leading, 18)
.padding(.bottom, 8)
.frame(maxWidth: .infinity, alignment: .leading)
}
.clipped()
.allowsHitTesting(expanded)
.disabled(!expanded)
.accessibilityHidden(!expanded)
}
}
}
struct RevealLayout: Layout {
var progress: CGFloat
var animatableData: CGFloat {
get { progress }
set { progress = newValue }
}
func sizeThatFits(
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout ()
) -> CGSize {
guard let child = subviews.first else { return .zero }
let width = proposal.width.flatMap { $0.isFinite ? $0 : nil }
let size = child.sizeThatFits(
ProposedViewSize(width: width, height: nil)
)
return CGSize(
width: width ?? size.width,
height: size.height * min(max(progress, 0), 1)
)
}
func placeSubviews(
in bounds: CGRect,
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout ()
) {
guard let child = subviews.first else { return }
child.place(
at: CGPoint(x: bounds.minX, y: bounds.minY),
anchor: .topLeading,
proposal: ProposedViewSize(width: bounds.width, height: nil)
)
}
}
Kritik değer progress değeridir. SwiftUI, animasyon sırasında eski ve yeni animatableData değerlerini aradeğerler. Burada bu, bildirilen yüksekliği sıfır ile alt görünümün ölçülen yüksekliği arasında sürekli olarak değiştirir. Örnek olarak, ölçülen yükseklik 160 puansa ilerleme 0,5 olduğunda 80 puarlık bir açılım elde edilir; bu sayılar uygulamada ölçülmüş sonuçlar değil, aritmetik örneklerdir. Animatable
Belirtilmemiş yükseklik, alt görünümden geçerli açılım yüksekliğine sığmasını istemek yerine önerilen genişlikteki ideal yüksekliğini ister. Bu, sarmalanan etiketler için önemlidir: Açılım, etiketleri aşamalı olarak sıkıştırmak yerine düzenlerini görünür kılmalıdır. Apple, belirtilmemiş boyutları ideal boyutlandırmayı elde etmenin bir yolu olarak açıklar. ProposedViewSize
Kırpma, bildirilen sınırların dışındaki kısmı gizler. Bu olmadan içerik, düzenin ayırdığı alanın dışına çizilebilir. Alttaki dolguyu, gösterildiği gibi açılımın içinde tutun; böylece daraltılan yüksekliğe katkıda bulunur. clipped(antialiased:)
Bu uygulama, daraltılmış durumda alt görünüm hiyerarşisini bilerek tutar. Etkileşimi devre dışı bırakır ve daraltılmış bölgeyi erişilebilirlik gösteriminden çıkarır; görünür düğme genişletme durumunu bildirir. Vuruş algılama ve erişilebilirlik görünürlüğü ayrı denetimlerdir; bu yüzden tek başına kırpma yeterli değildir. allowsHitTesting(_:), accessibilityHidden(_:)
Özel sürümü kullanmadan önce iç içe davranışı denetleyin
İşleyen bir açılımı, yalnızca en basit durumda doğru görünen bir animasyondan ayırmak için şu adımları izleyin.
Collectionöğesini, ardındanShapesöğesini genişletin. Her iki düzey değişirkenFollowing itemöğesini izleyin.ShapesaçıkkenCollectionöğesini daraltıp yeniden açın. İstenen alt öğe genişletme durumunun korunduğunu doğrulayın.- Animasyon bitmeden bir açılır düğmeye yeniden tıklayın. Açılımın arada boşluk bırakmadan tersine döndüğünü denetleyin.
- Pencereyi daraltın ve örnek etiketlerden birini daha uzun, yapay bir etiketle değiştirin. Metnin kaydırılmasını ve açılımın son yüksekliğini denetleyin.
Reduce Motionözelliğini etkinleştirin ve animasyonlu hareket olmadan genişletmenin kullanılabilir kaldığını doğrulayın.- Klavyeyle etkinleştirmeyi ve hem genişletilmiş hem daraltılmış grupların erişilebilirlik gösterimini denetleyin.
Özel düzen, bu etiketler ve iç içe yığınlar gibi sonlu yüksekliğe sahip içerikler için tasarlanmıştır. Açılımın içine dikey yönde sınırsız bir kaydırma bölgesi koyup bunun kullanışlı bir tam içerik yüksekliği sağlamasını beklemeyin. Uygulama açıkça ideal yükseklik ister; dolayısıyla alt görünümün bu öneriye anlamlı bir yanıtı olmalıdır. Bu, seçilen boyutlandırma tasarımının bir sonucudur. ProposedViewSize
Daraltılmış içeriği tutmak, oluşturma ve düzen maliyetlerini de tutmak anlamına gelir. Küçük bir hiyerarşi için sıradan bir VStack ile başlayın; bu istekli örneği büyük bir ağaca uygulamadan önce başka bir mimariyi değerlendirin. Veri odaklı satırlarda model kimliklerini genişletme ve yeniden sıralama boyunca kararlı tutun. Swift’in Identifiable protokolü, varlıkların kararlı kimliğini temsil eder; ForEach ise görünümler oluşturmak için kimlikli verileri kullanır. Identifiable, ForEach
AppKit ve TextKit’in yeri nedir?
Arayüz zaten AppKit kullanıyorsa NSOutlineView, hiyerarşik satır ve sütunlar için belgelenmiş kontroldür. expandItem(_:) yöntemi bir öğeyi genişletir; öğe zaten genişletilmişse veya genişletilemiyorsa işlem yapmaz. Bu yöntem bir SwiftUI animasyon eğrisi parametresi sunmaz; bu nedenle yukarıda gösterilen özel zamanlamayı sağladığını varsaymayın. NSOutlineView, expandItem(_:)
TextKit, NSTextView tarafından kullanılan mekanizmalar da dahil olmak üzere metin depolamayı, metin düzenini ve oluşturmayı yönetir. Sıradan etiketlerden oluşan bir hiyerarşi için bu, genişleyen kapsayıcıdan ayrı bir konudur. İçerik metin düzenleme veya özel metin düzeni gerektiriyorsa TextKit’i kullanın; SwiftUI liste genişletmesini canlandırmak için belgelenmiş bir seçenek değildir. TextKit
Yerel açılır gruplarla başlayın ve hedeflediğiniz Mac arayüzündeki davranışlarını doğrulayın. Görev denetimli bir yüksekliğe sahip açılım gerektiriyorsa özel düzen, bu yüksekliğe açıkça animasyonlanabilir bir değer kazandırır. Seçimi gereken etkileşime göre yapın: yerel liste davranışı, özel açılım geometrisi veya mevcut bir AppKit anahat görünümü.
Kaynaklar
- DisclosureGroup — Apple
- Animasyonlar — Apple
- move(edge:) — Apple
- Liste — Apple
- accessibilityReduceMotion — Apple
- DisclosureGroupStyle — Apple
- Layout — Apple
- sizeThatFits(_:) — Apple
- place(at:anchor:proposal:) — Apple
- Animatable — Apple
- ProposedViewSize — Apple
- clipped(antialiased:) — Apple
- allowsHitTesting(_:) — Apple
- accessibilityHidden(_:) — Apple
- Identifiable — Apple
- ForEach — Apple
- NSOutlineView — Apple
- expandItem(_:) — Apple
- TextKit — Apple