9.5 管理搜索界面的激活

原文链接: https://developer.apple.com/documentation/swiftui/managing-search-interface-activation

9.5 管理搜索界面的激活

以编程方式检测并关闭搜索框。

概述

人们通过轻点或点击来激活应用中的搜索框,之后就可以输入搜索词。在许多情况下,你的应用只需要对搜索文本值的相应变化做出反应——界面会通过你提供的绑定来更新这些值,如执行搜索操作所述。

不过,SwiftUI 也提供了一些控件,让你能以编程方式管理搜索界面。具体来说,你可以:

  • 用你提供给 searchable 修饰符的绑定来激活或取消激活该界面。
  • 通过读取环境值来检测该界面是否处于激活状态。
  • 通过调用存储在环境中的动作来关闭该界面。
  • 等到发生搜索提交事件后再开始搜索。

通过绑定控制激活

你可以给 searchable 修饰符的 isPresented 参数提供一个指向布尔值的 Binding,以编程方式控制搜索界面的激活。例如,要呈现一个出现时搜索界面就已激活的表单,可以创建一个初始值为 true 的绑定:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
struct SheetView: View {
    @State private var isPresented = true
    @State private var text = ""
  
    var body: some View {
        NavigationStack {
            SheetContent()
                .searchable(text: $text, isPresented: $isPresented)
        }
    }
}   

在 iOS 和 macOS 上,SwiftUI 在呈现搜索时会聚焦搜索框,在关闭搜索时会取消聚焦搜索框。搜索界面仍然呈现时,搜索框也可能失去焦点。例如,如果你的搜索界面包含一列文本输入框,有人可能把焦点移到其中一个文本输入框上,而没有关闭该界面。

检测搜索的激活

如果你需要知道搜索界面何时处于激活状态,可以用 Environment 属性包装器查询环境中的 isSearching 属性。下面的例子展示了一个视图,它根据该属性的状态更新所显示的文本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
struct SearchingExample: View {
    @State private var searchText = ""

    var body: some View {
        NavigationStack {
            SearchedView()
                .searchable(text: $searchText)
        }
    }
}

struct SearchedView: View {
    @Environment(\.isSearching) private var isSearching

    var body: some View {
        Text(isSearching ? "Searching" : "Not searching")
    }
}

当有人第一次轻点或点击搜索框时,isSearching 属性会变成 true。当他们取消搜索操作时,该属性会变成 false。如果你以编程方式关闭该界面(如下一节所述),它也会变成 false。

请务必在直接被或间接被某个 searchable(text:placement:prompt:) 视图修饰符包裹的视图内部读取该属性,就像上面例子中的 SearchedView 那样。如果你在该上下文之外读取它(例如把它放在 SearchingExample 视图中),就检测不到该属性值的任何变化。

关闭搜索界面

你可以用环境中的 dismissSearch 动作以编程方式取消激活该界面。例如,考虑一个视图,其中有一个用于呈现集合中第一个匹配项更多信息的 Button:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
struct ContentView: View {
    @State private var searchText = ""

    var body: some View {
        NavigationStack {
            SearchedView(searchText: searchText)
                .searchable(text: $searchText)
        }
    }
}

private struct SearchedView: View {
    var searchText: String

    let items = ["a", "b", "c"]
    var filteredItems: [String] { items.filter { $0 == searchText.lowercased() } }

    @State private var isPresented = false
    @Environment(\.dismissSearch) private var dismissSearch

    var body: some View {
        if let item = filteredItems.first {
            Button("Details about \(item)") {
                isPresented = true
            }
            .sheet(isPresented: $isPresented) {
                NavigationStack {
                    DetailView(item: item, dismissSearch: dismissSearch)
                }
            }
        }
    }
}

只有有人输入了能产生匹配的搜索文本之后,这个按钮才会出现。按钮的动作会显示一个表单,提供关于该项的更多信息,其中还有一个 Add 按钮,用于把该项加入已存储的条目列表:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
private struct DetailView: View {
    var item: String
    var dismissSearch: DismissSearchAction

    @Environment(\.dismiss) private var dismiss

    var body: some View {
        Text("Information about \(item).")
            .toolbar {
                Button("Add") {
                    // 在这里存储该项……

                    dismiss()
                    dismissSearch()
                }
            }
    }
}

人们可以向下拖动表单来关闭它,这相当于取消操作,并让进行中的搜索交互保持原样。他们也可以轻点 Add 按钮来存储该项。由于此时人们很可能已经用完了详情视图和搜索交互,按钮的闭包使用 dismiss 属性关闭表单,并用 dismissSearch 属性重置搜索框。

与 isSearching 属性一样,请只在 searchable 视图修饰符的层级内部读取 dismissSearch。如果你在该上下文之外从环境中读取它,调用该动作不会有任何效果。上面的例子是在 SearchedView 中读取该动作,并把它传入表单,因为表单有它自己的环境。如果搜索界面并未处于激活状态,该动作同样不会有任何效果。

对搜索提交做出响应

要指定当有人提交搜索查询(按下 Return 键)时 SwiftUI 所调用的动作,请添加 onSubmit(of:_:) 修饰符:

1
2
3
4
5
SearchedView()
    .searchable(text: $searchText)
    .onSubmit(of: .search) {
        submitCurrentSearchQuery()
    }

视应用结构的不同,你可以用不同方式利用搜索提交。例如,你可以借这个机会在搜索查询字符串中寻找可以转换为令牌的子串。反过来,对于非常慢的搜索操作(也许是因为它需要访问网络),你也可以等到提交事件发生后再执行搜索。