簡體   English   中英

如何以編程方式觸發 SwiftUI DatePicker?

[英]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 來完成。這是我的測試結果:

iPhone 模擬器的屏幕截圖顯示日期選擇器彈出窗口通過單擊按鈕或日期選擇器打開

您可以看到日歷彈出窗口通過單擊“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 accessibilityElementspo (self as? UIView)?.subviews )也可能有幫助。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM