Accessibility Trait là một thuộc tính của phần tử iOS xác định vai trò và hành vi của nó đối với VoiceOver. Trait cho trình đọc màn hình biết phần tử nên được đọc như thế nào và những cử chỉ nào có sẵn: đó là nút bấm, tiêu đề, liên kết hay trường tìm kiếm. Theo Apple UIAccessibilityTraits, 2024, hệ thống hỗ trợ hơn 15 hằng số có thể kết hợp bằng mặt nạ bit. Một trait được chọn đúng cách tiết kiệm tới 50% thời gian điều hướng cho người dùng VoiceOver.
Những Điểm Chính
Accessibility Trait là một cờ được đặt trên phần tử UIView để chỉ ra vai trò ngữ nghĩa của nó cho VoiceOver. Trait là một trong ba thành phần của bộ ba trợ năng Apple: Label (tên), Hint (mô tả), Trait (vai trò). iOS sử dụng mặt nạ bit UIAccessibilityTraits (UInt64), trong đó mỗi bit tương ứng với một vai trò cụ thể. VoiceOver đọc vai trò sau Label và Hint: “Nút Gửi. Sẽ mở một biểu mẫu” — “Nút” được thêm nhờ trait UIAccessibilityTraitButton.
Theo mặc định, UIButton nhận UIAccessibilityTraitButton, UILabel nhận UIAccessibilityTraitStaticText, UIImageView nhận UIAccessibilityTraitImage. Khi sử dụng điều khiển tùy chỉnh, nhà phát triển phải thiết lập trait theo cách thủ công. Apple Human Interface Guidelines, 2024, gọi đây là “một trong những bước quan trọng nhất để đảm bảo trợ năng”.
Nếu không có trait đúng, người dùng không biết nên áp dụng cử chỉ nào: chạm đơn (kích hoạt nút), chạm đúp (phóng to) hay cử chỉ vuốt (công tắc). Trait quyết định cử chỉ VoiceOver nào được kích hoạt trên phần tử.
UIAccessibilityTraits là một typealias UInt64. Mỗi trait là một hằng số với chính xác một bit được thiết lập. Ví dụ: UIAccessibilityTraitButton = 0x0000000000000001, UIAccessibilityTraitLink = 0x0000000000000002, UIAccessibilityTraitHeader = 0x0000000000000008. Các kết hợp đạt được bằng OR bit: 0x0001 | 0x0008 = 0x0009. VoiceOver phân tích mặt nạ và xác định hành vi.
iOS cung cấp hơn 15 hằng số trait. Hãy xem các loại chính được sử dụng trong 90% kịch bản:
| Trait | Hằng số | Hành vi VoiceOver |
|---|---|---|
| Button | UIAccessibilityTraitButton | Kích hoạt bằng chạm đúp |
| Header | UIAccessibilityTraitHeader | Điều hướng nhanh theo tiêu đề |
| Link | UIAccessibilityTraitLink | Kích hoạt như liên kết |
| StaticText | UIAccessibilityTraitStaticText | Chỉ đọc, không kích hoạt |
| SearchField | UIAccessibilityTraitSearchField | Trường tìm kiếm với hành vi đặc biệt |
| Image | UIAccessibilityTraitImage | Hình ảnh, không có cử chỉ kích hoạt |
| Selected | UIAccessibilityTraitSelected | Trạng thái “đã chọn” |
| PlaysSound | UIAccessibilityTraitPlaysSound | Phát âm thanh khi kích hoạt |
| KeyboardKey | UIAccessibilityTraitKeyboardKey | Phím bàn phím |
| TabBar | UIAccessibilityTraitTabBar | Phần tử thanh tab |
Các hằng số có sẵn trong UIKit từ iOS 3.0. iOS 14+ đã thêm hỗ trợ UIAccessibilityTraits trong SwiftUI qua bộ sửa đổi .accessibilityAddTraits().
UIAccessibilityTraitAdjustable — cho các giá trị có thể điều chỉnh (thanh trượt, bộ chọn, thanh trượt âm lượng). VoiceOver cho phép vuốt lên/xuống để thay đổi giá trị với bước được xác định qua accessibilityIncrement và accessibilityDecrement. UIAccessibilityTraitUpdatesFrequently — cho các phần tử có giá trị thay đổi thường xuyên (bộ đếm thời gian, chỉ báo tiến trình). VoiceOver không đọc giá trị mỗi khi thay đổi mà tạm dừng. UIAccessibilityTraitAllowsDirectInteraction — cho các phần tử mà người dùng có thể tương tác trực tiếp (bàn phím, vẽ), bỏ qua cử chỉ VoiceOver.
Một phần tử có thể có nhiều trait cùng lúc — kết hợp được thiết lập bằng OR bit (|). Ví dụ: một nút hiện đang được chọn — Button | Selected. VoiceOver sẽ thông báo: “Đã chọn. Đã lọc theo giá. Nút.”
Thiết lập trait trong mã:
filterButton.accessibilityTraits.insert(.button)
filterButton.accessibilityTraits.insert(.selected)
// Hoặc qua mặt nạ:
filterButton.accessibilityTraits = [.button, .selected]
Cho UIView tùy chỉnh nơi trait không được thiết lập theo mặc định:
class CustomToggle: UIControl {
override var accessibilityTraits: UIAccessibilityTraits {
get {
if isOn {
return [.button, .selected]
} else {
return .button
}
}
set {}
}
}
Quy tắc kết hợp: không quá 3-4 trait cho mỗi phần tử. Trait quá mức (ví dụ: Button + Link + Header) làm cho thông báo VoiceOver quá dài và gây nhầm lẫn. Theo Apple, “mỗi thuộc tính bổ sung làm tăng tải nhận thức của người dùng”.
Trong SwiftUI, trait được thiết lập bằng bộ sửa đổi .accessibilityAddTraits() và .accessibilityRemoveTraits(). Ví dụ: Text(“Tiêu đề”).font(.largeTitle).accessibilityAddTraits(.isHeader). Bộ sửa đổi .isHeader thêm UIAccessibilityTraitHeader. Danh sách trait SwiftUI: .isButton, .isHeader, .isLink, .isSelected, .isImage, .isSearchField, .isKeyboardKey, .isStaticText, .isSummaryElement, .isToggle, .playsSound, .startsMediaSession, .updatesFrequently, .allowsDirectInteraction, .causesPageTurn, .isModal, .tabBar.
StaticText thay vì Button — một điều khiển tùy chỉnh trông giống nút bấm nhận trait StaticText theo mặc định. VoiceOver không cung cấp cử chỉ kích hoạt, vì vậy người dùng không thể “nhấn” phần tử. Giải pháp: đặt rõ ràng .button.
Hình ảnh không có trait — UIImageView với trợ năng được bật nhận trait Image, ngay cả khi nó thực sự là nút để phóng to ảnh. Gán .button và Label “Phóng to ảnh.” Theo WWDC 2023, “Deliver an Exceptional Accessibility Experience”, 40% hồi quy trợ năng trong các phiên bản ứng dụng mới là do không khớp trait.
Header trên mọi phần tử — trait Header dành cho các tiêu đề cấu trúc của màn hình. Nếu mọi UILabel đều trở thành tiêu đề, bộ chọn VoiceOver ở chế độ “Tiêu đề” trở nên vô dụng — nó sẽ dừng ở mọi từ.
Một nguyên nhân phổ biến khiến mất trait là tái cấu trúc: nhà phát triển thay UIButton bằng UIControl để hiển thị tùy chỉnh. UIButton tự động nhận trait Button, UIControl thì không. Sau khi tái cấu trúc, bạn cần đặt rõ ràng accessibilityTraits = .button. Thêm kiểm tra trong đánh giá mã: “Nếu bạn đã thay UIButton bằng UIControl — hãy kiểm tra trait.”
Cho các phần tử có trạng thái thay đổi (ví dụ: nút thích), trait nên thay đổi động. Ở trạng thái “chưa thích” — Button, ở trạng thái “đã thích” — Button + Selected + Image (nếu có biểu tượng). VoiceOver thay đổi thông báo: “Thích. Nút.” so với “Đã chọn. Thích. Nút.” Sử dụng accessibilityValue để truyền đạt trạng thái nếu trait Selected không đủ. Áp dụng cho nút đăng ký, yêu thích, bộ lọc và công tắc.
Trên Android, không có tương đương trực tiếp với trait. Thay vì mặt nạ bit, những thứ sau được sử dụng:
Cho View tùy chỉnh trên Android, bạn cần ghi đè onInitializeAccessibilityNodeInfo:
class CustomButton @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : View(context, attrs) {
override fun onInitializeAccessibilityNodeInfo(
info: AccessibilityNodeInfo
) {
super.onInitializeAccessibilityNodeInfo(info)
info.className = "android.widget.Button"
info.isClickable = true
}
}
Các nhà phát triển Flutter nên sử dụng tham số semanticsRole trong widget Semantics: button, header, image, link, textField và các loại khác. Ngoài ra, semanticsLabel và semanticsHint có sẵn — tương đương đầy đủ của bộ ba iOS Label + Hint + Trait.
Cho các phiên bản web của ứng dụng di động (PWA, WebView), thuộc tính role từ WAI-ARIA được sử dụng: role="button", role="heading", role="link". Đây là tương đương trực tiếp của accessibilityTraits. Trong ứng dụng kết hợp, hãy xác minh rằng WebView truyền vai trò ARIA đến lớp trợ năng gốc. Để làm điều này, sử dụng giao thức UIAccessibilityContainerDataTable trên iOS hoặc setAccessibilityDelegate trên Android. WebView với JavaScript được bật có thể không truyền vai trò ARIA đúng cách — hãy kiểm tra riêng.
Trên Android, bạn có thể thêm hành động tùy chỉnh vào AccessibilityNodeInfo: AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK và ACTION_LONG_CLICK. Điều này tương đương với trait Button với cử chỉ bổ sung. Cho thanh trượt, sử dụng ACTION_SET_PROGRESS — tương đương với Adjustable. Cho Spinner và DatePicker — ACTION_SET_SELECTION, ACTION_SET_DATE và ACTION_SET_TIME.
Xcode Accessibility Inspector là công cụ chính cho iOS: chọn một phần tử và xem trường Traits. Nó sẽ hiển thị danh sách các trait đã thiết lập. Bộ chọn VoiceOver với chế độ “Phần tử” cho phép điều hướng qua tất cả các điều khiển trên màn hình.
Thử nghiệm tự động Swift để kiểm tra trait:
func testSubmitButtonTrait() {
let app = XCUIApplication()
app.launch()
let submitButton = app.buttons["Gửi"]
XCTAssertTrue(submitButton.isEnabled)
// XCUIElement không cung cấp quyền truy cập trực tiếp vào trait
// Kiểm tra qua kích hoạt cử chỉ
submitButton.tap()
XCTAssertTrue(app.staticTexts["Biểu mẫu đã được gửi"].exists)
}
Kiểm tra thủ công qua VoiceOver: bật VoiceOver, vuốt đến phần tử, chạm đúp — phần tử sẽ kích hoạt nếu nó là Button. Nếu phần tử không phản hồi với chạm đúp, trait không đúng. Sử dụng cử chỉ Rotor để chuyển đổi giữa các chế độ (“Tiêu đề”, “Liên kết”, “Nút”) — mỗi chế độ chỉ hiển thị các phần tử có trait tương ứng.
Trước iOS 14, kiểm thử đơn vị không có quyền truy cập trực tiếp vào accessibilityTraits. Bắt đầu từ iOS 14, thuộc tính có sẵn: XCTAssertEqual(customButton.accessibilityTraits, .button). Sử dụng điều này trong kiểm thử đơn vị để xác minh các điều khiển tùy chỉnh. Nên kiểm tra mỗi UIView tùy chỉnh mới về tính đúng đắn của trait, đặc biệt sau khi tái cấu trúc hoặc thay đổi lớp cha.
Câu hỏi thường gặp
Tối đa 3-4 trait cho mỗi phần tử. Số lượng lớn hơn làm cho thông báo VoiceOver bị dư thừa. Sử dụng kết hợp: Button + Selected, Header + StaticText.
UIAccessibilityTraitButton. iOS tự động thiết lập nó cho tất cả các phiên bản UIButton. Nếu bạn kế thừa từ UIView và mô phỏng nút bấm, trait cần được thiết lập thủ công.
Có, UIAccessibilityTraitAdjustable — cho các phần tử có giá trị có thể điều chỉnh (thanh trượt, bộ chọn, bộ đếm). VoiceOver cho phép vuốt lên/xuống để thay đổi giá trị và đọc trạng thái hiện tại.
Sử dụng bộ sửa đổi .accessibilityAddTraits(): Text(“Tiêu đề”).font(.title).accessibilityAddTraits(.isHeader). Phương thức hoạt động trên iOS 14+.
VoiceOver sẽ gán trait None. Phần tử sẽ không có vai trò — trình đọc màn hình chỉ đọc Label mà không chỉ ra loại. Người dùng sẽ không biết liệu cử chỉ kích hoạt có sẵn hay không.
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