WidgetKit iOS 17+: Widget Tương Tác với App Intents
Hướng dẫn đầy đủ tạo widget iOS tương tác với WidgetKit và App Intents. Nút bấm, công tắc, hoạt ảnh và thực hành tốt nhất cho iOS 17+ vào năm 2026.

iOS 17 đã cách mạng hóa WidgetKit bằng cách giới thiệu khả năng tương tác native. Widget không còn là hiển thị tĩnh nữa: giờ đây chúng có thể phản hồi các hành động của người dùng trực tiếp từ màn hình chính, mà không cần mở ứng dụng. Sự tiến hóa quan trọng này dựa trên framework App Intents và mang lại trải nghiệm người dùng mượt mà, hiện đại.
Bài viết này trình bày việc tạo đầy đủ widget tương tác iOS 17+, từ cấu hình dự án đến các mẫu nâng cao với hoạt ảnh và quản lý trạng thái.
Kiến Trúc Widget Tương Tác
Khả năng tương tác của widget iOS 17+ hoạt động thông qua framework App Intents. Khác với deep link truyền thống mở ứng dụng, App Intents cho phép thực thi mã trực tiếp từ widget, sau đó tự động làm mới hiển thị với dữ liệu mới.
import WidgetKit
import SwiftUI
import AppIntents
// Kiến trúc dựa trên ba thành phần chính:
// 1. Widget Timeline Provider - cung cấp dữ liệu
// 2. Widget View - hiển thị giao diện với Button/Toggle
// 3. App Intent - thực thi hành động khi chạm
struct TaskWidget: Widget {
// Mã định danh duy nhất của widget
let kind: String = "TaskWidget"
var body: some WidgetConfiguration {
// StaticConfiguration cho widget không có tham số
StaticConfiguration(
kind: kind,
provider: TaskTimelineProvider()
) { entry in
TaskWidgetView(entry: entry)
// Bắt buộc cho App Intents
.containerBackground(.fill.tertiary, for: .widget)
}
.configurationDisplayName("Nhiệm vụ")
.description("Quản lý nhiệm vụ của bạn từ màn hình chính.")
.supportedFamilies([.systemSmall, .systemMedium])
}
}Widget khai báo cấu hình của nó và chỉ định provider sẽ cung cấp dữ liệu. Thuộc tính .containerBackground là bắt buộc kể từ iOS 17 cho widget tương tác.
Tạo Timeline Provider
Timeline Provider quyết định khi nào và cách widget được làm mới. Đối với widget tương tác, nó cũng phải phản hồi các thay đổi gây ra bởi App Intents.
import WidgetKit
import SwiftUI
// Entry đại diện cho trạng thái widget tại một thời điểm
struct TaskEntry: TimelineEntry {
let date: Date
let tasks: [Task]
// Trạng thái tải cho phản hồi trực quan
var isLoading: Bool = false
}
// Mô hình dữ liệu được chia sẻ giữa ứng dụng và widget
struct Task: Identifiable, Codable {
let id: UUID
var title: String
var isCompleted: Bool
var priority: Priority
enum Priority: String, Codable {
case low, medium, high
}
}
struct TaskTimelineProvider: TimelineProvider {
// Placeholder hiển thị khi tải ban đầu
func placeholder(in context: Context) -> TaskEntry {
TaskEntry(
date: Date(),
tasks: [
Task(id: UUID(), title: "Nhiệm vụ mẫu", isCompleted: false, priority: .medium)
]
)
}
// Snapshot cho thư viện widget
func getSnapshot(in context: Context, completion: @escaping (TaskEntry) -> Void) {
let entry = TaskEntry(
date: Date(),
tasks: TaskDataManager.shared.fetchTasks().prefix(3).map { $0 }
)
completion(entry)
}
// Timeline đầy đủ với chính sách làm mới
func getTimeline(in context: Context, completion: @escaping (Timeline<TaskEntry>) -> Void) {
let tasks = TaskDataManager.shared.fetchTasks()
let entry = TaskEntry(date: Date(), tasks: Array(tasks.prefix(3)))
// Làm mới sau 15 phút hoặc sau hành động người dùng
let nextUpdate = Calendar.current.date(
byAdding: .minute,
value: 15,
to: Date()
) ?? Date()
let timeline = Timeline(
entries: [entry],
policy: .after(nextUpdate)
)
completion(timeline)
}
}Provider sử dụng TaskDataManager được chia sẻ để truy cập dữ liệu. Cách tiếp cận này đảm bảo đồng bộ hóa giữa ứng dụng chính và widget.
Để chia sẻ dữ liệu giữa ứng dụng và widget, cần cấu hình App Group trong capabilities của dự án. UserDefaults hoặc tệp phải sử dụng nhóm chia sẻ này.
Trình Quản Lý Dữ Liệu Chia Sẻ
Việc chia sẻ dữ liệu giữa ứng dụng và widget yêu cầu một container chung có thể truy cập qua App Group.
import Foundation
final class TaskDataManager {
// Singleton để truy cập toàn cục
static let shared = TaskDataManager()
// Mã định danh App Group được cấu hình trong Xcode
private let appGroupID = "group.com.example.taskapp"
// UserDefaults được chia sẻ giữa ứng dụng và widget
private var sharedDefaults: UserDefaults? {
UserDefaults(suiteName: appGroupID)
}
private let tasksKey = "tasks"
private init() {}
// Lấy nhiệm vụ từ bộ nhớ chia sẻ
func fetchTasks() -> [Task] {
guard let data = sharedDefaults?.data(forKey: tasksKey),
let tasks = try? JSONDecoder().decode([Task].self, from: data) else {
return []
}
return tasks
}
// Lưu với thông báo widget
func saveTasks(_ tasks: [Task]) {
guard let data = try? JSONEncoder().encode(tasks) else { return }
sharedDefaults?.set(data, forKey: tasksKey)
}
// Cập nhật một nhiệm vụ cụ thể
func updateTask(_ task: Task) {
var tasks = fetchTasks()
if let index = tasks.firstIndex(where: { $0.id == task.id }) {
tasks[index] = task
saveTasks(tasks)
}
}
// Chuyển đổi trạng thái hoàn thành
func toggleTaskCompletion(taskID: UUID) {
var tasks = fetchTasks()
if let index = tasks.firstIndex(where: { $0.id == taskID }) {
tasks[index].isCompleted.toggle()
saveTasks(tasks)
}
}
}Trình quản lý này đóng gói tất cả logic lưu trữ và sẽ được sử dụng bởi cả ứng dụng và App Intents của widget.
Tạo App Intent cho Tương Tác
App Intent định nghĩa hành động được thực thi khi người dùng tương tác với widget. iOS thực thi hành động này ở chế độ nền sau đó tự động làm mới widget.
import AppIntents
import WidgetKit
// Intent để chuyển đổi trạng thái nhiệm vụ
struct ToggleTaskIntent: AppIntent {
// Tiêu đề hiển thị trong phím tắt Siri
static var title: LocalizedStringResource = "Chuyển đổi trạng thái nhiệm vụ"
// Mô tả cho khả năng tiếp cận
static var description = IntentDescription("Đánh dấu nhiệm vụ là hoàn thành hoặc chưa hoàn thành.")
// Tham số: ID nhiệm vụ cần thay đổi
@Parameter(title: "ID nhiệm vụ")
var taskID: String
// Initializer bắt buộc cho AppIntent
init() {}
// Initializer với tham số để tạo từ view
init(taskID: UUID) {
self.taskID = taskID.uuidString
}
// Thực thi hành động
func perform() async throws -> some IntentResult {
// Chuyển đổi ID chuỗi sang UUID
guard let uuid = UUID(uuidString: taskID) else {
return .result()
}
// Cập nhật nhiệm vụ
TaskDataManager.shared.toggleTaskCompletion(taskID: uuid)
// Yêu cầu làm mới widget
WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")
return .result()
}
}Lời gọi WidgetCenter.shared.reloadTimelines kích hoạt làm mới widget ngay lập tức sau hành động, đảm bảo phản hồi trực quan tức thì.
Sẵn sàng chinh phục phỏng vấn iOS?
Luyện tập với mô phỏng tương tác, flashcards và bài kiểm tra kỹ thuật.
View Widget với Nút Tương Tác
View widget sử dụng thành phần Button chuẩn của SwiftUI với intent là hành động. iOS 17+ tự động chặn các tương tác này để thực thi App Intent.
import SwiftUI
import WidgetKit
struct TaskWidgetView: View {
let entry: TaskEntry
// Thích ứng với kích thước widget
@Environment(\.widgetFamily) var family
var body: some View {
VStack(alignment: .leading, spacing: 8) {
// Header với tiêu đề và bộ đếm
headerView
// Danh sách nhiệm vụ với nút tương tác
ForEach(entry.tasks.prefix(tasksLimit)) { task in
TaskRowView(task: task)
}
Spacer(minLength: 0)
}
.padding()
}
// Số lượng nhiệm vụ theo kích thước
private var tasksLimit: Int {
switch family {
case .systemSmall: return 2
case .systemMedium: return 3
default: return 4
}
}
private var headerView: some View {
HStack {
Text("Nhiệm vụ")
.font(.headline)
.fontWeight(.bold)
Spacer()
// Huy hiệu với số nhiệm vụ còn lại
let remaining = entry.tasks.filter { !$0.isCompleted }.count
Text("\(remaining)")
.font(.caption.bold())
.foregroundStyle(.white)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(remaining > 0 ? Color.orange : Color.green)
.clipShape(Capsule())
}
}
}
struct TaskRowView: View {
let task: Task
var body: some View {
// Nút với App Intent là hành động
Button(intent: ToggleTaskIntent(taskID: task.id)) {
HStack(spacing: 12) {
// Chỉ báo hoàn thành
Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
.font(.title3)
.foregroundStyle(task.isCompleted ? .green : .secondary)
// Tiêu đề nhiệm vụ
Text(task.title)
.font(.subheadline)
.strikethrough(task.isCompleted)
.foregroundStyle(task.isCompleted ? .secondary : .primary)
.lineLimit(1)
Spacer()
// Chỉ báo ưu tiên
priorityIndicator
}
.padding(.vertical, 6)
.padding(.horizontal, 10)
.background(Color(.systemBackground).opacity(0.5))
.cornerRadius(8)
}
.buttonStyle(.plain)
}
@ViewBuilder
private var priorityIndicator: some View {
switch task.priority {
case .high:
Image(systemName: "exclamationmark.circle.fill")
.foregroundStyle(.red)
case .medium:
Image(systemName: "minus.circle.fill")
.foregroundStyle(.orange)
case .low:
EmptyView()
}
}
}Cú pháp Button(intent:) kết nối trực tiếp nút với App Intent. Khi chạm, iOS thực thi perform() rồi tự động làm mới widget.
Toggle Tương Tác cho Widget
Đối với hành động kiểu bật/tắt, thành phần Toggle cung cấp một thay thế cho nút với phong cách iOS native.
import SwiftUI
import AppIntents
// Intent cụ thể cho Toggle với trạng thái rõ ràng
struct SetTaskCompletionIntent: AppIntent {
static var title: LocalizedStringResource = "Đặt trạng thái nhiệm vụ"
@Parameter(title: "ID nhiệm vụ")
var taskID: String
// Trạng thái mục tiêu: true = hoàn thành, false = chưa hoàn thành
@Parameter(title: "Hoàn thành")
var isCompleted: Bool
init() {}
init(taskID: UUID, isCompleted: Bool) {
self.taskID = taskID.uuidString
self.isCompleted = isCompleted
}
func perform() async throws -> some IntentResult {
guard let uuid = UUID(uuidString: taskID) else {
return .result()
}
var tasks = TaskDataManager.shared.fetchTasks()
if let index = tasks.firstIndex(where: { $0.id == uuid }) {
// Đặt trạng thái rõ ràng (không phải toggle)
tasks[index].isCompleted = isCompleted
TaskDataManager.shared.saveTasks(tasks)
}
WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")
return .result()
}
}
struct TaskToggleRowView: View {
let task: Task
var body: some View {
HStack {
Text(task.title)
.font(.subheadline)
.strikethrough(task.isCompleted)
Spacer()
// Toggle tương tác với intent
Toggle(
isOn: task.isCompleted,
intent: SetTaskCompletionIntent(
taskID: task.id,
isCompleted: !task.isCompleted
)
)
.toggleStyle(.switch)
.labelsHidden()
}
.padding(.vertical, 4)
}
}Toggle cung cấp tương tác trực quan hơn cho trạng thái nhị phân và tích hợp tự nhiên trong thiết kế iOS.
Widget không thể hiển thị cảnh báo, sheet hoặc điều hướng. Tất cả các hành động phải tự chứa và cập nhật trực tiếp trạng thái có thể nhìn thấy.
Hoạt Ảnh Làm Mới và Chuyển Tiếp
iOS 17+ cho phép hoạt ảnh chuyển tiếp trong khi làm mới widget sau hành động. Modifier .contentTransition kiểm soát các hoạt ảnh này.
import SwiftUI
import WidgetKit
struct AnimatedTaskRowView: View {
let task: Task
var body: some View {
Button(intent: ToggleTaskIntent(taskID: task.id)) {
HStack(spacing: 12) {
// Biểu tượng với hoạt ảnh chuyển tiếp
Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
.font(.title3)
.foregroundStyle(task.isCompleted ? .green : .secondary)
// Hoạt ảnh biểu tượng khi thay đổi
.contentTransition(.symbolEffect(.replace))
Text(task.title)
.font(.subheadline)
.strikethrough(task.isCompleted)
.foregroundStyle(task.isCompleted ? .secondary : .primary)
// Hoạt ảnh văn bản
.contentTransition(.opacity)
Spacer()
}
.padding(.vertical, 6)
.padding(.horizontal, 10)
.background(
RoundedRectangle(cornerRadius: 8)
.fill(task.isCompleted ? Color.green.opacity(0.1) : Color.clear)
)
// Hoạt ảnh nền
.animation(.easeInOut(duration: 0.3), value: task.isCompleted)
}
.buttonStyle(.plain)
}
}
// Widget với vô hiệu hóa hoạt ảnh
struct AnimatedTaskWidget: Widget {
let kind: String = "AnimatedTaskWidget"
var body: some WidgetConfiguration {
StaticConfiguration(
kind: kind,
provider: TaskTimelineProvider()
) { entry in
AnimatedTaskWidgetView(entry: entry)
.containerBackground(.fill.tertiary, for: .widget)
}
.configurationDisplayName("Nhiệm vụ Có Hoạt Ảnh")
.description("Widget với hoạt ảnh mượt mà.")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
// Kích hoạt hoạt ảnh nội dung
.contentMarginsDisabled()
}
}
struct AnimatedTaskWidgetView: View {
let entry: TaskEntry
var body: some View {
VStack(alignment: .leading, spacing: 8) {
headerView
ForEach(entry.tasks) { task in
AnimatedTaskRowView(task: task)
}
Spacer(minLength: 0)
}
.padding()
}
private var headerView: some View {
HStack {
Text("Nhiệm vụ")
.font(.headline.bold())
Spacer()
let completed = entry.tasks.filter(\.isCompleted).count
let total = entry.tasks.count
// Tiến trình có hoạt ảnh
Text("\(completed)/\(total)")
.font(.caption.bold())
.foregroundStyle(.secondary)
.contentTransition(.numericText())
}
}
}Hoạt ảnh .symbolEffect(.replace) và .numericText() tạo ra các chuyển tiếp mượt mà giữa các trạng thái, cải thiện đáng kể trải nghiệm người dùng.
Widget Có Thể Cấu Hình với AppIntentConfiguration
Đối với widget có thể tùy chỉnh bởi người dùng (bộ lọc, danh mục), AppIntentConfiguration thay thế StaticConfiguration.
import WidgetKit
import SwiftUI
import AppIntents
// Cấu hình được hiển thị cho người dùng
struct TaskWidgetConfigurationIntent: WidgetConfigurationIntent {
static var title: LocalizedStringResource = "Cấu hình nhiệm vụ"
static var description = IntentDescription("Tùy chỉnh hiển thị nhiệm vụ.")
// Bộ lọc theo ưu tiên
@Parameter(title: "Ưu tiên", default: .all)
var priorityFilter: PriorityFilter
// Hiển thị nhiệm vụ đã hoàn thành
@Parameter(title: "Hiển thị đã hoàn thành", default: true)
var showCompleted: Bool
// Số lượng nhiệm vụ tối đa
@Parameter(title: "Số nhiệm vụ", default: 3)
var maxTasks: Int
}
// Enum cho bộ lọc ưu tiên
enum PriorityFilter: String, AppEnum {
case all
case high
case medium
case low
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Ưu tiên"
static var caseDisplayRepresentations: [PriorityFilter: DisplayRepresentation] = [
.all: "Tất cả",
.high: "Cao",
.medium: "Trung bình",
.low: "Thấp"
]
}
// Provider thích ứng với cấu hình
struct ConfigurableTaskProvider: AppIntentTimelineProvider {
typealias Entry = TaskEntry
typealias Intent = TaskWidgetConfigurationIntent
func placeholder(in context: Context) -> TaskEntry {
TaskEntry(date: Date(), tasks: [])
}
func snapshot(for configuration: TaskWidgetConfigurationIntent, in context: Context) async -> TaskEntry {
let tasks = filteredTasks(for: configuration)
return TaskEntry(date: Date(), tasks: tasks)
}
func timeline(for configuration: TaskWidgetConfigurationIntent, in context: Context) async -> Timeline<TaskEntry> {
let tasks = filteredTasks(for: configuration)
let entry = TaskEntry(date: Date(), tasks: tasks)
let nextUpdate = Date().addingTimeInterval(15 * 60)
return Timeline(entries: [entry], policy: .after(nextUpdate))
}
// Áp dụng bộ lọc cấu hình
private func filteredTasks(for config: TaskWidgetConfigurationIntent) -> [Task] {
var tasks = TaskDataManager.shared.fetchTasks()
// Lọc theo ưu tiên
if config.priorityFilter != .all {
let priority = Task.Priority(rawValue: config.priorityFilter.rawValue) ?? .medium
tasks = tasks.filter { $0.priority == priority }
}
// Lọc đã hoàn thành nếu cần
if !config.showCompleted {
tasks = tasks.filter { !$0.isCompleted }
}
// Giới hạn số lượng
return Array(tasks.prefix(config.maxTasks))
}
}
// Widget với cấu hình người dùng
struct ConfigurableTaskWidget: Widget {
let kind: String = "ConfigurableTaskWidget"
var body: some WidgetConfiguration {
// AppIntentConfiguration cho widget có thể cấu hình
AppIntentConfiguration(
kind: kind,
intent: TaskWidgetConfigurationIntent.self,
provider: ConfigurableTaskProvider()
) { entry in
TaskWidgetView(entry: entry)
.containerBackground(.fill.tertiary, for: .widget)
}
.configurationDisplayName("Nhiệm vụ Tùy Chỉnh")
.description("Lọc và tùy chỉnh nhiệm vụ của bạn.")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
}
}Người dùng giờ đây có thể cấu hình widget thông qua nhấn lâu, mang lại trải nghiệm cá nhân hóa mà không cần mã bổ sung trong ứng dụng.
Xử Lý Lỗi và Trạng Thái Tải
UX tốt yêu cầu xử lý các trường hợp lỗi và trạng thái trung gian trong quá trình tương tác.
import AppIntents
import WidgetKit
struct ToggleTaskWithFeedbackIntent: AppIntent {
static var title: LocalizedStringResource = "Chuyển đổi nhiệm vụ với phản hồi"
@Parameter(title: "ID nhiệm vụ")
var taskID: String
init() {}
init(taskID: UUID) {
self.taskID = taskID.uuidString
}
func perform() async throws -> some IntentResult & ReturnsValue<Bool> {
guard let uuid = UUID(uuidString: taskID) else {
// Trả về thất bại im lặng
return .result(value: false)
}
// Mô phỏng hoạt động async (ví dụ: đồng bộ máy chủ)
do {
try await Task.sleep(for: .milliseconds(100))
TaskDataManager.shared.toggleTaskCompletion(taskID: uuid)
WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")
return .result(value: true)
} catch {
// Lỗi: không cập nhật widget
return .result(value: false)
}
}
}
// View với trạng thái tải
struct TaskRowWithLoadingView: View {
let task: Task
@State private var isLoading = false
var body: some View {
Button(intent: ToggleTaskIntent(taskID: task.id)) {
HStack(spacing: 12) {
// Chỉ báo có điều kiện
Group {
if isLoading {
ProgressView()
.scaleEffect(0.8)
} else {
Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
.foregroundStyle(task.isCompleted ? .green : .secondary)
}
}
.frame(width: 24, height: 24)
Text(task.title)
.font(.subheadline)
.strikethrough(task.isCompleted)
Spacer()
}
.padding(.vertical, 6)
.padding(.horizontal, 10)
.background(Color(.systemBackground).opacity(0.5))
.cornerRadius(8)
}
.buttonStyle(.plain)
.disabled(isLoading)
.opacity(isLoading ? 0.6 : 1.0)
}
}Phản hồi trực quan tức thì (độ mờ giảm, chỉ báo tải) thông báo cho người dùng rằng hành động của họ đã được ghi lại.
Thực Hành Tốt Nhất và Tối Ưu Hóa
Một số mẫu đảm bảo widget tương tác hiệu suất cao và đáng tin cậy.
import WidgetKit
import SwiftUI
// 1. Luôn vô hiệu hóa cache sau khi sửa đổi
final class WidgetRefreshManager {
static func refreshAllWidgets() {
// Làm mới tất cả widget của ứng dụng
WidgetCenter.shared.reloadAllTimelines()
}
static func refreshWidget(kind: String) {
// Làm mới một widget cụ thể
WidgetCenter.shared.reloadTimelines(ofKind: kind)
}
// Gọi từ ứng dụng sau khi sửa đổi dữ liệu
static func notifyDataChanged() {
Task { @MainActor in
refreshAllWidgets()
}
}
}
// 2. Giới hạn độ phức tạp của view
struct OptimizedWidgetView: View {
let entry: TaskEntry
var body: some View {
// Ưu tiên view đơn giản không có GeometryReader
VStack(alignment: .leading, spacing: 8) {
ForEach(entry.tasks.prefix(3)) { task in
// Thành phần nhẹ
minimalTaskRow(task)
}
}
.padding()
}
// View tối thiểu để tăng hiệu suất
@ViewBuilder
private func minimalTaskRow(_ task: Task) -> some View {
Button(intent: ToggleTaskIntent(taskID: task.id)) {
HStack {
Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
Text(task.title)
.lineLimit(1)
}
}
.buttonStyle(.plain)
}
}
// 3. Sử dụng @AppStorage cho trạng thái đơn giản
struct QuickSettingsWidgetView: View {
// Truy cập trực tiếp vào UserDefaults chia sẻ
@AppStorage("showCompletedTasks", store: UserDefaults(suiteName: "group.com.example.app"))
private var showCompleted = true
var body: some View {
// Trạng thái duy trì giữa các lần làm mới
Text(showCompleted ? "Hiển thị tất cả" : "Ẩn đã hoàn thành")
}
}
// 4. Tính toán trước dữ liệu trong provider
struct OptimizedTaskEntry: TimelineEntry {
let date: Date
let tasks: [Task]
// Dữ liệu được tính toán trước
let completedCount: Int
let pendingCount: Int
let highPriorityCount: Int
init(date: Date, tasks: [Task]) {
self.date = date
self.tasks = tasks
// Các tính toán được thực hiện một lần
self.completedCount = tasks.filter(\.isCompleted).count
self.pendingCount = tasks.filter { !$0.isCompleted }.count
self.highPriorityCount = tasks.filter { $0.priority == .high && !$0.isCompleted }.count
}
}Những tối ưu này đảm bảo widget phản hồi nhanh không tiêu tốn quá nhiều pin.
Sử dụng scheme widget trong Xcode để gỡ lỗi. Xem trước canvas cho phép kiểm tra các kích thước và trạng thái khác nhau mà không cần cài đặt trên thiết bị.
Kết Luận
WidgetKit iOS 17+ với App Intents biến widget thành các phần mở rộng tương tác thực sự của ứng dụng iOS. Kiến trúc khai báo này đơn giản hóa đáng kể việc phát triển trong khi cung cấp trải nghiệm người dùng native và mượt mà.
Danh Sách Kiểm Tra Widget Tương Tác iOS 17+
- ✅ Cấu hình App Group để chia sẻ dữ liệu
- ✅ Tạo Timeline Provider với làm mới phù hợp
- ✅ Triển khai App Intents cho mỗi hành động
- ✅ Sử dụng
Button(intent:)hoặcToggle(intent:)cho tương tác - ✅ Gọi
WidgetCenter.shared.reloadTimelinessau khi sửa đổi - ✅ Thêm
.containerBackgroundbắt buộc cho iOS 17+ - ✅ Triển khai hoạt ảnh chuyển tiếp mượt mà
- ✅ Xử lý trạng thái tải và lỗi
- ✅ Tối ưu hóa view cho hiệu suất pin
- ✅ Kiểm tra trên tất cả các kích thước widget được hỗ trợ
Bắt đầu luyện tập!
Kiểm tra kiến thức với mô phỏng phỏng vấn và bài kiểm tra kỹ thuật.
Thẻ
Chia sẻ
Bài viết liên quan

App Intents và Siri Shortcuts: tự động hóa iOS nâng cao 2026
Hướng dẫn đầy đủ về App Intents và Siri Shortcuts cho iOS 18+. Tạo hành động Siri tùy chỉnh, tích hợp Apple Intelligence và tự động hóa ứng dụng Swift trong năm 2026.

Combine vs async/await trong Swift: Mẫu Hình Di Cư Tiến Bộ
Hướng dẫn đầy đủ về di cư từ Combine sang async/await trong Swift: chiến lược tiến bộ, mẫu hình bắc cầu và sự cùng tồn tại của các mô hình trong codebase iOS.

Câu hỏi phỏng vấn về khả năng tiếp cận iOS năm 2026: VoiceOver và Dynamic Type
Chuẩn bị phỏng vấn iOS với những câu hỏi then chốt về khả năng tiếp cận: VoiceOver, Dynamic Type, các trait ngữ nghĩa và kiểm thử.