Skip to content

Instantly share code, notes, and snippets.

@lanserxt
Created August 4, 2026 20:09
Show Gist options
  • Select an option

  • Save lanserxt/9b3026ced6be863e9fcbbdc67fd98c97 to your computer and use it in GitHub Desktop.

Select an option

Save lanserxt/9b3026ced6be863e9fcbbdc67fd98c97 to your computer and use it in GitHub Desktop.
iOS 26: Data Detection - find entities in String
//
// DataDetectorView.swift
// WWDC25Demo
//
// Created by Anton Gubarenko on 04.08.2026.
//
import SwiftUI
import DataDetection
struct DataDetectorView: View {
@State private var model = DataDetectorModel()
var body: some View {
NavigationStack {
List {
Section {
Picker("Example", selection: $model.selectedExampleID) {
ForEach(model.examples) { example in
Text(example.title)
.tag(example.id)
}
}
Button("Detect") {
Task {
await model.detect()
}
}
.disabled(model.isDetecting)
}
if let example = model.selectedExample {
Section("Input") {
Text(example.text)
.textSelection(.enabled)
LabeledContent(
"Expected result",
value: example.expectedResult
)
}
}
if model.isDetecting {
Section {
ProgressView("Analyzing text")
}
}
if let errorMessage = model.errorMessage {
Section {
ContentUnavailableView(
"Detection Failed",
systemImage: "exclamationmark.triangle",
description: Text(errorMessage)
)
}
}
if !model.results.isEmpty {
Section("Detected Results") {
ForEach(model.results) { result in
VStack(alignment: .leading, spacing: 8) {
Text(result.type)
.font(.headline)
LabeledContent(
"Result",
value: result.matchedText
)
LabeledContent(
"Highlight",
value: result.highlightStyle
)
}
.padding(.vertical, 4)
}
}
} else if model.hasCompletedDetection && !model.isDetecting {
Section {
ContentUnavailableView(
"No Matches",
systemImage: "text.magnifyingglass",
description: Text(
"The detector did not find the requested semantic type in this example."
)
)
}
}
}
.navigationTitle("Data Detector")
.task {
await model.detect()
}
.onChange(of: model.selectedExampleID) {
Task {
await model.detect()
}
}
}
}
}
@MainActor
@Observable
final class DataDetectorModel {
struct Example: Identifiable, Hashable {
let id: String
let title: String
let text: String
let types: DataDetector.MatchType
let expectedType: String
let expectedResult: String
let options: DataDetector.Options
init(
id: String,
title: String,
text: String,
types: DataDetector.MatchType,
expectedType: String,
expectedResult: String,
options: DataDetector.Options = .init()
) {
self.id = id
self.title = title
self.text = text
self.types = types
self.expectedType = expectedType
self.expectedResult = expectedResult
self.options = options
}
}
struct DetectionResult: Identifiable, Hashable {
let id = UUID()
let type: String
let matchedText: String
let highlightStyle: String
}
private(set) var results: [DetectionResult] = []
private(set) var isDetecting = false
private(set) var hasCompletedDetection = false
private(set) var errorMessage: String?
var selectedExampleID = "email"
let examples: [Example] = {
var calendarOptions = DataDetector.Options()
calendarOptions.documentDate = Date()
calendarOptions.documentTimeZone = .current
calendarOptions.documentLanguageCode = "en"
calendarOptions.documentRegion = "US"
return [
Example(
id: "email",
title: "Email Address",
text: "Send the build to qa@example.com",
types: [.emailAddress],
expectedType: "emailAddress",
expectedResult: "qa@example.com"
),
Example(
id: "phone",
title: "Phone Number",
text: "Call me at +1 415 555 1212",
types: [.phoneNumber],
expectedType: "phoneNumber",
expectedResult: "+1 415 555 1212"
),
Example(
id: "link",
title: "Link",
text: "Read https://developer.apple.com/documentation/datadetection",
types: [.link],
expectedType: "link",
expectedResult: "https://developer.apple.com/documentation/datadetection"
),
Example(
id: "calendar",
title: "Calendar Event",
text: "Let's meet tomorrow at 10:30 AM",
types: [.calendarEvent],
expectedType: "calendarEvent",
expectedResult: "tomorrow at 10:30 AM",
options: calendarOptions
),
Example(
id: "address",
title: "Postal Address",
text: "Meet me at 1 Apple Park Way, Cupertino, CA 95014",
types: [.postalAddress],
expectedType: "postalAddress",
expectedResult: "1 Apple Park Way, Cupertino, CA 95014"
),
Example(
id: "money",
title: "Money Amount",
text: "The subscription costs €9.99 per month",
types: [.moneyAmount],
expectedType: "moneyAmount",
expectedResult: "€9.99"
),
Example(
id: "measurement",
title: "Measurement",
text: "The package weighs 2.5 kg and is 40 cm wide",
types: [.measurement],
expectedType: "measurement",
expectedResult: "2.5 kg, 40 cm"
),
Example(
id: "flight",
title: "Flight Number",
text: "My flight is BA 281 from London",
types: [.flightNumber],
expectedType: "flightNumber",
expectedResult: "BA 281"
),
Example(
id: "tracking",
title: "Tracking Number",
text: "Your UPS tracking number is 1Z999AA10123456784",
types: [.shipmentTrackingNumber],
expectedType: "shipmentTrackingNumber",
expectedResult: "1Z999AA10123456784"
),
Example(
id: "payment",
title: "Payment Identifier",
text: "Send the payment to anton@bank",
types: [.paymentIdentifier],
expectedType: "paymentIdentifier",
expectedResult: "anton@bank"
)
]
}()
var selectedExample: Example? {
examples.first { $0.id == selectedExampleID }
}
func detect() async {
guard let selectedExample else {
return
}
isDetecting = true
hasCompletedDetection = false
errorMessage = nil
results = []
let detectedResults = await scan(
text: selectedExample.text,
types: selectedExample.types,
options: selectedExample.options
)
results = detectedResults
hasCompletedDetection = true
isDetecting = false
}
}
private func scan(
text: String,
types: DataDetector.MatchType,
options: DataDetector.Options
) async -> [DataDetectorModel.DetectionResult] {
var detectedResults: [DataDetectorModel.DetectionResult] = []
for await match in text.dataDetectorMatches(
types,
options: options
) {
let matchedText = match.range.map {
String(text[$0])
} ?? "Unavailable"
detectedResults.append(
DataDetectorModel.DetectionResult(
type: typeName(for: match.details),
matchedText: matchedText,
highlightStyle: highlightDescription(
for: match.preferredHighlightStyle
)
)
)
}
return detectedResults
}
private func typeName(
for details: DataDetector.Match.SemanticDetails
) -> String {
switch details {
case .emailAddress:
"emailAddress"
case .phoneNumber:
"phoneNumber"
case .link:
"link"
case .postalAddress:
"postalAddress"
case .calendarEvent:
"calendarEvent"
case .moneyAmount:
"moneyAmount"
case .measurement:
"measurement"
case .flightNumber:
"flightNumber"
case .shipmentTrackingNumber:
"shipmentTrackingNumber"
case .paymentIdentifier:
"paymentIdentifier"
@unknown default:
"unknown"
}
}
private func highlightDescription(
for style: DataDetector.Match.HighlightStyle
) -> String {
switch style {
case .hidden:
"hidden"
case .regular:
"regular"
case .url:
"url"
@unknown default:
"unknown"
}
}
#Preview {
DataDetectorView()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment