SwiftUI-Search

2023-03-01  本文已影响0人  xiaofu666

为您的应用程序添加搜索界面

提出一个人们可以用来搜索应用程序中内容的界面。

通过将可搜索视图修饰符之一(如searchable(text:placement:prompt:)应用于NavigationSplitViewNavigation,或其中之一内的视图,将搜索界面添加到您的应用程序中。然后,工具栏中会出现一个搜索字段。搜索字段的精确位置和外观取决于您在代码中放置修饰符的平台及其配置。

image.png
创建字段的可搜索修饰符将Binding到表示搜索字段文本的字符串。您为用于进行搜索的字符串提供存储,也可以为离散搜索令牌数组提供存储。要了解如何管理搜索字段的数据,请参阅执行搜索操作。

自动放置搜索字段

您可以通过将searchable(text:placement:prompt:)修饰符添加到导航元素(如导航拆分视图)来自动放置搜索字段:

struct ContentView: View {
    @State private var departmentId: Department.ID?
    @State private var productId: Product.ID?
    @State private var searchText: String = ""

    var body: some View {
        NavigationSplitView {
            DepartmentList(departmentId: $departmentId)
        } content: {
            ProductList(departmentId: departmentId, productId: $productId)
        } detail: {
            ProductDetails(productId: productId)
        }
        .searchable(text: $searchText) // Adds a search field.
    }
}

通过这种配置,搜索字段出现在macOS中工具栏的后缘上。在iOS和iPadOS中,第一列或第二列分别在双列或三列导航视图中显示搜索字段。上面的三列示例将搜索字段放在iPad上中间列的顶部。


image.png

从结构上控制放置

要在iOS和iPadOS中将搜索字段添加到特定列中,请将可搜索修饰符添加到该列的视图中。例如,为了表明搜索涵盖上一个示例中的部门,您可以通过将修饰符添加到该列的Department视图而不是导航拆分视图中,将搜索字段放在第一列中:

NavigationSplitView {
    DepartmentList(departmentId: $departmentId)
        .searchable(text: $searchText)
} content: {
    ProductList(departmentId: departmentId, productId: $productId)
} detail: {
    ProductDetails(productId: productId)
}
image.png

编程控制放置

您也可以使用placement输入参数为搜索界面建议Search值。例如,您可以使用sidebar位置获得与macOS中上一个示例相同的结果:

NavigationSplitView {
    DepartmentList(departmentId: $departmentId)
} content: {
    ProductList(departmentId: departmentId, productId: $productId)
} detail: {
    ProductDetails(productId: productId)
}
.searchable(text: $searchText, placement: .sidebar)
image.png

如果SwiftUI无法满足放置请求,例如当您在未应用于导航拆分视图的可搜索修饰符中要求放置侧边栏时,SwiftUI则依赖于其自动放置规则。

为搜索字段设置提示

默认情况下,搜索字段包含搜索作为占位符文本,以提示人们如何使用该字段。您可以通过为可搜索修饰符的prompt输入参数设置字符串、Text视图或Localized来自定义提示。例如,您可以使用此来澄清“部门”列中的搜索字段在两个部门和每个部门的产品之间进行搜索:

DepartmentList(departmentId: $departmentId)
    .searchable(text: $searchText, prompt: "Departments and products")
image.png

执行搜索操作

根据您存储的搜索文本和可选令牌更新搜索结果。

要在应用程序的数据模型中进行搜索,请为查询文本创建存储,并使用可搜索视图修饰符显示。因为您管理存储,因此您可以检测它何时更改并更新搜索操作作为响应。通过在人员类型时更新搜索结果,您可以确保应用程序的搜索界面具有响应性。

您还可以选择为令牌提供存储,令牌是应用程序识别的离散搜索词。令牌提供了一种组合多个搜索词的方法,并使您更容易表明搜索词在应用程序中是常见或预期的。


image.png

为字符串提供存储

可搜索修饰符将Bindingtext输入的字符串值。该字符串作为SwiftUI显示的搜索查询字段的存储。您可以使用State属性在视图中创建此存储,并将其初始化为空字符串:

@State private var searchText: String = ""

为了更容易在不同视图之间共享搜索查询,您可以在可观察对象中创建已发布的值,该对象是应用程序模型的一部分:

class Model: ObservableObject {
    @Published var searchText: String = ""
}

无论哪种情况,通过在值中添加美元符号($)前缀,将此字符串的Binding传递到可搜索视图修饰符中:

struct ContentView: View {
    @EnvironmentObject private var model: Model
    @State private var departmentId: Department.ID?
    @State private var productId: Product.ID?

    var body: some View {
        NavigationSplitView {
            DepartmentList(departmentId: $departmentId)
        } content: {
            ProductList(departmentId: departmentId, productId: $productId)
                .searchable(text: $model.searchText)
        } detail: {
            ProductDetails(productId: productId)
        }
    }
}

为代币提供存储

除了搜索字符串外,当您使用具有tokens参数的可搜索修饰符之一时,搜索字段还可以显示令牌,例如searchable(text:tokens:placement:prompt:token:)
您通过定义一组符合Identifiable协议的值来创建令牌,然后实例化值集合。例如,您可以创建水果令牌的枚举:

enum FruitToken: String, Identifiable, Hashable, CaseIterable {
    case apple
    case pear
    case banana
    var id: Self { self }
}

然后将新的已发布属性添加到您的模型中,以存储令牌集合:

@Published var tokens: [FruitToken] = []

要显示令牌,请提供tokens数组的Binding作为可搜索修饰符的tokens输入参数,并描述如何使用token闭包绘制每个令牌。从闭包中,返回表示作为输入给出的令牌的View。例如,您可以使用Text视图来表示每个令牌:

ProductList(departmentId: departmentId, productId: $productId)
    .searchable(text: $model.searchText, tokens: $model.tokens) { token in
        switch token {
        case .apple: Text("Apple")
        case .pear: Text("Pear")
        case .banana: Text("Banana")
        }
    }

您可以使用Text视图表示令牌,如上述示例所示。在iOS和iPadOS中,您可以改用aLabel。确保视图清楚地表示相应的搜索查询,如果您使用标签,令牌符合搜索查询字段的高度。令牌出现在搜索字段的开头,在任何纯文本之前。以下显示了当tokens数组包含apple和banana令牌时,搜索字段的外观:


image.png

将令牌添加到搜索中

为人们提供一种将令牌添加到搜索字段的方法。你可以用不同的方式做到这一点。例如,您可以:

进行搜索

当您检测到搜索查询中的更改时,您的应用程序可以开始搜索。您如何执行搜索操作取决于您的应用程序如何存储和呈现数据。一种方法是根据列表项中的字段是否与搜索查询匹配来过滤List显示的元素。例如,您可以创建一种方法,仅返回产品数组中的项目,其名称与搜索文本或当前搜索字段中的令牌之一匹配:

func filteredProducts(
    products: [Product],
    searchText: String,
    tokens: [FruitToken]
) -> [Product] {
    guard !searchText.isEmpty || !tokens.isEmpty else { return products }
    return products.filter { product in
        product.name.lowercased().contains(searchText.lowercased()) ||
        tokens.map({ $0.rawValue }).contains(product.name.lowercased())
    }
}

考虑搜索的复杂性和更改搜索词的成本。如果成本很高,例如更新需要网络访问,或者对于复杂的过滤器逻辑,请考虑预取和缓存数据或减少更新频率。或者,您可以等到有人提交查询后再进行搜索。

Search

让人们在您的应用程序中搜索文本或其他内容。

要在应用程序中显示搜索字段,请创建和管理搜索文本的存储,也可以选择用于称为令牌的离散搜索词。然后通过将可搜索视图修饰符应用于应用程序中的视图,将存储绑定到搜索字段。
当人们与字段交互时,他们会隐式地修改底层存储,从而修改搜索参数。您的应用程序相应地更新其界面的其他部分。为了增强搜索交互,您还可以:

搜索应用程序的数据模型

func searchable(text: Binding<String>, placement: SearchFieldPlacement, prompt: Text?) -> some View
将此视图标记为可搜索,该视图配置搜索字段的显示。
func searchable(text: Binding<String>, placement: SearchFieldPlacement, prompt: LocalizedStringKey) -> some View
将此视图标记为可搜索,该视图配置搜索字段的显示。
func searchable<S>(text: Binding<String>, placement: SearchFieldPlacement, prompt: S) -> some View
将此视图标记为可搜索,该视图配置搜索字段的显示。
func searchable<C, T, S>(text: Binding<String>, tokens: Binding<C>, placement: SearchFieldPlacement, prompt: S, token: (C.Element) -> T) -> some View
将此视图标记为可搜索,该视图配置搜索字段的显示。
func searchable<C, T>(text: Binding<String>, tokens: Binding<C>, placement: SearchFieldPlacement, prompt: LocalizedStringKey, token: (C.Element) -> T) -> some View
将此视图标记为可搜索,该视图配置搜索字段的显示。
func searchable<C, T>(text: Binding<String>, tokens: Binding<C>, placement: SearchFieldPlacement, prompt: Text?, token: (C.Element) -> T) -> some View
将此视图标记为可搜索,该视图配置搜索字段的显示。
struct SearchFieldPlacement
搜索字段在视图层次结构中的位置。

提出搜索建议

func searchSuggestions<S>(() -> S) -> some View
配置此视图的搜索建议。
func searchSuggestions(Visibility, for: SearchSuggestionsPlacement.Set) -> some View
配置如何在此视图中显示搜索建议。
func searchCompletion<T>(T) -> some View
将搜索令牌与此视图的值相关联。
func searchCompletion(String) -> some View
将完全形成的字符串与此视图的值相关联。
func searchable<C, T>(text: Binding<String>, tokens: Binding<C>, suggestedTokens: Binding<C>, placement: SearchFieldPlacement, prompt: LocalizedStringKey, token: (C.Element) -> T) -> some View
将此视图标记为可搜索,该视图配置搜索字段的显示。
func searchable<C, T>(text: Binding<String>, tokens: Binding<C>, suggestedTokens: Binding<C>, placement: SearchFieldPlacement, prompt: Text?, token: (C.Element) -> T) -> some View
将此视图标记为可搜索,该视图配置搜索字段的显示。
func searchable<C, T, S>(text: Binding<String>, tokens: Binding<C>, suggestedTokens: Binding<C>, placement: SearchFieldPlacement, prompt: S, token: (C.Element) -> T) -> some View
将此视图标记为可搜索,该视图配置搜索字段的显示。
struct SearchSuggestionsPlacement
SwiftUI显示搜索建议的方式。

限制搜索范围

func searchScopes<V, S>(Binding<V>, scopes: () -> S) -> some View
配置此视图的搜索范围。
func searchScopes<V, S>(Binding<V>, activation: SearchScopeActivation, () -> S) -> some View
使用指定的激活策略配置此视图的搜索范围。
struct SearchScopeActivation
可搜索修饰符可以显示或隐藏搜索范围的方式。

检测和关闭搜索

var isSearching: Bool
一个布尔值,指示用户何时搜索。
var dismissSearch: DismissSearchAction
结束当前搜索交互的操作。
struct DismissSearchAction
可以结束搜索交互的操作。

在具有查找和替换的视图中搜索文本

func findNavigator(isPresented: Binding<Bool>) -> some View
以编程方式呈现文本编辑器视图的查找和替换界面。
func findDisabled(Bool) -> some View
防止在文本编辑器中查找和替换操作。
func replaceDisabled(Bool) -> some View
防止文本编辑器中的替换操作。

SearchFieldPlacement

搜索字段在视图层次结构中的位置。

您可以给任何可搜索修饰符一个首选位置,例如searchable(text:placement:prompt:)

var body: some View {
    NavigationView {
        PrimaryView()
        SecondaryView()
        Text("Select a primary and secondary item")
    }
    .searchable(text: $text, placement: .sidebar)
}

根据包含的视图层次,SwiftUI可能无法满足您的请求。

获取搜索字段位置

static let automatic: SearchFieldPlacement
SwiftUI会自动放置搜索字段。
static let navigationBarDrawer: SearchFieldPlacement
搜索字段出现在导航栏中。
static func navigationBarDrawer(displayMode: SearchFieldPlacement.NavigationBarDrawerDisplayMode) -> SearchFieldPlacement
搜索字段使用指定的显示模式出现在导航栏中。
static let sidebar: SearchFieldPlacement
搜索字段出现在导航视图的侧边栏中。
static let toolbar: SearchFieldPlacement
搜索字段出现在工具栏中。

支持类型

struct NavigationBarDrawerDisplayMode
一种确定何时显示出现在导航栏中的搜索字段的模式。

SearchSuggestionsPlacement

SwiftUI显示搜索建议的方式。

您可以使用searchSuggestions(_:for:)修饰符来影响SwiftUI显示搜索建议的模式:

enum FruitSuggestion: String, Identifiable {
    case apple, banana, orange
    var id: Self { self }
}

@State private var text = ""
@State private var suggestions: [FruitSuggestion] = []

var body: some View {
    MainContent()
        .searchable(text: $text) {
            ForEach(suggestions) { suggestion in
                Text(suggestion.rawValue)
                    .searchCompletion(suggestion.rawValue)
            }
            .searchSuggestions(.hidden, for: .content)
        }
}

在上述示例中,SwiftUI仅在建议菜单中显示搜索建议。当您想在容器中呈现搜索建议时,您可能需要这样做,例如与您自己的搜索结果集内联。
您可以通过查询搜索建议中的search环境值来获取当前搜索建议位置。

获得安置

static var automatic: SearchSuggestionsPlacement
搜索建议根据周围的上下文自动呈现。
static var content: SearchSuggestionsPlacement
搜索建议在应用程序的主要内容中呈现。
static var menu: SearchSuggestionsPlacement
搜索建议呈现在附加到搜索字段的菜单中。

比较位置

static func == (SearchSuggestionsPlacement, SearchSuggestionsPlacement) -> Bool
指示两个搜索建议位置是否相等。
static func != (Self, Self) -> Bool
指示两个搜索建议位置是否不相等。

支持类型

struct Set
一套高效的搜索建议显示模式。

管理搜索界面激活

程序化检测并关闭搜索字段。

人们通过点击或单击应用程序中的搜索字段来激活它,之后他们可以输入搜索词。在许多情况下,您的应用程序只需要对搜索文本值的相应变化做出反应,该界面通过您提供的绑定进行更新,如执行搜索操作中所述。

然而,SwiftUI还提供了控件,使您能够直接与搜索界面交互。特别是,你可以:

检测搜索激活

要检测搜索界面何时处于活动状态,请使用Environment属性包装器查询环境的is属性。以下示例显示了一个视图,该视图根据属性的状态更新其显示的文本:

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")
    }
}

当有人首次点击或单击搜索字段时,is属性变为true。当他们取消或提交搜索操作时,属性变为false。如下一节所述,如果您以编程方式关闭接口,它也会变成false

请务必从直接或间接由searchable(text:placement:prompt:)视图修饰符之一包装的视图内部读取属性,如上述示例中的Searched。如果您从该上下文之外读取属性值,就像将其放在Searching视图中一样,您将不会检测到属性值的任何变化。

关闭搜索界面

您可以使用环境的dismiss操作以编程方式停用界面。例如,考虑一个带有Button的视图,该视图显示有关集合中第一个匹配项目的更多信息:

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)
                }
            }
        }
    }
}

只有在有人输入产生匹配的搜索文本后,该按钮才会可见。该按钮的操作显示一个表,该表提供有关该项目的更多信息,包括用于将该项目添加到存储的项目列表中的“添加”按钮:

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") {
                    // Store the item here...

                    dismiss()
                    dismissSearch()
                }
            }
    }
}

人们可以通过向下拖动工作表来关闭工作表,有效地取消操作,保持正在进行的搜索交互不变。或者,他们可以点击添加按钮来存储项目。由于此时人们可能同时使用详细信息视图和搜索交互,因此按钮的关闭使用 dismiss属性来关闭工作表, dismiss属性重置搜索字段。

is属性一样,请确保仅从可搜索视图修饰符的层次结构中读取 dismiss。如果您从该上下文之外的环境中读取该操作,则调用该操作没有效果。上面的示例从Searched读取操作,并将其传递到工作表中,因为工作表有自己的环境。如果搜索界面未激活,该操作也不起作用。

对搜索提交做出反应

要指定SwiftUI在有人提交搜索查询时(通过按Return键)调用的操作,请添加theonSubmitSubmit(of:_:)修饰符:

SearchedView()
    .searchable(text: $searchText)
    .onSubmit(of: .search) {
        submitCurrentSearchQuery()
    }

根据应用程序的结构,您可以以不同的方式使用搜索提交。例如,您可以借此机会在可以转换为令牌的搜索查询字符串中查找子字符串。或者,对于非常慢的搜索操作,也许是因为它需要网络访问,您可以在执行搜索之前等待提交事件。

检测和关闭搜索

var isSearching: Bool
一个布尔值,指示用户何时搜索。
var dismissSearch: DismissSearchAction
结束当前搜索交互的操作。
struct DismissSearchAction
可以结束搜索交互的操作。

SearchScopeActivation

可搜索修饰符可以显示或隐藏搜索范围的方式。

类型属性

static var automatic: SearchScopeActivation
范围栏的自动激活。
static var onSearchPresentation: SearchScopeActivation
一种激活,系统在显示搜索后显示搜索范围,并在搜索取消后隐藏搜索范围。
static var onTextEntry: SearchScopeActivation
激活,系统在搜索字段中开始键入时显示搜索范围,并在搜索取消后隐藏搜索范围。
func searchScopes<V, S>(Binding<V>, scopes: () -> S) -> some View
配置此视图的搜索范围。
func searchScopes<V, S>(Binding<V>, activation: SearchScopeActivation, () -> S) -> some View
使用指定的激活策略配置此视图的搜索范围。

DismissSearchAction

可以结束搜索交互的操作。

使用 dismiss环境值获取给定Environment的此结构的实例。然后调用实例来关闭当前的搜索交互。您直接调用实例,因为它定义了Swift在您调用实例时调用的callFunction()方法。
当您关闭搜索时,SwiftUI:

使用此操作可基于其他用户交互取消搜索操作。例如,考虑一个带有按钮的可搜索视图,该按钮显示有关集合中第一个匹配项的更多信息:

struct ContentView: View {
    @State private var searchText = ""

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

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)
                }
            }
        }
    }
}

只有在用户输入产生匹配的搜索文本后,该按钮才可见。当用户点击按钮时,SwiftUI会显示一个表,该表提供有关项目的更多信息,包括将项目添加到存储的项目列表中的添加按钮:

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") {
                    // Store the item here...

                    dismiss()
                    dismissSearch()
                }
            }
    }
}

只有在用户输入产生匹配的搜索文本后,该按钮才可见。当用户点击按钮时,SwiftUI会显示一个表,该表提供有关项目的更多信息,包括将项目添加到存储的项目列表中的添加按钮:

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") {
                    // Store the item here...

                    dismiss()
                    dismissSearch()
                }
            }
    }
}

人们可以通过向下拖动工作表来关闭工作表,有效地取消操作,保持正在进行的搜索交互不变。或者,人们可以点击添加按钮来存储项目。由于此时使用您的应用程序的人可能会同时完成详细信息视图和搜索交互,因此按钮的关闭还使用dismiss属性来关闭工作表,并使用dismiss属性来重置搜索字段。

重要
如上例所示,从搜索视图内部访问操作,而不是从搜索视图的父级或其他层次结构(如工作表)访问操作。SwiftUI在您应用可搜索修饰符的视图环境中设置值,并且不会将值传播到视图层次结构上。

func callAsFunction()
关闭当前的搜索操作(如果有的话)。
var isSearching: Bool
一个布尔值,指示用户何时搜索。
var dismissSearch: DismissSearchAction
结束当前搜索交互的操作。
上一篇下一篇

猜你喜欢

热点阅读