
Contents
Chào bạn đến với Fx Studio. Chúng ta tiếp tục hành trình với thế giới SwiftUI trong series notes này. Bài viết này sẽ nói về một chủ đề mới và cũng là một chủ đề khá quan trọng. Đó là Life Cycle.
Trước khi vào bài, bạn là một người mới hay chưa biết về SwiftUI hoặc đã quên mất nó rồi. Bạn có thể cập nhật lại ở link bài viết sau:
Nếu mọi thứ đã ổn rồi, thì …
Bắt đầu thôi!
Chuẩn bị
Với SwiftUI App Life Cycle, đây là một tính năng mới nhất được Apple cung cấp. Do đó, bạn cần phải chuẩn bị phiên bản môi trường và Xcode mới nhất. Thông tin cấu hình như sau:
-
- Xcode 12
- Swift 5.3
- SwiftUI 2.0
- MacOS 10.15.x
Về demo, bạn hãy bắt đầu bằng một project mới với SwiftUI. Ngoài ra, bạn có thể checkout lại toàn bộ code demo của series ở đây:
-
- Toàn bộ repo: https://github.com/fx-studio/swiftui-notes
- Project bài trước: https://github.com/fx-studio/swiftui-notes/tree/main/005-ExtractingSubviews
1. UIKit App Delegate vs. SwiftUI App
Life Cycle là một trong những thứ mà một dev iOS cần phải nắm được. Hiểu được các hình thái/trạng thái của ứng dụng mình đang như thế nào. Bạn sẽ biết được sự kiện gì sẽ xảy ra khi bạn tương tác với ứng dụng của bạn … vâng vâng và mây mây!
Bạn có thể đọc thêm về Life Cycle với UIKit AppDelegate tại đây.
Mình xin phép không trình bày về nó để tập trung vào Life Cycle mới.
Từ khai sinh lập địa của iOS, chúng ta vẫn sử dụng duy nhất một Life Cycle với đại diện là AppDelegate. Nhưng cũng theo sự phát triển của lập trình thì iOS cũng không ngoài xu thế đó. Nhưng cái hay mà Apple đem tới cho bạn là việc …
Âm thầm chuẩn bị giúp bạn.
Tất cả những gì mình chia sẻ lại từ bài viết này, thì đã được Apple chuẩn bị trước đó 1 năm. Còn quay về với 1 năm trước thì bạn có Xcode 11. Với Xcode 11, project của bạn không chỉ có một màn hình main
. Bạn có thể thêm các cấu hình để AppDelegate cho phép UIKit có thể triển khai trên nhiều màn hình thiết bị khác nhau.
Nhưng đó là sự phát triển của UIKit. Bạn có thể xem lại bài viết này để thấy sự thay đổi trong Life Cycle của UIKit. Nó chuyển từ AppDelegate thành AppDelegate + SceneDelegate.
Và cái mà bạn cần quan tâm trong series này. Chính là SwiftUI. Và tới Xcode 12, Apple đã ưu ái cho nó riêng một cách quản lý Life Cycle của riêng SwiftUI.
2. Create a new App
Chúng ta sẽ bắt đầu từng bước một để khai phá Life Cycle mới nào! Trước tiên, bạn hãy tạo một project mới với SwiftUI.
Với tuỳ chọn Interface
là SwiftUI, thì phần Life Cycle
bạn có thêm 2 sự lựa chọn.
- UIKit App Delegate : đây là cách truyền thống với sự quản lý từ UIKit.
- SwiftUI App : đây là cách mới, thời đại mới.
Với lựa chọn Interface là Storyboard thì bạn chỉ có duy nhất một sự lựa chọn cho Life Cycle mà thôi. Đừng cố gắng thử làm gì! Ahihi!
Trước tiên, chúng ta hãy dạo chơi một vòng với SwiftUI App mới có những gì. Sau khi tạo xong, project chúng ta sẽ hiện ra. Thứ cần quan tâm là cấu trúc file cho template mới này có những gì.
Đập vào mắt là số lượng file có vẻ ít đi rất nhiều. Hay nói cách khác, SwiftUI App với template rất đơn giản. Bạn chỉ cần tập trung vào 2 file sau:
- ContentView.swift : đây là nơi chưa màn hình đầu tiên. À chính xác là view đầu tiên.
- TheNewAppApp.swift : mình đặt tên hơi củ chuối nên có 2 chứ App. Bạn chỉ cần biết có hậu tố
App
phía sau tên. Nó tương tự như file AppDelegate vậy. Nó sẽ quản lý toàn bộ ứng dụng.
3. SwiftUI App Protocol
Điểm khởi đầu mới.
Ta sẽ xem có gì trong file SwiftUI App mới đó.
import SwiftUI @main struct TheNewAppApp: App { var body: some Scene { WindowGroup { ContentView() } } }
3.1. @main
Nếu bạn nào thực sự tìm hiểu mỗi lần Swift mới ra mắt thì có những gì thay đổi. Thì mới hiểu được từ khoá @main
có tầng lớp ý nghĩa như thế nào. Mình sẽ tóm tắt lại như sau:
-
- Có từ Swift 5.3
- Không sử dụng được với file
main.swift
- Chỉ định struct/class nào đó là điểm bắt đầu khi khởi chạy một chương trình
- Nếu thêm 2 lần
@main
thì báo lỗi - Không thêm vào thì cũng bị lỗi
Bạn có thể đọc thêm về Swift 5.3 và từ khoá @main
tại đây.
3.2. App Protocol
Ấn tượng tiếp theo là bạn có một protocol với cái tên rất bá đạo, là App
. Nó quá cụ thể luôn. Đây chính là ứng dụng của bạn. Với việc implement protocol này, bạn cần phải khai báo thêm một phương thức của nó là:
@SceneBuilder var body: Self.Body { get }
Đây là một SceneBuilder, thứ cần để ứng dụng hiển thị cho người dùng thấy được. Trong đó, bạn cần tiếp tục khai báo thêm một WindowGroup. Em này lại cần một em là ContentView. Chắc phần này cũng dễ đoán mà thôi.
3.3. Scene
Bạn cũng đã biết mục đích chính của SwiftUI là có thể đưa ứng dụng lên nhiều nền tảng khác nhau. Do đó, thành phần Scene này cũng khá là linh hoạt.
Với mặc định từ Xcode 12 tạo ra thì bạn có đại diện chính là WindowGroup. Nó như là window
trong UIKit hay nhiều window
với MacOS.
Tuy nhiên, với nền tảng khác thì không chỉ có một Window mà còn thêm nhiều thứ khác nữa. Ví dụ: MacOS có những view đặc biệt như Setting thì chúng ta có thể if else
sau nha. Bạn xem thêm tại đây.
Thêm tí tổng hợp về họ hàng bà con nhà nó như sau:
-
- WindowGroup
- DocumentGroup
- Settings
- WKNotificationScene
- CommandsBuilder
- CommandMenu
- Command Protocol
Có điều kiện, mình sẽ viết bài riêng cho từng em một nha. Giờ tập trung sang phần chính tiếp theo nào!
4. App States Callbacks
Tiếp tục, bạn cần phải lắng nghe được sự thay đổi của trạng thái ứng dụng. Trong iOS 14, Apple cung cấp cho bạn ScenePhase để giúp bạn theo dõi trạng thái. Việc theo dõi này sẽ theo dõi từ môi tường. Do đó, bạn cần tạo thêm một biến môi trường cho Scene.
@Environment(\.scenePhase) private var scenePhase
Bạn đã có Scene rồi, giờ sử dụng thêm phương thức onChange(of:)
để lắng nghe các giá trị từ biến môi trường.
struct TheNewAppApp: App { @Environment(\.scenePhase) private var scenePhase var body: some Scene { WindowGroup { ContentView() }.onChange(of: scenePhase) { phase in switch phase { case .background: print("App State : Background") case .inactive: print("App State : Inactive") case .active: print("App State : Active") @unknown default: print("App State : Unknown") } } } }
Bạn sử dụng đoạn code trên để tiết kiệm thời gian. Sau đó hãy build ứng dụng lên các thiết bị iOS và xem kết quả.
5. App Init
Với AppDelegate, function didFinishLaunchWithOptions
được xem là rất thần thánh. Nơi ứng dụng bắt đầu khởi tạo và là nơi để chứa hết những gì cần chạy lúc ban đầu. Và tương tự với SwiftUI App. Bạn chỉ cần nhẹ nhàng thêm function init
huyền thoại vào là ổn.
struct TheNewAppApp: App { @Environment(\.scenePhase) private var scenePhase init() { // Bla bla bla } var body: some Scene { WindowGroup { ContentView() }.onChange(of: scenePhase) { phase in switch phase { case .background: print("App State : Background") case .inactive: print("App State : Inactive") case .active: print("App State : Active") @unknown default: print("App State : Unknown") } } } }
Còn muốn dùng làm gì thì tuỳ ý bạn nha!
6. Deeplink URLs
Phần tiếp theo, bạn cần là việc mở ứng dụng bằng một link URL. Không chỉ có mỗi việc mở ứng dụng mà chúng ta có thể làm nhiều thứ với nó. Như truyền thêm giá trị vào ứng dụng từ bên ngoài.
Giang hồ gọi là Deeplink.
Với AppDelegate, ta có thể handle nó thông qua phương thức sau application(_:open:options:)
. Còn với SwiftUI App ta cũng có phương thức tương tự.
WindowGroup { ContentView(name: name) .onOpenURL(perform: { url in print(url.absoluteURL) }) }.onChange(of: scenePhase) { phase in switch phase { case .background: print("App State : Background") case .inactive: print("App State : Inactive") case .active: print("App State : Active") @unknown default: print("App State : Unknown") } }
Với onOpenURL
, sử dụng cho Scene top trong ứng dụng. Ta có thể đón nhận các url
và khởi động app lên.
(Về setting cho Deeplink URL thì mình không trình bày ở bài viết này. Còn lại hình dưới đây là setting của mình.)
Để cho demo thêm sinh động ta sẽ implement thêm một chút nữa cho em ContentView. Nội dung cập nhật như sau:
- Với việc hiển thị tên dưới chữ
Hello, world!
. - Giá trị tên này sẽ truyền từ một link và mở bằng Safari.
Code của ContentView như sau:
struct ContentView: View { let name: String var body: some View { VStack { Text("Hello, world!") .padding() Text((name != "") ? name : "---") .font(.title) .foregroundColor(Color.blue) .padding() } } }
Bạn chuyển sang file App
, thêm 1 extension nhằm đọc giá trị từ query string của url
một cách nhanh chóng.
extension URL { func valueOf(_ queryParamaterName: String) -> String? { guard let url = URLComponents(string: self.absoluteString) else { return nil } return url.queryItems?.first(where: { $0.name == queryParamaterName })?.value } }
Sau đó, bạn sẽ hoàn thiện struct App như sau:
struct TheNewAppApp: App { @Environment(\.scenePhase) private var scenePhase @State var name: String = "" init() { // Bla bla bla } var body: some Scene { WindowGroup { ContentView(name: name) .onOpenURL(perform: { url in name = url.valueOf("name") ?? "" print(url.absoluteURL) print(name) }) }.onChange(of: scenePhase) { phase in switch phase { case .background: print("App State : Background") case .inactive: print("App State : Inactive") case .active: print("App State : Active") @unknown default: print("App State : Unknown") } } } }
Trong đó:
- Tạo một thuộc tính
@State
làname
, để lưu trữ giá trị và truyền cho ContentView - Tại
onOpenURL
, phân tíchurl
và gián giá trị mới choname
.
Cú pháp mở app từ Safari với url như sau: (cài đặt link url theo hình ở trên)
fxapp://fxapp.com?name=FxStudio
Build ứng dụng và xem kết quả:
- Safari sau khi gõ đường dẫn trên
- Ứng dụng được gọi lại từ background
EZ Game!
7. Connect to AppDelegate
Mặc dù SwiftUI App có thể độc lập với UIKit. Nhưng khi bạn phát triển ứng dụng trên iOS thì mối quan hệ này vẫn còn vương vấn rất nhiều. Hiện nay, nhiều thư viện hay dịch vụ của bên thứ ba vẫn cần được khởi tạo tại AppDelegate. Hoặc bạn có thể tracking nhiều hơn với AppDelegate.
Do đó, SwiftUI cũng giúp cho bạn có thể nhận được hay triệu hồi các phương thức AppDelegate. Ta sẽ thực hiện theo các bước sau:
Bước 1: tạo lại file AppDelegate.swift
import UIKit class MyAppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { print("AppDelegate > didFinishLaunchingWithOptions > called") return true } }
Trong đó:
- Triệu hồi UIKit trong file này
- Kế thừa UIResponder
- Implement protocol UIApplicationDelegate cho nó chắc
- Thêm phương thức
didFinishLaunchWithOptions
- Không khai báo hay khởi tạo thêm các
window
&scene
Đặc biệt trong file không có từ khoá sau @UIApplicationMain hay @main . Vì đây là SwiftUI App, main
đã được chỉ định ở nơi khác rồi.
Bước 2: Tạo adaptor từ file App. Thêm một thuộc tính như sau:
@UIApplicationDelegateAdaptor(MyAppDelegate.self) private var appDelegate
Khi ứng dụng được khởi tạo và chạy thì hàm didFinishLaunchWithOptions
cũng sẽ được gọi chạy theo. Bạn build lại ứng dụng và cảm nhận kết quả nha!
Tạm kết
- Tạo SwiftUI App với protocol App.
- Sử dụng
@main
để chỉ định struct/class nào là bắt đầu ứng dụng, - Tracking được các trạng thái của app bằng SwiftUI App.
- Sử dụng Deeplink vào trong SwiftUI App.
- Kết nối ngược lại với AppDelegate Life cycle.
Okay! Tới đây, mình xin kết thúc bài viết này. Và nếu có gì thắc mắc hay góp ý cho mình thì bạn có thể để lại bình luận hoặc gởi email theo trang Contact.
Cảm ơn bạn đã đọc bài viết này!
Related Posts:
Written by chuotfx
Hãy ngồi xuống, uống miếng bánh và ăn miếng trà. Chúng ta cùng nhau đàm đạo về đời, về code nhóe!
Leave a Reply Cancel reply
Fan page
Tags
Recent Posts
- Khi Cô Đơn Gặp Python
- Học vì tồn tại
- PARA – Phương pháp phân bổ tài nguyên giúp nâng cao hiệu quả sáng tạo
- Phù thủy phiên dịch ý tưởng
- XML Delimiters – Mở khóa thế giới prompt phức tạp
- Instructions – Cung cấp hướng dẫn cho các Gen AI
- SMART – Hướng dẫn dành tạo Prompt cho người mới bắt đầu
- Nhìn lại năm 2024
- CO-STAR – Công thức vàng để viết Prompt hiệu quả cho LLM
- Prompt Engineering trong 10 phút
You may also like:
Archives
- March 2025 (1)
- January 2025 (7)
- December 2024 (4)
- September 2024 (1)
- July 2024 (1)
- June 2024 (1)
- May 2024 (4)
- April 2024 (2)
- March 2024 (5)
- January 2024 (4)
- February 2023 (1)
- January 2023 (2)
- November 2022 (2)
- October 2022 (1)
- September 2022 (5)
- August 2022 (6)
- July 2022 (7)
- June 2022 (8)
- May 2022 (5)
- April 2022 (1)
- March 2022 (3)
- February 2022 (5)
- January 2022 (4)
- December 2021 (6)
- November 2021 (8)
- October 2021 (8)
- September 2021 (8)
- August 2021 (8)
- July 2021 (9)
- June 2021 (8)
- May 2021 (7)
- April 2021 (11)
- March 2021 (12)
- February 2021 (3)
- January 2021 (3)
- December 2020 (3)
- November 2020 (9)
- October 2020 (7)
- September 2020 (17)
- August 2020 (1)
- July 2020 (3)
- June 2020 (1)
- May 2020 (2)
- April 2020 (3)
- March 2020 (20)
- February 2020 (5)
- January 2020 (2)
- December 2019 (12)
- November 2019 (12)
- October 2019 (19)
- September 2019 (17)
- August 2019 (10)