The lifecycle of a SwiftUI view.
Bài đăng này đã không được cập nhật trong 4 năm
- 
Một trong những điều khác biệt của SwiftUIvới những người tiền nhiệm nhưUIKit,AppKitlà cácviewchủ yếu được khai báo dưới dạngvalue typenhưstructthay vìclass.
- 
Đây là một trong những thay đổi trong thiết kế kiến trúc khiến APISwiftUIhoạt động nhẹ nhàng, linh hoạt. Thay đổi này đôi khi khiến cho nhữngdevelopertrong đó có tôi thường xuyên nhầm lẫn vì các kiến thức lập trình hướng đối tượng đã sử dụng từ trước.
- 
Vì vậy trong bài viết này chúng ta hãy cùng dành thời gian để nghiên cữu một cách cẩn thận, kỹ lưỡng hơn về ý nghĩa, cách sử dụng của SwiftUItrong cách khai báo, tương tác với UI và hơn nữa là tìm cách để có thể tìm ra các phương pháp mới tốt hơn việc sử dụngUIKit,AppKittrong các project mới.
1/ Vai trò của property body:
- 
Property bodytrongView Protocolcó lẽ là điều khó hiểu nhất trongSwiftUIđặc biệt là khi nó liên quan mật thiết đến việcupdatecủaviewcũng nhưrendering cycle.
- 
Trong UIKit,AppKitchúng ta sử dụng nhữngmethodnhưviewDidLoadhaylayoutSubviewsđể nhận biết các event của hệ thống cũng như xử lý một đoạn logic trong khi vớiSwiftUIbody propertycó thể render lại view mà lại không sử dụng nhữngmethodtrên.
- 
body propertycho phép chúng tarender viewdựa trênstatehiện tại của nó và hệ thống sẽ dựa vàostatehiện tại của của nó để xem xét việc có cầnrenderlại view không. Ví dụ như khi build mộtUIKitViewControllerchúng ta thườngtriggercácupdatecủamodelvớimethodviewWillAppearđể đảm bảo rằngviewControllerluôn luônrenderlạiviewvớidatamới nhất:
class ArticleViewController: UIViewController {
    private let viewModel: ArticleViewModel
    
    ...
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        viewModel.update()
    }
}
- Khi chuyển sang SwiftUIthay vì ý tưởngrenderlạiviewmỗi khiviewWillAppearthì chúng ta triển khai việcviewModelsẽupdatekhi xử lýbody property` mới nhất:
struct ArticleView: View {
    @ObservedObject var viewModel: ArticleViewModel
    var body: some View {
        viewModel.update()
        return VStack {
            Text(viewModel.article.text)
            ...
        }
    }
}
- 
Tuy nhiên có vấn đề với việc triển khai bên trên là bodycủaviewsẽ bị so sánh ngay khi cácviewModelcó sự thay đổi nghĩa là chúng ta sẽ gây ra rất nhiều việcupdatekhông cần thiết củamodel.
- 
Dễ nhận thấy rằng body propertykhông phải là là nơi thuận tiện để xử lý những việcupdatekhông cần thiết trên mà thay vào đóSwiftUIđã cung cấp một vài tính năng tương tự như trongUIKit,AppKit. Chúng ta đang nói đến tùy chỉnhonAppeartương tự nhưviewWillAppeartrongcontrolelr:
struct ArticleView: View {
    @ObservedObject var viewModel: ArticleViewModel
    var body: some View {
        VStack {
            Text(viewModel.article.text)
            ...
        }
        .onAppear(perform: viewModel.update)
    }
}
2/ The initializer problem:
- 
Life cyclelà một vấn đề quan trọng mà mỗidevelopercần lưu tâm khi làm việc vớiview. Thực tế chúng ta dễ nhận thấy cácviewtrongSwiftUIcòn không có một vòng đời đúng nghĩa vì chúng ta sử dụngvalue typemà không phải làreference type.
- 
Khi chúng ta muốn thay đổi một ArticleViewvà update lại viewModel mỗi khiappđượcresumemỗi khi chuyển vềbackgroundthay vì mỗi khiviewappear. Một cách để chúng ta thực hiện điều đó là theo dõi mỗi đối tượng thông quaNotificationCenterkhi khởi tạo như sau:
struct ArticleView: View {
    @ObservedObject var viewModel: ArticleViewModel
    private var cancellable: AnyCancellable?
    init(viewModel: ArticleViewModel) {
        self.viewModel = viewModel
        cancellable = NotificationCenter.default.publisher(
    for: UIApplication.willEnterForegroundNotification
)
.sink { _ in
    viewModel.update()
}
    }
    var body: some View {
        VStack {
            Text(viewModel.article.text)
            ...
        }
    }
}
- Triển khai trên hoạt động bình thường cho đến khi chúng ta những ArticleViewvào cácviewkhác. Để diễn đạt trường hợp này chúng ta cùng tạo ra nhiềuArticleViewvaluenhưArticleListViewbằng việc sử dụngListvàNavigationLink:
struct ArticleListView: View {
    @ObservedObject var store: ArticleStore
    var body: some View {
        List(store.articles) { article in
            NavigationLink(article.title,
                destination: ArticleView(
    viewModel: ArticleViewModel(
        article: article,
        store: store
    )
)
            )
        }
    }
}
- 
NavigationLinksẽ yêu cầu chúng ta cung cấp chi tiếtdestinationcho từngviewđến và chúng ta đãsetuptrongNotificationCenterkhi khởi tạoArticleView. Cácobservationsẽ đượcactivengay lập tức mặc dù cácviewcòn chưa đượcrender.
- 
Do đó chúng ta nên triển khai các functioncàng nhỏ càng tốt và cácArticleViewsẽ được update khiappđược chuyển vềforegroundthay vìupdatetừngArticleViewModelmột.
- 
Để thực hiện triển khai trên chúng ta cần đến onReceivethay vì sử dụngNotificationCenterđể thao dõi quá trìnhviewđược khởi tạo. Thêm vào đó chúng ta không còn cầnCombine cancellablenữa:
struct ArticleView: View {
    @ObservedObject var viewModel: ArticleViewModel
    var body: some View {
        VStack {
            Text(viewModel.article.text)
            ...
        }
        .onReceive(NotificationCenter.default.publisher(
    for: UIApplication.willEnterForegroundNotification
)) { _ in
    viewModel.update()
}
    }
}
- Khi SwiftUIview được khởi tạo không đồng nghĩa với việc nó sẽ được hiển thị lên hoặc sử dụng. Đó là lí do tại saoSwiftUIyêu cầu chúng ta cần tạo ra cácviewtrước thay vì khởi tạo từng view một.
3/ Ensuring that UIKit and AppKit views can be properly reused:
- 
Chúng ta có thể đưa UIKit,AppKitvào sử dụng cùng SwiftUI thông quaProtocolUIViewPresentablevà chúng ta sẽ chịu trách nghiệm tạo vàupdatecácinstancecủaviewđang được hiển trị và sử dụng.
- 
Để minh họa chúng ta cùng renderNSAttributedStringbằng cách sử dụnginstanceUIKitnhưUILabel:
struct AttributedText: UIViewRepresentable {
    var string: NSAttributedString
    private let label = UILabel()
    func makeUIView(context: Context) -> UILabel {
        label.attributedText = string
        return label
    }
    func updateUIView(_ view: UILabel, context: Context) {
        // No-op
    }
}
- 
Tuy nhiên để sử dụng triển khai bên trên chúng ta cần giải quyết 2 vấn đề lớn - Trong trường hợp chúng ta khởi tạo UILabelbằng cáchassignnó cho property nghĩa là chúng ta không thể tái khởi tạo klaij nó mỗi khistructđược khởi tạo lại.
- Không updatelại view vớiupdateUIViewmethod,labelsẽ tiếp tụcrenderattributedTextgiống như cũ vàassignlại chomakeUIViewmặc dùstringđã đượcupdate.
 
- Trong trường hợp chúng ta khởi tạo 
- 
Cùng khỏi tạo UILabelvớimakeUIViewmethod. Chúng ta luônassignlạistringcủalabelvớiattributedTextpropertymỗi lầnupdateUIViewđược gọi:
struct AttributedText: UIViewRepresentable {
    var string: NSAttributedString
    func makeUIView(context: Context) -> UILabel {
        UILabel()
    }
    func updateUIView(_ view: UILabel, context: Context) {
        view.attributedText = string
    }
}
- Với cách trên thì cuối cùng UILabelcủa chúng ta cũng có thể tái sử dụng vàattributedTextluôn luôn đượcupdatevớiwrapperstringproperty.
All rights reserved
 
  
 