บทความ

[iOS] มาทำ Widget Home Screen กันวัยรุ่น

หลายคนที่ใช้ อุปกรณ์ Apple กันน่าจะเคยใช้ Widget ทั้ง iPhone / iPad / Mac จะเห็นบางแอปก็มี บางแอปก็ไม่มี มันทำงานยังไงนะ หรือใครกำลังพัฒนา Application แล้วอยากมี Widget บนแอปที่กำลังทำอยู่ วันนี้ผมจะมาแชร์วิธีทำไปพร้อมๆ กันครับ

image

เนื้อหาที่จะมาแชร์มีดังนี้

Widget คืออะไร

สิ่งที่ต้องมีก่อนทำ Widget

เริ่มสร้าง Widget

สร้าง View แยกแต่ละ Size ของ Widget

การ Update ข้อมูล Widget Timeline

เพิ่ม Configuration ให้ตัว Widget

ทำ Deep link Widget

Widget Limitations

1. Widget คืออะไร

image

Widget คือ พื้นที่การแสดงข้อมูลของแอปพลิเคชัน โดยที่เราไม่ต้องเข้าแอปพลิเคชันเพื่อไปดูข้อมูลนั้นๆ เช่น เราต้องการดูสภาพอากาศวันนี้เราก็สามารถดูผ่าน Widget บน home screen ได้โดยไม่ต้องเข้าแอปสภาพอากาศไปดูข้อมูล แต่แอปพลิเคชันนั้นจะต้อง ทำ Widget มารองรับเพื่อแสดงผลด้วย

2. สิ่งที่ต้องมีก่อนทำ Widget

Xcode 12.0 +

พื้นฐาน SwiftUI

3. เริ่มสร้าง Widget

Widget ที่เราจะสร้างมีรายละเอียดดังนี้

โชว์ Favourite Vehicle ในหน้า Home Screen

Widget แต่ละขนาดจะโชว์ UI ที่แตกต่างกัน

Widget สามารถปรับเปลี่ยนการแสดงผลตามค่า config

สุดท้ายจะออกมาหน้าตาแบบนี้

มาเริ่มกันเลยอันดับแรก สร้าง New Project ขึ้นมาโดยตั้งชื่อ Project ว่า FavouriteVehicle ดังภาพ

image

หลังจากสร้าง Project เสร็จแล้วเราจะทำการสร้าง Widget Extension

ไปที่ File -> New -> Target หลังจากนั้นพิมพ์ Widget ในช่อง Search เลือก Widget Extension แล้วกด Next

image

หลังจากกด Next ให้ตั้งชื่อ Product Name -> FavouriteVehicleWidget

Include Configuration Intent -> คือส่วนที่ทำให้ User สามารถตั้งค่า Configuration Widget ได้เดียวเราจะมาทำกันทีหลังครับ ตอนนี้ให้ติ๊กออกก่อน

กด Finish

image

หลังจากกด Finish Xcode จะถามว่าต้องการ เพิ่ม Scheme ตัว Widget Extension ไหมให้กด Activate เพื่อใช้ Run ตัว Widget

image

เราจะได้ Folder ใหม่เพิ่มเข้ามาตามภาพ

image

สร้าง Model

อันดับแรกสุดเราจะสร้าง Model เพื่อนำไปโชว์ในหน้า Widget กัน

สร้าง Folder ชื่อ Model แล้วสร้างไฟล์ VehicleDetails.swift โดยเราจะเก็บ name, description, image ส่วน id เราจะเอาไปใช้ทำ configuration กับ deeplink ทีหลัง

import Foundation

public struct VehicleDetails {
public let name: String
public let description: String
public let image: String

init(name: String, description: String, image: String) {
self.name = name
self.description = description
self.image = image
}
}

extension VehicleDetails: Identifiable {
public var id: String {
name
}
}

สร้าง Data Provider

Data Provider มีหน้าที่ส่งค่า Vehicles กลับมา ให้สร้าง Folder ชื่อ Data แล้วสร้างไฟล์ชื่อ VehiclesProvider.swift จะได้ตามนี้

struct VehiclesData {
static func fetchAllVehicles() -> [VehicleDetails] {
let vehicles = [
VehicleDetails(name: "Car", description: "I 💙 Car!!", image: "car"),
VehicleDetails(name: "Airplane", description: "I 💚 Airplan!!", image: "airplane"),
VehicleDetails(name: "Bus", description: "I 💛 Bus!!", image: "bus"),
VehicleDetails(name: "Ferry", description: "I 🧡 Ferry!!", image: "ferry"),
VehicleDetails(name: "Scooter", description: "I 💜 Car!!", image: "scooter")
]
return vehicles
}
}

อย่าลืมติ๊ก Apply Target ตัว Widget Extension ในไฟล์ VehicleDetails.swift กับ VehiclesProvider.swift ด้วยละวัยรุ่น เพราะไฟล์ที่สร้างมาจะถูกนำไปใช้ในส่วน Widget Extension

image

หลังจากเราสร้าง VehicleDetails.swift กับ VehiclesProvider.swift แล้ว Folder จะได้ประมาณนี้

image

4. สร้าง View แยกแต่ละ Size ของ Widget

มาถึงขั้นตอนที่เราจะออกแบบหน้า view ให้ไปแสดงในส่วนของ widget กันสุดท้ายหน้าตาจะออกมาแบบนี้

image
image
image

เริ่มแรกสร้าง Folder ชื่อ View ใน Folder FavouriteVehicleWidget หลังจากนั้นสร้างไฟล์ SwiftUI ชื่อ VehicleWidgetView.swift หลังจากสร้างเสร็จ import WidgetKit ด้วยนะ

สร้าง View สำหรับ Widget Small / Medium / Large

struct VehicleSmallView: View {
let vehicleDetails: VehicleDetails

var body: some View {
ZStack {
Color(.systemTeal)
VStack(spacing: 12) {
Image(systemName: vehicleDetails.image)
Text(vehicleDetails.name)
}
}
}
}

struct VehicleMediumView: View {
let vehicleDetails: VehicleDetails

var body: some View {
ZStack {
Color(.systemTeal)
VStack(spacing: 12) {
HStack {
Image(systemName: vehicleDetails.image)
Text(vehicleDetails.name)
}
Text(vehicleDetails.description)
}
.padding()
}
}
}

struct VehicleLargeView: View {
let vehicleDetails: VehicleDetails

var body: some View {
ZStack {
Color(.systemTeal)
VStack(spacing: 12) {
Image(systemName: vehicleDetails.image)
Text(vehicleDetails.name)
Text(vehicleDetails.description)
}
}
}
}

Add ในส่วนของ Preview Provider UI เข้าไปเพื่อให้ง่ายต่อการสร้าง UI หรือ Debug UI ตัว Canvas เราก็จะได้ UI Widget มาโชว์แล้ว

struct VehicleWidgetView_Previews: PreviewProvider {
static var previews: some View {
VehicleSmallView(vehicleDetails: .init(name: "Ferry", description: "I love ferry", image: "ferry"))
.previewContext(WidgetPreviewContext(family: .systemSmall))

VehicleMediumView(vehicleDetails: .init(name: "Bus", description: "I like bus", image: "bus"))
.previewContext(WidgetPreviewContext(family: .systemMedium))

VehicleLargeView(vehicleDetails: .init(name: "Scooter", description: "I like scooter", image: "scooter"))
.previewContext(WidgetPreviewContext(family: .systemLarge))
}
}

หลังจากนั้นเราจะแก้ในส่วน VehicleWidgetView ให้เป็นตัวดักทางเข้า View ว่าจะ แสดง Widget Size แบบไหน โดยอ้างอิงจากตัวแปร family โดยเราจะ Handle Switch Case Family หากเงื่อนไขการ Render ตรง Size ไหนก็จะเข้าเงื่อนไข View Size นั้นๆ

import SwiftUI
import WidgetKit

struct VehicleWidgetView: View {
@Environment(\.widgetFamily) var family: WidgetFamily
let vehicleDetails: VehicleDetails

@ViewBuilder
var body: some View {
switch family {
case .systemSmall:
VehicleSmallView(vehicleDetails: vehicleDetails)
case .systemMedium:
VehicleMediumView(vehicleDetails: vehicleDetails)
case .systemLarge:
VehicleLargeView(vehicleDetails: vehicleDetails)
default:
EmptyView()
}
}
}

5. การ Update ข้อมูล Widget Timeline

ต่อไปเราจะนำ Data ที่เราสร้างขึ้นมาพร้อมกับหน้า View นำไปแสดงในส่วนของ Widget โดย Data Widget เราจะทำให้เปลี่ยนข้อมูลทุกๆ 1 นาที

ขั้นตอนแรกเราจะเปลี่ยนชื่อ Model TimelineEntry จาก SimpleEntry เป็น VehicleDetailsEntry และเพิ่ม ตัวแปรสำหรับส่งค่า Vehicle Details ในไฟล์ FavouriteVehicleWidget.swift

struct VehicleDetailsEntry: TimelineEntry {
let date: Date
let vehicleDetails: VehicleDetails
}

หลังจากนั้นมาในส่วน Provider ทำการเปลี่ยนไปใช้โมเดล VehicleDetailsEntry ตามนี้

Placeholder

ข้อมูล Widger ตอน Render ครั้งแรกจะนำ View Placholder โดยใช้ข้อมูลส่วนนี้นำไปโชว์

struct Provider: TimelineProvider {
func placeholder(in context: Content) -> VehicleDetailsEntry {
VehicleDetailsEntry(
date: Date(),
vehicleDetails: .init(
name: "Car",
description: "I Love Car",
image: "car")
}
}

Get Snapshot

ข้อมูลที่จะเอาไปโชว์ตอน Preview สร้าง Widget ตอนที่จะเพิ่ม Widget ลงในหน้า Home Screen

func getSnapshot(in context: Context, completion: @escaping (VehicleDetailsEntry) -> ()) {
let entry = VehicleDetailsEntry(
date: Date(),
vehicleDetails: .init(
name: "Ferry",
description: "I Love Ferry",
image: "ferry")
)
completion(entry)
}

Get Timeline

การปั้น Data Timeline เพื่อนำไป Update Widget จากตัวอย่าง เราจะปั้น 1 Timeline ที่ประกอบไปด้วย Data Vehicle Details 5 ก้อน โดย อัปเดตข้อมูล Widget ทุกๆ 1 นาที

func getTimeline(in context: Context, completion: @escaping(Timeline) -> ()) {
var entries: [VehicleDetailsEntry] = []
let currentDate = Date()
let vehiclesDetails = VehiclesData.fetchAllVehicles()
for index in 0..<5 {
let entryDate = Calendar.current.date(byAdding: .minute, value: index, to: currentDate)!
let entry = VehicleDetailsEntry(date: entryDate, vehicleDetails: vehiclesDetails[index])
entries.append(entry)
}

let timeline = Timeline(entries: entries, policy: .atEnd)
completion(timeline)
}

โดยส่วน Object Timeline นั้นจะมีส่วน Policy ที่เราต้อง Set ค่า Policy ลงไป

TimelineReloadPolicy มี 3 ประเภท

.atEnd - Widget Request Data Timeline ใหม่หลังจากแสดงข้อมูลชุดสุดท้ายของ Timeline เดิมไปแล้ว

.after — Widget Request Data Timeline ใหม่โดยอ้างอิงจากค่าเวลาที่ส่งเข้ามา เช่น ส่งมา 1 ชั่วโมง การ Request Timeline ใหม่ก็จะเกิดขึ้นหลังจาก 1 ชั่วโมง ผ่านไปหลังจากแสดงข้อมูล Timeline เดิมเสร็จแล้ว

.never - Widget ไม่ Request Data Timeline ใหม่หลังจากแสดงข้อมูลชุดสุดท้ายของ Timlieline เก่าไปแล้ว

หลังจากนั้นเปลี่ยนทางเข้า View ไปเป็นอันที่เราสร้างขึ้นมาตามนี้

struct FavouriteVehicleWidgetEntryView : View {
var entry: Provider.Entry

var body: some View {
VehicleWidgetView(vehicleDetails: entry.vehicleDetails)
}
}

หลังจากนั้นเราจะมาเพิ่ม Support Families ให้ตัว Widget รวมถึง Display Name และ Description ให้ตัว Widget ตามนี้

kind คือ ID ของ Widget ต้องเป็น Unique String

StaticConfiguration คือ Object เพื่อแสดงเนื้อหาของ Widget โดยรับ kind และ Object Provider เพื่ออัปเดต Timeline Widget View

.supportedFamilies กำหนดว่า Widget Support ขนาดไหนบ้าง ซึ่งเราจะเพิ่มในส่วนของที่เราสร้าง View มา 3 ขนาด คือ systemSmall, systemMedium, systemLarge

configurationDisplayName คือ Title Name Widget

description คือ คำอธิบายของ Widget นั้นๆ

@main
struct FavouriteVehicleWidget: Widget {
let kind: String = "FavouriteVehicleWidget"

var body: some WidgetConfiguration {
StaticConfiguration(kind: kind, provider: Provider()) { entry in
FavouriteVehicleWidgetEntryView(entry: entry)
}
.configurationDisplayName("Favourite Vehicle")
.description("Display Favourite Vehicle")
.supportedFamilies([.systemSmall, systemMedium, systemLarge])
}
}

ส่วน Preview ก็ให้เพิ่มข้อมูลเพื่อใช้ Preview ลงไป

struct FavouriteVehicleWidget_Previews: PreviewProvider {
static var previews: some View {
FavouriteVehicleWidgetEntryView(
entry: VehicleDetailsEntry(
date: Date(),
vehicleDetails: .init(
name: "Ferry",
description: "I Love Ferry",
image: "ferry")
)
)
.previewContext(WidgetPreviewContext(family: systemSmall))
}
}

กดเลือก Schema Widget แล้ว Run เลยวัยรุ่นเมืองไทย ตอนนี้เราก็จะได้ Widget ที่มี 3 ขนาดตามที่เราสร้างมาและ Reload Data ทุกๆ 1 นาทีแล้ว 👏👏 🎉🎉

image

6. เพิ่ม Configuration ให้ตัว Widget

ต่อไปเราจะเพิ่ม Configuration ให้ตัว Widget แสดงผลตามที่ User ตั้งค่าได้โดยเราจะให้ User สามารถแก้ไข Widget เพื่อเลือก Vehicle ที่ชอบแล้วนำไปแสดงบน Widget มาเริ่มกันเลย

ซึ่งส่วนนี้หากเราติ๊ก Include Configuration Intent ตอนสร้าง Project จะได้ไฟล์ ในส่วนนี้มาแต่เราจะมาสร้างเพิ่มทีหลังกัน

สร้าง Intent Definition

อันดับแรก ไปที่ File -> New -> File ค้นหา Sirikit Intent Definition File กดถัดไปโดยตั้งชื่อตามนี้ FavouriteVehicleIntents

image
image

หลังจากนั้นเปิดไฟล์ FavouriteVehicleIntents.intentdefinition แล้วกด + New Intent เปลี่ยนชื่อเป็น SelectVehicle

image

จากนั้นสร้าง New Type กด + แล้วไปที่ New Type ตั้งชื่อว่า VehicleINO จะได้ properties identifier กับ displayString

image

หลังจากเพิ่ม Type Vehicle แล้วกลับมาที่ SelectVehicle

เปลี่ยน Category ให้เป็น View

ติ๊ก Intent is eligible for widget

ติ๊ก User can edit value in Shortcuts widget

ติ๊ก Options are provideed dynamically

กด + ในช่อง parameter เพื่อเพิ่ม Parameter เลือกเปลี่ยนชื่อเป็น vehicle และตรง Type ให้เลือก VehicleINO ที่เราพึ่งสร้างไปจะได้ตามนี้

image

สร้าง Intents Extension

เราจะมาสร้าง Intent Extention กันต่อ ให้ไปที่ Project กด + แล้วเลือก Intents Extension

image

ตั้งชื่อว่า FavouriteVehicle Intent แล้วกด Finish แล้วกด Activate Scheme

image

เราจะกลับมาติ๊ก Target Membership ของตัว FavouriteVehicle Intent โดยไฟล์ที่ต้องติ๊ก Apply Target มีดังนี้

image

VehiclesProvider.swift

VehicleDetails.swift

FavouriteVehicleIntents.intentdefinition

เชื่อม Class Intent Definition กับ Intent Extension

คลิก Project แล้วไปที่ Target FavouriteVehicle Intent ที่เราพึ่งสร้าง ตรง Support Intent ให้ใส่ ชื่อ class SelectVehicleIntent ลงไปตามที่เราพึ่งสร้างใน intent definition

image

Provide Data Configuration

ต่อไปเราจะมาสร้างในส่วน Provide Data ในส่วนของ Configuration ให้คลิกไปที่ไฟล์ IntentHandler.swift

Apply Protocol ชื่อ SelectVehicleIntentHandling

Protocol นี้จะ Generate Auto มาให้หลังจากเราสร้าง Custom Intent ในไฟล์ Intent Definition และเชื่อม Class Intent Definition กับ Intent Extension แล้ว

provideVehicleOptionsCollection เราจะส่ง Data Vehicles ทั้งหมดที่มีให้ User เลือก Configuration จาก Widget โดยปั้น Object VehicleINO ที่เราสร้างขึ้นมา แล้วส่งค่า id และ name จะได้ตามนี้

class IntentHandler: INExtension {
override func handler(for intent: INIntent) -> Any {
// This is the default implementation. If you want different objects to handle different intents,
// you can override this and return the handler you want for that particular intent.

return self
}
}

extension IntentHandler: SelectVehicleIntentHandling {
func provideVehicleOptionsCollection(
for intent: SelectVehicleIntent,
with completion: @escaping (INObjectCollection?, Error?) -> Void
) {
var vehicles = [VehicleINO]()
VehiclesData.fetchAllVehicles().forEach { vehicle in
let vehicleINO = VehicleINO(identifier: vehicle.id, display: vehicle.name)
vehicles.append(vehicleINO)
}
completion(INObjectCollection(items: vehicles), nil)
}
}

เปลี่ยน StaticConfiguration เป็น IntentConfiguration

ตอนนี้ Widget เป็นแบบ StaticConfiguration อยู่ดังนั้นเราจะมาเปลี่ยนให้เป็น IntentConfiguration กัน

StaticConfiguration คือ Widget ที่ไม่สามารถแก้ไขตั้งค่า Widget ได้

IntentConfiguration คือ Widget ที่สามารถแก้ไขตั้งค่า Widget ได้

Provider เปลี่ยน Protocol จาก TimelineProvider เป็น IntentTimelineProvider ตัว typealias ของ Entry ให้ใช้ VehicleDetailsEntry และ typealias ของ Intent ให้ใช้ SelectVehicleIntent

struct Provider: IntentTimelineProvider {
typealias Entry = VehicleDetailsEntry
typealias Intent = SelectVehicleIntent
....
....
}

function getSnapshot จะมีการเพิ่ม Parameter Configuration เพิ่มเข้ามา เราจะทำการแก้ไขให้ ส่ง Vehicle ตรงตามค่า configuration ที่รับมาแล้ว return Vehicle Model ที่มีค่า Id ตรงกับ ค่า Configuration

func getSnapshot(for configuration: SelectVehicleIntent, in context: Context, completion: @escaping (VehicleDetailsEntry) -> Void) {
let vehicle = VehiclesData.fetchAllVehicles().first ?? VehicleDetails(name: "Car", description: "I Love Car", image: "car")
var entry = VehicleDetailsEntry(date: Date(), vehicleDetails: vehicle)
guard
let id = configuration.vehicle?.identifier,
let vehicle = VehiclesData.fetchAllVehicles().first(where: { vehicle in
vehicle.id == id
})
else {
return completion(entry)
}
entry = VehicleDetailsEntry(date: Date(), vehicleDetails: vehicle)
completion(entry)
}

function getTimeline จะมีการเพิ่ม Parameter Configuration เหมือนกันดังนั้นเราจะเพิ่มเงื่อนไขหาก User เลือก Configuration มาเราจะส่ง Vehicle Id ที่ตรงกับ Configuration นั้นกลับไปเช่นเดียวกัน

func getTimeline(for configuration: SelectVehicleIntent, in context: Context, completion: @escaping (Timeline) -> Void) {
var entries = [VehicleDetailsEntry]()
let vehicle = VehiclesData.fetchAllVehicles().first(where: { vehicle in
vehicle.id == configuration.vehicle?.identifier
}) ?? VehicleDetails(name: "Car", description: "I Love Car", image: "car")
let entry = VehicleDetailsEntry(date: Date(), vehicleDetails: vehicle)
entries.append(entry)
let timeline = Timeline(entries: entries, policy: .never)
completion(timeline)
}

แก้ไขตัว Widget จาก Staticonfiguration ให้เป็น IntentConfiguration โดยค่าที่ต้องส่งเพิ่มเข้าไปคือ intent มาจาก Class ที่เราสร้างเอาไว้ก่อนหน้านี้

@main
struct FavouriteVehicleWidget: Widget {
let kind: String = "FavouriteVehicleWidget"

var body: some WidgetConfiguration {
IntentConfiguration(
kind: kind,
intent: SelectVehicleIntent.self,
provider: Provider()
) { entry in
FavouriteVehicleWidgetEntryView(entry: entry)
}
.configurationDisplayName("Favourite Vehilce")
.description("Display Favourite Vehicle")
.supportedFamilies([.systemSmall, .systemMedium, .systemLarge])
}
}

จากนั้นเรามาลอง Run Widget กัน ให้เลือก Scheme Widget แล้วกด Run เราจะสามารถแก้ไข Widget ได้แล้วโดยข้อมูลที่แสดงบน Widget จะอ้างอิงจาก การเลือก โดยเทียบค่า id ให้ตรงกัน ทุกครั้งที่เราเลือก Config ตัว function getTimeline จะถูกเรียกอีกครั้งและส่ง timeline ชุดใหม่มาแสดง

image
image
image

เราก็จะได้ Vehicle นำไปแสดงบน Widget ตามที่เราเลือกแล้วเกินปุยมุ้ยวัยรุ่น

7. ทำ Deep Link Widget

อันดับแรกเราจะสร้างหน้า Vehicle Detail ไว้รองรับ Deep Link เมื่อ Tap Widget ให้มาเปิดหน้านี้

เริ่มสร้าง Folder View และสร้างไฟล์ SwiftUI ชื่อ VehicleDetailsView.swift

import SwiftUI

struct VehicleDetailsView: View {
@Environment(\.dismiss) var dismiss
let vehicleDetails: VehicleDetails

var body: some View {
ZStack {
Color(.systemPink)
VStack(spacing: 16) {
Image(systemName: vehicleDetails.image)
Text(vehicleDetails.name)
Text(vehicleDetails.description)
Button("Dismiss") {
dismiss()
}
}
}
.ignoresSafeArea()
}
}

struct VehicleDetailsView_Previews: PreviewProvider {
static var previews: some View {
VehicleDetailsView(vehicleDetails: .init(name: "Car", description: "I Love Car", image: "car"))
}
}

Folder ที่ได้จะเป็นแบบนี้

image

จากนั้นมาเพิ่ม Url ใน model VehicleDetails เพื่อนำมาใช้ในการเปิด Deep Link

import Foundation

public struct VehicleDetails {
public let name: String
public let description: String
public let image: String
public let url: URL?

init(name: String, description: String, image: String) {
self.name = name
self.description = description
self.image = image
self.url = URL(string: "vehicle://\(name)")
}
}

extension VehicleDetails: Identifiable {
public var id: String {
name
}
}

ทำการเพิ่ม View Modifier ของ VehicleSmallView / VehicleMediumView / VehicleLargeView ด้วย .widgetURL(vehicleDetail.url)

image
image
image

หลังจากนั้นมาเพิ่มในส่วนของ ContentView

เพิ่ม model vehicleDetails ไว้ส่งเป็น binding ตอนเปิดหน้า VehicleDetailsView.swift และเก็บค่า model เมื่อเปิด url

เพิ่ม onOpenURL เราจะ Handle โดยการเช็ก url ที่เปิดมาจาก Widget ตรงกับ Data ชุดไหนเราก็จะเอา Data ชุดนั้น Assign เก็บไปที Model VehicleDetails ที่เราสร้างมา

เพิ่ม fullScreenCover โดยส่งตัวแปร binding vehicleDetails เมื่อเราเปิด url แล้วเก็บค่าเข้า vehicleDetails ก็จะทำการเปิด present full screen ไปที่หน้า VehicleDetailsView

struct ContentView: View {
@State private var vehicleDetails: VehicleDetails?

var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
}
.padding()
.onOpenURL { url in
guard let vehicleDetails = VehiclesData.fetchAllVehicles().first(where: { $0.url == url }) else { return }
self.vehicleDetails = vehicleDetails
}
.fullScreenCover(item: $vehicleDetails, content: { vehicleDetails in
VehicleDetailsView(vehicleDetails: vehicleDetails)
})
}
}

ผลลัพธ์ที่ได้ก็จะออกมาเป็นแบบนี้ตอนกด Tap Widget ตัวแอปจะไปหน้า VehicleDetails และส่งข้อมูล Data ที่ตรงกับ ค่า Config Widget เพื่อนำมาแสดง

8. Widget Limitations

Widget รองรับเฉพาะบน SwiftUI เท่านั้น บน UIKit หมดสิทธิ์

Widget ไม่สามารถทำ Animation หรือ ใส่ Video ได้

Widget ไม่สามารถใส่ Scroll View ได้

Widget Size ไม่สามารถ Custom ได้ต้องทำขนาดที่ Apple กำหนดมาให้เท่านั้น Small / Medium / Large / Extra Large (iPad)

Widget บางครั้งเมื่อถึงเวลา Reload Data แล้ว Widget จะไม่แสดงข้อมูลล่าสุดจนกว่า User จะเลื่อนหน้าจอไปดู Widget

มาถึงตอนนี้แล้วบอกเลยการทำ Widget ไม่ได้ยากอย่างที่คิด และสิ่งที่น่าสนใจมากกว่านั้นคือ Apple พึ่งปล่อย Widget Lock Screen มาไม่นาน อาจไปลองศึกษาเพิ่มเติมกันได้ครับ

สุดท้ายนี้หากอธิบายตรงไหนผิดพลาดประการใด ต้องอภัยมา ณ ที่นี้ด้วยครับ

ขอบคุณวัยรุ่นทุกคนที่เข้ามาอ่านครับผม

References

Creating a Widget Extension

Making a Configurable Widget

ผู้เขียน: Vorapat Phorncharroenroj | 09 ธ.ค. 65 | อ่าน: 2428
บทความล่าสุด