Usa NSTokenField di AppKit all’interno di SwiftUI, restituendo un oggetto personalizzato per le etichette riconosciute e una stringa per il testo normale. Scegli lo stile di visualizzazione per ogni oggetto, così le etichette note appaiono come token mentre il testo libero resta semplice.
Questa guida crea un editor compatto di etichette nella tua app per Mac. Il suo vocabolario sintetico contiene Cedar e Slate; si possono inserire anche frasi come draft notes. L’editor restituisce i suoi contenuti al termine della modifica. È un campo per voci miste, in cui le virgole fungono da separatori, non un editor di frasi che conserva ogni segno di punteggiatura.
Scegli il controllo adatto all’attività di modifica
Apple descrive NSTokenField come un campo di testo che converte il testo inserito in token visivamente distinti. Il suo delegato può fornire oggetti rappresentati, stringhe di visualizzazione, stringhe modificabili e uno stile separato per ogni oggetto. Questi punti di estensione sono il punto di partenza supportato per questa interfaccia. NSTokenField, NSTokenFieldDelegate.
Prima di implementarlo, fai una distinzione: accettare voci arbitrarie e modificare un documento arbitrario sono attività diverse. Un campo di etichette separate da virgole può usare le virgole come separatori. Un editor di modelli potrebbe dover conservare virgole, spazi e testo nelle posizioni esatte attorno agli oggetti incorporati.
| Attività di modifica | Punto di partenza appropriato |
|---|---|
| Inserire etichette riconosciute insieme a voci di testo arbitrarie | NSTokenField con stili per singolo oggetto |
| Inserire un’unica stringa normale | SwiftUI TextField |
| Comporre testo continuo con oggetti incorporati e punteggiatura esatta | NSTextView con testo attribuito e allegati |
SwiftUI documenta TextField come un’interfaccia modificabile associata a un valore. NSViewRepresentable fornisce il ponte documentato per inserire una vista AppKit in SwiftUI. Per modificare documenti più articolati, NSTextView supporta testo formattato, allegati, selezione, gestione dell’input e testo contrassegnato. Queste funzionalità giustificano le scelte indicate sopra; la tabella è una raccomandazione implementativa che ne deriva. TextField, NSViewRepresentable, NSTextView.
Mantieni distinta l’identità dei token dal testo normale
Usa un modello ordinato con due casi: un token riconosciuto e testo letterale. In questo modo conservi una distinzione che andrebbe persa appiattendo tutto in un’unica stringa di visualizzazione. Gli array Swift mantengono l’ordine degli elementi; la guida Apple sui campi token specifica che objectValue contiene un array di oggetti rappresentati, comprese le stringhe quando i valori rappresentati sono stringhe. Array, Ottenere e impostare i valori dei campi token.
In questo esempio, il riconoscimento è volutamente esatto e distingue tra maiuscole e minuscole. Cedar diventa un token; cedar resta testo. La conversione non rimuove spazi, non converte in minuscolo, non rifiuta e non deduplica le voci normali. Questa regola rende l’esempio prevedibile senza riscrivere silenziosamente ciò che è stato inserito.
I tre file seguenti compongono un’app di esempio completa. In Xcode, crea un progetto App per macOS che usi SwiftUI e un SDK macOS aggiornato. Sostituisci il punto di ingresso dell’app generato con il terzo file e aggiungi i primi due allo stesso target. Il codice usa i casi di stile dei token documentati attualmente, tra cui .none e .rounded. Gli esempi meno recenti potrebbero usare nomi diversi; verifica l’SDK con cui compili. NSTokenField.TokenStyle.
Definisci il modello e l’oggetto rappresentato
Inserisci questo codice in Entry.swift. L’oggetto immutabile LabelToken è quello che AppKit riceve per un’etichetta riconosciuta. Entry è il modello di valore restituito all’interfaccia SwiftUI.
import Foundation
enum Entry: Equatable {
case token(String)
case text(String)
var summary: String {
switch self {
case .token(let value):
return "Token: \(String(reflecting: value))"
case .text(let value):
return "Text: \(String(reflecting: value))"
}
}
}
final class LabelToken: NSObject {
let label: String
init(_ label: String) {
self.label = label
super.init()
}
}
enum FieldReadError: Error {
case unexpectedValue
case unexpectedObject
}
Inserisci il campo nativo in SwiftUI
Inserisci questo codice in MixedEntryField.swift. Il wrapper inizializza il controllo una sola volta e mantiene aggiornato il callback durante gli aggiornamenti di SwiftUI. Il suo coordinatore implementa il delegato AppKit. Questo segue il ciclo di vita documentato da Apple per creazione, aggiornamento e coordinatore. SwiftUI gestisce il layout della vista rappresentata, quindi il wrapper non ne imposta la cornice. NSViewRepresentable.
import AppKit
import SwiftUI
@MainActor
struct MixedEntryField: NSViewRepresentable {
let initialEntries: [Entry]
var onCommit: (Result<[Entry], FieldReadError>) -> Void
func makeCoordinator() -> Coordinator {
Coordinator(onCommit: onCommit)
}
func makeNSView(context: Context) -> NSTokenField {
let field = NSTokenField()
field.delegate = context.coordinator
field.isEditable = true
field.isSelectable = true
field.placeholderString = "Cedar, Slate, or free text"
field.tokenizingCharacterSet = CharacterSet(charactersIn: ",")
field.setAccessibilityLabel("Labels and free text")
field.objectValue = initialEntries.map { entry -> Any in
switch entry {
case .token(let label):
return LabelToken(label)
case .text(let text):
return text
}
}
return field
}
func updateNSView(_ field: NSTokenField, context: Context) {
context.coordinator.onCommit = onCommit
}
final class Coordinator: NSObject, NSTokenFieldDelegate {
var onCommit: (Result<[Entry], FieldReadError>) -> Void
private let knownLabels: Set<String> = ["Cedar", "Slate"]
init(onCommit: @escaping
(Result<[Entry], FieldReadError>) -> Void) {
self.onCommit = onCommit
super.init()
}
func tokenField(
_ tokenField: NSTokenField,
representedObjectForEditing editingString: String
) -> Any? {
if knownLabels.contains(editingString) {
return LabelToken(editingString)
}
return editingString
}
func tokenField(
_ tokenField: NSTokenField,
styleForRepresentedObject object: Any
) -> NSTokenField.TokenStyle {
object is LabelToken ? .rounded : .none
}
private func label(for object: Any) -> String? {
if let token = object as? LabelToken {
return token.label
}
return object as? String
}
func tokenField(
_ tokenField: NSTokenField,
displayStringForRepresentedObject object: Any
) -> String? {
label(for: object)
}
func tokenField(
_ tokenField: NSTokenField,
editingStringForRepresentedObject object: Any
) -> String? {
label(for: object)
}
func controlTextDidEndEditing(_ notification: Notification) {
guard let field = notification.object as? NSTokenField else {
return
}
onCommit(readEntries(from: field))
}
private func readEntries(
from field: NSTokenField
) -> Result<[Entry], FieldReadError> {
guard let value = field.objectValue else {
return .success([])
}
guard let objects = value as? [Any] else {
return .failure(.unexpectedValue)
}
var entries: [Entry] = []
for object in objects {
if let token = object as? LabelToken {
entries.append(.token(token.label))
} else if let text = object as? String {
entries.append(.text(text))
} else {
return .failure(.unexpectedObject)
}
}
return .success(entries)
}
}
}
Il metodo di conversione restituisce la stringa originale in fase di modifica per una voce non riconosciuta. Apple documenta esplicitamente la restituzione della stringa di modifica quando non esiste un oggetto rappresentato personalizzato. Il delegato dello stile sostituisce lo stile predefinito del campo per ciascun oggetto; le etichette riconosciute usano .rounded e le stringhe .none. La guida architetturale archiviata di Apple descrive inoltre come sostituire i token incapsulati con token di testo semplice tramite questo metodo del delegato. representedObjectForEditing, styleForRepresentedObject, Come funzionano i campi token.
Sono importanti entrambi i metodi di conversione delle stringhe. Il metodo di visualizzazione fornisce un’etichetta leggibile per un oggetto rappresentato. Il metodo di modifica fornisce la sua forma testuale modificabile; Apple documenta la restituzione di nil in quel metodo quando un token non deve essere modificabile. Questo esempio restituisce invece l’etichetta. displayStringForRepresentedObject, editingStringForRepresentedObject.
Aggiungi una piccola interfaccia per ispezionare i valori confermati
Inserisci questo codice in MixedTokenDemoApp.swift. Mantieni una sola dichiarazione @main dell’app nel target. Il secondo campo di testo offre un punto di destinazione esplicito per spostare il focus della tastiera fuori dal campo misto.
import SwiftUI
@main
struct MixedTokenDemoApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
@MainActor
struct ContentView: View {
@State private var focusDestination = ""
@State private var output = "Finish editing to inspect the entries."
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text("Labels and free text")
.font(.headline)
MixedEntryField(
initialEntries: [.token("Cedar")]
) { result in
switch result {
case .success(let entries):
output = entries.isEmpty
? "No entries."
: entries.map(\.summary).joined(separator: "\n")
case .failure:
output = "Could not read all entries. The field was kept."
}
}
.frame(height: 28)
Text("Use commas between entries; then move to the next field.")
.font(.caption)
TextField("Move focus here to finish editing",
text: $focusDestination)
Text(output)
.font(.system(.body, design: .monospaced))
.textSelection(.enabled)
}
.padding(20)
.frame(minWidth: 540, minHeight: 250)
}
}
Conferma le modifiche senza interrompere la modifica in corso
Questo esempio espone uno snapshot confermato invece di un binding che cambia a ogni pressione di tasto. Apple definisce controlTextDidEndEditing come la notifica che segnala la fine della modifica e la conferma delle modifiche. Leggere gli oggetti rappresentati in quel momento offre all’interfaccia circostante un punto preciso in cui usare il risultato. controlTextDidEndEditing.
Evita di assegnare objectValue incondizionatamente da updateNSView. Apple documenta che impostare questa proprietà mentre una cella è in fase di modifica interrompe la modifica prima di assegnare il nuovo valore. Leggerla durante la modifica richiama anche la convalida. Sono operazioni rilevanti, non una sincronizzazione passiva. objectValue.
Il wrapper tratta quindi initialEntries come valore iniziale. Cambiare questo argomento dopo la creazione non ricarica il campo. Se in seguito la tua applicazione deve applicare sostituzioni esterne, progetta un’operazione di sostituzione esplicita con una regola per le modifiche in sospeso. Prima di sostituire i valori rappresentati, termina e conferma la modifica corrente oppure scartala deliberatamente.
Il decodificatore evita anche compactMap: un oggetto rappresentato non riconosciuto genera un errore invece di scomparire dal risultato. L’interfaccia segnala l’errore e lascia intatto il campo. È importante quando si estende l’esempio con altri tipi di oggetto.
Verifica il comportamento delle voci miste sulle versioni di macOS di destinazione
Il codice di questo documento è stato esaminato confrontandolo con le dichiarazioni API recuperate; non è stato compilato né eseguito qui, perché l’ambiente disponibile è Linux e non include una toolchain Swift. Compila e prova l’app su un Mac prima di integrarla in un’interfaccia più ampia.
Usa questa sequenza diagnostica per distinguere i problemi di riconoscimento, stile e conferma:
- Verifica che l’oggetto iniziale
Cedarabbia l’aspetto di un token. - Aggiungi
Slatee digita una virgola. Verifica che l’etichetta riconosciuta assuma l’aspetto di un token. - Aggiungi
draft notes, quindi sposta il focus sul secondo campo. Verifica che la frase resti semplice e compaia come testo nell’output confermato. - Inserisci
cedarin minuscolo. Verifica che resti testo, secondo la regola di corrispondenza esatta dell’esempio. - Modifica un’etichetta riconosciuta esistente trasformandola in una frase sconosciuta, termina la modifica e controlla il tipo risultante.
- Rimuovi tutte le voci e termina la modifica. Verifica che l’output indichi che il campo è vuoto.
- Prova la selezione, l’eliminazione, l’annullamento e la composizione del testo con i metodi di input che intendi supportare. Verifica che gli aggiornamenti SwiftUI non correlati non interrompano la modifica.
In questo esempio la virgola è sintassi: separa le voci e non viene conservata come contenuto letterale. Gli spazi non sono configurati come separatori. Non aggiungere altri delimitatori alla leggera se le frasi devono poter contenere quei caratteri. L’insieme dei delimitatori è controllato tramite tokenizingCharacterSet. tokenizingCharacterSet.
Non usare ripetute pressioni del tasto Invio come unico test di conferma. La descrizione archiviata di Apple spiega che Invio può convertire la stringa corrente in un token oppure invocare l’azione del controllo. Spostare il focus su un altro controllo modificabile rende più semplice esaminare il termine della modifica in questo esempio. L’archivio fornisce il contesto sul comportamento; la documentazione attuale fornisce le dichiarazioni Swift usate sopra. Come funzionano i campi token.
Quando dovresti usare TextKit invece?
Scegli il sistema di testo più articolato quando il risultato richiesto è prosa continua con token incorporati in posizioni arbitrarie. Per esempio, conservare la punteggiatura esatta in Arrange [Cedar], then add a note. richiede un contratto di modifica diverso da quello che consuma le virgole tra le voci.
La base supportata è NSTextView con testo attribuito e oggetti NSTextAttachment. Apple documenta gli allegati come valori di attributo in una stringa attribuita, con una cella dell’allegato su macOS per il disegno e l’interazione con il mouse. Le funzionalità degli allegati basate su viste sono documentate inoltre per macOS 12 e versioni successive. Queste API forniscono elementi di base; non includono il modello completo dei token, la regola di inserimento o il formato di esportazione della tua app. NSTextView, NSTextAttachment.
Se scegli questa strada, rispetta i vincoli di TextKit: Apple avverte che l’accesso esplicito a layoutManager di un NSTextView o di un contenitore di testo su macOS 12 e versioni successive lo fa passare alla modalità di compatibilità basata su NSLayoutManager. Decidi quale sistema di layout del testo userà la tua implementazione prima di combinare esempi. NSTextView.
Per un campo compatto di etichette riconosciute e voci di testo libero, inizia con il campo token nativo e le conversioni del suo delegato. Mantieni intatto il testo normale, rendi espliciti i separatori e leggi gli oggetti rappresentati confermati. Passa a un editor di documenti quando la struttura esatta del testo incorporato diventa parte del requisito.
Fonti
- NSTokenField — Apple
- NSTokenFieldDelegate — Apple
- NSTokenField.TokenStyle — Apple
- tokenField(_:representedObjectForEditing:) — Apple
- tokenField(_:styleForRepresentedObject:) — Apple
- tokenField(_:displayStringForRepresentedObject:) — Apple
- tokenField(_:editingStringForRepresentedObject:) — Apple
- tokenizingCharacterSet — Apple
- NSViewRepresentable — Apple
- TextField — Apple
- objectValue — Apple
- controlTextDidEndEditing(_:) — Apple
- Array — Apple
- Ottenere e impostare i valori dei campi token — Archivio della documentazione Apple
- Come funzionano i campi token — Archivio della documentazione Apple
- NSTextView — Apple
- NSTextAttachment — Apple