@Environment trong SwiftUI là một property wrapper để đọc các giá trị từ môi trường hệ thống, tự động phân phối qua hệ phân cấp View. Thành phần này cung cấp quyền truy cập vào bảng màu, ngôn ngữ vùng, kích thước phông chữ, managedObjectContext và hàng chục tham số hệ thống khác. Theo Tài liệu Apple Developer (2025), @Environment đảm bảo rằng bất kỳ thay đổi nào trong giá trị môi trường sẽ kích hoạt việc vẽ lại tất cả các View đã đăng ký, cung cấp các cập nhật giao diện phản ứng mà không cần gọi thủ công.
Các điểm chính
@Environment là một property wrapper SwiftUI được thiết kế để đọc các giá trị từ môi trường hệ thống. Môi trường là một container phân cấp các giá trị mà SwiftUI tự động phân phối từ View cha đến View con. Mỗi giá trị môi trường được xác định bằng một khóa — một kiểu tuân theo giao thức EnvironmentKey.
Cơ chế môi trường giống như dependency injection ở cấp độ framework: hệ thống cung cấp một tập hợp các giá trị được xác định trước — bảng màu (sáng/tối), ngôn ngữ vùng, kích thước phông chữ, managedObjectContext cho Core Data, dismiss để đóng màn hình, và nhiều giá trị khác. Một View khai báo @Environment với một khóa cụ thể sẽ tự động nhận được giá trị hiện tại và vẽ lại khi nó thay đổi.
Kiến trúc môi trường SwiftUI dựa trên giao thức EnvironmentValues — một cấu trúc chứa tất cả các giá trị hệ thống. Mỗi giá trị được lưu trữ như một thuộc tính của cấu trúc này với getter và setter. @Environment sử dụng key path để truy cập vào một thuộc tính cụ thể: @Environment(\.colorScheme) — truy cập bảng màu, @Environment(\.locale) — truy cập ngôn ngữ vùng.
Property wrapper @Environment thực hiện hai cơ chế chính: đọc giá trị từ môi trường và đăng ký các thay đổi của nó. Khi một View được tạo, SwiftUI duyệt qua tất cả các thuộc tính @Environment và liên kết chúng với các giá trị tương ứng từ ngữ cảnh hiện tại. Nếu một View cha thay đổi giá trị thông qua bộ điều chỉnh .environment(), tất cả các View con đọc giá trị đó sẽ tự động được vẽ lại.
Một tính năng quan trọng: @Environment hỗ trợ các giá trị tùy chọn. Nếu một giá trị không được đặt trong hệ phân cấp, giá trị mặc định được xác định trong EnvironmentKey sẽ được trả về. Đối với các khóa hệ thống, giá trị mặc định luôn hợp lý — ví dụ, bảng màu mặc định là .light. Đối với các khóa tùy chỉnh, nhà phát triển xác định giá trị mặc định trong phương thức defaultValue của giao thức EnvironmentKey.
struct EnvironmentReaderView: View {
@Environment(\.colorScheme) var colorScheme
@Environment(\.locale) var locale
@Environment(\.sizeCategory) var sizeCategory
var body: some View {
VStack {
Text("Current scheme: \(colorScheme == .dark ? "Dark" : "Light")")
Text("Locale: \(locale.identifier)")
Text("Font size: \(sizeCategory)")
}
}
}
Trong ví dụ, View đọc ba giá trị môi trường hệ thống. Khi colorScheme thay đổi — ví dụ, người dùng đã bật chế độ tối trong cài đặt — View tự động vẽ lại với giá trị mới. Tương tự khi vùng hoặc kích thước phông chữ (Dynamic Type) thay đổi. View không cần đăng ký thông báo hoặc gọi refresh — SwiftUI quản lý điều này tự động.
SwiftUI cung cấp hàng chục giá trị môi trường hệ thống bao phủ các khía cạnh khác nhau của giao diện và hành vi. Bảng màu (\.colorScheme) là một trong những giá trị được sử dụng nhiều nhất, cho phép giao diện thích ứng với chủ đề sáng và tối. Ngôn ngữ vùng (\.locale) chứa cài đặt khu vực của người dùng để định dạng ngày tháng, số và tiền tệ.
Đối với Core Data, managedObjectContext (\.managedObjectContext) được sử dụng — một ngữ cảnh được truyền qua môi trường từ container bền vững. Đối với điều hướng, dismiss (\.dismiss) có sẵn để đóng màn hình hiện tại và isPresented (\.isPresented) cho các trình bày phương thức. Đối với lịch và múi giờ — calendar và timeZone tương ứng.
| Key Path | Loại | Mục đích |
|---|---|---|
| \.colorScheme | ColorScheme | Chủ đề sáng hoặc tối |
| \.locale | Locale | Cài đặt khu vực |
| \.sizeCategory | ContentSizeCategory | Kích thước phông Dynamic Type |
| \.managedObjectContext | NSManagedObjectContext | Ngữ cảnh Core Data |
| \.dismiss | DismissAction | Đóng màn hình |
| \.calendar | Calendar | Lịch hiện tại |
| \.timeZone | TimeZone | Múi giờ |
| \.horizontalSizeClass | UserInterfaceSizeClass | Kích thước màn hình ngang |
Để truy cập các giá trị hệ thống, sử dụng key path với dấu chấm: @Environment(\.dismiss) var dismiss. Trình biên dịch kiểm tra sự tồn tại của key path trong EnvironmentValues, vì vậy một khóa không chính xác sẽ gây ra lỗi biên dịch. Apple thêm các giá trị hệ thống mới với mỗi phiên bản iOS — danh sách đầy đủ có sẵn trong tài liệu EnvironmentValues.
Mặc dù tên gần giống nhau, @Environment và @EnvironmentObject phục vụ các mục đích khác nhau. @Environment đọc các giá trị hệ thống hoặc tùy chỉnh được đăng ký thông qua EnvironmentKey. @EnvironmentObject là một property wrapper cho một ObservableObject được truyền qua môi trường theo kiểu, không có khóa rõ ràng.
@EnvironmentObject được sử dụng cho dependency injection: một View cha tạo một đối tượng (ví dụ, ViewModel) và truyền nó đến các View con thông qua bộ điều chỉnh .environmentObject(). Các View con nhận nó thông qua @EnvironmentObject và có thể đọc và sửa đổi các thuộc tính của nó. Mặt khác, @Environment là chỉ đọc đối với các giá trị hệ thống và không hỗ trợ phản hồi.
| Tham số | @Environment | @EnvironmentObject |
|---|---|---|
| Mục đích | Giá trị hệ thống và tùy chỉnh | Tiêm ObservableObject |
| Khóa | Key path EnvironmentValues | Theo kiểu đối tượng |
| Ghi | Chỉ đọc | Đọc và ghi |
| Giá trị tùy chỉnh | Thông qua EnvironmentKey | Thông qua lớp ObservableObject |
| Giá trị mặc định | Có (defaultValue) | Không (phải được truyền) |
Trong thực tế: sử dụng @Environment để truy cập các tham số hệ thống (chủ đề, ngôn ngữ vùng, kích thước phông chữ) và các cấu hình tùy chỉnh không thay đổi trong thời gian chạy. Sử dụng @EnvironmentObject để truyền ViewModel hoặc dịch vụ qua hệ phân cấp View khi trạng thái cần được sửa đổi từ các thành phần con.
Hãy xem xét việc tạo một giá trị môi trường tùy chỉnh. Để làm điều này, bạn cần xác định một cấu trúc tuân theo giao thức EnvironmentKey và mở rộng EnvironmentValues với một thuộc tính mới. Điều này cho phép truyền cấu hình chủ đề hoặc cài đặt ứng dụng qua toàn bộ cây View mà không cần props.
struct AppThemeKey: EnvironmentKey {
static let defaultValue: AppTheme = .system
}
extension EnvironmentValues {
var appTheme: AppTheme {
get { self[AppThemeKey.self] }
set { self[AppThemeKey.self] = newValue }
}
}
enum AppTheme { case system, light, dark }
Giao thức EnvironmentKey yêu cầu triển khai thuộc tính tĩnh defaultValue — giá trị sẽ được sử dụng nếu View cha không đặt môi trường tùy chỉnh. Việc mở rộng EnvironmentValues thêm một thuộc tính tính toán appTheme sử dụng subscript với khóa. Sau đó, bất kỳ View nào cũng có thể đọc giá trị thông qua @Environment(\.appTheme).
struct ThemedView: View {
@Environment(\.appTheme) var appTheme
@Environment(\.colorScheme) var colorScheme
var body: some View {
VStack {
if appTheme == .dark || (appTheme == .system && colorScheme == .dark) {
Text("Dark mode active")
.foregroundStyle(.white)
.background(Color.black)
} else {
Text("Light mode active")
.foregroundStyle(.black)
.background(Color.white)
}
}
}
}
struct ContentView: View {
@State private var selectedTheme = AppTheme.system
var body: some View {
ThemedView()
.environment(\.appTheme, selectedTheme)
}
}
ThemedView đọc hai môi trường: appTheme tùy chỉnh và colorScheme hệ thống. Sự kết hợp cho phép cấu hình chủ đề linh hoạt: người dùng có thể chọn chủ đề Sáng, Tối hoặc Hệ thống. Nếu chọn Hệ thống, giá trị được lấy từ colorScheme, tự động thay đổi khi chuyển đổi chủ đề trong cài đặt iOS. View cha (ContentView) đặt giá trị appTheme thông qua bộ điều chỉnh .environment().
struct ModalView: View {
@Environment(\.dismiss) var dismiss
@State private var name = ""
var body: some View {
NavigationStack {
Form {
TextField("Your name", text: $name)
Button("Save") { dismiss() }
}
.navigationTitle("Edit Profile")
}
}
}
Ví dụ này minh họa cách sử dụng thực tế của dismiss — một thể hiện của DismissAction từ môi trường. Gọi dismiss() như một hàm sẽ đóng màn hình phương thức hoặc quay lại NavigationLink. Yêu cầu duy nhất là View phải được trình bày dưới dạng phương thức hoặc nằm trong NavigationStack. dismiss được xác định tự động từ ngữ cảnh: nếu View được mở dưới dạng sheet — sheet sẽ đóng, nếu dưới dạng popover — popover sẽ đóng.
Câu hỏi thường gặp
Không, @Environment chỉ đọc. Để thay đổi giá trị, hãy sử dụng @EnvironmentObject với ObservableObject hoặc @Binding. Các EnvironmentKeys tùy chỉnh có thể có setter trong phần mở rộng, nhưng thay đổi qua nó không kích hoạt cập nhật giao diện — điều này khả thi về mặt kỹ thuật nhưng không được khuyến nghị.
@Binding tạo kết nối hai chiều với nguồn sự thật (State, StateObject, ObservableObject). @Environment là đọc một chiều từ ngữ cảnh phân cấp. @Binding phù hợp để truyền dữ liệu đến View con, @Environment — để truy cập cài đặt hệ thống hoặc toàn cục.
Xác định một cấu trúc triển khai giao thức EnvironmentKey với defaultValue tĩnh. Sau đó mở rộng EnvironmentValues với một thuộc tính sử dụng getter/setter thông qua subscript[key]. Sau khi đăng ký, sử dụng @Environment(\.yourKey) để đọc và .environment(\.yourKey, value) để đặt.
SwiftUI cung cấp hơn 50 giá trị hệ thống: colorScheme, locale, sizeCategory, managedObjectContext, dismiss, calendar, timeZone, horizontalSizeClass, verticalSizeClass, accessibilityEnabled, layoutDirection, legibilityWeight và các giá trị khác. Danh sách đầy đủ trong tài liệu EnvironmentValues.
Có, @Environment hoạt động trong Preview, nhưng các giá trị mặc định có thể khác với trình mô phỏng. Để kiểm tra trong Preview, hãy sử dụng bộ điều chỉnh .environment() trực tiếp trong mã Preview: ThemedView().environment(\.colorScheme, .dark). Điều này cho phép kiểm tra trực quan các trạng thái môi trường khác nhau.
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