App Intents dan Siri Shortcuts: otomatisasi iOS lanjutan 2026
Panduan lengkap App Intents dan Siri Shortcuts untuk iOS 18+. Membuat aksi Siri kustom, mengintegrasikan Apple Intelligence, dan mengotomatisasi aplikasi Swift di 2026.

Pada tahun 2026, dengan Apple Intelligence dan framework App Intents, aplikasi iOS memasuki era di mana niat pengguna lebih penting daripada antarmuka grafis. Aplikasi yang tidak mengekspos intent menjadi tidak terlihat dalam OS yang berorientasi AI. App Intents menjadi fondasi yang memungkinkan Siri, Spotlight, widget, dan Action Button untuk berinteraksi dengan fungsionalitas aplikasi.
Artikel ini menyajikan pembuatan App Intents dan Siri Shortcuts secara lengkap untuk iOS 18+, dari konsep dasar hingga integrasi dengan Apple Intelligence dan App Intent Domains.
Memahami framework App Intents
Framework App Intents, diperkenalkan pada iOS 16, memodernisasi pembuatan intent di Swift dengan menggantikan framework SiriKit Intents lama. Arsitektur deklaratif ini memungkinkan pembuatan aksi yang dapat ditemukan oleh sistem: Spotlight, aplikasi Pintasan, Siri, dan Action Button.
import AppIntents
// AppIntent merepresentasikan aksi yang dapat dilakukan pengguna
struct CreateTaskIntent: AppIntent {
// Judul yang ditampilkan di Pintasan dan Siri
static var title: LocalizedStringResource = "Buat tugas"
// Deskripsi untuk aksesibilitas dan saran
static var description = IntentDescription(
"Membuat tugas baru di aplikasi."
)
// Parameter dengan validasi otomatis
@Parameter(title: "Judul tugas")
var taskTitle: String
// Parameter opsional dengan nilai default
@Parameter(title: "Prioritas", default: .medium)
var priority: TaskPriority
// Eksekusi aksi
func perform() async throws -> some IntentResult & ReturnsValue<TaskEntity> {
// Membuat tugas via service
let task = TaskService.shared.createTask(
title: taskTitle,
priority: priority
)
// Mengembalikan entity yang dibuat untuk chaining
return .result(value: TaskEntity(task: task))
}
}Intent mendeklarasikan parameter melalui property wrapper @Parameter, memungkinkan Siri meminta nilai yang hilang. Method perform() mengeksekusi logika bisnis dan mengembalikan hasil bertipe.
Mendefinisikan App Entities untuk data
App Entities merepresentasikan "kata benda" aplikasi: objek yang dioperasikan oleh intent. Mereka memungkinkan Siri memahami dan memanipulasi data aplikasi.
import AppIntents
// Model data internal
struct Task: Identifiable, Codable {
let id: UUID
var title: String
var priority: TaskPriority
var isCompleted: Bool
var dueDate: Date?
}
// Entity yang diekspos ke sistem
struct TaskEntity: AppEntity {
// Identifier unik wajib
var id: UUID
// Properti yang dapat ditampilkan
var title: String
var priority: TaskPriority
var isCompleted: Bool
// Konfigurasi tampilan di sistem
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Tugas"
// Representasi visual instance
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(title)",
subtitle: "\(priority.rawValue)",
image: .init(systemName: isCompleted ? "checkmark.circle.fill" : "circle")
)
}
// Query default untuk mencari entity
static var defaultQuery = TaskEntityQuery()
// Initializer dari model internal
init(task: Task) {
self.id = task.id
self.title = task.title
self.priority = task.priority
self.isCompleted = task.isCompleted
}
}
// Query untuk mencari dan memfilter entity
struct TaskEntityQuery: EntityQuery {
// Pencarian berdasarkan identifier
func entities(for identifiers: [UUID]) async throws -> [TaskEntity] {
TaskService.shared.fetchTasks()
.filter { identifiers.contains($0.id) }
.map { TaskEntity(task: $0) }
}
// Saran yang ditampilkan di antarmuka
func suggestedEntities() async throws -> [TaskEntity] {
TaskService.shared.fetchTasks()
.filter { !$0.isCompleted }
.prefix(5)
.map { TaskEntity(task: $0) }
}
}EntityQuery mendefinisikan bagaimana sistem mencari dan menyarankan entity. Method entities(for:) dan suggestedEntities() memberi makan antarmuka Siri dan Pintasan.
Gunakan AppEnum untuk tipe dengan himpunan nilai tetap (prioritas, status), dan AppEntity untuk tipe dinamis yang dibuat pengguna (tugas, catatan, kontak).
Membuat App Enums untuk nilai tetap
App Enums mengekspos tipe enumerasi ke sistem, memungkinkan Siri menawarkan pilihan kontekstual.
import AppIntents
// Enum yang diekspos ke sistem
enum TaskPriority: String, AppEnum, Codable {
case low
case medium
case high
// Nama tipe yang ditampilkan
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Prioritas"
// Representasi setiap kasus
static var caseDisplayRepresentations: [TaskPriority: DisplayRepresentation] = [
.low: DisplayRepresentation(
title: "Rendah",
image: .init(systemName: "arrow.down.circle")
),
.medium: DisplayRepresentation(
title: "Sedang",
image: .init(systemName: "minus.circle")
),
.high: DisplayRepresentation(
title: "Tinggi",
image: .init(systemName: "exclamationmark.circle")
)
]
}
// Enum untuk status tugas
enum TaskStatus: String, AppEnum, Codable {
case pending
case inProgress
case completed
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Status"
static var caseDisplayRepresentations: [TaskStatus: DisplayRepresentation] = [
.pending: "Menunggu",
.inProgress: "Sedang berlangsung",
.completed: "Selesai"
]
}Representasi visual (ikon SF Symbols) memperkaya tampilan di Pintasan dan saran Siri.
Mengimplementasikan AppShortcutsProvider
AppShortcutsProvider mengekspos App Shortcuts ke sistem, membuatnya tersedia segera tanpa konfigurasi pengguna. Pintasan ini muncul di Spotlight, Siri, dan Action Button.
import AppIntents
// Provider yang mendeklarasikan semua pintasan aplikasi
struct TaskAppShortcutsProvider: AppShortcutsProvider {
// Maksimum 10 pintasan per aplikasi
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
// Pintasan untuk membuat tugas
AppShortcut(
intent: CreateTaskIntent(),
phrases: [
// Placeholder .applicationName WAJIB ada
"Buat tugas dengan \(.applicationName)",
"Tugas baru di \(.applicationName)",
"Tambahkan tugas ke \(.applicationName)"
],
shortTitle: "Buat tugas",
systemImageName: "plus.circle"
)
// Pintasan untuk mendaftar tugas
AppShortcut(
intent: ListTasksIntent(),
phrases: [
"Tampilkan tugas saya di \(.applicationName)",
"Tampilkan tugas dari \(.applicationName)",
"Apa saja tugas saya \(.applicationName)"
],
shortTitle: "Tugas saya",
systemImageName: "list.bullet"
)
// Pintasan dengan parameter dinamis
AppShortcut(
intent: CompleteTaskIntent(),
phrases: [
"Selesaikan \(\.$taskName) di \(.applicationName)",
"Tandai \(\.$taskName) sebagai selesai dengan \(.applicationName)"
],
shortTitle: "Selesaikan tugas",
systemImageName: "checkmark.circle"
)
}
}Frasa suara harus menyertakan placeholder \(.applicationName) agar Siri mengidentifikasi aplikasi tujuan. Parameter dinamis seperti \(\.$taskName) memungkinkan perintah kontekstual.
Aplikasi dapat mendeklarasikan maksimum 10 App Shortcuts. Sebaiknya prioritaskan aksi yang paling sering dan berguna bagi pengguna.
Intent dengan parameter kompleks
Intent dapat menerima parameter kompleks termasuk entity dan opsi konfigurasi.
import AppIntents
struct CompleteTaskIntent: AppIntent {
static var title: LocalizedStringResource = "Selesaikan tugas"
static var description = IntentDescription(
"Menandai tugas sebagai selesai."
)
// Parameter entity dengan pencarian otomatis
@Parameter(title: "Tugas")
var task: TaskEntity
// Parameter opsional dengan tanggal
@Parameter(title: "Tanggal selesai")
var completionDate: Date?
// Konfigurasi dialog Siri
static var parameterSummary: some ParameterSummary {
Summary("Selesaikan \(\.$task)") {
\.$completionDate
}
}
func perform() async throws -> some IntentResult & ProvidesDialog {
// Memperbarui tugas
TaskService.shared.completeTask(
id: task.id,
completionDate: completionDate ?? Date()
)
// Umpan balik suara untuk Siri
return .result(
dialog: "Tugas \(task.title) telah ditandai sebagai selesai."
)
}
}
// Intent yang mengembalikan daftar entity
struct ListTasksIntent: AppIntent {
static var title: LocalizedStringResource = "Daftar tugas"
// Filter opsional
@Parameter(title: "Status", default: nil)
var statusFilter: TaskStatus?
@Parameter(title: "Prioritas", default: nil)
var priorityFilter: TaskPriority?
func perform() async throws -> some IntentResult & ReturnsValue<[TaskEntity]> {
var tasks = TaskService.shared.fetchTasks()
// Terapkan filter
if let status = statusFilter {
tasks = tasks.filter {
switch status {
case .completed: return $0.isCompleted
case .pending, .inProgress: return !$0.isCompleted
}
}
}
if let priority = priorityFilter {
tasks = tasks.filter { $0.priority == priority }
}
let entities = tasks.map { TaskEntity(task: $0) }
return .result(value: entities)
}
}ParameterSummary mendefinisikan bagaimana Siri menyajikan intent selama eksekusi suara, memberikan umpan balik yang alami.
Siap menguasai wawancara iOS Anda?
Berlatih dengan simulator interaktif, flashcards, dan tes teknis kami.
Integrasi Apple Intelligence
iOS 18 memperkenalkan App Intent Domains, koleksi API yang dirancang untuk fungsionalitas spesifik. Domain ini memungkinkan Apple Intelligence memahami dan mengeksekusi aksi dengan akurasi yang lebih tinggi.
import AppIntents
// Konformitas ke domain Bookmarks untuk integrasi Apple Intelligence
struct SaveBookmarkIntent: AppIntent {
static var title: LocalizedStringResource = "Simpan bookmark"
// Parameter URL dengan validasi otomatis
@Parameter(title: "URL")
var url: URL
@Parameter(title: "Judul", default: nil)
var title: String?
@Parameter(title: "Folder", default: nil)
var folder: BookmarkFolderEntity?
// Buka aplikasi jika perlu
static var openAppWhenRun: Bool = false
func perform() async throws -> some IntentResult & ProvidesDialog {
let bookmark = BookmarkService.shared.save(
url: url,
title: title,
folder: folder?.id
)
return .result(
dialog: "Bookmark disimpan: \(bookmark.title)"
)
}
}
// Intent dengan kesadaran konten layar (iOS 18.4+)
struct AnalyzeScreenContentIntent: AppIntent {
static var title: LocalizedStringResource = "Analisis konten"
// Akses ke konteks layar via Apple Intelligence
@Parameter(title: "Konteks")
var screenContext: String?
func perform() async throws -> some IntentResult & ProvidesDialog {
guard let context = screenContext else {
return .result(dialog: "Tidak ada konten untuk dianalisis.")
}
// Memproses konten yang diekstrak oleh Apple Intelligence
let analysis = ContentAnalyzer.analyze(context)
return .result(dialog: analysis.summary)
}
}Domain pradefinisi (Books, Camera, Spreadsheets) memungkinkan Siri merespons permintaan dengan akurat berkat model yang dilatih pada tugas-tugas spesifik ini.
Aksi Siri dengan konfirmasi pengguna
Untuk aksi sensitif, sistem dapat meminta konfirmasi sebelum eksekusi.
import AppIntents
struct DeleteTaskIntent: AppIntent {
static var title: LocalizedStringResource = "Hapus tugas"
@Parameter(title: "Tugas")
var task: TaskEntity
// Memerlukan konfirmasi pengguna
static var isDiscoverable: Bool = true
func perform() async throws -> some IntentResult & ProvidesDialog {
// Meminta konfirmasi via dialog
try await requestConfirmation(
result: .result(
dialog: "Yakin ingin menghapus \(task.title)?"
)
)
// Hapus setelah konfirmasi
TaskService.shared.deleteTask(id: task.id)
return .result(
dialog: "Tugas \(task.title) telah dihapus."
)
}
}
// Intent dengan beberapa langkah dialog
struct ScheduleTaskIntent: AppIntent {
static var title: LocalizedStringResource = "Jadwalkan tugas"
@Parameter(title: "Tugas")
var task: TaskEntity
@Parameter(title: "Tanggal")
var scheduledDate: Date
@Parameter(title: "Pengingat", default: true)
var setReminder: Bool
static var parameterSummary: some ParameterSummary {
When(\.$setReminder, .equalTo, true) {
Summary("Jadwalkan \(\.$task) untuk \(\.$scheduledDate) dengan pengingat")
} otherwise: {
Summary("Jadwalkan \(\.$task) untuk \(\.$scheduledDate)")
}
}
func perform() async throws -> some IntentResult & ProvidesDialog {
TaskService.shared.schedule(
taskId: task.id,
date: scheduledDate,
reminder: setReminder
)
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .short
let formattedDate = dateFormatter.string(from: scheduledDate)
return .result(
dialog: "Tugas dijadwalkan untuk \(formattedDate)."
)
}
}Method requestConfirmation menjeda eksekusi sampai validasi pengguna, melindungi dari aksi yang tidak disengaja.
Intent dalam widget interaktif
App Intents terintegrasi secara alami dengan WidgetKit untuk membuat widget interaktif iOS 17+.
import AppIntents
import WidgetKit
// Intent yang dioptimalkan untuk widget (eksekusi cepat)
struct ToggleTaskFromWidgetIntent: AppIntent {
static var title: LocalizedStringResource = "Toggle tugas"
@Parameter(title: "ID tugas")
var taskID: String
init() {}
init(taskID: UUID) {
self.taskID = taskID.uuidString
}
// Tanpa dialog untuk widget
func perform() async throws -> some IntentResult {
guard let uuid = UUID(uuidString: taskID) else {
return .result()
}
TaskService.shared.toggleCompletion(taskId: uuid)
// Refresh widget seketika
WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")
return .result()
}
}
// View widget dengan tombol interaktif
import SwiftUI
struct TaskWidgetView: View {
let task: Task
var body: some View {
Button(intent: ToggleTaskFromWidgetIntent(taskID: task.id)) {
HStack {
Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
.foregroundStyle(task.isCompleted ? .green : .secondary)
Text(task.title)
.strikethrough(task.isCompleted)
}
.padding()
}
.buttonStyle(.plain)
}
}Widget menggunakan sintaks Button(intent:) untuk menghubungkan interaksi langsung ke App Intent, tanpa membuka aplikasi.
Konfigurasi Action Button
Action Button pada iPhone 15 Pro dan yang lebih baru dapat memicu App Shortcuts secara langsung.
import AppIntents
// Intent yang dioptimalkan untuk Action Button
struct QuickCaptureIntent: AppIntent {
static var title: LocalizedStringResource = "Pencatatan cepat"
static var description = IntentDescription(
"Membuat tugas dengan judul secara cepat."
)
// Buka aplikasi untuk input
static var openAppWhenRun: Bool = true
func perform() async throws -> some IntentResult & OpensIntent {
// Notifikasi untuk membuka layar pencatatan cepat
NotificationCenter.default.post(
name: .quickCaptureTriggered,
object: nil
)
return .result(opensIntent: ShowQuickCaptureViewIntent())
}
}
// Deklarasikan di AppShortcutsProvider untuk Action Button
extension TaskAppShortcutsProvider {
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
// ... pintasan lainnya
AppShortcut(
intent: QuickCaptureIntent(),
phrases: [
"Pencatatan cepat \(.applicationName)",
"Catatan cepat \(.applicationName)"
],
shortTitle: "Catat",
systemImageName: "bolt.circle"
)
}
}Pengguna dapat mengkonfigurasi Action Button untuk memicu pintasan ini melalui Pengaturan > Tombol Aksi.
iOS 18 memperkenalkan macros @DeferredProperty dan @ComputedProperty untuk mengurangi boilerplate. App Intents juga dapat berada dalam Swift Packages untuk reuse lintas platform.
Intents dalam Swift Packages
App Intents dapat didefinisikan dalam Swift Packages untuk berbagi antara iOS, macOS, dan watchOS.
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "TaskIntents",
platforms: [
.iOS(.v17),
.macOS(.v14),
.watchOS(.v10)
],
products: [
.library(name: "TaskIntents", targets: ["TaskIntents"])
],
dependencies: [],
targets: [
.target(
name: "TaskIntents",
dependencies: []
)
]
)
// Sources/TaskIntents/SharedIntents.swift
import AppIntents
// Intent berbagi lintas platform
public struct SharedCreateTaskIntent: AppIntent {
public static var title: LocalizedStringResource = "Buat tugas"
@Parameter(title: "Judul")
public var taskTitle: String
public init() {}
public func perform() async throws -> some IntentResult {
// Logika berbagi
await TaskRepository.shared.create(title: taskTitle)
return .result()
}
}
// Ekstensi spesifik platform di aplikasi
#if os(iOS)
extension SharedCreateTaskIntent {
// Perilaku spesifik iOS
static var openAppWhenRun: Bool = false
}
#endifArsitektur ini mempertahankan satu basis kode untuk intent sambil mengadaptasi perilaku per platform.
Menguji App Intents
App Intents dapat diuji secara unit seperti kode Swift apa pun.
import XCTest
import AppIntents
@testable import TaskApp
final class TaskIntentTests: XCTestCase {
override func setUp() {
super.setUp()
// Reset service untuk pengujian terisolasi
TaskService.shared.reset()
}
func testCreateTaskIntent() async throws {
// Given
var intent = CreateTaskIntent()
intent.taskTitle = "Tugas pengujian"
intent.priority = .high
// When
let result = try await intent.perform()
// Then
let tasks = TaskService.shared.fetchTasks()
XCTAssertEqual(tasks.count, 1)
XCTAssertEqual(tasks.first?.title, "Tugas pengujian")
XCTAssertEqual(tasks.first?.priority, .high)
}
func testCompleteTaskIntent() async throws {
// Given
let task = TaskService.shared.createTask(
title: "Tugas untuk diselesaikan",
priority: .medium
)
var intent = CompleteTaskIntent()
intent.task = TaskEntity(task: task)
// When
_ = try await intent.perform()
// Then
let updatedTask = TaskService.shared.fetchTask(id: task.id)
XCTAssertTrue(updatedTask?.isCompleted ?? false)
}
func testEntityQuery() async throws {
// Given
let task1 = TaskService.shared.createTask(title: "Tugas 1", priority: .low)
let task2 = TaskService.shared.createTask(title: "Tugas 2", priority: .high)
let query = TaskEntityQuery()
// When
let entities = try await query.entities(for: [task1.id, task2.id])
// Then
XCTAssertEqual(entities.count, 2)
}
}Pengujian memverifikasi perilaku intent secara independen dari antarmuka sistem.
Praktik terbaik dan optimisasi
Beberapa pola memastikan App Intents yang berkinerja baik dan pengalaman pengguna yang optimal.
import AppIntents
// 1. Frasa terlokalisasi
struct LocalizedTaskIntent: AppIntent {
static var title: LocalizedStringResource = "task.create.title"
static var description = IntentDescription(
"task.create.description",
categoryName: "task.category"
)
@Parameter(title: "task.parameter.title")
var taskTitle: String
func perform() async throws -> some IntentResult {
// ...
return .result()
}
}
// 2. Penanganan error yang tepat
enum TaskIntentError: Error, CustomLocalizedStringResourceConvertible {
case taskNotFound
case invalidInput
case serviceUnavailable
var localizedStringResource: LocalizedStringResource {
switch self {
case .taskNotFound:
return "Tugas tidak ditemukan."
case .invalidInput:
return "Data tidak valid."
case .serviceUnavailable:
return "Layanan sementara tidak tersedia."
}
}
}
struct RobustTaskIntent: AppIntent {
static var title: LocalizedStringResource = "Intent yang tangguh"
@Parameter(title: "ID")
var taskId: String
func perform() async throws -> some IntentResult & ProvidesDialog {
guard let uuid = UUID(uuidString: taskId) else {
throw TaskIntentError.invalidInput
}
guard let task = TaskService.shared.fetchTask(id: uuid) else {
throw TaskIntentError.taskNotFound
}
return .result(dialog: "Tugas ditemukan: \(task.title)")
}
}
// 3. Optimisasi EntityQuery
struct OptimizedTaskQuery: EntityStringQuery {
// Pencarian teks yang dioptimalkan
func entities(matching string: String) async throws -> [TaskEntity] {
// Pencarian sisi service dengan limit
TaskService.shared.search(query: string, limit: 10)
.map { TaskEntity(task: $0) }
}
// Saran terbatas demi kinerja
func suggestedEntities() async throws -> [TaskEntity] {
TaskService.shared.fetchRecentTasks(limit: 5)
.map { TaskEntity(task: $0) }
}
}
// 4. Focus Filter untuk mode Fokus
struct TaskFocusFilter: SetFocusFilterIntent {
static var title: LocalizedStringResource = "Filter tugas"
@Parameter(title: "Tampilkan hanya prioritas tinggi")
var showHighPriorityOnly: Bool
func perform() async throws -> some IntentResult {
TaskService.shared.setFocusFilter(highPriorityOnly: showHighPriorityOnly)
return .result()
}
}Pola-pola ini memastikan integrasi yang mulus dengan sistem sambil mempertahankan kinerja optimal.
Kesimpulan
App Intents dan Siri Shortcuts mengubah cara pengguna berinteraksi dengan aplikasi iOS. Pada tahun 2026, dengan Apple Intelligence, mengekspos intent tidak lagi opsional tetapi esensial untuk memberikan pengalaman modern dan intuitif.
Daftar periksa App Intents iOS 18+
- ✅ Buat AppIntents untuk aksi utama aplikasi
- ✅ Definisikan AppEntities untuk data yang dapat dimanipulasi
- ✅ Gunakan AppEnum untuk tipe enumerasi
- ✅ Implementasikan AppShortcutsProvider dengan frasa suara
- ✅ Patuhi batas maksimum 10 App Shortcuts
- ✅ Sertakan
\(.applicationName)di semua frasa - ✅ Konfigurasikan ParameterSummary untuk umpan balik Siri
- ✅ Integrasikan domain Apple Intelligence jika sesuai
- ✅ Uji intent dengan pengujian unit
- ✅ Lokalisasikan judul dan deskripsi
Mulai berlatih!
Uji pengetahuan Anda dengan simulator wawancara dan tes teknis kami.
Tag
Bagikan
Artikel terkait

WidgetKit iOS 17+: Widget Interaktif dengan App Intents
Panduan lengkap untuk membuat widget iOS interaktif dengan WidgetKit dan App Intents. Tombol, toggle, animasi, dan praktik terbaik untuk iOS 17+ pada 2026.

Combine vs async/await di Swift: Pola Migrasi Progresif
Panduan lengkap migrasi dari Combine ke async/await di Swift: strategi progresif, pola jembatan, dan koeksistensi paradigma di basis kode iOS.

Pertanyaan Wawancara Aksesibilitas iOS di 2026: VoiceOver dan Dynamic Type
Persiapkan wawancara iOS dengan pertanyaan kunci aksesibilitas: VoiceOver, Dynamic Type, trait semantik, dan audit.