SwiftUI — searchable

在 SwiftUI 中,searchable 修饰符添加了强大的搜索功能,使得用户能够轻松地在数据中搜索。它允许你在视图中添加搜索栏,并对搜索输入进行处理。 searchable 可以应用于多种视图,如 ListScrollView 等。

主要功能: #

  • 显示搜索栏,用户可以输入搜索文本。
  • 处理搜索输入,并动态更新视图内容。
  • 配置搜索栏的属性,如提示文本、搜索范围等。

如何使用 searchable #

基本用法 #

使用 searchable,你可以很容易地将搜索功能添加到列表视图中。让我们来看一个简单的示例:

示例代码:基本使用 #

import SwiftUI

struct ContentView: View {
    @State private var searchText = ""
    let items = ["Apple", "Banana", "Cherry", "Date", "Fig", "Grape"]

    var body: some View {
        NavigationView {
            List {
                ForEach(searchResults, id: \.self) { item in
                    Text(item)
                }
            }
            .searchable(text: $searchText)
            .navigationTitle("Fruits")
        }
    }

    var searchResults: [String] {
        if searchText.isEmpty {
            return items
        } else {
            return items.filter { $0.contains(searchText) }
        }
    }
}

解释:

  1. 状态变量 searchText:用于存储用户的搜索输入。
  2. 添加 searchable 修饰符:在 List 视图上添加 searchable,绑定到 searchText
  3. searchResults 计算属性:根据 searchText 过滤项,并动态更新列表内容。

自定义搜索栏 #

你可以自定义搜索栏的提示文本、范围等,提高用户体验。

示例代码:自定义搜索栏 #

import SwiftUI

struct ContentView: View {
    @State private var searchText = ""
    @State private var selectedScope = 0
    let items = ["Apple", "Banana", "Cherry", "Date", "Fig", "Grape"]

    var body: some View {
        NavigationView {
            List {
                ForEach(searchResults, id: \.self) { item in
                    Text(item)
                }
            }
            .searchable(text: $searchText, prompt: "Search fruits")
            .searchScopes($selectedScope) {
                Text("All")
                Text("A-D")
                Text("E-H")
            }
            .navigationTitle("Fruits")
        }
    }

    var searchResults: [String] {
        if searchText.isEmpty {
            return items
        } else {
            return items.filter { matchesScope($0) && $0.contains(searchText) }
        }
    }

    func matchesScope(_ item: String) -> Bool {
        switch selectedScope {
        case 1:
            return item < "E"
        case 2:
            return item >= "E"
        default:
            return true
        }
    }
}

解释:

  1. prompt 参数:自定义搜索栏的提示文本。
  2. searchScopes:定义搜索范围的选项,用户可以选择不同的范围过滤项。
  3. matchesScope 函数:根据选定的范围过滤项。

高级用法:结合 onChangesearchCompletion #

可以结合使用 onChange 处理搜索文本的变化,或使用 searchCompletion 提供搜索建议。

示例代码:高级用法 #

import SwiftUI

struct ContentView: View {
    @State private var searchText = ""
    @State private var selectedScope = 0
    let items = ["Apple", "Banana", "Cherry", "Date", "Fig", "Grape"]
    let suggestions = ["Apple", "Banana", "Grape"]

    var body: some View {
        NavigationView {
            List {
                ForEach(searchResults, id: \.self) { item in
                    Text(item)
                }
            }
            .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always), prompt: "Search fruits")
            .onChange(of: searchText) { newValue in
                // 搜索文本变化时的处理逻辑
                print("New search text: \(newValue)")
            }
            .searchSuggestions {
                ForEach(suggestions, id: \.self) { suggestion in
                    Text(suggestion).searchCompletion(suggestion)
                }
            }
            .navigationTitle("Fruits")
        }
    }

    var searchResults: [String] {
        if searchText.isEmpty {
            return items
        } else {
            return items.filter { $0.contains(searchText) }
        }
    }
}

解释:

  1. placement 参数:设置搜索栏的位置,这里指定在 navigationBarDrawer 中总是显示。
  2. onChange:监控搜索文本的变化并执行相应的操作。
  3. searchSuggestions:提供搜索建议,通过 searchCompletion 自动填充搜索文本。

对视图的影响 #

添加 searchable 修饰符会在视图中显示搜索栏,使用户能够输入搜索文本并动态过滤视图内容。它对视图有以下影响:

  1. 用户体验:提供搜索功能,提升用户的查找效率和体验。
  2. 视图更新:根据搜索输入动态更新和过滤视图内容,提供更精准的数据展示。
  3. 布局searchable 通常会添加在视图顶部的导航栏或列表顶部,不影响子视图的布局。

总结 #

searchable 是一个强大的 SwiftUI 修饰符,让开发者可以轻松添加搜索功能。通过结合使用不同参数和方法,你可以自定义和扩展搜索栏的功能,满足不同的应用需求。

主要功能和用法#

  1. 基本搜索:添加搜索栏,绑定搜索文本状态。
  2. 自定义搜索栏:设置提示文本和搜索范围。
  3. 高级用法:监控搜索文本变化,提供搜索建议。

这些功能不仅提升了用户体验,还为应用开发带来了更大的灵活性和可定制性。不妨在你的 SwiftUI 项目中尝试使用 searchable,为用户提供更便捷的搜索功能。

scope #

searchablescope 是一个概念,用于在搜索时提供多种范围选项。用户可以通过选择不同的范围来过滤搜索结果,这样可以更灵活地控制搜索条件和结果。 scope 提供了一种增强搜索体验的方法,类似于在邮件应用中按“所有邮件”“已发邮件”等进行分类搜索。

在 SwiftUI 中,你可以使用 searchScopes 修饰符来定义搜索的范围,并绑定到一个状态变量以控制当前选定的范围。

示例代码:使用搜索范围(Scope) #

import SwiftUI

struct ContentView: View {
    @State private var searchText = ""
    @State private var selectedScope = 0  // 当前选定的搜索范围索引
    let items = ["Apple", "Banana", "Cherry", "Date", "Fig", "Grape"]
    let scopes = ["All", "A-D", "E-G"] // 定义范围选项

    var body: some View {
        NavigationView {
            List {
                ForEach(searchResults, id: \.self) { item in
                    Text(item)
                }
            }
            .searchable(text: $searchText, prompt: "Search fruits")
            .searchScopes($selectedScope) {
                ForEach(scopes.indices, id: \.self) { index in
                    Text(scopes[index])
                }
            }
            .navigationTitle("Fruits")
        }
    }

    var searchResults: [String] {
        if searchText.isEmpty {
            return items.filter { matchesScope($0) }
        } else {
            return items.filter { matchesScope($0) && $0.contains(searchText) }
        }
    }

    func matchesScope(_ item: String) -> Bool {
        switch selectedScope {
        case 1:
            return item.lowercased() <= "d"
        case 2:
            return item.lowercased() >= "e" && item.lowercased() <= "g"
        default:
            return true
        }
    }
}

解释:

  1. 状态变量

    • searchText: 用于存储用户的搜索输入。
    • selectedScope: 一个整数,用于跟踪当前选定的范围索引。
  2. 定义范围选项

    • scopes: 一个包含范围选项的数组,每个范围代表搜索的不同条件。
  3. searchable 修饰符

    • text: $searchText: 绑定搜索文本到状态变量。
    • prompt: "Search fruits": 提示用户的搜索栏标题。
  4. searchScopes 修饰符

    • $selectedScope: 绑定选中的范围索引到状态变量。
    • 为每个范围选项使用 ForEach 创建一个 Text 视图。
  5. 搜索结果过滤

    • searchResults: 一个计算属性,根据 searchTextselectedScope 来过滤数组 items
    • matchesScope 函数:根据 selectedScope 的值来判断每个项是否应包括在搜索结果中。

示例代码:更复杂的范围选项 #

还可以配置更复杂的搜索范围,如组合筛选条件或使用自定义视图。

import SwiftUI

struct ContentView: View {
    @State private var searchText = ""
    @State private var selectedScope = 0  // 当前选定的搜索范围索引
    let items = ["Apple", "Banana", "Cherry", "Date", "Eggplant", "Fig", "Grape"]
    let scopes = ["All", "Starts with A-D", "Starts with E-G", "Contains 'a'"]

    var body: some View {
        NavigationView {
            List {
                ForEach(searchResults, id: \.self) { item in
                    Text(item)
                }
            }
            .searchable(text: $searchText, prompt: "Search fruits")
            .searchScopes($selectedScope) {
                ForEach(scopes.indices, id: \.self) { index in
                    Text(scopes[index])
                }
            }
            .navigationTitle("Fruits")
        }
    }

    var searchResults: [String] {
        if searchText.isEmpty {
            return items.filter { matchesScope($0) }
        } else {
            return items.filter { matchesScope($0) && $0.lowercased().contains(searchText.lowercased()) }
        }
    }

    func matchesScope(_ item: String) -> Bool {
        switch selectedScope {
        case 1:
            return item.lowercased() <= "d"
        case 2:
            return item.lowercased() >= "e" && item.lowercased() <= "g"
        case 3:
            return item.lowercased().contains("a")
        default:
            return true
        }
    }
}

解释:

  • scopes 定义了更多更具体的范围选项,如 “Starts with A-D” 和 “Contains ‘a’"。
  • matchesScope 函数增加了更多条件,以根据选择的 scope 对项目进行筛选。

注意事项#

  1. 范围选项的UI

    • searchScopes 创建的范围选项通常显示在 SearchBar 中,可以根据需要自定义这些视图。
    • Text 是最常用的范围选项视图,你也可以使用其他视图组件。
  2. 性能考虑

    • 随着项目数量的增加,搜索和过滤操作可能会对性能产生影响。确保搜索逻辑高效,必要时采用分页处理或后台异步操作。
  3. 用户体验

    • 提供清晰的范围选项标签,让用户容易理解每个范围的作用。
    • 动态更新搜索结果,提高交互体验。

总结 #

searchablescope 提供了一种灵活的方式来增强搜索功能。通过定义不同的搜索范围和筛选条件,用户可以更方便地在数据集中找到他们感兴趣的信息。这些功能不仅提升了用户体验,也为开发者提供了更强大的搜索控制能力。希望通过这些示例代码和解释,你能在 SwiftUI 项目中充分利用 searchable 和搜索范围,打造符合需求的搜索体验。

自定义 scope 样式 #

在 SwiftUI 中,searchScopes 默认会创建一个简洁的控件(类似 Segmented Control 或选项卡)来让用户选择搜索范围。虽然 searchScopes 本身无法直接提供样式选项,但你可以通过一些技巧间接修饰其外观,或者使用自定义的视图实现类似的效果。

以下是一些实现方式的介绍和示例代码。


1. 默认样式:使用 searchScopes #

searchScopes 使用的范围切换控件是系统样式的,类似于一个 SegmentedControl。默认情况下,你无法直接修改它的外观,但可以通过其他属性实现部分定制。

示例代码:默认 searchScopes #

import SwiftUI

struct ContentView: View {
    @State private var searchText = ""
    @State private var selectedScope = 0 // 当前选定的搜索范围索引
    let scopes = ["All", "Starts with A-D", "Starts with E-G"]

    var body: some View {
        NavigationView {
            List(searchResults, id: \.self) { item in
                Text(item)
            }
            .searchable(text: $searchText)
            .searchScopes($selectedScope) {
                ForEach(scopes.indices, id: \.self) { index in
                    Text(scopes[index])
                }
            }
            .navigationTitle("Customizable Scope")
        }
    }

    var searchResults: [String] {
        let items = ["Apple", "Banana", "Cherry", "Date", "Eggplant", "Fig", "Grape"]
        if searchText.isEmpty {
            return filter(scope: selectedScope, for: items)
        } else {
            return filter(scope: selectedScope, for: items.filter { $0.contains(searchText) })
        }
    }

    func filter(scope: Int, for items: [String]) -> [String] {
        switch scope {
        case 1:
            return items.filter { $0 <= "D" }
        case 2:
            return items.filter { $0 >= "E" && $0 <= "G" }
        default:
            return items
        }
    }
}

运行效果#

  • 系统范围切换显示在搜索栏下方,样式类似于 SegmentedControl
  • 上述控件样式是遵循系统主题的,无法直接通过 SwiftUI 修改其颜色或形状。

2. 自定义 Scope 样式:完全使用自定义视图 #

如果默认的 searchScopes 不满足需求,你可以利用 SwiftUI 的其他 UI 组件,如 PickerSegmentedControl 来构建自定义的范围选择控件。

示例代码:自定义范围样式 #

import SwiftUI

struct ContentView: View {
    @State private var searchText = ""
    @State private var selectedScope = 0 // 当前选定的搜索范围索引
    let scopes = ["All", "A-D", "E-G"]

    var body: some View {
        NavigationView {
            VStack {
                // 自定义范围切换控件
                ScopePicker(selectedScope: $selectedScope, scopes: scopes)
                    .padding()

                // 搜索结果列表
                List(searchResults, id: \.self) { item in
                    Text(item)
                }
            }
            .searchable(text: $searchText)
            .navigationTitle("Custom Scope Picker")
        }
    }

    var searchResults: [String] {
        let items = ["Apple", "Banana", "Cherry", "Date", "Eggplant", "Fig", "Grape"]
        if searchText.isEmpty {
            return filter(scope: selectedScope, for: items)
        } else {
            return filter(scope: selectedScope, for: items.filter { $0.contains(searchText) })
        }
    }

    func filter(scope: Int, for items: [String]) -> [String] {
        switch scope {
        case 1:
            return items.filter { $0 <= "D" }
        case 2:
            return items.filter { $0 >= "E" && $0 <= "G" }
        default:
            return items
        }
    }
}

// 自定义范围选择器视图
struct ScopePicker: View {
    @Binding var selectedScope: Int
    let scopes: [String]

    var body: some View {
        Picker("Select Scope", selection: $selectedScope) {
            ForEach(scopes.indices, id: \.self) { index in
                Text(scopes[index]).tag(index)
            }
        }
        .pickerStyle(SegmentedPickerStyle()) // 设置为分段控件样式
        .background(RoundedRectangle(cornerRadius: 10).fill(Color.blue.opacity(0.1)))
        .padding()
    }
}

运行效果: #

  • 自定义了范围切换控件 ScopePicker
  • 使用 Picker 组件加上 .pickerStyle(SegmentedPickerStyle()) 实现类似分段控件的样式。
  • 添加了背景装饰,例如圆角矩形。

优点: #

  • 完全自定义视图风格。
  • 通过 Picker 或其他控件,可更轻松调整外观。

3. 更复杂的自定义(例如按钮样式的范围选择) #

通过使用 Button 组件,你可以设计更自由的范围切换样式,比如按钮样式的范围选择器。

示例代码:按钮风格的范围选择 #

import SwiftUI

struct ContentView: View {
    @State private var searchText = ""
    @State private var selectedScope = 0
    let scopes = ["All", "A-D", "E-G"]

    var body: some View {
        NavigationView {
            VStack {
                // 自定义按钮风格范围选择器
                HStack {
                    ForEach(scopes.indices, id: \.self) { index in
                        Button(action: {
                            selectedScope = index
                        }) {
                            Text(scopes[index])
                                .fontWeight(selectedScope == index ? .bold : .regular)
                                .foregroundColor(selectedScope == index ? .white : .blue)
                                .padding()
                                .background(selectedScope == index ? Color.blue : Color.clear)
                                .cornerRadius(10)
                                .overlay(
                                    RoundedRectangle(cornerRadius: 10)
                                        .stroke(Color.blue, lineWidth: 1)
                                )
                        }
                    }
                }
                .padding()

                // 搜索结果列表
                List(searchResults, id: \.self) { item in
                    Text(item)
                }
            }
            .searchable(text: $searchText)
            .navigationTitle("Button Style Scope")
        }
    }

    var searchResults: [String] {
        let items = ["Apple", "Banana", "Cherry", "Date", "Eggplant", "Fig", "Grape"]
        if searchText.isEmpty {
            return filter(scope: selectedScope, for: items)
        } else {
            return filter(scope: selectedScope, for: items.filter { $0.contains(searchText) })
        }
    }

    func filter(scope: Int, for items: [String]) -> [String] {
        switch scope {
        case 1:
            return items.filter { $0 <= "D" }
        case 2:
            return items.filter { $0 >= "E" && $0 <= "G" }
        default:
            return items
        }
    }
}

运行效果: #

  • 范围切换通过按钮实现,每个按钮都有选中状态(蓝色背景)和未选中状态(白色背景)。
  • 使用类似 SegmentedControl 样式但更灵活丰富。

总结 #

searchablescope 默认提供了一种简洁和直观的方式来切换搜索范围。虽然默认样式无法直接修改,但你可以通过以下方式实现样式修饰:

  1. 默认 searchScopes

    • 系统样式,类似 iOS 的 SegmentedControl
    • 无法直接修改外观,但可通过与应用整体风格搭配使用。
  2. 通过 Picker 自定义

    • 使用 Picker.pickerStyle(SegmentedPickerStyle()) 实现易用的范围选择器。
    • 可轻松调整背景、形状和外观。
  3. 按钮视图完全自定义

    • 使用 Button 来设计按需的范围选择样式,更灵活,自定义状态和交互效果。

通过上述技术,你可以为 searchablescope 创建更合适的样式,满足应用的设计需求,并为用户提供更佳的体验。

搜索框位置 #

在 SwiftUI 的 searchable 中,搜索框的位置通过 placement 参数进行配置。默认情况下,搜索框会出现在适当的位置,例如 navigationBar 或列表的顶部。通过调整 placement 参数,你可以将搜索框显示在不同的位置,例如导航栏中、顶部悬浮、还是列表嵌入等。

下面是如何调整搜索框位置的方法以及具体示例。


searchableplacement 参数 #

searchable 修饰符中,placement 可以指定搜索框的显示位置。目前提供了以下几个选项:

Placement 值及含义 #

  1. .automatic (默认值)

    • 系统自动决定搜索框的位置。如果 List 位于 NavigationView 中,搜索框一般会嵌入导航栏中,否则会嵌入到列表顶部。
  2. .navigationBarDrawer(displayMode: .always)

    • 搜索框定位在导航栏内,类似于 iOS 的大标题形式。
    • displayMode 可以是:
      • .always:总是显示搜索框。
      • .automatic:用户下拉时显示搜索框。
  3. .toolbar

    • 搜索框作为工具栏(toolbar)的一部分,显示在页面顶部的工具栏区域。
  4. .content

    • 搜索框嵌入到视图内容中,显示在 List 或滚动内容的顶部。

示例代码:调整搜索框的位置 #

以下代码展示了如何根据 placement 配置搜索框的位置:

1. 默认位置 (.automatic) #

import SwiftUI

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

    var body: some View {
        NavigationView {
            List(1...20, id: \.self) { item in
                Text("Item \(item)")
            }
            .searchable(text: $searchText) // 默认位置
            .navigationTitle("Default Position")
        }
    }
}
运行效果: #
  • 搜索框自动嵌入到适当位置,例如导航栏或列表顶部。

2. 导航栏内 (.navigationBarDrawer) #

import SwiftUI

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

    var body: some View {
        NavigationView {
            List(1...20, id: \.self) { item in
                Text("Item \(item)")
            }
            .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
            .navigationTitle("In Navigation Bar")
        }
    }
}
效果: #
  • 搜索框显示在导航栏的标题下方,并始终可见。

3. 工具栏内 (.toolbar) #

import SwiftUI

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

    var body: some View {
        NavigationView {
            List(1...20, id: \.self) { item in
                Text("Item \(item)")
            }
            .searchable(text: $searchText, placement: .toolbar)
            .navigationTitle("In Toolbar")
        }
    }
}
效果: #
  • 搜索框显示在工具栏区域,通常位于导航栏的顶部。

4. 内容区嵌入 (.content) #

import SwiftUI

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

    var body: some View {
        NavigationView {
            List(1...20, id: \.self) { item in
                Text("Item \(item)")
            }
            .searchable(text: $searchText, placement: .content)
            .navigationTitle("In Content")
        }
    }
}
效果: #
  • 搜索框嵌入到内容中,显示在列表(List)的顶部。

动态切换位置:基于条件调整搜索框位置 #

如果需要根据某些条件动态调整搜索框的位置,可以通过状态变量来控制。

import SwiftUI

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

    let placements: [SearchFieldPlacement] = [.automatic, .navigationBarDrawer(displayMode: .always), .toolbar, .content]
    let placementNames = ["Automatic", "Navigation Bar", "Toolbar", "Content"]

    var body: some View {
        NavigationView {
            VStack {
                Picker("Select Placement", selection: $selectedPlacement) {
                    ForEach(placements.indices, id: \.self) { index in
                        Text(placementNames[index])
                    }
                }
                .pickerStyle(SegmentedPickerStyle())
                .padding()

                List(1...20, id: \.self) { item in
                    Text("Item \(item)")
                }
                .searchable(text: $searchText, placement: placements[selectedPlacement])
            }
            .navigationTitle("Dynamic Position")
        }
    }
}

效果: #

  • 使用 Picker 切换搜索框的位置。
  • searchableplacement 动态绑定到数组中的值。

最佳实践与注意事项 #

  1. 选择适合的位置

    • 如果应用有导航栏,建议使用 .navigationBarDrawer,它会显示在导航栏下方,与系统风格一致。
    • 如果布局需要更高的可定制性,或应用没有嵌套导航栏,可以使用 .content
  2. 与工具栏结合

    • 如果应用在顶部区域已经有复杂的工具栏图标,可以使用 .toolbar,将搜索框整合进工具栏。
  3. 动态位置切换

    • 当应用需要用户在不同场景下切换搜索框位置时,可以通过状态变量灵活绑定。
  4. 与样式结合

    • 搜索框的位置可以结合自定义样式来设计更直观的用户体验,比如特定背景色、动态视觉效果等。

总结 #

在 SwiftUI 中,searchable 提供了多种位置选项,通过 placement 参数可以轻松调整搜索框的位置。你可以使用:

  • .automatic:默认位置,视上下文决定。
  • .navigationBarDrawer:显示在导航栏中,遵循系统风格。
  • .toolbar:添加到工具栏中。
  • .content:嵌入至内容中,显示在滚动视图的顶部。

结合这些选项,你可以针对不同的界面布局和需求选择最合适的搜索框位置,让用户拥有更直观的搜索体验!

中文输入法 #

在 SwiftUI 的 searchable 中,当你输入文字时,绑定到 text 参数的值会实时更新。这种默认行为对于即时搜索是合适的,但当使用中文输入法(或其他支持拼音输入的输入法)时,每次键入一个字母或者标点符号都会触发搜索,这可能导致以下问题:

  1. 用户正在拼音输入过程中,未完成的输入会被当作搜索内容(例如输入“pinyin”,会触发多次搜索)。
  2. 不希望未完成拼音阶段时触发不必要的搜索逻辑,可能导致性能问题或错误。

为了避免这种现象,可以监测用户是否完成输入,在拼音输入结束(即转换为中文字符)之后再触发搜索操作。主要有以下几种方法来处理中文输入法的这种情况:

方法 1:使用 onSubmit 监听 “提交” 操作 #

searchable 提供了一个 onSubmit 回调,当用户完成输入并点击键盘上的 Search(搜索)按钮 或按下回车键后,才会触发回调。

示例代码 #

import SwiftUI

struct ContentView: View {
    @State private var searchText = ""
    @State private var searchResults: [String] = []

    let items = ["苹果", "香蕉", "橘子", "葡萄", "草莓"]

    var body: some View {
        NavigationView {
            List(searchResults, id: \.self) { item in
                Text(item)
            }
            .searchable(text: $searchText, prompt: "搜索水果")
            .onSubmit(of: .search) {  // 用户点击键盘上的 "搜索" 按钮时触发
                performSearch()
            }
            .navigationTitle("水果搜索")
        }
    }

    func performSearch() {
        // 搜索逻辑仅在拼音输入完成后触发
        if !searchText.isEmpty {
            searchResults = items.filter { $0.contains(searchText) }
        } else {
            searchResults = []
        }
    }
}

运行效果 #

  1. 在搜索框中输入拼音,不会触发搜索逻辑;
  2. 只有当输入完成并按下键盘上的 “搜索” 按钮(或回车键)时,才会触发搜索;
  3. 搜索结果会通过 performSearch() 更新。

优点 #

  • 不再需要监测输入的拼音过程,只有用户明确完成一个输入动作时才处理。

方法 2:监听 text 的最终完成状态 #

当用户输入文字时,我们可以监听输入框内容的变化,但使用一个延迟机制来避免中途的拼音触发搜索逻辑。

示例代码:用 Debounce 实现延迟触发 #

import SwiftUI
import Combine

struct ContentView: View {
    @State private var searchText = ""
    @State private var finalSearchText = ""
    @State private var searchResults: [String] = []

    let items = ["苹果", "香蕉", "橘子", "葡萄", "草莓", "西瓜", "桃子"]
    private var debounceTimer: AnyCancellable?

    var body: some View {
        NavigationView {
            List(searchResults, id: \.self) { item in
                Text(item)
            }
            .searchable(text: $searchText, prompt: "输入内容搜索")
            .onChange(of: searchText) { newValue in
                debounceSearch(with: newValue)
            }
            .navigationTitle("搜索(防抖)")
        }
    }

    func debounceSearch(with text: String) {
        // 取消之前的延迟任务
        debounceTimer?.cancel()
        // 设置一个延迟触发任务(例如 0.5 秒)
        debounceTimer = Just(text)
            .delay(for: .milliseconds(500), scheduler: RunLoop.main) // 延迟 500 毫秒
            .sink { [weak self] value in
                self?.finalSearchText = value
                self?.performSearch()
            }
    }

    func performSearch() {
        // 过滤结果
        if !finalSearchText.isEmpty {
            searchResults = items.filter { $0.contains(finalSearchText) }
        } else {
            searchResults = []
        }
    }
}

代码解析 #

  1. debounceTimer 实现搜索延迟
    • 每次用户输入时,都会重新设置一个延迟任务(debounce)。
    • 只有在用户停止输入 500 毫秒后(模拟输入完成),搜索逻辑才会启动。
  2. 更新 finalSearchText 并搜索
    • 拼音中间的部分(未完成的输入)不会直接触发搜索逻辑。
    • 一旦拼音完成,延迟机制触发,会用最终的字符串进行搜索。

运行效果 #

  1. 中文输入时(如输入拼音的中间状态),不会启动搜索逻辑。
  2. 停止输入后 500 毫秒,输入框的最终内容会被更新,并触发搜索。

优点 #

  • 非常适合输入实时展示搜索结果的场景(输入完成后短时间内触发搜索)。
  • 用户体验流畅,不会因拼音输入卡顿。

方法 3:通过 TextField 修改 “完成输入” 行为 #

如果需要更细粒度地控制(例如在完全避免拼音被当作搜索内容时),可以使用 TextField.onEditingChanged.onCommit 来监听用户完成输入的时刻。

示例代码:结合输入完成事件 #

import SwiftUI

struct ContentView: View {
    @State private var searchText = ""
    @State private var searchResults: [String] = []

    let items = ["苹果", "香蕉", "橘子", "葡萄", "草莓"]

    var body: some View {
        NavigationView {
            VStack {
                HStack {
                    TextField("输入进行搜索", text: $searchText, onEditingChanged: { isEditing in
                        if !isEditing {
                            performSearch() // 当输入完成时触发搜索
                        }
                    })
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .padding()
                }
                List(searchResults, id: \.self) { item in
                    Text(item)
                }
            }
            .navigationTitle("中文搜索")
        }
    }

    func performSearch() {
        // 只在输入完成后搜索
        if !searchText.isEmpty {
            searchResults = items.filter { $0.contains(searchText) }
        } else {
            searchResults = []
        }
    }
}

运行效果 #

  • 在输入完成时(中文拼音转换后),触发 performSearch
  • 键入拼音字符过程中不会触发多余的事件。

优点 #

  • 控制精确,只有结束编辑时才真正处理逻辑。

总结:选择合适方案 #

针对中文输入法实时搜索的问题,不同场景下有不同的最佳方案:

  1. 即时搜索,但禁止拼音中间状态触发

    • 使用 延迟触发(Debounce) 的方法(方法 2)。
  2. 用户完成输入后再触发搜索

    • 使用 onSubmit(方法 1),按下搜索键后触发。
  3. 更细粒度控制结束输入行为

    • 使用 TextFieldonEditingChangedonCommit(方法 3)。

根据功能需求选择适当的方案,确保中文输入能够正确处理拼音步骤,不引入额外的性能开销和错误搜索体验。

本文共 6958 字,上次修改于 Jan 18, 2025