[英]How to trigger SwiftUI DatePicker Programmatically?
如下圖所示,如果您鍵入日期“2023 年 1 月 11 日”,它會顯示日期選擇器。 我想要實現的是在其他地方有一個按鈕,當該按鈕被點擊時,自動顯示這個日期選擇器。
有誰知道是否有辦法實現它?
DatePicker("Jump to", selection: $date, in: dateRange, displayedComponents: [.date])
以下是對@rob mayoff 的回答的測試。 我仍然不明白為什么它還不起作用。
我在 Xcode 14.2 上使用 iPhone 14 和 iOS 16.2 模擬器以及設備進行了測試。 我注意到的是,雖然調用了triggerDatePickerPopover()
,但它永遠無法到達button.accessibilityActivate()
。
import SwiftUI
struct ContentView: View {
@State var date: Date = .now
let dateRange: ClosedRange<Date> = Date(timeIntervalSinceNow: -864000) ... Date(timeIntervalSinceNow: 864000)
var pickerId: String { "picker" }
var body: some View {
VStack {
DatePicker(
"Jump to",
selection: $date,
in: dateRange,
displayedComponents: [.date]
)
.accessibilityIdentifier(pickerId)
Button("Clicky") {
triggerDatePickerPopover()
print("Clicky Triggered")
}
}
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
extension NSObject {
func accessibilityDescendant(passing test: (Any) -> Bool) -> Any? {
if test(self) { return self }
for child in accessibilityElements ?? [] {
if test(child) { return child }
if let child = child as? NSObject, let answer = child.accessibilityDescendant(passing: test) {
return answer
}
}
for subview in (self as? UIView)?.subviews ?? [] {
if test(subview) { return subview }
if let answer = subview.accessibilityDescendant(passing: test) {
return answer
}
}
return nil
}
}
extension NSObject {
func accessibilityDescendant(identifiedAs id: String) -> Any? {
return accessibilityDescendant {
// For reasons unknown, I cannot cast a UIView to a UIAccessibilityIdentification at runtime.
return ($0 as? UIView)?.accessibilityIdentifier == id
|| ($0 as? UIAccessibilityIdentification)?.accessibilityIdentifier == id
}
}
func buttonAccessibilityDescendant() -> Any? {
return accessibilityDescendant { ($0 as? NSObject)?.accessibilityTraits == .button }
}
}
extension ContentView {
func triggerDatePickerPopover() {
if
let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = scene.windows.first,
let picker = window.accessibilityDescendant(identifiedAs: pickerId) as? NSObject,
let button = picker.buttonAccessibilityDescendant() as? NSObject
{
print("triggerDatePickerPopover")
button.accessibilityActivate()
}
}
}
更新 2:我跟進了調試說明。 似乎使用完全相同的代碼。 我的檢查員缺少可訪問性標識符。 不知道為什么……現在感覺很煩躁。
這是下載項目的鏈接https://www.icloud.com/iclouddrive/040jHC0jwwJg3xgAEvGZShqFg#DatePickerTest
更新 3:@rob mayoff 的解決方案非常棒。 對於閱讀的任何人,如果它在您的情況下不起作用。 等一下這可能只是由於設備或模擬器准備好訪問。
SwiftUI 不提供以編程方式觸發日歷彈出窗口的直接方式。
但是,我們可以使用可訪問性 API 來完成。這是我的測試結果:
您可以看到日歷彈出窗口通過單擊“Clicky”按鈕或日期選擇器本身打開。
首先,我們需要一種使用可訪問性 API 找到選擇器的方法。讓我們為選擇器分配一個可訪問性標識符:
struct ContentView: View {
@State var date: Date = .now
let dateRange: ClosedRange<Date> = Date(timeIntervalSinceNow: -864000) ... Date(timeIntervalSinceNow: 864000)
var pickerId: String { "picker" }
var body: some View {
VStack {
DatePicker(
"Jump to",
selection: $date,
in: dateRange,
displayedComponents: [.date]
)
.accessibilityIdentifier(pickerId)
Button("Clicky") {
triggerDatePickerPopover()
}
}
.padding()
}
}
在我們編寫triggerDatePickerPopover
之前,我們需要一個 function 來搜索可訪問性元素樹:
extension NSObject {
func accessibilityDescendant(passing test: (Any) -> Bool) -> Any? {
if test(self) { return self }
for child in accessibilityElements ?? [] {
if test(child) { return child }
if let child = child as? NSObject, let answer = child.accessibilityDescendant(passing: test) {
return answer
}
}
for subview in (self as? UIView)?.subviews ?? [] {
if test(subview) { return subview }
if let answer = subview.accessibilityDescendant(passing: test) {
return answer
}
}
return nil
}
}
讓我們用它來編寫一個方法來搜索具有特定 id 的元素:
extension NSObject {
func accessibilityDescendant(identifiedAs id: String) -> Any? {
return accessibilityDescendant {
// For reasons unknown, I cannot cast a UIView to a UIAccessibilityIdentification at runtime.
return ($0 as? UIView)?.accessibilityIdentifier == id
|| ($0 as? UIAccessibilityIdentification)?.accessibilityIdentifier == id
}
}
}
我在測試中發現,即使UIView
被記錄為符合UIAccessibilityIdentification
協議(它定義了accessibilityIdentifier
屬性),將UIView
轉換為UIAccessibilityIdentification
在運行時也不起作用。 所以上面的方法比你想象的要復雜一點。
事實證明,選擇器有一個充當按鈕的子元素,而該按鈕正是我們需要激活的。 因此,讓我們也編寫一個搜索按鈕元素的方法:
func buttonAccessibilityDescendant() -> Any? {
return accessibilityDescendant { ($0 as? NSObject)?.accessibilityTraits == .button }
}
最后我們可以編寫triggerDatePickerPopover
方法:
extension ContentView {
func triggerDatePickerPopover() {
if
let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
let window = scene.windows.first,
let picker = window.accessibilityDescendant(identifiedAs: pickerId) as? NSObject,
let button = picker.buttonAccessibilityDescendant() as? NSObject
{
button.accessibilityActivate()
}
}
}
你說我的代碼有問題。 因為它對我有用,所以很難診斷問題。 嘗試啟動 Accessibility Inspector(從 Xcode 的菜單欄中選擇 Xcode > Open Developer Tool > Accessibility Inspector)。 告訴它以模擬器為目標,然后使用右尖括號按鈕逐步瀏覽輔助功能元素,直到到達 DatePicker。 然后 hover 將鼠標懸停在檢查器的層次結構窗格中的行上,然后單擊圓圈內的右箭頭。 它應該看起來像這樣:
請注意,檢查器會看到代碼中設置的日期選擇器的標識符“picker”,並且該選擇器在層次結構中有一個按鈕子項。 如果你的看起來不同,你需要找出原因並更改代碼以匹配。
單步執行accessibilityDescendant
方法並手動轉儲子項(例如po accessibilityElements
和po (self as? UIView)?.subviews
)也可能有幫助。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.