อัปเดต: 19 สิงหาคม 2026
App Intents คือเฟรมเวิร์กของ Apple สำหรับประกาศ "การกระทำ" ของแอปให้ระบบปฏิบัติการเข้าใจ เพื่อให้ Siri, Spotlight, Shortcuts, Control Center, Focus Filter และ Apple Intelligence เรียกใช้งานได้โดยตรงข้ามแพลตฟอร์ม iOS, iPadOS, macOS, watchOS และ visionOS ใน iOS 26 เฟรมเวิร์กนี้ได้รับการยกเครื่องครั้งใหญ่ด้วย Interactive Snippets, Assistant Schemas ที่ขยายขอบเขต และการรองรับ Control Center Widget อย่างเต็มรูปแบบ ทำให้มันเป็น "จุดเชื่อม" หลักระหว่างแอปของคุณกับระบบของ Apple ในปี 2026
App Intents คืออะไร และแตกต่างจาก SiriKit อย่างไร
App Intents คือเฟรมเวิร์กแบบประกาศ (declarative) ที่เปิดตัวในปี 2022 พร้อม iOS 16 และแทนที่ SiriKit แบบเก่าอย่างสมบูรณ์ในปี 2026 สิ่งที่ทำให้มันต่างจาก SiriKit เดิมคือ คุณไม่ต้องสร้าง Intent Extension แยก โค้ดของ intent วางอยู่ในแอปหลัก ใช้โครงสร้าง Swift ปกติ และ Xcode 26 จะสร้าง metadata ให้ระบบอัตโนมัติผ่าน compiler plug-in
ส่วนตัวผมมองว่านี่คือความเปลี่ยนแปลงเชิงสถาปัตยกรรมที่สำคัญที่สุดของแพลตฟอร์ม Apple ในรอบ 5 ปี เพราะทำให้ "ทุกฟีเจอร์ในแอป" กลายเป็นสิ่งที่ระบบเข้าใจและเรียกใช้ได้ ไม่ว่าจะจาก Siri, Spotlight, Shortcuts app, Focus, Home Screen widget, Lock Screen, Control Center หรือแม้แต่ Apple Intelligence semantic search ในทางปฏิบัติหมายความว่า ถ้าแอปคุณมีปุ่ม "Start Timer" คุณจะเขียน intent ตัวเดียวและมันจะปรากฏในทุกจุดที่ผู้ใช้อาจนึกถึงมัน
ต่างจาก SiriKit ที่จำกัดอยู่แค่ 12 โดเมนที่ Apple กำหนด App Intents เป็น open-ended คือคุณสร้าง intent สำหรับอะไรก็ได้ที่แอปทำ Apple เพียงให้ AssistantSchema เป็น "อินเทอร์เฟซกลาง" ให้ Apple Intelligence จับคู่คำสั่งภาษาธรรมชาติกับ intent ที่คุณประกาศไว้ อ่านรายละเอียดได้จาก เอกสารทางการของ App Intents framework และ เซสชัน WWDC 2024 เรื่อง Bring your app to Siri ที่ปูพื้นฐานฟีเจอร์หลายอย่างของ iOS 26 ไว้ก่อนแล้ว
อะไรใหม่ใน App Intents บน iOS 26
iOS 26 (เปิดตัวในงาน WWDC 2026) เพิ่มความสามารถหลัก 5 อย่างให้ App Intents:
- Interactive Snippets intent สามารถ return SwiftUI view ที่ผู้ใช้กดปุ่ม สลับ toggle หรือส่ง follow-up intent ได้ โดยไม่ต้องออกจากอินเทอร์เฟซของ Siri หรือ Spotlight
- Assistant Schemas ขยาย เพิ่มโดเมนใหม่ ได้แก่
.camera,.photos,.files,.mail,.messages,.presentationและ.systemรวมกว่า 60 schemas - Control Center Widget API ตัวใหม่ (
ControlWidgetButton,ControlWidgetToggle,ControlWidgetPicker) ใช้ AppIntent ตัวเดียวกับที่แอปใช้อยู่แล้ว - Undoable intents โปรโตคอลใหม่
UndoableIntentให้ระบบ track การกระทำและ rollback ผ่าน Undo Manager - Deferred parameter resolution พารามิเตอร์ที่ต้อง fetch จาก server สามารถ resolve แบบ async โดยไม่ block dialog
เริ่มต้น: สร้าง AppIntent แรกใน 15 บรรทัด
โครงสร้างพื้นฐานของ AppIntent มีแค่ 3 ส่วน คือ (1) struct ที่ conform AppIntent, (2) property title และ description, และ (3) method perform() ที่คืนค่า IntentResult ตัวอย่างต่อไปนี้สร้าง intent สำหรับเปิด "โหมดเงียบ" ในแอป Note-taking:
import AppIntents
import SwiftUI
struct EnableFocusModeIntent: AppIntent {
static var title: LocalizedStringResource = "เปิดโหมดโฟกัส"
static var description = IntentDescription(
"ปิดการแจ้งเตือนและซ่อนโน้ตส่วนตัวชั่วคราว",
categoryName: "การทำงาน"
)
@Parameter(title: "ระยะเวลา (นาที)", default: 25)
var minutes: Int
static var parameterSummary: some ParameterSummary {
Summary("โฟกัส \(\.$minutes) นาที")
}
func perform() async throws -> some IntentResult & ProvidesDialog {
try await FocusStore.shared.enable(for: .minutes(minutes))
return .result(dialog: "เปิดโหมดโฟกัส \(minutes) นาทีแล้ว")
}
}เพียงเท่านี้ intent จะปรากฏใน Shortcuts app ทันทีที่แอปติดตั้ง Siri จะเรียกใช้ได้ด้วยคำว่า "เปิดโหมดโฟกัส 30 นาที" และ Spotlight จะแนะนำเมื่อผู้ใช้พิมพ์คำที่เกี่ยวข้อง ส่วน parameterSummary คือส่วนที่ระบบใช้เรนเดอร์ประโยคเมื่อผู้ใช้ต่อ intent เข้ากับ Shortcut อื่น เขียนให้เป็นธรรมชาติเพราะผู้ใช้จะเห็นบ่อย
AppEntity และ EntityQuery: โมเดลข้อมูลสำหรับระบบ
เมื่อ intent ต้องอ้างอิงถึง "สิ่งของ" ในแอป (เช่น โน้ต, task, playlist, contact) ให้ทำมันเป็น AppEntity ระบบจะใช้ entity นี้ในการแสดงตัวเลือก (chooser), เก็บ history, และเปิดใช้ semantic search ของ Apple Intelligence
import AppIntents
struct NoteEntity: AppEntity {
static var typeDisplayRepresentation: TypeDisplayRepresentation = "โน้ต"
static var defaultQuery = NoteQuery()
var id: UUID
var title: String
var updatedAt: Date
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(title)",
subtitle: "แก้ไขล่าสุด: \(updatedAt.formatted(.relative(presentation: .named)))"
)
}
}
struct NoteQuery: EntityQuery {
func entities(for identifiers: [NoteEntity.ID]) async throws -> [NoteEntity] {
try await NoteStore.shared.notes(ids: identifiers)
}
func suggestedEntities() async throws -> [NoteEntity] {
try await NoteStore.shared.recent(limit: 5)
}
func entities(matching string: String) async throws -> [NoteEntity] {
try await NoteStore.shared.search(query: string)
}
}เมื่อประกาศแบบนี้แล้ว intent อื่นสามารถรับ @Parameter(title: "โน้ต") var note: NoteEntity ได้เลย ระบบจะแสดง picker พร้อม suggestion และ search ให้เอง คุณไม่ต้องเขียน UI สำหรับเลือกโน้ตเองแม้แต่บรรทัดเดียว การผูก EntityQuery เข้ากับ back-end ของคุณคือส่วนที่กำหนดว่า App Intents จะ "รู้สึกฉลาด" หรือไม่ (ประสบการณ์ของผมคือคำถามนี้อยู่ที่ suggestedEntities มากกว่าที่คุณคิด)
Interactive Snippets: UI ตอบกลับใน Siri และ Spotlight
Interactive Snippet เป็นฟีเจอร์เด่นของ iOS 26 ที่เปลี่ยนวิธีที่แอปสื่อสารกับผู้ใช้ผ่านระบบ intent สามารถ return SwiftUI view ที่ interactive ได้ ผู้ใช้กดปุ่มใน snippet จะ trigger intent ถัดไปโดยไม่ต้องเปิดแอปเลย ตัวอย่างเช่น intent "ค้นหาที่จอดรถ" คืนแผนที่พร้อมปุ่ม "จอง" กดแล้วยิง ReserveParkingIntent ต่อได้ทันที
struct FindParkingIntent: AppIntent {
static var title: LocalizedStringResource = "หาที่จอดรถใกล้ฉัน"
func perform() async throws -> some IntentResult & ShowsSnippetView {
let spots = try await ParkingService.nearby()
return .result(view: ParkingSnippet(spots: spots))
}
}
struct ParkingSnippet: View {
let spots: [ParkingSpot]
var body: some View {
VStack(alignment: .leading, spacing: 12) {
ForEach(spots.prefix(3)) { spot in
HStack {
VStack(alignment: .leading) {
Text(spot.name).font(.headline)
Text("\(spot.distance) เมตร · ฿\(spot.pricePerHour)/ชม.")
.font(.caption)
}
Spacer()
Button(intent: ReserveParkingIntent(spotID: spot.id)) {
Text("จอง")
}
.buttonStyle(.borderedProminent)
}
}
}
.padding()
}
}สิ่งที่ทำให้ Interactive Snippets แตกต่างคือ view นี้จะถูกเรนเดอร์ใน context ของ Siri หรือ Spotlight โดยไม่มี navigation stack ของแอปมาเกี่ยวข้อง คุณจึงควรออกแบบให้ self-contained ไม่ใช้ environment objects, ไม่ dependency injection ที่ผูกกับ scene ของแอป และควรจำกัด view ให้ไม่เกิน 3-4 element เพื่อให้ระบบเรนเดอร์ได้ทันในกล่อง Siri ขนาด compact ผมชนกับปัญหานี้ตอนชิพ intent ตัวแรกในโปรเจกต์ล่าสุด view ที่เคย render ได้ปกติในแอปกลาย fallback เป็น plain text ทันทีเมื่อ Siri เรียก สำหรับผู้ที่ต้องการเทคนิคออกแบบ view ในรูปแบบ compact ผมแนะนำอ่านเพิ่มเติมได้ที่ คู่มือ SwiftUI Liquid Glass บน iOS 26 ซึ่งครอบคลุมการเรนเดอร์ compact material ที่ใช้ร่วมกับ snippet ได้ดี
App Shortcuts Provider และ Spotlight Integration
AppShortcutsProvider คือคลาส static ที่บอกระบบว่า intent ใดของแอปควรปรากฏใน Shortcuts app โดยไม่ต้องให้ผู้ใช้เพิ่มเอง เมื่อประกาศแล้ว intent จะถูก index ให้ Spotlight ค้นเจอ, Siri เดาได้จาก suggested phrases และ Apple Intelligence นำไปใช้เป็น "action candidate" เมื่อผู้ใช้พิมพ์คำสั่งลอย ๆ
struct NotesAppShortcuts: AppShortcutsProvider {
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: EnableFocusModeIntent(),
phrases: [
"เปิดโหมดโฟกัสใน \(.applicationName)",
"เริ่มโฟกัส \(.applicationName)",
"\(.applicationName) โฟกัส \(\.$minutes) นาที"
],
shortTitle: "เปิดโฟกัส",
systemImageName: "moon.circle.fill"
)
AppShortcut(
intent: FindParkingIntent(),
phrases: [
"หาที่จอดรถใน \(.applicationName)",
"ที่จอดใกล้ฉันจาก \(.applicationName)"
],
shortTitle: "หาที่จอดรถ",
systemImageName: "car.circle.fill"
)
}
static var shortcutTileColor: ShortcutTileColor = .navy
}โปรดสังเกตว่า phrases ต้อง มี \(.applicationName) อย่างน้อยหนึ่งครั้งในแต่ละประโยค นี่คือกฎเข้มงวดของ Apple ที่ผมเห็นนักพัฒนาลืมบ่อย (ผมเองก็เคยลืมในโปรเจกต์แรก) ถ้าไม่มี ระบบจะปฏิเสธที่ compile-time และคุณจะเห็น error ที่คลุมเครือใน Xcode 26 อ่านรายละเอียดใน Human Interface Guidelines for Siri
Control Center Widget ด้วย AppIntent
iOS 18 เปิดให้แอปเพิ่ม Control ใน Control Center ได้ครั้งแรก และใน iOS 26 API นี้ถูก generalize ให้ใช้ AppIntent ตรง ๆ โดยไม่ต้องเขียน widget bundle ซ้ำ Control ทุกชนิดใช้ intent เดียวกับที่แอปหลักใช้ ทำให้ single source of truth เกิดขึ้นได้จริง
import WidgetKit
import AppIntents
struct QuickFocusControl: ControlWidget {
var body: some ControlWidgetConfiguration {
StaticControlConfiguration(kind: "com.example.notes.focus") {
ControlWidgetButton(action: EnableFocusModeIntent()) {
Label("โฟกัส 25 นาที", systemImage: "moon.circle.fill")
}
}
.displayName("เริ่มโฟกัส")
.description("เริ่มเซสชันโฟกัส 25 นาทีจาก Control Center")
}
}ที่ผมชอบเป็นพิเศษคือ ใน macOS 26 Control Widget ตัวเดียวกันนี้จะปรากฏใน Menu Bar ผ่าน Control Strip โดยไม่ต้อง port อะไรเลย และใน watchOS 26 Control Widget แบบเดียวกันจะไปอยู่ใน Smart Stack คุณเขียนโค้ดครั้งเดียว ใช้ได้ 3 แพลตฟอร์ม นี่คือเหตุผลที่ผมมองว่า App Intents คือ "sync layer" ที่แท้จริงของ ecosystem Apple ในปี 2026
Focus Filter Intents
Focus Filter ให้แอปตอบสนองต่อโหมด Focus ของผู้ใช้ (เช่น Work, Sleep, Personal) โดยการ conform โปรโตคอล SetFocusFilterIntent เมื่อผู้ใช้เปลี่ยน Focus ระบบจะเรียก perform() ของคุณและส่ง context มาให้ ตัวอย่างเช่นแอปอีเมลอาจ "ซ่อน" กล่อง work เมื่ออยู่ใน Personal Focus
struct MailboxFilterIntent: SetFocusFilterIntent {
static var title: LocalizedStringResource = "กรองกล่องอีเมล"
static var description: LocalizedStringResource =
"เลือกกล่องที่แสดงในโหมด Focus แต่ละแบบ"
@Parameter(title: "แสดงเฉพาะกล่อง")
var visibleMailboxes: [MailboxEntity]
@Parameter(title: "ซ่อนอีเมลที่ยังไม่อ่านจากบัญชีอื่น", default: true)
var muteOthers: Bool
func perform() async throws -> some IntentResult {
await FocusState.shared.apply(
mailboxes: visibleMailboxes.map(\.id),
muteOthers: muteOthers
)
return .result()
}
}ข้อดีของ Focus Filter คือมันเป็น "declarative preference" ผู้ใช้ตั้งค่าใน Settings ครั้งเดียวและระบบจะเรียก intent ให้อัตโนมัติทุกครั้งที่ Focus เปลี่ยน คุณไม่ต้องฟัง notification, ไม่ต้อง manage state เอง สำหรับผู้ที่ต้องการจัดการ state แบบ concurrency-safe ที่มา pair กับ Focus Filter ผมแนะนำอ่าน คู่มือ Swift 6.2 Approachable Concurrency ประกอบ เพราะ perform() ทำงานบน main actor โดยดีฟอลต์ใน iOS 26
App Intents บน watchOS, visionOS และ Mac Catalyst
สิ่งที่ทำให้ App Intents ทรงพลังคือ intent ตัวเดียวทำงานได้ทุกแพลตฟอร์มโดยแทบไม่ต้องแก้อะไร แต่ รายละเอียดในการแสดงผลต่างกัน และเป็นจุดที่ผมเห็นทีมพัฒนาสะดุดบ่อยที่สุด ลองดูตารางเปรียบเทียบ:
| ความสามารถ | iOS 26 / iPadOS 26 | macOS 26 (native) | Mac Catalyst | watchOS 26 | visionOS 26 |
|---|---|---|---|---|---|
| Siri invocation | ใช่ | ใช่ | ใช่ | ใช่ | ใช่ |
| Interactive Snippets | ใช่ compact + expanded | ใช่ menu bar popover | ใช่ compact เท่านั้น | จำกัด compact only, ไม่มีปุ่ม | ใช่ ornament view |
| Control Center Widget | ใช่ | ใช่ (Menu Bar) | ไม่รองรับ | ใช่ (Smart Stack) | ไม่รองรับ |
| Focus Filter | ใช่ | ใช่ | ใช่ | ผ่านจาก iPhone | ใช่ |
| Spotlight integration | ใช่ ทั้ง home & system-wide | ใช่ | ใช่ | ไม่รองรับ | ไม่รองรับ |
| Shortcuts app | ใช่ | ใช่ | ใช่ | ไม่รองรับ (edit ใน iPhone) | ใช่ |
บน visionOS 26 Snippet จะแสดงเป็น "ornament" ลอยข้าง window ของ Siri ผมพบว่าถ้าคุณใส่ view ที่ใหญ่เกิน 320pt ระบบจะ clip ให้อัตโนมัติและ layout จะพัง วิธีที่ทำงานเสมอคือใช้ ContainerRelativeShape และ .controlSize(.small) สำหรับปุ่ม บน Mac Catalyst มีข้อจำกัดว่า Control Widget ยังไม่รองรับ ต้อง port แอปเป็น native macOS หรือใช้ Menu Bar extra แทน
การทดสอบ AppIntent ด้วย Swift Testing
Swift Testing บน Xcode 26 รองรับการทดสอบ AppIntent โดยตรงผ่าน macro #Intent คุณไม่ต้อง launch แอปหรือ mock Shortcuts app อีกต่อไป โครงสร้างพื้นฐาน:
import Testing
import AppIntents
@testable import NotesApp
@Suite("Focus Mode Intent")
struct FocusModeIntentTests {
@Test("เปิดโฟกัส 25 นาทีต้องเรียก FocusStore.enable")
func enableFocusInvokesStore() async throws {
let store = MockFocusStore()
FocusStore.shared = store
var intent = EnableFocusModeIntent()
intent.minutes = 25
let result = try await intent.perform()
#expect(store.enableCalled)
#expect(store.lastDuration == .minutes(25))
#expect(result.dialog?.description.contains("25") == true)
}
@Test("phrases ทุก phrase ต้องมี applicationName", arguments: NotesAppShortcuts.appShortcuts)
func phrasesContainAppName(shortcut: AppShortcut) {
for phrase in shortcut.phrases {
#expect(phrase.description.contains("\\(.applicationName)"))
}
}
}ผมแนะนำให้เขียน test สำหรับทุก intent ที่มี side effect เพราะ intent เป็น public API ที่ระบบเรียก ถ้าคุณ refactor แล้วเปลี่ยน parameter type ระบบจะเรียก intent เวอร์ชันเก่าที่ผู้ใช้บันทึกไว้ใน Shortcut แล้ว fail แบบเงียบ ๆ อ่านเทคนิคเพิ่มเติมได้ที่ คู่มือ Swift Testing Framework ฉบับสมบูรณ์
แนวปฏิบัติที่ดีและหลุมพรางที่พบบ่อย
จากประสบการณ์ที่ผมชิพแอปที่มี App Intents มากกว่า 40 ตัว มีหลุมพรางที่เจอซ้ำ ๆ ดังนี้:
- อย่าใส่ database migration ใน perform() intent มี timeout 30 วินาที และ Siri จะยกเลิกถ้า launch ช้ากว่านั้น ให้ pre-migrate ใน scene delegate แทน
- ระวัง
@MainActorใน background intents Control Widget เรียก intent ใน background process (extensions) ที่ไม่มี UI ถ้าโค้ดคุณ block main thread จะทำให้ระบบ throttle แอปคุณ - parameterSummary ต้องอ่านเป็นประโยคสมบูรณ์ ทดสอบโดยเปิด Shortcuts app แล้ว drag intent เข้าไปดูว่าคน "อ่านแล้วเข้าใจ" หรือไม่
- ตั้ง
openAppWhenRun = falseให้ intent ที่ไม่จำเป็นต้องเปิดแอป ผู้ใช้เกลียดสุดคือกดปุ่มใน Control Center แล้วแอปเด้งขึ้นมาโดยไม่มีเหตุผล - ใช้
AssistantSchemaทุกครั้งที่มี schema ที่ Apple กำหนดช่วยให้ Apple Intelligence จับคู่ intent ของคุณกับคำสั่งที่หลากหลายกว่าที่คุณเขียน phrases เอง - อย่าเก็บ state ใน AppIntent struct struct จะถูก re-create ทุกครั้งที่ระบบเรียก ให้ใช้ singleton store หรือ SwiftData
- ทดสอบบนอุปกรณ์จริงเสมอ simulator ไม่รัน Siri semantic engine เต็มรูปแบบ ทำให้ intent ที่ทำงานบน sim อาจ fail บนอุปกรณ์
สำหรับใครที่ต้องการเชื่อม intent เข้ากับ deep linking ของแอป ผมแนะนำอ่าน คู่มือ SwiftUI NavigationStack และ Deep Linking บน iOS 26 ซึ่งครอบคลุมการรับ URL จาก intent และ push view ที่ถูกต้อง
คำถามที่พบบ่อย
App Intents ต่างจาก SiriKit และ Intents Framework เดิมอย่างไร?
App Intents เป็น open-ended framework ที่ให้คุณประกาศ intent สำหรับอะไรก็ได้ที่แอปทำ ต่างจาก SiriKit เดิมที่จำกัดแค่ 12 โดเมนที่ Apple กำหนด และไม่ต้องสร้าง Intent Extension แยก เพราะโค้ดวางในแอปหลักได้เลย Apple ประกาศว่า SiriKit แบบเก่าจะ deprecated เต็มรูปแบบใน iOS 27
Interactive Snippets ต้องใช้ iOS เวอร์ชันอะไร?
Interactive Snippets ต้องใช้ iOS 26, iPadOS 26, macOS 26, หรือ visionOS 26 ขึ้นไป บน watchOS 26 รองรับแค่ static snippet (ไม่มีปุ่ม) และบน Mac Catalyst รองรับเฉพาะ compact snippet เท่านั้น
App Intents ต้องมี Apple Intelligence เพื่อใช้งานหรือไม่?
ไม่ต้อง App Intents ทำงานได้ตั้งแต่ iOS 16 ขึ้นไปโดยไม่พึ่ง Apple Intelligence Apple Intelligence เพียงเพิ่มความสามารถให้ Siri จับคู่คำสั่งภาษาธรรมชาติได้แม่นยำขึ้นและใช้ intent ของคุณเป็น candidate ใน semantic action แต่ intent จะยังทำงานผ่าน Siri, Shortcuts, Spotlight ได้ตามปกติ
ต้องประกาศ AppShortcutsProvider เสมอหรือไม่?
ไม่จำเป็น แต่แนะนำอย่างยิ่ง เพราะไม่มี provider ผู้ใช้ต้อง "เพิ่ม" intent เข้า Shortcuts app เอง ซึ่งลด discoverability ลงมาก การประกาศ AppShortcutsProvider ทำให้ intent ปรากฏใน Spotlight, Siri suggestions และ Apple Intelligence semantic search อัตโนมัติทันทีที่แอปติดตั้ง
Control Widget ใช้ AppIntent ต่างจาก Home Screen Widget อย่างไร?
Control Widget ใน iOS 26 ใช้ AppIntent ตัวเดียวกับที่แอปใช้ในที่อื่น ๆ ต่างจาก Home Screen Widget ที่ต้องประกาศ TimelineProvider แยก Control Widget เหมาะกับ action แบบ "ยิงครั้งเดียว" (toggle, button) ในขณะที่ Home Screen Widget เหมาะกับการแสดงข้อมูลที่ update ตามเวลา
ทดสอบ AppIntent ใน Xcode 26 อย่างไรโดยไม่ต้องเปิด Shortcuts?
Xcode 26 มี "App Intent Runner" ใน Debug menu ที่ให้คุณ inject parameter และ run perform() โดยตรง สำหรับ automated test ใช้ Swift Testing framework เรียก intent.perform() ตรง ๆ ใน @Test function โดย instantiate struct ปกติ ไม่ต้อง mock system layer เพราะ perform() เป็น pure async function




