How to make Live Activities and Dynamic Island iOS 16.1

ในงาน WWDC 2022 ได้มีการเปิดตัวฟีเจอร์ใหม่ขึ้นมาที่ชื่อว่า Live activity ซึ่ง Live activity เป็นเหมือน Notification รูปแบบหนึ่งที่อยู่บนหน้าจอล็อกสกรีน ข้อดีคือสามารถอัปเดตข้อมูลได้แบบเรียลไทม์ ยกตัวอย่างเช่น สั่งอาหารจากร้านค้าเราสามารถตรวจสอบได้ว่ามีสถานะเป็นอย่างไรบ้างแล้ว อีกนานเท่าไหร่ถึงจะได้รับอาหาร หรือตัวอย่างหนึ่งก็คือ เราต้องการที่จะดูผลกีฬาที่กำลังแข่งขันอยู่เราก็สามารถทราบผลของกีฬาที่กำลังแข่งขันอยู่ได้แบบเรียลไทม์
ในวันที่มีการเปิดตัว iPhone 14 ได้มีนวัตกรรมใหม่จากทาง Apple ถูกปล่อยมาใหม่นั่นคือ Dynamic Island ซึ่งจะอยู่ใน iPhone 14 Pro และ iPhone 14 Pro Max เท่านั้น โดย Dynamic Island มีด้วยกัน 3 รูปแบบ
Expanded เป็นรูปแบบที่แสดงข้อมูลได้มากที่สุด และเป็นเพียงรูปแบบเดียวที่สามารถเพิ่มปุ่มเพื่อเพิ่มการทำงานอะไรบางอย่างให้ได้

Compact เป็นรูปแบบมาตรฐานของ dynamic island เมื่อมีการเรียกการใช้งาน เมื่อทำการกดที่ dynamic island จะทำการเข้าไปยังแอพที่ทำการเปิด dynamic island

minimal โหมดนี้จะทำงานก็ต่อเมื่อมีการทำงานของ live activity 2 แอพโดย 2 ส่วน
ซ้าย เป็นส่วนที่ live activity ถูกทำงานล่าสุด
ขวา เป็นส่วนที่ live activity ทำงานอยู่ก่อนแล้ว และมีการทำงานที่แอพอื่นเข้ามา

Let’s Start
Step 1
เพิ่ม Widget Extension ไปยังโปรเจ็กต์ของคุณ (File -> New -> Target -> Widget Extension)

💡 อย่าลืมเช็ค Include Live Activity ถูกเช็คไว้ไหม
Step 2
เพิ่ม NSSupportsLiveActivities ที่มีไทป์เป็นBoolean และต้องมีค่าเป็นYES ใน Info.plist

Step 3
หลังจากสร้าง Widget Extension แล้วเราจะได้ Files ให้เราสร้าง Attributes สำหรับใช้ใน Live Activity
import ActivityKit
public struct FootballMatch: ActivityAttributes {
// ContentState เป็น model ในส่วนที่มีการเปลี่ยนแปลงได้ตลอดเวลา
public typealias ContentState = Score
public struct Score: Codable, Hashable {
let score1: Int
let score2: Int
let information: String
}
// นอกจากที่อยู่ใน ContentState จะเป็นค่าที่ไม่ต้องการให้เปลี่ยนแปลง
let fullNameFirstTeam: String
let shortNameFirstTeam: String
let firstTeamLogo: String
let fullNameSecondTeam: String
let shortNameSecondTeam: String
let secondTeamLogo: String
}
Step 4
สร้างหน้าตาให้กับ Live Activity โดยจะแบ่งเป็นสองส่วนด้วยกัน คือ ส่วนของ Live Activity และ ส่วนของ Dynamic Island
struct LiveScoreWidgetLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: FootballMatch.self) { context in
// Lock screen/banner UI goes here
LockScreenLiveActivityView(context: context)
} dynamicIsland: { context in
DynamicIsland {
// Expanded UI goes here. Compose the expanded UI through
// various regions, like leading/trailing/center/bottom
DynamicIslandExpandedRegion(.leading) {
VStack {
Image(uiImage: UIImage(named: context.attributes.firstTeamLogo)!)
.resizable().frame(width: 70, height: 50)
Text(context.attributes.shortNameFirstTeam)
}
}
DynamicIslandExpandedRegion(.trailing) {
VStack {
Image(uiImage: UIImage(named: context.attributes.secondTeamLogo)!)
.resizable().frame(width: 70, height: 50)
Text(context.attributes.shortNameSecondTeam)
}
}
DynamicIslandExpandedRegion(.bottom) {
Text(context.state.information)
}
DynamicIslandExpandedRegion(.center) {
VStack {
Text("\(context.state.score1.description) - \(context.state.score2.description)")
.font(.largeTitle)
}
}
} compactLeading: {
HStack {
Text(context.attributes.shortNameFirstTeam)
Text(context.state.score1.description)
}
} compactTrailing: {
HStack {
Text(context.state.score2.description)
Text(context.attributes.shortNameSecondTeam)
}
} minimal: {
Image(systemName: "sportscourt.circle")
.foregroundColor(.indigo)
}
}
}
}
private struct LockScreenLiveActivityView: View {
let context: ActivityViewContext
var body: some View {
VStack(alignment: .leading) {
HStack {
VStack {
Image(uiImage: UIImage(named: context.attributes.firstTeamLogo)!)
.resizable()
.frame(width: 70, height: 50)
.cornerRadius(8)
Text(context.attributes.fullNameFirstTeam)
}
Spacer()
VStack {
Text("\(context.state.score1.description) - \(context.state.score2.description)")
.font(.largeTitle)
}
Spacer()
VStack {
Image(uiImage: UIImage(named: context.attributes.secondTeamLogo)!)
.resizable()
.frame(width: 70, height: 50)
.cornerRadius(8)
Text(context.attributes.fullNameSecondTeam)
}
}
Divider()
if context.state.information != "" {
HStack{
Image("ic_information")
.resizable()
.frame(width: 20, height: 20)
Text(context.state.information)
.multilineTextAlignment(.leading)
.font(.body.bold())
.lineLimit(1)
}.padding(.bottom, 8)
}
}.padding([.leading, .trailing, .top], 8)
}
}
Step 5
Starting an Activity
เราเรียกใช้งาน Activity ได้ด้วยเมธอดด้านล่างนี้
static func request(attributes: Attributes, contentState: Activity.ContentState, pushType: PushType? = nil) throws -> Activity
ในกรณีตัวอย่างที่ได้ยกขึ้นมา ผมต้องการที่จะติดตามการแข่งขันฟุตบอลที่กำลังแข่งขันอยู่ ดังนั้นจึงเรียกเปิดการใช้งาน Activity
private func startActivity() {
let attributes = FootballMatch(fullNameFirstTeam: "THAILAND", shortNameFirstTeam: "THAI", firstTeamLogo: "Thailand", fullNameSecondTeam: "JAPAN", shortNameSecondTeam: "JAP", secondTeamLogo: "Japan")
let contentState = FootballMatch.ContentState(score1: 0, score2: 0, information: "")
do {
currentActivity = try Activity.request(attributes: attributes, contentState: contentState)
} catch {
print(error.localizedDescription)
}
}
Updating an Activity
เราสามารถใช้เมธอดดังต่อไปนี้เพื่ออัปเดตข้อมูลให้กับ Activity
func update(using contentState: Activity.ContentState) async
ในแอปที่เราสร้าง จะมีการอัปเดตทุกๆครั้งที่มีข้อมูลสำคัญเกี่ยวการแข่งขันที่กำลังดำเนินการแข่งขันอยู่ ดังนั้นเราจะต้องส่งข้อมูลที่จะอัปเดตข้อมูลที่ใหม่เพื่อไปบอก Activity ดังนี้
private func updateActivity() {
let contentState = FootballMatch.ContentState(score1: 1, score2: 0, information: "Surasak get goal!!")
Task {
await currentActivity?.update(using: contentState)
}
}
💡ในการอัปเดตข้อมูลของ Activity เราสามารถส่งแจ้งเตือนด้วย AlertConfiguration ได้อีกด้วย
Ending an Activity
ถ้าเราต้องการที่จะยกเลิกการทำงานของ Activity เราสามารถใช้เมธอดนี้ในการยกเลิกการทำงาน
func end(using contentState: Activity.ContentState? = nil, dismissalPolicy: ActivityUIDismissalPolicy = .default) async
ในกรณีนี้ เราต้องการที่จะปิด Activity ที่แสดงของการแข่งขันที่เราเปิดไว้อยู่ เราจะใช้เมธอด end(using: , dismissalPolicy: )
private func stopActivity() {
let state = FootballMatch.ContentState(score1: 4, score2: 3, information: "Somsak got hat-trick!!")
Task {
await currentActivity?.end(using: state, dismissalPolicy: .af)
}
}
dismissalPolicy จะอยู่ด้วยกันทั้งหมด 3 ประเภท
immediate
รูปแบบการปิดของ immediate ก็คือระบบจะทำการลบ Activity ลงทันที
default
เราสามารถลบได้เอง หรือ ปล่อยทิ้งไว้เป็นเวลา 4 ชั่วโมงหลังจากที่สั่งปิดการทำงานของ Activity ลง เพื่อให้ระบบเป็นตัวลบ Activity ออกจากหน้าจอให้
after
เราสามารถกำหนดระยะเวลาในการหน่วงการลบ Activity ลงได้ หลังจากที่ทำการสั่งปิด Activity นั้นไปแล้ว
สามารถเข้าไปดูโค้ดได้ที่ด้านล่างนี้
Limitation
ไม่รองรับการดัดแปลงแอนิเมชันเมื่อทำการอัปเดต Live Activity
Live Activity อยู่ได้นานสูงสุด 12 ชั่วโมง
ไม่สามารถเข้าถึง network หรือการรับอัปเดตตำแหน่ง
ข้อมูลที่ใช้ในการอัปเดตต้องมีขนาดไม่เกิน 4kb
Reference
บทความล่าสุด

ผู้ร่วมก่อตั้ง Empire Market ถูกตัดสินจำคุก 40 ปี
ศาลสหรัฐฯ สั่งจำคุก Raheim Hamilton ผู้ร่วมก่อตั้ง Empire Market เป็นเวลา 40 ปี ข้อหาเปิดเว็บมืดค้ายาเสพติดและสิ่งผิดกฎหมายมูลค่ากว่า 430 ล้านดอลลาร์

อังกฤษเดินหน้าคว่ำบาตร 3 เว็บเทรดคริปโต เอี่ยวช่วยรัสเซียเลี่ยงมาตรการลงโทษ
สหราชอาณาจักรประกาศคว่ำบาตรเว็บเทรดคริปโต 3 แห่ง พร้อมผู้ให้บริการชำระเงินอีกหลายราย หลังสงสัยว่าช่วยรัสเซียหลบเลี่ยงมาตรการคว่ำบาตรจากชาติตะวันตก

ฝรั่งเศสจ่อเก็บภาษีคริปโตใหม่ ในงบประมาณปี 2027
คณะกรรมาธิการการเงินของฝรั่งเศสหนุนข้อเสนอใหม่ เตรียมเก็บภาษีการสวอป Stablecoin และจัดหนักภาษีคนรวยที่ย้ายประเทศ โดยคิดจากกำไรคริปโตที่ยังไม่ได้ขาย