Als je een programmatisch gemaakte NSTextView tekst wilt laten accepteren, schakel je bewerken in, koppel je de view aan je venster en roep je de makeFirstResponder(_:) van dat venster aan. Het venster moet ook key kunnen worden. Hieronder vind je een compleet voorbeeld en een gerichte reeks stappen voor probleemoplossing.
Wat moet er kloppen voordat typen werkt?
Deze gids behandelt één taak: een klein Mac-appvenster maken waarin iemand meerregelige tekst kan typen en bewerken. De gids gebruikt AppKit rechtstreeks en legt daarna uit welke lifecycle-keuzes daarbij horen wanneer je de editor in SwiftUI insluit.
Drie afzonderlijke voorwaarden zijn van belang. De tekstweergave moet bewerken toestaan, het venster moet toetsenbordgebeurtenissen ontvangen en de tekstweergave moet de bedoelde first responder binnen dat venster zijn. Apple beschrijft de keyWindow van de applicatie als het venster dat momenteel toetsenbordgebeurtenissen ontvangt; alleen een venster weergeven bepaalt niet welke view tekst moet bewerken. Apple: keyWindow
Door isEditable = true in te stellen, sta je bewerken toe en wordt de tekstweergave ook selecteerbaar. Apple vermeldt dat tekstweergaven standaard bewerkbaar zijn, maar door dit expliciet in te stellen is de configuratie in het voorbeeld duidelijk. Alleen bewerkbaarheid bepaalt niet waar toetsenbordinvoer naartoe gaat. Apple: isEditable
Gebruik voor de focus de makeFirstResponder(textView) van het omvattende venster. Apple instrueert ontwikkelaars expliciet om deze methode te gebruiken in plaats van rechtstreeks de becomeFirstResponder() van de tekstweergave aan te roepen. Zo kan het venster de overgang tussen responders coördineren. Apple: NSTextView
Maak een volledig bewerkbaar venster in Swift
Het volgende zelfstandige programma is bedoeld voor macOS 14 of nieuwer en gebruikt een actuele Swift-toolchain met de macOS SDK. De synthetische venstertitel is Local Editor en de beginnende tekst is Sample note. De tekst blijft in het geheugen; dit voorbeeld implementeert geen opslag.
Sla het volledige programma op als EditorDemo.swift. Gebruik het als een zelfstandig uitvoerbaar bestand en voeg het niet toe als tweede entry point aan een bestaande app. Swift staat slechts één entry point per programma toe; het type met @main hieronder levert dat entry point. The Swift Programming Language: Attributes
import AppKit
@MainActor
final class EditorDelegate: NSObject, NSApplicationDelegate {
private var editorWindow: NSWindow?
func applicationDidFinishLaunching(_ notification: Notification) {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 640, height: 420),
styleMask: [.titled, .closable, .miniaturizable, .resizable],
backing: .buffered,
defer: false
)
window.title = "Local Editor"
window.isReleasedWhenClosed = false
let scrollView = NSTextView.scrollableTextView()
guard let textView = scrollView.documentView as? NSTextView else {
fatalError("Expected an NSTextView document view")
}
textView.isEditable = true
textView.isSelectable = true
textView.isRichText = false
textView.allowsUndo = true
textView.font = NSFont.systemFont(ofSize: 16)
textView.textContainerInset = NSSize(width: 12, height: 12)
textView.string = "Sample note\n"
textView.setSelectedRange(
NSRange(location: (textView.string as NSString).length, length: 0)
)
scrollView.frame = NSRect(x: 0, y: 0, width: 640, height: 420)
scrollView.autoresizingMask = [.width, .height]
window.contentView = scrollView
window.initialFirstResponder = textView
editorWindow = window
let mainMenu = NSMenu()
let appMenuItem = NSMenuItem()
mainMenu.addItem(appMenuItem)
let appMenu = NSMenu()
appMenu.addItem(NSMenuItem(
title: "Quit Local Editor",
action: #selector(NSApplication.terminate(_:)),
keyEquivalent: "q"
))
appMenuItem.submenu = appMenu
NSApp.mainMenu = mainMenu
window.center()
window.makeKeyAndOrderFront(nil)
NSApp.activate()
guard textView.window === window,
textView.acceptsFirstResponder else {
print("Editor is not ready to receive focus")
return
}
let accepted = window.makeFirstResponder(textView)
print("Focus request returned:", accepted)
print("Editor is first responder:", window.firstResponder === textView)
print("Window is key:", window.isKeyWindow)
}
func applicationShouldTerminateAfterLastWindowClosed(
_ sender: NSApplication
) -> Bool {
true
}
}
@main
@MainActor
enum EditorDemo {
static func main() {
let application = NSApplication.shared
application.setActivationPolicy(.regular)
let delegate = EditorDelegate()
application.delegate = delegate
withExtendedLifetime(delegate) {
application.run()
}
}
}
Compileer en start het programma op een Mac met Xcode of de Command Line Tools vanuit de map met het bestand:
xcrun swiftc -parse-as-library \
-target "$(uname -m)-apple-macos14.0" \
-framework AppKit EditorDemo.swift -o EditorDemo
./EditorDemo
Het voorbeeld is gecontroleerd aan de hand van de opgehaalde documentatie, maar kon in deze onderzoeksomgeving niet worden gecompileerd of uitgevoerd. Deze omgeving draait op Linux en heeft geen Swift-compiler. Controleer het venstergedrag op macOS met de onderstaande stappen.
Waarom deze configuratie toetsenbordinvoer mogelijk maakt
De convenience-methode scrollableTextView() retourneert een NSScrollView; de code haalt de tekstweergave op via documentView. Door de teksteditor in een scroll view te plaatsen, heeft dit kleine voorbeeld een eenvoudige container voor meerregelige inhoud. De factory is beschikbaar vanaf macOS 10.14. Apple: scrollableTextView()
De code koppelt de container voordat om focus wordt gevraagd. Ook stelt de code initialFirstResponder in, door Apple omschreven als de view die wordt geselecteerd wanneer het venster voor het eerst verschijnt. De latere aanroep van makeFirstResponder(_:) vraagt expliciet om de editor en levert een resultaat op dat je kunt controleren. De initiële focus en een later focusverzoek horen bij verschillende momenten in de lifecycle van het venster. Apple: initialFirstResponder, Apple: makeFirstResponder(_:)
Het venster gebruikt een titelbalkstijl zodat het key kan worden. makeKeyAndOrderFront(nil) vraagt zowel om weergave van het venster als om de key-status. Apple maakt onderscheid met orderFront(_:), dat de volgorde wijzigt zonder het key-venster te veranderen. Apple: canBecomeKey, Apple: makeKeyAndOrderFront(_:)
De aanroep van activate() is de reden dat dit specifieke programma macOS 14 of nieuwer vereist. Apple beschrijft activering als een verzoek, niet als een garantie. Als het venster verschijnt maar geen invoer ontvangt, activeer je de app door op het venster te klikken en controleer je het opnieuw. Zie een onmiddellijke diagnostische print niet als garantie dat de activering al is voltooid. Apple: activate()
Het entry point en de delegate zijn gemarkeerd met @MainActor; Apple definieert de executor daarvan als equivalent aan de main dispatch queue. Het programma start ook de eventloop van AppKit met run(), zodat AppKit na het voltooien van de configuratie gebruikersgebeurtenissen kan verwerken. Apple: MainActor, Apple: run()
Onderzoek een zichtbare editor die toch geen tekst accepteert
Doorloop de volgende stappen in deze volgorde. Zo controleer je afzonderlijk de koppeling, geschiktheid van het venster, responderselectie en bewerkbaarheid. Elke waarneming wijst naar een specifieke vervolgstap.
| Controle | Betekenis van het resultaat | Vervolgstap |
|---|---|---|
textView.window === window |
De editor hoort bij het bedoelde venster. | Als dit onwaar is, voltooi dan eerst de koppeling van de viewhiërarchie en vraag daarna om focus. |
window.canBecomeKey |
Het venster kan de key-status ontvangen. | Als dit onwaar is, controleer dan de stijl of aangepaste subclass. |
window.isKeyWindow |
Dit venster is momenteel key binnen de app. | Activeer de app, selecteer het bedoelde venster en controleer het opnieuw. |
textView.acceptsFirstResponder |
De editor accepteert een focusverzoek. | Als dit onwaar is, controleer dan de configuratie en eventuele overschrijvingen in subclasses. |
window.firstResponder === textView |
Deze specifieke editor heeft de first-responderstatus. | Als dit onwaar is, controleer dan de focusovergang en de huidige responder. |
textView.isEditable |
Bewerken door de gebruiker is ingeschakeld. | Stel dit in op true als bewerken gewenst is. |
De respondercontrole is vooral nuttig omdat makeFirstResponder(_:) een subtiel gedocumenteerd gedrag heeft: de methode kan true retourneren als de aangevraagde responder weigert first responder te worden en het venster zelf first responder wordt. Apple zegt ook dat je acceptsFirstResponder moet controleren voordat je het verzoek expliciet doet. Controleer daarom zowel de retourwaarde als de identiteit van de resulterende firstResponder. Apple: makeFirstResponder(_:)
Een resultaat van false kan betekenen dat de bestaande first responder weigert zijn status op te geven. Bekijk het gedrag van die responder en eventuele betrokken bewerkingsdelegate. Apple vermeldt bijvoorbeeld dat de delegate van een tekstweergave het beëindigen van bewerken kan weigeren via textShouldEndEditing(_:). Hetzelfde focusverzoek herhalen zonder de weigering op te lossen, pakt de oorzaak niet aan. Apple: makeFirstResponder(_:), Apple: NSTextView
Voer voor een eenvoudige handmatige controle Blue square in, druk op Return, voer Green circle in en gebruik daarna de pijltoetsen en Delete om de tekst te bewerken. Verklein of vergroot het venster en typ verder. Dit zijn voorgestelde verificatiestappen, geen gerapporteerde testresultaten. Sluit het venster wanneer je klaar bent; het voorbeeld wordt dan beëindigd.
Wat verandert er bij een venster zonder rand?
Een zichtbaar venster zonder rand kan nog steeds niet key worden. Apple vermeldt dat een gewone NSWindow zonder titelbalkstijl standaard niet key wordt. De ondersteunde aanpassing is om canBecomeKey te overschrijven in een subclass van NSWindow en true terug te geven wanneer dat venster toetsenbordinvoer moet ontvangen. Apple: canBecomeKey
Begin met het voorbeeld met titelbalk om te controleren of tekstinvoer werkt. Als de interface later een venster zonder rand vereist, pas dan de vensterimplementatie aan en behoud dezelfde controles voor koppeling, bewerkbaarheid en responders. Een andere vensterweergave vervangt die vereisten niet.
Hoe laat je dezelfde editor in SwiftUI werken?
Gebruik NSViewRepresentable om een AppKit-tekstweergave in een SwiftUI-hiërarchie te integreren. Apple wijst het maken en de initiële configuratie toe aan makeNSView(context:), het bijwerken van de status aan updateNSView(_:context:) en de coördinatie met delegate- of target-action-berichten aan een coordinator. Apple: NSViewRepresentable
Een praktisch ontwerp voor deze editor is om de scroll view als de gerepresenteerde view terug te geven en een verwijzing naar de documenttekstweergave bij te houden in de coordinator of een aangepaste container. Configureer de bewerkbaarheid tijdens het maken. Stuur wijzigingen in gebruikerstekst door via de delegate als de omringende SwiftUI-interface een binding nodig heeft.
Behandel koppeling als een aparte gebeurtenis, los van het maken. Controleer voordat je om focus vraagt of de window van de editor niet-nil is. Een AppKit-subclass kan viewDidMoveToWindow() gebruiken om wijzigingen in de hiërarchie waar te nemen; Apple vermeldt dat een nil window daar betekent dat de view momenteel in geen venster staat. Dit biedt een gedocumenteerde lifecycle-hook om een focusverzoek te implementeren dat rekening houdt met de koppeling. Apple: viewDidMoveToWindow()
Vraag als implementatiebeleid één keer om initiële focus voor de bedoelde presentatie, of als reactie op een expliciete actie zoals Focus Editor. Roep makeFirstResponder(_:) niet onvoorwaardelijk aan bij elke SwiftUI-update: volgens dat beleid zou de focus telkens terugkeren nadat iemand een ander besturingselement selecteert. Controleer het omvattende venster op het moment van het verzoek in plaats van aan te nemen dat een eerder opgeslagen venster nog steeds geschikt is.
Stel de tekenreeks van de editor ook niet bij elke update opnieuw in. Vergelijk in een wrapper die met een binding werkt de binnenkomende status met de huidige tekst en houd rekening met gemarkeerde tekst en de selectie voordat je inhoud vervangt. Het zelfstandige voorbeeld initialiseert de tekenreeks één keer; voor een gesynchroniseerde editor is een expliciet updatebeleid nodig.
Laat SwiftUI de grootte van de gerepresenteerde hoofdview bepalen. Apple waarschuwt dat het rechtstreeks wijzigen van frame of bounds van die view strijdig is met het layoutbeheer van SwiftUI. De rechtstreekse frameconfiguratie in het zelfstandige AppKit-programma is daarom geen maatgevend patroon voor een hoofdview waarvan SwiftUI de layout beheert. Apple: NSViewRepresentable
Vereist het accepteren van tekstinvoer een aangepaste TextKit-stack?
Gebruik voor deze taak het bestaande bewerkingsgedrag van NSTextView. Apple beschrijft deze als de voorkant van het tekstsysteem die tekst tekent en selectie, wijzigingen, invoerbeheer en sneltoetsen afhandelt. Voor gewoon typen hoef je deze voorzieningen niet opnieuw op te bouwen. Apple: NSTextView
De keuze van de TextKit-engine staat los van toetsenbordfocus. Apple vermeldt dat init(frame:) standaard NSLayoutManager gebruikt, terwijl een handmatig samengestelde tekstweergave op macOS 12 of nieuwer NSTextLayoutManager kan gebruiken via de aangewezen initializer. Kies een engine op basis van je layoutvereisten; van engine wisselen vervangt niet de noodzaak om de editor first responder te maken. Apple: init(frame:textContainer:)
Eén diagnostische stap vereist voorzichtigheid: layoutManager uitlezen is niet noodzakelijk een passieve controle. Apple vermeldt dat toegang tot deze eigenschap op een tekstweergave of tekstcontainer die het nieuwere systeem gebruikt de compatibiliteitsmodus met NSLayoutManager kan activeren. Het voorbeeld vraagt deze eigenschap niet op en veronderstelt niet welke engine de scrollbare convenience-factory kiest. Apple: NSTextView
Een werkend resultaat is concreet: het bedoelde appvenster is actief en key, de bewerkbare tekstweergave is de first responder en typen verandert de weergegeven tekst. Stel dit gedrag eerst vast met het kleine venster en behoud daarna dezelfde controles wanneer je SwiftUI-integratie, aangepaste layout of extra besturingselementen toevoegt.
Bronnen
- NSTextView — Apple.
- isEditable — Apple.
- keyWindow — Apple.
- makeFirstResponder(_:) — Apple.
- initialFirstResponder — Apple.
- makeKeyAndOrderFront(_:) — Apple.
- canBecomeKey — Apple.
- scrollableTextView() — Apple.
- activate() — Apple.
- MainActor — Apple.
- run() — Apple.
- NSViewRepresentable — Apple.
- viewDidMoveToWindow() — Apple.
- init(frame:textContainer:) — Apple.
- Attributes — The Swift Project.