Leg*_*ang 5 date datepicker ios swift swiftui
如下图所示,如果您输入日期“2023 年 1 月 11 日”,它会显示日期选择器。我想要实现的是在其他地方有一个按钮,当单击该按钮时,自动显示此日期选择器。
有谁知道是否有办法实现它?
DatePicker("Jump to", selection: $date, in: dateRange, displayedComponents: [.date])
Run Code Online (Sandbox Code Playgroud)
以下是对@rob mayoff 答案的测试。我仍然不明白为什么它还不起作用。
我使用 iPhone 14 和 iOS 16.2 模拟器在 Xcode 14.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()
}
}
}
Run Code Online (Sandbox Code Playgroud)
更新2:我跟进了调试指令。似乎使用完全相同的代码。我的检查员缺少可访问性标识符。不知道为什么……现在感觉心烦意乱。

这是下载项目的链接https://www.icloud.com/iclouddrive/040jHC0jwwJg3xgAEvGZShqFg#DatePickerTest
更新 3:@rob mayoff 的解决方案非常棒!对于任何阅读的人。如果它对您的情况不起作用,请等待。这可能只是由于设备或模拟器准备好可访问性。
回想起来,我最初使用辅助功能 API 的解决方案有点冒险,因为苹果DatePicker将来可能会改变辅助功能结构。我应该先仔细看看DatePicker文档并注意到.datePickerStyle(.graphical)修饰符,它允许您直接使用弹出窗口中显示的日历视图。
由于 SwiftUI 不支持 iPhone 上的自定义弹出窗口(.popover修改器显示一个工作表),一个相当简单的解决方案是将图形DatePicker直接放入您的 中VStack,并使用任意数量的按钮显示/隐藏它。您可以将按钮设置为默认样式DatePicker按钮。
这是一个完整的示例。它看起来像这样:
\n\n这是代码:
\nstruct ContentView: View {\n @State var date = Date.now\n @State var isShowingDatePicker = false\n\n let dateRange: ClosedRange<Date> = Date(timeIntervalSinceNow: -864000) ... Date(timeIntervalSinceNow: 864000)\n\n private var datePickerId: String { "picker" }\n\n var body: some View {\n ScrollViewReader { reader in\n ScrollView(.vertical) {\n VStack {\n VStack {\n HStack {\n Text("Jump to")\n Spacer()\n Button(\n action: { toggleDatePicker(reader: reader) },\n label: { Text(date, format: Date.FormatStyle.init(date: .abbreviated)) }\n )\n .buttonStyle(.bordered)\n .foregroundColor(isShowingDatePicker ? .accentColor : .primary)\n }\n\n VStack {\n DatePicker("Jump to", selection: $date, in: dateRange, displayedComponents: .date)\n .datePickerStyle(.graphical)\n .frame(height: isShowingDatePicker ? nil : 0, alignment: .top)\n .clipped()\n .background {\n RoundedRectangle(cornerRadius: 8, style: .continuous)\n .foregroundColor(Color(UIColor.systemBackground))\n .shadow(radius: 1)\n }\n }\n .padding(.horizontal, 8)\n }.id(datePickerId)\n\n filler\n\n Button("Clicky") { toggleDatePicker(true, reader: reader) }\n }\n }\n }\n .padding()\n }\n\n private func toggleDatePicker(_ show: Bool? = nil, reader: ScrollViewProxy) {\n withAnimation(.easeOut) {\n isShowingDatePicker = show ?? !isShowingDatePicker\n if isShowingDatePicker {\n reader.scrollTo(datePickerId)\n }\n }\n }\n\n private var filler: some View {\n HStack {\n Text(verbatim: "Some large amount of content to make the ScrollView useful.\\n\\n" + Array(repeating: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", count: 4).joined(separator: "\\n\\n"))\n .lineLimit(nil)\n Spacer()\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\nSwiftUI 不提供以编程方式触发日历弹出窗口的直接方法。
\n但是,我们可以使用辅助功能 API 来完成此操作。这是我的测试的样子:
\n\n您可以看到通过单击 \xe2\x80\x98Clicky\xe2\x80\x99 按钮或日期选择器本身打开了日历弹出窗口。
\n首先,我们需要一种使用辅助功能 API 来查找选择器的方法。让我们为选择器分配一个可访问性标识符:
\nstruct ContentView: View {\n @State var date: Date = .now\n let dateRange: ClosedRange<Date> = Date(timeIntervalSinceNow: -864000) ... Date(timeIntervalSinceNow: 864000)\n\n var pickerId: String { "picker" }\n\n var body: some View {\n VStack {\n DatePicker(\n "Jump to",\n selection: $date,\n in: dateRange,\n displayedComponents: [.date]\n )\n .accessibilityIdentifier(pickerId)\n\n Button("Clicky") {\n triggerDatePickerPopover()\n }\n }\n .padding()\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n在我们可以写之前triggerDatePickerPopover,我们需要一个搜索可访问性元素树的函数:
extension NSObject {\n func accessibilityDescendant(passing test: (Any) -> Bool) -> Any? {\n\n if test(self) { return self }\n\n for child in accessibilityElements ?? [] {\n if test(child) { return child }\n if let child = child as? NSObject, let answer = child.accessibilityDescendant(passing: test) {\n return answer\n }\n }\n\n for subview in (self as? UIView)?.subviews ?? [] {\n if test(subview) { return subview }\n if let answer = subview.accessibilityDescendant(passing: test) {\n return answer\n }\n }\n\n return nil\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n让我们用它来编写一个方法来搜索具有特定 id 的元素:
\nextension NSObject {\n func accessibilityDescendant(identifiedAs id: String) -> Any? {\n return accessibilityDescendant {\n // For reasons unknown, I cannot cast a UIView to a UIAccessibilityIdentification at runtime.\n return ($0 as? UIView)?.accessibilityIdentifier == id\n || ($0 as? UIAccessibilityIdentification)?.accessibilityIdentifier == id\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n我在测试中发现,即使UIView记录为符合UIAccessibilityIdentification协议(定义accessibilityIdentifier属性),将 a 转换UIView为 aUIAccessibilityIdentification在运行时也不起作用。所以上面的方法比你想象的要复杂一些。
事实证明,选择器有一个充当按钮的子元素,而该按钮就是我们需要激活的按钮。因此,让我们编写一个也搜索按钮元素的方法:
\n func buttonAccessibilityDescendant() -> Any? {\n return accessibilityDescendant { ($0 as? NSObject)?.accessibilityTraits == .button }\n }\nRun Code Online (Sandbox Code Playgroud)\n最后我们可以写出triggerDatePickerPopover方法:
extension ContentView {\n func triggerDatePickerPopover() {\n if\n let scene = UIApplication.shared.connectedScenes.first as? UIWindowScene,\n let window = scene.windows.first,\n let picker = window.accessibilityDescendant(identifiedAs: pickerId) as? NSObject,\n let button = picker.buttonAccessibilityDescendant() as? NSObject\n {\n button.accessibilityActivate()\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n你说我的代码有问题。由于它对我有用,因此很难诊断问题。尝试启动辅助功能检查器(从 Xcode 的菜单栏中选择 Xcode > 打开开发人员工具 > 辅助功能检查器)。告诉它以模拟器为目标,然后使用直角括号按钮逐步浏览辅助功能元素,直到到达 DatePicker。然后将鼠标悬停在检查器的层次结构窗格中的行上,然后单击圆圈内的右箭头。它应该看起来像这样:
\n\n请注意,检查器会看到代码中设置的日期选择器的标识符 \xe2\x80\x9cpicker\xe2\x80\x9d,并且选择器在层次结构中有一个按钮子级。如果您的看起来不同,您需要找出原因并更改代码以匹配。
\n单步执行accessibilityDescendant方法并手动转储子项(例如po accessibilityElements和po (self as? UIView)?.subviews)也可能有所帮助。
| 归档时间: |
|
| 查看次数: |
3548 次 |
| 最近记录: |