
Natural Language
- 2.6k installs
- 944 repo stars
- Updated July 15, 2026
- dpearson2699/swift-ios-skills
natural-language is a Swift skill for Apple NaturalLanguage analysis and Translation framework in-app language features.
About
NaturalLanguage plus Translation covers on-device text analysis and in-app translation for iOS, macOS, and visionOS. NaturalLanguage APIs include NLTokenizer for word sentence and paragraph segmentation, NLTagger for language identification, part-of-speech tagging, named entity recognition, and sentiment scoring, plus NLEmbedding for word and sentence vectors. Custom NLModel classifiers and taggers are supported for domain-specific labeling. Translation framework adds TranslationSession and LanguageAvailability on iOS 18 plus with system presentation on iOS 17.4 plus, requiring installed languages for direct TranslationSession use. NLTokenizer and NLTagger instances are not thread-safe and must be confined to one queue. Scope boundaries hand off OCR to vision-framework, speech to speech-recognition, UI locale strings to ios-localization, and generative summarization to apple-on-device-ai. Common mistakes warn against cross-thread tagger reuse, assuming translation availability without LanguageAvailability checks, and mixing framework responsibilities. Review checklists cover availability gates, thread confinement, and translation language installation before shipping multilingual.
- NLTokenizer, NLTagger, and NLEmbedding on-device text analysis.
- Language identification, POS, NER, sentiment, and custom NLModel support.
- TranslationSession and LanguageAvailability for in-app translation iOS 18+.
- Thread-safety rules for NaturalLanguage class instances.
- Scope boundaries to vision, speech, localization, and on-device AI skills.
Natural Language by the numbers
- 2,617 all-time installs (skills.sh)
- +110 installs in the week ending Jul 29, 2026 (Skillselion tracking)
- Ranked #75 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Security screen: LOW risk (skills.sh audit)
- Data as of Jul 31, 2026 (Skillselion catalog sync)
natural-language capabilities & compatibility
- Capabilities
- tokenization with word sentence paragraph units · pos tagging, ner, and sentiment via nltagger · word and sentence embeddings with nlembedding · translationsession with installed language check · custom nlmodel classifier and tagger patterns
- Use cases
- frontend · translation
- Platforms
- macOS
- Runs
- Runs locally
- Pricing
- Free
What natural-language says it does
Use this skill after you already have text.
npx skills add https://github.com/dpearson2699/swift-ios-skills --skill natural-languageAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2.6k |
|---|---|
| repo stars | ★ 944 |
| Security audit | 3 / 3 scanners passed |
| Last updated | July 15, 2026 |
| Repository | dpearson2699/swift-ios-skills ↗ |
How do I add sentiment, NER, embeddings, or translation to an iOS app with correct availability checks?
Add language identification, sentiment, NER, embeddings, and Translation framework support to iOS apps.
Who is it for?
iOS developers adding on-device NLP or translation to apps after text is already available.
Skip if: Skip for OCR capture, speech-to-text, or generative Apple Intelligence summarization workflows.
When should I use this skill?
User adds language identification, sentiment analysis, NER, embeddings, or TranslationSession to Swift apps.
What you get
On-device tokenization, tagging, embeddings, and translation sessions with thread-safe usage.
- NaturalLanguage Swift pipeline
- Entity and sentiment extraction code
- Production review checklist
By the numbers
- Eval references four NaturalLanguage APIs: NLTokenizer, NLLanguageRecognizer, NLTagger, NLEmbedding
- Includes a text-analysis-pipeline eval for iOS 26 inbox features
Files
NaturalLanguage + Translation
Analyze natural language text for tokenization, part-of-speech tagging, named entity recognition, sentiment analysis, language identification, and word/sentence embeddings. Translate text between languages with the Translation framework. Targets Swift 6.3 / iOS 26+.
This skill covers two related frameworks: NaturalLanguage (NLTokenizer,NLTagger,NLEmbedding) for on-device text analysis, and Translation (TranslationSession,LanguageAvailability) for language translation.
Scope boundary: Use this skill after you already have text. It owns tokenization, language identification, POS/NER tagging, sentiment, embeddings, custom NLModel classifiers/taggers, and in-app translation. Hand off OCR to vision-framework, speech-to-text to speech-recognition, UI strings and locale formatting to ios-localization, and generative summarization or Apple Intelligence workflows to apple-on-device-ai.
Contents
- Setup
- Tokenization
- Language Identification
- Part-of-Speech Tagging
- Named Entity Recognition
- Sentiment Analysis
- Text Embeddings
- Translation
- Common Mistakes
- Review Checklist
- References
Setup
Import NaturalLanguage for text analysis and Translation for language translation. No special entitlements or capabilities are required for NaturalLanguage. Translation has split availability: system translation presentation is iOS 17.4+ / macOS 14.4+, while TranslationSession, .translationTask(), LanguageAvailability, and batch translation require iOS 18+ / macOS 15+. Direct TranslationSession(installedSource:target:) is the non-UI option, but only when the source and target languages are already installed on device.
import NaturalLanguage
import TranslationNaturalLanguage classes (NLTokenizer, NLTagger) are not thread-safe. Use each instance from one thread or dispatch queue at a time.
Tokenization
Segment text into words, sentences, or paragraphs with NLTokenizer.
import NaturalLanguage
func tokenizeWords(in text: String) -> [String] {
let tokenizer = NLTokenizer(unit: .word)
tokenizer.string = text
let range = text.startIndex..<text.endIndex
return tokenizer.tokens(for: range).map { String(text[$0]) }
}Token Units
| Unit | Description |
|---|---|
.word | Individual words |
.sentence | Sentences |
.paragraph | Paragraphs |
.document | Entire document |
Enumerating with Attributes
Use enumerateTokens(in:using:) to detect numeric or emoji tokens.
let tokenizer = NLTokenizer(unit: .word)
tokenizer.string = text
tokenizer.enumerateTokens(in: text.startIndex..<text.endIndex) { range, attributes in
if attributes.contains(.numeric) {
print("Number: \(text[range])")
}
return true // continue enumeration
}Language Identification
Detect the dominant language of a string with NLLanguageRecognizer.
func detectLanguage(for text: String) -> NLLanguage? {
NLLanguageRecognizer.dominantLanguage(for: text)
}
// Multiple hypotheses with confidence scores
func languageHypotheses(for text: String, max: Int = 5) -> [NLLanguage: Double] {
let recognizer = NLLanguageRecognizer()
recognizer.processString(text)
return recognizer.languageHypotheses(withMaximum: max)
}Constrain the recognizer to expected languages for better accuracy on short text.
let recognizer = NLLanguageRecognizer()
recognizer.languageConstraints = [.english, .french, .spanish]
recognizer.processString(text)
let detected = recognizer.dominantLanguagePart-of-Speech Tagging
Identify nouns, verbs, adjectives, and other lexical classes with NLTagger.
func tagPartsOfSpeech(in text: String) -> [(String, NLTag)] {
let tagger = NLTagger(tagSchemes: [.lexicalClass])
tagger.string = text
var results: [(String, NLTag)] = []
let range = text.startIndex..<text.endIndex
let options: NLTagger.Options = [.omitPunctuation, .omitWhitespace]
tagger.enumerateTags(in: range, unit: .word, scheme: .lexicalClass, options: options) { tag, tokenRange in
if let tag {
results.append((String(text[tokenRange]), tag))
}
return true
}
return results
}Common Tag Schemes
| Scheme | Output |
|---|---|
.lexicalClass | Part of speech (noun, verb, adjective) |
.nameType | Named entity type (person, place, organization) |
.nameTypeOrLexicalClass | Combined NER + POS |
.lemma | Base form of a word |
.language | Per-token language |
.sentimentScore | Sentiment polarity score |
Named Entity Recognition
Extract people, places, and organizations.
func extractEntities(from text: String) -> [(String, NLTag)] {
let tagger = NLTagger(tagSchemes: [.nameType])
tagger.string = text
var entities: [(String, NLTag)] = []
let options: NLTagger.Options = [.omitPunctuation, .omitWhitespace, .joinNames]
tagger.enumerateTags(
in: text.startIndex..<text.endIndex,
unit: .word,
scheme: .nameType,
options: options
) { tag, tokenRange in
if let tag, tag != .other {
entities.append((String(text[tokenRange]), tag))
}
return true
}
return entities
}
// NLTag values: .personalName, .placeName, .organizationNameSentiment Analysis
Score text sentiment from -1.0 (negative) to +1.0 (positive).
func sentimentScore(for text: String) -> Double? {
let tagger = NLTagger(tagSchemes: [.sentimentScore])
tagger.string = text
let (tag, _) = tagger.tag(
at: text.startIndex,
unit: .paragraph,
scheme: .sentimentScore
)
return tag.flatMap { Double($0.rawValue) }
}Text Embeddings
Measure semantic similarity between words or sentences with NLEmbedding.
func wordSimilarity(_ word1: String, _ word2: String) -> Double? {
guard let embedding = NLEmbedding.wordEmbedding(for: .english) else { return nil }
return embedding.distance(between: word1, and: word2, distanceType: .cosine)
}
func findSimilarWords(to word: String, count: Int = 5) -> [(String, Double)] {
guard let embedding = NLEmbedding.wordEmbedding(for: .english) else { return [] }
return embedding.neighbors(for: word, maximumCount: count, distanceType: .cosine)
}Sentence embeddings compare entire sentences.
func sentenceSimilarity(_ s1: String, _ s2: String) -> Double? {
guard let embedding = NLEmbedding.sentenceEmbedding(for: .english) else { return nil }
return embedding.distance(between: s1, and: s2, distanceType: .cosine)
}Translation
System Translation Overlay
Show the built-in translation UI with .translationPresentation().
import SwiftUI
import Translation
struct TranslatableView: View {
@State private var showTranslation = false
let text = "Hello, how are you?"
var body: some View {
Button { showTranslation = true } label: {
Text(text)
}
.buttonStyle(.plain)
.translationPresentation(
isPresented: $showTranslation,
text: text
)
}
}Programmatic Translation
Use .translationTask() for programmatic translations within a view context.
struct TranslatingView: View {
@State private var translatedText = ""
@State private var translationErrorMessage: String?
@State private var configuration: TranslationSession.Configuration?
var body: some View {
VStack {
Text(translatedText)
Button("Translate") {
configuration = .init(source: Locale.Language(identifier: "en"),
target: Locale.Language(identifier: "es"))
}
}
.translationTask(configuration) { session in
do {
let response = try await session.translate("Hello, world!")
await MainActor.run {
translatedText = response.targetText
translationErrorMessage = nil
}
} catch {
let message = error.localizedDescription
await MainActor.run {
translationErrorMessage = message
}
}
}
}
}Batch Translation
Translate multiple strings in a single session.
.translationTask(configuration) { session in
do {
let requests = texts.enumerated().map { index, text in
TranslationSession.Request(sourceText: text,
clientIdentifier: "\(index)")
}
let responses = try await session.translations(from: requests)
for response in responses {
print("\(response.sourceText) -> \(response.targetText)")
}
} catch {
// Handle cancellation, unsupported languages, or download refusal.
}
}Checking Language Availability
let availability = LanguageAvailability()
let status = await availability.status(
from: Locale.Language(identifier: "en"),
to: Locale.Language(identifier: "ja")
)
switch status {
case .installed: break // Ready to translate offline
case .supported: break // Needs download
case .unsupported: break // Language pair not available
}Common Mistakes
DON'T: Share NLTagger/NLTokenizer across threads
These classes are not thread-safe and will produce incorrect results or crash.
// WRONG
let sharedTagger = NLTagger(tagSchemes: [.lexicalClass])
DispatchQueue.concurrentPerform(iterations: 10) { _ in
sharedTagger.string = someText // Data race
}
// CORRECT
await withTaskGroup(of: Void.self) { group in
for _ in 0..<10 {
group.addTask {
let tagger = NLTagger(tagSchemes: [.lexicalClass])
tagger.string = someText
// process...
}
}
}DON'T: Confuse NaturalLanguage with Core ML
NaturalLanguage provides built-in linguistic analysis. Use Core ML for custom trained models. They complement each other via NLModel.
// WRONG: Trying to do NER with raw Core ML
let coreMLModel = try MLModel(contentsOf: modelURL)
// CORRECT: Use NLTagger for built-in NER
let tagger = NLTagger(tagSchemes: [.nameType])
// Or load a custom Core ML model via NLModel
let nlModel = try NLModel(mlModel: coreMLModel)
tagger.setModels([nlModel], forTagScheme: .nameType)DON'T: Assume embeddings exist for all languages
Not all languages have word or sentence embeddings available on device.
// WRONG: Force unwrap
let embedding = NLEmbedding.wordEmbedding(for: .japanese)!
// CORRECT: Handle nil
guard let embedding = NLEmbedding.wordEmbedding(for: .japanese) else {
// Embedding not available for this language
return
}DON'T: Create a new tagger per token
Creating and configuring a tagger is expensive. Reuse it for the same text.
// WRONG: New tagger per word
for word in words {
let tagger = NLTagger(tagSchemes: [.lexicalClass])
tagger.string = word
}
// CORRECT: Set string once, enumerate
let tagger = NLTagger(tagSchemes: [.lexicalClass])
tagger.string = fullText
tagger.enumerateTags(in: fullText.startIndex..<fullText.endIndex,
unit: .word, scheme: .lexicalClass, options: []) { tag, range in
return true
}DON'T: Ignore language hints for short text
Language detection on short strings (under ~20 characters) is unreliable. Set constraints or hints to improve accuracy.
// WRONG: Detect language of a single word
let lang = NLLanguageRecognizer.dominantLanguage(for: "chat") // French or English?
// CORRECT: Provide context
let recognizer = NLLanguageRecognizer()
recognizer.languageHints = [.english: 0.8, .french: 0.2]
recognizer.processString("chat")Review Checklist
- [ ]
NLTokenizerandNLTaggerinstances used from a single thread - [ ] Tagger created once per text, not per token
- [ ] Language detection uses constraints/hints for short text
- [ ]
NLEmbeddingavailability checked before use (returns nil if unavailable) - [ ] Translation
LanguageAvailabilitychecked before attempting translation - [ ]
.translationTask()used within a SwiftUI view hierarchy - [ ] Batch translation uses
clientIdentifierto match responses to requests - [ ] Sentiment scores handled as optional (may return nil for unsupported languages)
- [ ]
.joinNamesoption used with NER to keep multi-word names together - [ ] Custom ML models loaded via
NLModel, not raw Core ML
References
- Extended patterns (custom models, contextual embeddings, gazetteers): references/translation-patterns.md
- Natural Language framework
- NLTokenizer
- NLTagger
- NLEmbedding
- NLLanguageRecognizer
- Translation framework
- TranslationSession
- TranslationSession.Strategy
- LanguageAvailability
{
"skill_name": "natural-language",
"evals": [
{
"id": 0,
"name": "text-analysis-pipeline",
"prompt": "I'm adding an iOS 26 inbox feature that detects the message language, tokenizes words, extracts people and organizations, computes a sentiment score, and finds semantically similar keywords for search suggestions. Sketch the NaturalLanguage implementation and review checklist.",
"expected_output": "A NaturalLanguage implementation outline that uses NLTokenizer, NLLanguageRecognizer, NLTagger, and NLEmbedding accurately, handles language/support limits, and avoids thread-safety and per-token tagger mistakes.",
"files": [],
"expectations": [
"Uses NLTokenizer with the correct token unit and keeps tokenizer/tagger instances confined to one thread or dispatch queue at a time.",
"Uses NLLanguageRecognizer with languageConstraints or languageHints when text is short or expected languages are known.",
"Uses NLTagger with .nameType, .sentimentScore, and appropriate options such as .omitWhitespace, .omitPunctuation, and .joinNames.",
"Treats sentiment and tags as optional and handles unsupported languages or missing tag schemes instead of force-unwrapping.",
"Checks NLEmbedding word or sentence embedding availability and does not assume embeddings exist for every language."
]
},
{
"id": 1,
"name": "translation-availability-review",
"prompt": "Review this SwiftUI translation plan: use the system translation popover on iOS 17.4, use TranslationSession and .translationTask for programmatic batch translation on the same deployment target, prefer .highFidelity because it can use a server, and ignore errors because the framework downloads languages automatically.",
"expected_output": "A correction-focused Translation framework review that splits API availability, keeps translation on-device, handles language availability and throwing APIs, and explains SwiftUI and direct-session usage safely.",
"files": [],
"expectations": [
"Separates translationPresentation availability from TranslationSession, translationTask, LanguageAvailability, and batch translation availability.",
"Corrects highFidelity/lowLatency guidance without claiming TranslationSession sends content to a server.",
"Uses LanguageAvailability status checks and handles .installed, .supported, and .unsupported.",
"Wraps TranslationSession throwing calls such as translate, translations(from:), and prepareTranslation in error handling.",
"Explains that direct TranslationSession(installedSource:target:) outside SwiftUI requires installed languages, while SwiftUI translationTask can request downloads."
]
},
{
"id": 2,
"name": "sibling-boundary-routing",
"prompt": "I have one feature request: scan text from a receipt image, transcribe a spoken note, tokenize and identify the language of the resulting text, detect names and sentiment, find semantically similar keywords with embeddings, maybe add a custom text classifier/tagger, translate a summary to Spanish, localize UI strings with String Catalogs (.xcstrings), plurals, RTL, date and currency formatting, and maybe use Apple Intelligence to generate the summary. Which parts belong in the NaturalLanguage skill and which should be handed to sibling skills?",
"expected_output": "A boundary-aware routing answer that keeps NaturalLanguage/Translation responsible for text analysis and translation, and routes OCR, speech transcription, localization, and generative summarization to the appropriate sibling skills.",
"files": [],
"expectations": [
"Keeps tokenization, language identification, named entity recognition, sentiment analysis, embeddings, and in-app translation in the natural-language scope.",
"Routes receipt-image OCR to the Vision framework skill rather than treating NaturalLanguage as OCR.",
"Routes spoken-note transcription to the Speech framework skill rather than treating NaturalLanguage as speech recognition.",
"Routes UI strings, String Catalogs, pluralization, and locale-aware formatting to the iOS localization skill.",
"Routes Apple Intelligence or generative summarization to the on-device AI skill rather than presenting NaturalLanguage as a generative LLM framework."
]
}
]
}
NaturalLanguage + Translation Extended Patterns
Overflow reference for the natural-language skill. Contains advanced patterns that exceed the main skill file's scope.
Contents
- Custom NLModel Integration
- Contextual Embeddings
- Gazetteers for Domain Vocabulary
- NLTagger with Multiple Schemes
- Translation with Replacement Action
- Translation Session Strategies
- SwiftUI Integration Patterns
- Lemmatization
Custom NLModel Integration
Load a Create ML text classifier or word tagger into NLTagger via NLModel.
import NaturalLanguage
import CoreML
func setupCustomTagger() throws -> NLTagger {
let mlModel = try MLModel(contentsOf: modelURL)
let nlModel = try NLModel(mlModel: mlModel)
let tagger = NLTagger(tagSchemes: [.nameType])
tagger.setModels([nlModel], forTagScheme: .nameType)
return tagger
}
// Direct prediction without a tagger
func classifyText(_ text: String) throws -> String? {
let mlModel = try MLModel(contentsOf: modelURL)
let nlModel = try NLModel(mlModel: mlModel)
return nlModel.predictedLabel(for: text)
}
// Predictions with confidence scores
func classifyWithConfidence(_ text: String) throws -> [String: Double] {
let mlModel = try MLModel(contentsOf: modelURL)
let nlModel = try NLModel(mlModel: mlModel)
return nlModel.predictedLabelHypotheses(for: text, maximumCount: 5)
}Contextual Embeddings
NLContextualEmbedding produces context-aware vectors where the same word gets different vectors based on surrounding text.
import NaturalLanguage
func contextualVectors(for text: String) throws -> [([Double], Range<String.Index>)] {
guard let embedding = NLContextualEmbedding(language: .english) else {
return []
}
// Check and load assets
guard embedding.hasAvailableAssets else {
embedding.requestAssets { result, error in
// Handle download
}
return []
}
try embedding.load()
defer { embedding.unload() }
let result = try embedding.embeddingResult(for: text, language: .english)
var vectors: [([Double], Range<String.Index>)] = []
result.enumerateTokenVectors(in: text.startIndex..<text.endIndex) { vector, range in
vectors.append((vector, range))
return true
}
return vectors
}Finding Available Contextual Embeddings
let embeddings = NLContextualEmbedding.contextualEmbeddings(forValues: [
.languages: [NLLanguage.english.rawValue]
])
for embedding in embeddings {
print("Model: \(embedding.modelIdentifier)")
print("Dimension: \(embedding.dimension)")
print("Max length: \(embedding.maximumSequenceLength)")
}Gazetteers for Domain Vocabulary
Override or supplement tagger results with custom term-to-label mappings.
func setupGazetteer() throws -> NLTagger {
let dictionary: [String: [String]] = [
"PRODUCT": ["iPhone", "MacBook Pro", "Apple Watch"],
"FEATURE": ["Dynamic Island", "ProMotion", "MagSafe"]
]
let gazetteer = try NLGazetteer(dictionary: dictionary, language: .english)
let tagger = NLTagger(tagSchemes: [.nameType])
tagger.setGazetteers([gazetteer], for: .nameType)
return tagger
}
// Persist a gazetteer to disk
func saveGazetteer(_ dictionary: [String: [String]], to url: URL) throws {
try NLGazetteer.write(dictionary, language: .english, to: url)
}NLTagger with Multiple Schemes
Request multiple tag schemes in a single tagger for efficient processing.
func analyzeText(_ text: String) {
let tagger = NLTagger(tagSchemes: [.lexicalClass, .nameType, .lemma])
tagger.string = text
let range = text.startIndex..<text.endIndex
let options: NLTagger.Options = [.omitWhitespace, .omitPunctuation, .joinNames]
tagger.enumerateTags(in: range, unit: .word, scheme: .nameTypeOrLexicalClass,
options: options) { tag, tokenRange in
let word = String(text[tokenRange])
let (lemmaTag, _) = tagger.tag(at: tokenRange.lowerBound,
unit: .word, scheme: .lemma)
let lemma = lemmaTag?.rawValue ?? word
print("\(word) -> tag: \(tag?.rawValue ?? "?"), lemma: \(lemma)")
return true
}
}Translation with Replacement Action
Replace the source text in-place after translation using the replacement action.
import SwiftUI
import Translation
struct EditableTranslationView: View {
@State private var text = "Hello, how are you?"
@State private var showTranslation = false
var body: some View {
TextEditor(text: $text)
.toolbar {
Button("Translate") { showTranslation = true }
}
.translationPresentation(
isPresented: $showTranslation,
text: text,
replacementAction: { translated in
text = translated
}
)
}
}Translation Session Strategies
Control whether translations prioritize quality or speed. Strategy selection requires iOS 26.4+ / macOS 26.4+. Translation content is processed on device; .highFidelity uses Apple Intelligence models when available, and .lowLatency uses traditional models.
import Translation
// High fidelity: more fluent translations when Apple Intelligence is available
let highQualityConfig = TranslationSession.Configuration(
source: Locale.Language(identifier: "en"),
target: Locale.Language(identifier: "ja"),
preferredStrategy: .highFidelity
)
// Low latency: faster traditional translation models
let fastConfig = TranslationSession.Configuration(
source: Locale.Language(identifier: "en"),
target: Locale.Language(identifier: "ja"),
preferredStrategy: .lowLatency
)Preparing a Translation Session
Pre-download models before translating to avoid UI delays.
.translationTask(configuration) { session in
do {
try await session.prepareTranslation()
// Models are now ready, translate without delay
let response = try await session.translate(sourceText)
await MainActor.run {
translatedText = response.targetText
}
} catch {
// Handle download refusal, cancellation, or unsupported language pairs.
}
}For non-UI translation, initialize TranslationSession(installedSource:target:) only after the source and target languages are installed; this initializer throws when the required languages are unavailable.
SwiftUI Integration Patterns
Language Analysis View
import SwiftUI
import NaturalLanguage
@Observable
@MainActor
final class TextAnalyzer {
var tokens: [String] = []
var detectedLanguage: String = ""
var sentimentLabel: String = ""
func analyze(_ text: String) {
guard !text.isEmpty else { return }
// Tokenize
let tokenizer = NLTokenizer(unit: .word)
tokenizer.string = text
tokens = tokenizer.tokens(for: text.startIndex..<text.endIndex)
.map { String(text[$0]) }
// Language
detectedLanguage = NLLanguageRecognizer.dominantLanguage(for: text)?
.rawValue ?? "Unknown"
// Sentiment
let tagger = NLTagger(tagSchemes: [.sentimentScore])
tagger.string = text
let (tag, _) = tagger.tag(at: text.startIndex, unit: .paragraph,
scheme: .sentimentScore)
if let score = tag.flatMap({ Double($0.rawValue) }) {
sentimentLabel = score > 0.1 ? "Positive" :
score < -0.1 ? "Negative" : "Neutral"
}
}
}
struct TextAnalysisView: View {
@State private var text = ""
@State private var analyzer = TextAnalyzer()
var body: some View {
Form {
TextField("Enter text", text: $text)
.onChange(of: text) { _, newValue in
analyzer.analyze(newValue)
}
Section("Results") {
LabeledContent("Language", value: analyzer.detectedLanguage)
LabeledContent("Sentiment", value: analyzer.sentimentLabel)
LabeledContent("Words", value: "\(analyzer.tokens.count)")
}
}
}
}Requesting NLTagger Assets
Some tag schemes require downloadable assets. Request them before tagging.
NLTagger.requestAssets(for: .japanese, tagScheme: .nameType) { result, error in
switch result {
case .available:
// Assets loaded, safe to tag Japanese text
break
case .notAvailable:
// Assets not available for this language/scheme
break
case .error:
print("Asset request error: \(error?.localizedDescription ?? "")")
@unknown default:
break
}
}Lemmatization
Get the base form of words for indexing or search normalization.
func lemmatize(_ text: String) -> [String] {
let tagger = NLTagger(tagSchemes: [.lemma])
tagger.string = text
var lemmas: [String] = []
tagger.enumerateTags(
in: text.startIndex..<text.endIndex,
unit: .word,
scheme: .lemma,
options: [.omitPunctuation, .omitWhitespace]
) { tag, range in
lemmas.append(tag?.rawValue ?? String(text[range]))
return true
}
return lemmas
}
// "The cats were running quickly" -> ["the", "cat", "be", "run", "quickly"]Related skills
How it compares
Pick natural-language for on-device Apple NaturalLanguage pipelines; use server-side NLP skills when analysis requires custom models or cross-platform shared inference.
FAQ
Are NLTagger instances thread-safe?
No. NaturalLanguage classes must be used from one thread or dispatch queue at a time.
What iOS version needs TranslationSession?
TranslationSession, translationTask, and LanguageAvailability require iOS 18 plus and macOS 15 plus.
Where does OCR belong instead?
Hand off OCR text capture to vision-framework; this skill starts after text exists.