Bir grubu ve alt öğelerini birlikte taşımak için alt öğeleri üst modelin içinde saklayın ve üst koleksiyonu yeniden sıralayın. Bu kılavuz, her grubun tek bir satır olarak taşındığı ve alt öğelerinin gruba bağlı kaldığı küçük bir SwiftUI Mac listesi oluşturuyor.
Görev, kendi uygulamanızdaki grupları yeniden sıralamaktır: örneğin, Tile A1 ve Tile A2 öğelerini içeren Group A grubunu Group B grubunun altına taşımak. Örnek, sentetik yerel veriler kullanır ve SwiftUI’nin liste taşıma geri çağrısının yanında açık Move Up ve Move Down düğmeleri sunar.
Taşınabilir tek bir öğe olarak ne sayılmalı?
Taşıma birimini verilerinizdeki birimle eşleştirin. Bu arayüzde dış dizi grupları, her grup da kendi alt öğe dizisini içerir. Bu dış diziyi yeniden sıralamak, her grubun alt öğelerini ve bunların mevcut sırasını korurken grup sırasını değiştirir.
Her grubu bileşik bir liste satırı olarak gösterin: VStack içinde bir başlık, taşıma düğmeleri ve girintili alt öğe etiketleri. onMove(perform:) öğesini dıştaki ForEach öğesine ekleyin. Apple, geri çağrıdaki ofsetlerin bu dinamik görünümün temelindeki koleksiyona karşılık geldiğini belgeliyor; burada bu koleksiyon grup dizisidir. Apple’ın taşıma geri çağrısı belgeleri.
Gruplara ve alt öğelere kararlı tanımlayıcılar verin. Apple’ın ForEach yapısı, Identifiable üzerinden veya açık bir tanımlayıcı anahtar yolu kullanılarak tanımlanmış öğeler gerektirir. Bu tasarımda tanımlayıcılar modellere aittir ve konumları değiştiğinde de değişmeden kalır. Yeniden sıralanabilir içeriğin kimliği olarak dizi konumlarını kullanmaktan kaçının. Apple’ın ForEach belgeleri.
SwiftUI Group görünümü, aşağıda kullanılan uygulama modeli değildir. Bir kapsayıcıya ad vermek veya başlık göstermek, hangi alt kayıtların onunla birlikte taşınması gerektiğini belirlemez. Üst öğenin sakladığı children dizisi bu ilişkiyi açıkça ortaya koyar.
Eksiksiz bir SwiftUI örneği oluşturun
Xcode’da SwiftUI arayüzünü kullanan ve dağıtım hedefi macOS 12 veya üzeri olan bir macOS uygulama projesi oluşturun. Oluşturulan uygulama giriş dosyasını aşağıdaki eksiksiz programla değiştirin; hedefte yalnızca bir @main uygulama bildirimi bırakın. Örnek, taşımayı incelemeyi kolaylaştırmak için özellikle sade bir liste ve metin düğmeleri kullanır.
import Foundation
import SwiftUI
private struct DemoItem: Identifiable {
let id: String
let title: String
}
private struct DemoGroup: Identifiable {
let id: String
let title: String
let children: [DemoItem]
}
@main
struct GroupMoveDemoApp: App {
var body: some Scene {
WindowGroup {
GroupCollectionView()
}
}
}
private struct GroupCollectionView: View {
@State private var groups: [DemoGroup] = [
DemoGroup(
id: "group-a", title: "Group A",
children: [
DemoItem(id: "a1", title: "Tile A1"),
DemoItem(id: "a2", title: "Tile A2")
]
),
DemoGroup(
id: "group-b", title: "Group B",
children: [
DemoItem(id: "b1", title: "Tile B1")
]
),
DemoGroup(
id: "group-c", title: "Group C",
children: []
)
]
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text("Arrange Groups")
.font(.title2)
.padding(.horizontal)
List {
ForEach(groups) { group in
VStack(alignment: .leading, spacing: 8) {
HStack {
Text(group.title).font(.headline)
Spacer()
Button("Move Up") {
moveOne(id: group.id, down: false)
}
.disabled(group.id == groups.first?.id)
Button("Move Down") {
moveOne(id: group.id, down: true)
}
.disabled(group.id == groups.last?.id)
}
.buttonStyle(BorderlessButtonStyle())
ForEach(group.children) { child in
Text(child.title)
.padding(.leading, 16)
}
}
.padding(.vertical, 8)
}
.onMove(perform: moveGroups)
}
}
.padding(.vertical)
.frame(minWidth: 520, minHeight: 320)
}
private func moveGroups(from source: IndexSet, to destination: Int) {
withAnimation {
groups.move(fromOffsets: source, toOffset: destination)
}
}
private func moveOne(id: String, down: Bool) {
guard let index = groups.firstIndex(where: { $0.id == id }) else {
return
}
let neighbor = down ? index + 1 : index - 1
guard groups.indices.contains(neighbor) else {
return
}
let destination = down ? index + 2 : index - 1
moveGroups(from: IndexSet(integer: index), to: destination)
}
}
Dıştaki ForEach, her grup için bir VStack oluşturur. İç içe ForEach, alt öğeleri bu satırın içinde gösterir; ayrı bir alt öğe taşıma işleyicisi yoktur. Her iki taşıma düğmesi de grubun mevcut konumunu kimliğine göre bulur ve onMove tarafından kullanılan değişikliğin aynısını çağırır.
Uygulamayı çalıştırın ve Group A yanındaki Move Down düğmesine tıklayın. Beklenen sıra Group B, Group A, Group C olur; Tile A1 ve Tile A2 yine Group A altında görünür. Bu grubun yanındaki Move Up düğmesine tıklamak başlangıç sırasını geri getirir. Group C, alt öğesi olmayan bir grubun da taşınabilir olduğunu gösterir.
Bu program sıralamayı geçerli görünümün ömrü boyunca bellekte tutar; herhangi bir kalıcı depolama katmanı içermez. Başvuruları ve taşıma mantığı incelendi, ancak kullanılabilir kabukta Swift derleyicisi olmadığı için burada derlenmedi. Uygulamaya entegre etmeden önce Mac’te Xcode ile derleyip arayüzü kontrol edin.
Aşağı taşımada neden farklı bir hedef kullanılır?
move(fromOffsets:toOffset:) öğesine geçirilen hedef, özgün koleksiyondaki bir ekleme sınırıdır. Apple bunu, taşınan öğelerin önüne ekleneceği ve 0...count aralığında bulunan ofset olarak tanımlar. Tek bir öğenin kendi ofsetini veya onu izleyen ofseti geçirmek hiçbir değişiklik yapmaz. Apple’ın koleksiyon taşıma belgeleri.
Örnek başlangıç sırası [A, B, C] için bu bağımsız işlemler aradaki farkı gösterir:
| İstenen sonuç | Kaynak ofseti | move öğesine geçirilen hedef |
Sonuç |
|---|---|---|---|
A öğesini B öğesinin altına taşı |
0 |
2 |
[B, A, C] |
B öğesini A öğesinin üstüne taşı |
1 |
0 |
[B, A, C] |
A öğesini sona taşı |
0 |
3 |
[B, C, A] |
A öğesini yerinde bırak |
0 |
1 |
[A, B, C] |
Aşağı taşıma düğmesinin index + 2 kullanmasının nedeni budur: bir sonraki komşudan sonraki öğenin önüne veya listenin sonuna ekleme yapar. Yukarı taşıma düğmesi index - 1 kullanır. onMove tarafından doğrudan sağlanan ofsetler için bu düğmeye özgü hesabı yeniden uygulamadan değerleri koleksiyon yöntemine iletin.
Örnekteki hedef hesabı tek bir komşu taşıma işlemi için geçerlidir. Taşıma geri çağrısında birden çok kaynak ofseti varsa, taşınan öğelerin sırasının korunacağını belgeleyen koleksiyon yöntemiyle tam IndexSet değerini kullanın. Apple’ın koleksiyon taşıma belgeleri.
Hangi arayüz için farklı bir uygulama gerekir?
Uygulamayı, grubun nasıl göründüğüne ve kullanıcıların alt öğeleriyle nasıl etkileşim kurması gerektiğine göre seçin. Bileşik satır örneği, bir grubun her zaman tek blok olarak taşınması gerektiğinde uygun bir seçenektir. Alt öğelere bağımsız liste satırları yerine bu bloğun içinde etiketler verir.
| Arayüz gereksinimi | Uygun başlangıç noktası | Taşıma sorumluluğu |
|---|---|---|
| Grup başlığı ve alt öğeleri tek bir taşınabilir blok oluşturuyor | Her grup için bir bileşik satır içeren dış ForEach |
Üst diziyi yeniden sıralayın |
| İç içe geçmiş alt öğeleri olan genişletilebilir hiyerarşi | OutlineGroup |
Amaçlanan üst koleksiyon için model taşıma mantığını uygulayın |
| Her grubun bir bölüm olduğu AppKit koleksiyonu | NSCollectionView |
Veri kaynağını güncelleyin, ardından bölüm taşımasını koleksiyon görünümüne bildirin |
| Bir öğe içinde özel düzenlenebilir metin | Öğenin düzenleyicisinde TextKit | Grup sıralamasını çevresindeki koleksiyon modelinde tutun |
Genişletilebilir ağaçlar için açık taşıma kuralları gerekir
Apple, OutlineGroup yapısını dalları genişletip daraltmak için görünürlük denetimleri içeren, tanımlanmış ve ağaç biçiminde düzenlenmiş verilerin görünümü olarak açıklar. macOS 11 ve sonraki sürümlerde kullanılabilir. Belgelenen sunum davranışı, uygulamanız için genel bir alt ağaç yeniden sıralama politikası belirlemez. Apple’ın OutlineGroup belgeleri.
Bir ağaç için hedefi kesin olarak tanımlayın: bir üst öğeyi kardeşleri arasında taşımakla başka bir üst öğenin içine taşımak farklı işlemlerdir. Kardeşleri yeniden sıralamak, tek bir üst diziyi değiştirir. Üst öğeyi yeniden ebeveynlemek; düğümü eski üst öğesinden kaldırmayı, yeni üst öğeye eklemeyi ve hedefin düğümün kendi alt öğelerinden biri olmasını engellemeyi gerektirir. Bunlar ağaç yapısından türetilen uygulama modeli kurallarıdır.
Görev en üst düzey grupları bir bütün olarak taşımaksa bileşik satır uygulaması bu ek hedef politikasına gerek bırakmaz. Alt öğelerin bağımsız olarak seçilmesi veya yeniden sıralanması gerekiyorsa, hiyerarşiyi görünür satırlara düzleştirmeden önce bu etkileşimleri açıkça tasarlayın.
AppKit, bir bölümü içindeki öğelerle birlikte taşımayı destekler
Arayüzünüzde her grubun bir bölüm olarak gösterildiği bir NSCollectionView varsa Apple, moveSection(_:toSection:) API’sini sunar. Belgeleri, bölümlerin ve içerdikleri öğelerin yeniden düzenlenmesini açıkça kapsar ve API çağrılmadan önce veri kaynağının güncellenmesini gerektirir. API macOS 10.11 ve sonraki sürümlerde kullanılabilir. Apple’ın bölüm taşıma belgeleri.
Bu yolu izlerken kaynak ve son hedef bölüm dizinlerini belirleyin, grup verilerini yeniden sıralayın ve bölüm taşıma API’siyle değişikliği koleksiyon görünümüne bildirin. SwiftUI ekleme sınırı hesabını, AppKit’in beklediği hedef bölüm dizininden ayrı tutun. Bu iki yöntem hedeflerini farklı biçimlerde tanımlar.
Bir AppKit koleksiyonunu SwiftUI içine yerleştirmek için AppKit görünümü oluşturup güncellemeye yönelik Apple belgeli sarmalayıcı olan NSViewRepresentable yapısını kullanın. Koleksiyon etkileşimlerini uygun koordinasyon mekanizmasıyla SwiftUI modeline iletin ve gösterilen görünümün dış yerleşimini SwiftUI’nin yönetmesini sağlayın. Apple’ın NSViewRepresentable belgeleri.
TextKit, bir öğenin içindeki metin düzenini yönetir
Apple, macOS 12 ve sonraki sürümlerde kullanılabilen NSTextLayoutManager yapısını özel metin düzeni ve sunumunu yöneten bileşen olarak tanımlar. Sorumlulukları metin kapsayıcıları, öğeleri ve düzen parçalarıyla ilgilidir. Bu koleksiyon görevi açısından mimari sonuç, bir alt öğe özel bir TextKit düzenleyicisi içerse bile grup sıralamasını koleksiyon modelinde tutmaktır. Apple’ın NSTextLayoutManager belgeleri.
Yanlış taşınan bir grubu nasıl teşhis edersiniz?
En küçük görünür örnekle başlayın ve tek bir işlemden sonra modeli inceleyin. Aşağıdaki kontroller, sıralama hatasını görüntülenen satırlarla temel veriler arasındaki uyumsuzluktan ayırmaya yardımcı olur.
- Yalnızca başlık taşınıyor: Alt öğelerin düz bir dizide ilişkisiz girdiler olarak saklanıp saklanmadığını kontrol edin. Onları içeren üst nesneyi taşıyın veya düz gösterim için açık bir blok taşıma işlemi uygulayın.
- Aşağı taşıma hiçbir şey yapmıyor: Düğmenin
index + 1geçirip geçirmediğini kontrol edin. Tek bir kaynak öğesi için bu sınır öğeyi yerinde bırakır. - Filtreleme sonrasında yanlış grup taşınıyor: Görünür ofsetlerin tam diziye uygulanıp uygulanmadığını kontrol edin. Değişiklikten önce görünür grup kimliklerini kanonik modelde karşılıklarına çözümleyin ve filtrelenmiş bir hedefin nereye ait olacağını tanımlayın.
- Sıra hemen geri dönüyor: Görüntülenen dizinin her çizimde yeniden sıralanıp sıralanmadığını kontrol edin. Elle sıralamanın, görüntülemenin gerçekten koruduğu bir sıraya dayanması gerekir.
- Bir alt öğe bağımsız taşınıyor: Taşıma işleyicisinin nereye eklendiğini kontrol edin. Bu örnekte işleyici yalnızca dış grup koleksiyonuna aittir.
Entegrasyondan önce listenin başına ve sonuna taşıma, geriye tek grup kalması ve boş grup durumlarını kontrol edin. Görünümün yanı sıra model değişmezlerini de doğrulayın: aynı grup kimlikleri kalmalı, her grup aynı alt öğe kimliklerini korumalı ve alt öğelerin sırası değişmemelidir. Bu kontroller, üst öğe ile alt öğelerinin birlikte taşınması gereksinimini doğrudan sınar.
Kompakt bir Mac arayüzü için taşınabilir her satırda bir üst model ve ortak bir yeniden sıralama işleviyle başlayın. Böylece kullanıcı, alt öğeleri bağlı kalırken Group A grubunu tek bir işlemle taşıyabilir. Arayüz bağımsız alt öğe etkileşimi veya bölüm tabanlı koleksiyon davranışı gerektirdiğinde hiyerarşik ya da AppKit uygulaması ekleyin.
Kaynaklar
- onMove(perform:) — Apple
- ForEach — Apple
- move(fromOffsets:toOffset:) — Apple
- OutlineGroup — Apple
- moveSection(_:toSection:) — Apple
- NSViewRepresentable — Apple
- NSTextLayoutManager — Apple