viewDidLoad là phương thức đầu tiên mà UIKit gọi sau khi tải View của UIViewController vào bộ nhớ. Theo Apple Developer Documentation, phương thức này được gọi đúng một lần trong suốt thời gian tồn tại của controller. viewDidLoad là nơi chính để thiết lập giao diện ban đầu, đăng ký cell và khởi tạo dữ liệu.
Những điểm chính
viewDidLoad là một phương thức instance của UIViewController mà UIKit gọi ngay sau khi View của controller được tải vào bộ nhớ. Tại thời điểm này, tất cả các thuộc tính IBOutlet đã được kết nối với các thành phần giao diện, nhưng View chưa được thêm vào hệ thống phân cấp cửa sổ và chưa hiển thị cho người dùng. Nhà phát triển ghi đè phương thức này để thực hiện cấu hình màn hình ban đầu.
Phương thức này là một phần của ViewController Lifecycle và xuất hiện ngay sau loadView nếu View được tạo theo chương trình, hoặc sau khi tải từ Storyboard. Trong một dự án điển hình, viewDidLoad là phương thức được ghi đè thường xuyên nhất của UIViewController, vì nó cung cấp một điểm an toàn để làm việc với các subview đã tồn tại và sẵn sàng để cấu hình.
Một chi tiết quan trọng: tại thời điểm viewDidLoad được gọi, kích thước của View chưa tương ứng với kích thước cuối cùng — Auto Layout chưa hoàn thành các lượt xử lý và frame có thể khác so với dự kiến. Đối với các tính toán phụ thuộc vào kích thước, hãy sử dụng viewDidLayoutSubviews.
Thời điểm gọi viewDidLoad phụ thuộc vào cách controller được khởi tạo. Trong hầu hết các trường hợp, UIKit tự động gọi phương thức này khi thuộc tính view của controller được truy cập lần đầu tiên — đây được gọi là cơ chế lazy-loading của UIViewController.
Khi NavigationController hoặc TabBarController hiển thị màn hình của bạn lần đầu tiên, UIKit kiểm tra xem View đã được tải chưa. Nếu chưa — loadView được gọi (hoặc tải từ Storyboard), sau đó viewDidLoad được kích hoạt ngay lập tức. Đây là kịch bản tiêu chuẩn và nó xảy ra một lần cho mỗi instance controller.
override func viewDidLoad() {
super.viewDidLoad()
print("View đã được tải — bạn có thể cấu hình giao diện")
setupUI()
configureTableView()
}
viewDidLoad không được gọi lại khi quay lại màn hình qua nút back hoặc dismiss. Nếu logic của bạn phụ thuộc vào việc màn hình xuất hiện lại — hãy đặt nó trong viewWillAppear. Đây là một trong những lỗi khái niệm phổ biến nhất: các nhà phát triển mong đợi viewDidLoad sẽ kích hoạt mỗi lần hiển thị, nhưng UIKit chỉ gọi nó một lần.
Đôi khi các nhà phát triển truy cập cưỡng bức vào view của controller để kích hoạt tải trước: let _ = controller.view. Điều này buộc loadView và viewDidLoad được gọi trước khi controller xuất hiện trên màn hình. Kỹ thuật này được sử dụng khi bạn cần chuẩn bị View trước để chuyển tiếp mượt mà.
viewDidLoad được dành cho các thao tác thiết lập một lần không phụ thuộc vào việc màn hình có hiển thị hay không. Sử dụng đúng phương thức này là chìa khóa cho kiến trúc sạch và hành vi controller có thể dự đoán trước.
Trong viewDidLoad, bạn đăng ký các tệp nib và lớp cho UITableView và UICollectionView, cấu hình delegate và đặt giá trị ban đầu cho các thuộc tính của thành phần UI. Vì tất cả IBOutlet đã được kết nối tại thời điểm này, bạn có thể truy cập an toàn label.text, imageView.image và các thuộc tính subview khác.
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
tableView.register(
CustomCell.self,
forCellReuseIdentifier: CustomCell.identifier
)
title = "Màn hình chính"
}
Ở đây bạn tạo viewModel, khởi tạo data source với các mảng và đăng ký nhận thông báo cần hoạt động trong suốt thời gian tồn tại của controller. Ví dụ, đăng ký UIApplication.willEnterForegroundNotification để cập nhật dữ liệu khi quay lại từ nền là một ứng cử viên tốt cho viewDidLoad. ViewModel trong kiến trúc iOS hiện đại hoạt động như cầu nối giữa controller và logic nghiệp vụ, và khởi tạo nó trong viewDidLoad đảm bảo dữ liệu sẵn sàng khi màn hình xuất hiện lần đầu tiên.
Hãy đặc biệt chú ý đến việc cấu hình data source cho bảng và bộ sưu tập. Nếu bảng của bạn sử dụng UIFetchedResultsController hoặc NSFetchedResultsController với Core Data, hãy khởi tạo fetch request và delegate trong viewDidLoad. Điều này đảm bảo rằng khi màn hình xuất hiện lần đầu tiên, bảng sẽ đã được điền dữ liệu mà không cần yêu cầu bổ sung.
Trong viewDidLoad, bạn cấu hình các nút NavigationBar, thiết lập large title, thêm search controller và đặt các nút edit/done. Các thành phần này hiếm khi thay đổi khi màn hình được hiển thị lại, vì vậy khởi tạo chúng ở đây là tối ưu.
Không phải tất cả các thao tác đều phù hợp trong viewDidLoad. Một số hành động đặt trong phương thức này dẫn đến tiêu thụ bộ nhớ quá mức, hành vi không chính xác hoặc lỗi khi màn hình được hiển thị lại.
Tránh bắt đầu các yêu cầu mạng mà kết quả chỉ ảnh hưởng đến UI. Nếu yêu cầu hoàn thành trước khi màn hình xuất hiện, người dùng sẽ không thấy kết quả và nếu hoàn thành sau — dữ liệu có thể đã lỗi thời. Bắt đầu tải trong viewDidLoad, nhưng cập nhật UI trong viewWillAppear.
Không thực hiện các thao tác trong viewDidLoad phụ thuộc vào kích thước và vị trí của View. Tại thời điểm gọi, Auto Layout chưa hoàn thành các lượt xử lý và frame có thể chưa phải là cuối cùng. Đối với các tính toán, hãy sử dụng viewDidLayoutSubviews hoặc ghi đè updateViewConstraints.
Không đăng ký các thông báo chỉ có liên quan khi màn hình hiển thị. Thông báo bàn phím, thông báo thay đổi nội dung từ các controller con — hãy đăng ký chúng trong viewWillAppear và hủy đăng ký trong viewDidDisappear để tránh các cuộc gọi không cần thiết và rò rỉ.
Không gọi các phương thức yêu cầu màn hình hiển thị. Ví dụ, cố gắng hiển thị UIAlertController từ viewDidLoad sẽ gây ra lỗi vì View của controller chưa được thêm vào hệ thống phân cấp cửa sổ. Bất kỳ thao tác UI nào phụ thuộc vào window hoặc presentedViewController chỉ nên được thực hiện sau khi màn hình xuất hiện.
Không khởi tạo tài nguyên nặng một cách không cần thiết. Nếu màn hình hiếm khi được hiển thị hoặc dữ liệu không được hiển thị ngay lập tức, hãy hoãn việc tạo các đối tượng tốn tài nguyên cho đến khi thực sự cần thiết. Khởi tạo lười (lazy) các thuộc tính trong Swift là một cơ chế tích hợp để giải quyết vấn đề này: một thuộc tính với bộ điều chỉnh lazy sẽ chỉ được tạo khi truy cập lần đầu, tiết kiệm bộ nhớ và tăng tốc tải màn hình.
Không sử dụng viewDidLoad cho các thao tác cần được thực hiện mỗi khi màn hình xuất hiện. Đây là lỗi cơ bản nhất: các nhà phát triển mới thường đặt logic cập nhật dữ liệu trong viewDidLoad và ngạc nhiên rằng khi quay lại từ màn hình khác, bảng không tải lại. Nếu một thao tác nên lặp lại mỗi lần hiển thị — hãy sử dụng viewWillAppear. Nếu nó nên thực thi một lần trong suốt vòng đời — hãy sử dụng viewDidLoad. Hãy nhớ quy tắc đơn giản này để tránh hầu hết các vấn đề với vòng đời UIViewController.
Hãy xem ba ví dụ thực tế minh họa cách sử dụng đúng viewDidLoad trong các dự án thực tế. Mỗi ví dụ giải quyết một tác vụ cấu hình màn hình cụ thể.
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(
PhotoCell.self,
forCellWithReuseIdentifier: PhotoCell.reuseId
)
collectionView.register(
HeaderView.self,
forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader,
withReuseIdentifier: HeaderView.reuseId
)
viewModel.delegate = self
viewModel.fetchInitialPage()
}
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel()
label.text = "Xin chào thế giới!"
label.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(label)
NSLayoutConstraint.activate([
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor)
])
}
Trong viewDidLoad, bạn cũng cấu hình các thành phần hiển thị khi không có dữ liệu: trạng thái trống, bộ tải, placeholder. Các thành phần này được tạo một lần và tái sử dụng mỗi khi màn hình xuất hiện. Ẩn hoặc hiện các thành phần này được quản lý trong viewWillAppear tùy thuộc vào dữ liệu hiện tại.
override func viewDidLoad() {
super.viewDidLoad()
emptyStateLabel = UILabel()
emptyStateLabel.text = "Không có dữ liệu"
emptyStateLabel.textAlignment = .center
emptyStateLabel.isHidden = true
view.addSubview(emptyStateLabel)
activityIndicator = UIActivityIndicatorView(style: .medium)
activityIndicator.hidesWhenStopped = true
view.addSubview(activityIndicator)
}
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(
self,
selector: #selector(handleEnterForeground),
name: UIApplication.willEnterForegroundNotification,
object: nil
)
}
@objc private func handleEnterForeground() {
refreshContent()
}
Câu hỏi thường gặp
Trong điều kiện bình thường, không — UIKit gọi viewDidLoad một lần sau khi tải View vào bộ nhớ. Nếu controller bị hủy và được tạo lại, viewDidLoad sẽ chạy cho instance mới.
Có, bắt buộc. Gọi super.viewDidLoad đảm bảo UIKit thực hiện thiết lập nội bộ cần thiết để Lifecycle hoạt động chính xác. Luôn gọi super đầu tiên bên trong phương thức.
viewDidLoad được gọi một lần khi View được tải. viewWillAppear được gọi mỗi lần trước khi màn hình xuất hiện. Cái đầu tiên dành cho thiết lập một lần, cái thứ hai để cập nhật dữ liệu và trạng thái.
Các thao tác đồng bộ nặng trong viewDidLoad chặn luồng chính và làm chậm việc hiển thị màn hình. Tải không đồng bộ có thể chấp nhận được, nhưng khi cập nhật UI sau khi hoàn thành, cần tính đến khả năng màn hình có thể đã bị ẩn.
Bạn không thể gọi viewDidLoad trực tiếp — UIKit gọi nó. Để buộc tải View, hãy truy cập thuộc tính controller.view. Điều này sẽ tự động kích hoạt loadView và viewDidLoad.
Tổng kết
Chúng tôi sẽ phát triển ứng dụng di động chìa khóa trao tay
IT Sectr tạo các ứng dụng iOS và Android cho các công ty khởi nghiệp và doanh nghiệp từ năm 2017. Chúng tôi sẽ tư vấn và đề xuất giải pháp tốt nhất cho bạn.
Đọc thêm