W przypadku niestandardowej, jednokolumnowej kolekcji w aplikacji na Maca użyj SwiftUI List z powiązaniem zaznaczenia opartym na zbiorze stabilnych identyfikatorów elementów. Dostosuj wiersze, pozostawiając natywnej kontrolce obsługę zaznaczania zakresu i zaznaczania z klawiszem Command.
Ten samouczek tworzy kolekcję sztucznych etykiet od Tile 01 do Tile 12. Zadaniem czytelnika jest zaznaczanie sąsiadujących elementów, dodawanie lub usuwanie pojedynczych elementów oraz zachowanie czytelności zaznaczenia podczas filtrowania lub odwracania kolejności wyświetlania. W dalszej części opisano ścieżkę AppKit dla kolekcji wymagającej siatki lub innego niestandardowego układu.
Jak powinno działać zaznaczanie zakresu i z klawiszem Command?
Podręcznik użytkownika Maca firmy Apple opisuje dobrze znaną interakcję: kliknij pierwszy element, a następnie kliknij ostatni z wciśniętym klawiszem Shift, aby zaznaczyć sąsiadujące elementy, w tym wszystkie pomiędzy nimi. Kliknięcie z wciśniętym klawiszem Command zaznacza pojedyncze elementy, które nie muszą sąsiadować; kliknięcie zaznaczonego elementu z wciśniętym klawiszem Command usuwa go z zaznaczenia wielu elementów. To podstawowe interakcje, które należy zachować w tej kolekcji. Zaznaczanie elementów na ekranie Maca.
W początkowej implementacji użyj jednej uporządkowanej listy. Dzięki temu pojęcie „pomiędzy” ma jednoznaczne znaczenie: chodzi o wiersze znajdujące się między dwoma skrajnymi elementami w bieżącej kolejności wyświetlania. Kolekcja przestrzenna wymaga podjęcia dodatkowej decyzji — czy zakres ma podążać za kolejnością danych, czy za obszarem układu — którą należy osobno przetestować.
Wybierz kontrolkę odpowiednio do tego, co użytkownik zaznacza:
| Zadanie czytelnika | Odpowiedni punkt wyjścia | Reprezentacja zaznaczenia |
|---|---|---|
| Zaznaczanie całych niestandardowych wierszy | SwiftUI List |
Zbiór identyfikatorów elementów |
| Zaznaczanie całych kafelków w niestandardowym układzie | AppKit NSCollectionView |
Ścieżki indeksów zaznaczenia mapowane na identyfikatory modelu |
| Zaznaczanie znaków w etykiecie | Zaznaczanie tekstu w SwiftUI | Zaznaczenie tekstu udostępniane przez widok tekstowy |
| Zaznaczanie zakresów w dokumencie tekstowym | System tekstowy AppKit | Zakresy znaków lub tekstu |
Apple opisuje List jako jednokolumnowy kontener z opcjonalnym zaznaczaniem, a NSCollectionView jako uporządkowaną kolekcję z możliwością dostosowania układu. Są to obsługiwane punkty wyjścia do zaznaczania elementów. List, NSCollectionView.
Utwórz kompletny przykład w SwiftUI
Utwórz w Xcode projekt aplikacji na macOS używający SwiftUI. W tym przykładzie ustaw docelową wersję wdrożeniową na macOS 14 lub nowszą: użyta niżej dwuparametrowa zamknięta onChange wymaga macOS 14. Zastąp wygenerowaną deklarację aplikacji tym kodem, pozostawiając w celu dokładnie jeden punkt wejścia @main. onChange(of:initial:_:), App.
Poniższy kod przedstawia kompletną, niewielką aplikację wraz ze sztucznymi danymi i oknem. Nie używa zewnętrznych zasobów. Kod został sprawdzony pod kątem zgodności z udokumentowanymi interfejsami API, ale nie został tutaj skompilowany ani uruchomiony w systemie macOS.
import Foundation
import SwiftUI
struct Tile: Identifiable {
let id: Int
let title: String
}
@main
struct CollectionSelectionDemoApp: App {
var body: some Scene {
WindowGroup {
TileCollectionView()
}
}
}
struct TileCollectionView: View {
private let tiles: [Tile] = (1...12).map {
Tile(id: $0, title: String(format: "Tile %02d", $0))
}
@State private var selectedIDs: Set<Int> = []
@State private var query = ""
@State private var reversed = false
private var visibleTiles: [Tile] {
let filtered = tiles.filter {
query.isEmpty ||
$0.title.localizedCaseInsensitiveContains(query)
}
return reversed ? Array(filtered.reversed()) : filtered
}
private var selectionDescription: String {
let names = visibleTiles
.filter { selectedIDs.contains($0.id) }
.map(\.title)
return names.isEmpty ? "None selected" : names.joined(separator: ", ")
}
var body: some View {
VStack(alignment: .leading, spacing: 12) {
TextField("Filter tiles", text: $query)
.textFieldStyle(.roundedBorder)
HStack {
Button("Reverse order") {
reversed.toggle()
}
Button("Clear selection") {
selectedIDs.removeAll()
}
.disabled(selectedIDs.isEmpty)
}
List(visibleTiles, selection: $selectedIDs) { tile in
HStack(spacing: 12) {
RoundedRectangle(cornerRadius: 4)
.fill(Color.gray.opacity(0.2))
.frame(width: 32, height: 32)
VStack(alignment: .leading) {
Text(tile.title)
Text("Sample item \(tile.id)")
.font(.caption)
.foregroundStyle(.secondary)
}
Spacer()
}
.padding(.vertical, 4)
.tag(tile.id)
}
Text("\(selectedIDs.count) selected")
Text(selectionDescription)
.font(.caption)
}
.padding()
.frame(minWidth: 440, minHeight: 480)
.onChange(of: query) { _, _ in
let visibleIDs = Set(visibleTiles.map(\.id))
selectedIDs.formIntersection(visibleIDs)
}
}
}
Typ elementu powiązania zaznaczenia odpowiada typowi Tile.ID: oba używają Int. Apple dokumentuje, że powiązanie zaznaczenia listy ze zbiorem typu identyfikatora jej danych umożliwia zaznaczanie wielu elementów. Na Macu zaznaczanie wielu elementów nie wymaga przejścia w tryb edycji. Obsługa zaznaczania na listach.
Wiersz nadal można dostosować: zawiera neutralny kształt, tytuł i etykietę dodatkową. Nie ma obsługi kliknięcia na poziomie wiersza ani osobnej operacji przełączania zaznaczenia. Pozostawienie obsługi zaznaczenia liście zapobiega sytuacji, w której dwa niezależne fragmenty kodu aktualizują zaznaczenie w odpowiedzi na tę samą interakcję.
Oddziel tożsamość elementu od kolejności wyświetlania
Przykład celowo rozdziela trzy pojęcia: uporządkowaną tablicę elementów, zbiór zaznaczonych identyfikatorów i przefiltrowaną prezentację tej tablicy. Swift definiuje tablice jako uporządkowane kolekcje, a zbiory jako kolekcje unikatowych wartości bez określonej kolejności. Dlatego zbiór nadaje się do przechowywania informacji o przynależności, a tablica dostarcza kolejności prezentacji. Typy kolekcji.
Identyfikatory całkowite są stałe w tym sztucznym zbiorze danych. Odwrócenie tablicy zmienia położenie elementu, ale nie jego tożsamość. W prawdziwym modelu przypisz każdemu elementowi stabilny identyfikator podczas jego tworzenia lub wczytywania i zachowaj go przy kolejnych aktualizacjach widoku. Nie generuj nowej tożsamości przy każdym wyliczeniu wiersza.
Podsumowanie zaznaczenia przechodzi po visibleTiles i sprawdza przynależność do selectedIDs. To wybór implementacyjny wynikający z właściwości tych kolekcji: podsumowanie powstaje w kolejności wyświetlania, zamiast opierać się na kolejności iteracji zbioru.
Filtrowanie wymaga jawnej decyzji produktowej. W tym przykładzie przyjęto politykę zaznaczania tylko widocznych elementów: po każdej zmianie filtra zaznaczenie jest przecinane ze zbiorem identyfikatorów, które nadal są widoczne. Ukryte elementy tracą zaznaczenie, a wyczyszczenie filtra go nie przywraca. Etykiety i tablica źródłowa pozostają bez zmian.
Na przykład zaznacz Tile 02, Tile 03 i Tile 04. Wpisz 04 w polu Filter tiles. Zgodnie z polityką przyjętą w tym przykładzie zaznaczone pozostaje tylko Tile 04. Usuń filtr, a dwa pozostałe elementy pojawią się ponownie bez zaznaczenia. To ilustracyjny opis działania kodu, a nie zgłoszony wynik jego uruchomienia.
Inna aplikacja może wymagać zachowania zaznaczenia podczas filtrowania. W takim przypadku przenieś zarządzanie zaznaczeniem do modelu, określ sposób uwzględniania ukrytych zaznaczeń w liczniku i podsumowaniu, a następnie przetestuj uzgadnianie stanu z wyświetlaną kontrolką. Samo usunięcie operacji przecięcia nie definiuje kompletnej polityki obsługi ukrytych zaznaczeń.
Kiedy używać widoku kolekcji AppKit?
Użyj NSCollectionView, gdy kolekcja wymaga kafelków w wielu kolumnach lub układu innego niż pojedyncza lista. Apple rozdziela źródło danych kolekcji, kontrolery widoków elementów, delegata i obiekt układu. Dzięki temu możesz dostosowywać wygląd i położenie, pozostawiając obsługę interakcji kontrolce kolekcji. NSCollectionView.
Skonfiguruj implementację AppKit w następującej kolejności:
- Utwórz widok kolekcji i przypisz mu nowoczesny układ, na przykład
NSCollectionViewFlowLayout. - Udostępnij źródło danych i zarejestruj klasę elementu lub plik nib używany do tworzenia komórek.
- Ustaw
isSelectablenatrue, aallowsMultipleSelectionnatrue. - Ustaw
allowsEmptySelectionzależnie od tego, czy zadanie dopuszcza brak zaznaczenia. - Odczytaj
selectionIndexPaths, a następnie mapuj każdą prawidłową ścieżkę indeksu na odpowiadający jej identyfikator modelu. - Wyświetl zaznaczenie w widoku elementu, korzystając ze stanu
isSelected.
Zaznaczanie wielu elementów jest domyślnie wyłączone. Jego włączenie jest więc jawnym krokiem konfiguracji, a nie czymś, co wynika z samego narysowania kafelków wyglądających na zaznaczalne. Właściwość elementu isSelected informuje, czy dany element jest zaznaczony. allowsMultipleSelection, isSelected.
Traktuj ścieżki indeksów jako pozycje w bieżącym źródle danych. Apple dokumentuje, że selectionIndexPaths jest aktualizowane podczas interaktywnego zaznaczania i można je również przypisać programowo. Przypisanie ścieżki indeksu, która nie wskazuje prawidłowego elementu źródła danych, powoduje wyjątek. Po zmianie kolejności lub filtrowaniu odtwórz pozycje zaznaczenia na podstawie stabilnych identyfikatorów zamiast ponownie używać nieaktualnych pozycji. selectionIndexPaths.
Zachowaj udokumentowany zakres działania: allowsMultipleSelection umożliwia zaznaczenie więcej niż jednego elementu; jego dokumentacja nie określa kompletnego algorytmu wyboru punktu początkowego zakresu dla każdego niestandardowego układu ani kombinacji modyfikatorów. Zweryfikuj działanie Shift-kliknięcia w rzeczywistym układzie i obsługiwanych wersjach macOS. Jeśli zadanie wymaga precyzyjnego zakresu liniowego, lepszym punktem wyjścia jest jednokolumnowa implementacja opisana powyżej.
W aplikacji SwiftUI wymagającej kontrolki AppKit osadź ją za pomocą NSViewRepresentable. Utwórz hierarchię AppKit w makeNSView, uzgadniaj zmiany modelu w updateNSView i użyj koordynatora do przekazywania zdarzeń delegata do stanu SwiftUI. SwiftUI steruje ramką i granicami reprezentowanego widoku. NSViewRepresentable.
Aktualizacje programowe również wymagają jawnej ścieżki synchronizacji. Apple informuje, że zarówno collectionView(_:shouldSelectItemsAt:), jak i collectionView(_:didSelectItemsAt:) są pomijane, gdy zaznaczenie jest ustawiane programowo. Obsługuj aktualizacje modelu we wspólnej operacji zamiast zakładać, że te wywołania zwrotne uruchomią się po każdym przypisaniu. collectionView(_:shouldSelectItemsAt:), collectionView(_:didSelectItemsAt:).
Czy zaznaczanie tekstu zapewnia tę samą funkcję?
SwiftUI textSelection(.enabled) steruje zaznaczaniem tekstu w widokach. Zastosowanie go do kontenera sprawia, że każdy zawarty w nim widok tekstowy można zaznaczać. Dokumentacja nie opisuje powiązania zaznaczenia elementów kolekcji ani wspólnego modelu zaznaczania zakresu dla dowolnych kart. Używaj go, gdy zadanie polega na zaznaczaniu znaków w etykiecie. textSelection(_:).
W interfejsie dokumentu tekstowego AppKit udostępnia zaznaczenia znaków przez NSTextView.selectedRanges. Zakresy są uporządkowane, niepokrywające się i nieciągłe. NSTextSelection.textRanges w TextKit również reprezentuje logicznie uporządkowane zakresy tekstu i jest dostępne od macOS 12. Te interfejsy API opisują pozycje w tekście, więc mapowanie ich na identyfikatory kafelków wymaga osobnego projektu właściwego dla aplikacji. selectedRanges, textRanges.
Zanim włączysz zaznaczanie tekstu wewnątrz etykiety wiersza, zdecyduj, jakiej interakcji ma służyć. W zadaniu opisanym w tym samouczku kliknięcie etykiety powinno uczestniczyć w zaznaczaniu całego wiersza, dlatego przykład nie konfiguruje zaznaczania tekstu.
Sprawdź interakcję za pomocą niewielkiej tabeli zaznaczeń
Przeprowadź te kontrole w oknie samej aplikacji. Pierwsze cztery sprawdzają podstawowe konwencje zaznaczania na Macu; pozostałe weryfikują decyzje przykładu dotyczące tożsamości i filtrowania. Podstawą są interakcje opisane przez Apple, a sztuczne etykiety ułatwiają sprawdzenie oczekiwanych zaznaczonych elementów. Zaznaczanie elementów na ekranie Maca.
| Działanie | Oczekiwany wynik |
|---|---|
Kliknij Tile 02 |
Jeden zaznaczony element |
Kliknij Tile 05 z wciśniętym klawiszem Shift |
Zaznaczone elementy od Tile 02 do Tile 05 |
Kliknij Tile 08 z wciśniętym klawiszem Command |
Tile 08 zostaje dodany do zaznaczenia |
Kliknij zaznaczony Tile 03 z wciśniętym klawiszem Command |
Tile 03 zostaje usunięty z zaznaczenia |
Kliknij Clear selection |
Puste zaznaczenie i podsumowanie None selected |
Zaznacz Tile 04, a następnie odwróć kolejność |
Zaznaczony identyfikator nadal wskazuje Tile 04 |
Zaznacz kilka wierszy, a następnie filtruj według 04 |
Zaznaczony pozostaje tylko wcześniej zaznaczony element, który nadal jest widoczny |
| Wyczyść filtr | Ukryte wiersze wracają, ale odrzucone zaznaczenia nie zostają przywrócone |
Sprawdź także nawigację klawiaturą po kliknięciu listy, przewijanie zaznaczonych wierszy poza widok i z powrotem oraz rozpoczęcie nowego zakresu po filtrowaniu. W przypadku siatki AppKit dodaj testy przechodzące przez granicę wiersza. Jeśli wynik różni się od zamierzonego działania, najpierw uprość interfejs do zwykłej natywnej kontrolki, a następnie ponownie dodawaj niestandardową zawartość wierszy i obsługę gestów, po jednej rzeczy naraz.
Pozostaw obsługę interakcji natywnej kontrolce, przechowuj przynależność w oparciu o stabilne identyfikatory, a kolejność powierz wyświetlanej tablicy. Przy tak jasno określonych rolach niestandardowy styl wierszy, filtrowanie i sortowanie mogą wspierać to samo zrozumiałe zadanie zaznaczania.
Źródła
- Zaznaczanie elementów na ekranie Maca — Wsparcie Apple.
- List — Dokumentacja dla deweloperów Apple.
- App — Dokumentacja dla deweloperów Apple.
- onChange(of:initial:_:) — Dokumentacja dla deweloperów Apple.
- Typy kolekcji — Język programowania Swift, projekt Swift.
- NSCollectionView — Dokumentacja dla deweloperów Apple.
- allowsMultipleSelection — Dokumentacja dla deweloperów Apple.
- selectionIndexPaths — Dokumentacja dla deweloperów Apple.
- isSelected — Dokumentacja dla deweloperów Apple.
- NSViewRepresentable — Dokumentacja dla deweloperów Apple.
- collectionView(_:shouldSelectItemsAt:) — Dokumentacja dla deweloperów Apple.
- collectionView(_:didSelectItemsAt:) — Dokumentacja dla deweloperów Apple.
- textSelection(_:) — Dokumentacja dla deweloperów Apple.
- selectedRanges — Dokumentacja dla deweloperów Apple.
- textRanges — Dokumentacja dla deweloperów Apple.