Swiftui:使用自定义搜索栏过滤列表

发布于 2025-02-07 04:49:24 字数 3211 浏览 1 评论 0原文

我已经创建了一个使用textfield的自定义搜索栏,并尝试使用它过滤我的列表。

[![Enter映像说明在此处] [1]] [1]

我的列表由a到z部分组成。现在,我在搜索返回nil时要过滤部分和列表有问题。这是我的代码:

@State private var searchText = ""
    let lists = loadGlossary().keys.sorted()
    let glossary = loadGlossary()

var body: some View {
        
        VStack(spacing: -10) {
            
            //Navigation bar
            NavigationBarView(title: "Glossary", action: {}, titleColor: .primary, closeColor: .primary.opacity(0.5))
                .padding()
            
            //Search Bar
            TextField("Search", text: $searchText)
                .padding()
                .frame(height: 45)
                .background(.gray.opacity(0.1))
                .clipShape(RoundedRectangle(cornerRadius: 10))
                .padding()
                .overlay {
                    HStack {
                        Spacer()
                        Button {
                            searchText = ""
                        } label: {
                            Label("clear", systemImage: "xmark.circle.fill")
                                .foregroundColor(.gray)
                                .opacity(searchText.isEmpty ? 0 : 1)
                                .padding(30)
                        }
                        .labelStyle(.iconOnly)
                    }
                }
            
            //List
            List {
                ForEach(Array(wordDict.keys).sorted(by: <), id: \.self) { character in
                    ///Section
                    Section(header: Text("\(character)").font(Font.system(size:18, weight: .bold, design: .serif)).foregroundColor(.primary)) {
                        ///List
                        ForEach(wordDict[character] ?? [""], id: \.self) { word in
                            VStack(alignment: .leading, spacing: 5) {
                                Text(word)
                                    .font(.system(size: 17, weight: .bold, design: .serif))
                                Divider()
                                Text(getDescription(word))
                                    .foregroundColor(.secondary)
                            }
                        }
                        .padding()
                    }
                }
                .listRowSeparator(.hidden)
            }
            .listStyle(.plain)
            .padding(.top)
        }
    }

如果您帮助正确过滤部分和项目,我将很感激。

编辑: 更改:

@State private var wordDict: [String:[String]] = [:]

    func loadData() -> [String:[String]] {
        let letters = Set(lists.compactMap( { $0.first } ))
        var dict: [String:[String]] = [:]
        for letter in letters {
            dict[String(letter)] = lists.filter( { $0.first == letter } ).sorted()
        }
        return dict
    }

并在此处加载第一个数据:

.onAppear {
        wordDict = loadData()
    }

我添加

TextField("Search", text: $searchText)
                    .onChange(of: searchText) {
                        wordDict = getFilteredWords(query: $0)
                    }

对于textfield

I have created a custom search bar with TextField and trying to filter my List with it.

[![enter image description here][1]][1]

My list consists of A to Z sections. Now I have a problem with filtering both section and list while searching which is returning nil. Here is my code:

@State private var searchText = ""
    let lists = loadGlossary().keys.sorted()
    let glossary = loadGlossary()

var body: some View {
        
        VStack(spacing: -10) {
            
            //Navigation bar
            NavigationBarView(title: "Glossary", action: {}, titleColor: .primary, closeColor: .primary.opacity(0.5))
                .padding()
            
            //Search Bar
            TextField("Search", text: $searchText)
                .padding()
                .frame(height: 45)
                .background(.gray.opacity(0.1))
                .clipShape(RoundedRectangle(cornerRadius: 10))
                .padding()
                .overlay {
                    HStack {
                        Spacer()
                        Button {
                            searchText = ""
                        } label: {
                            Label("clear", systemImage: "xmark.circle.fill")
                                .foregroundColor(.gray)
                                .opacity(searchText.isEmpty ? 0 : 1)
                                .padding(30)
                        }
                        .labelStyle(.iconOnly)
                    }
                }
            
            //List
            List {
                ForEach(Array(wordDict.keys).sorted(by: <), id: \.self) { character in
                    ///Section
                    Section(header: Text("\(character)").font(Font.system(size:18, weight: .bold, design: .serif)).foregroundColor(.primary)) {
                        ///List
                        ForEach(wordDict[character] ?? [""], id: \.self) { word in
                            VStack(alignment: .leading, spacing: 5) {
                                Text(word)
                                    .font(.system(size: 17, weight: .bold, design: .serif))
                                Divider()
                                Text(getDescription(word))
                                    .foregroundColor(.secondary)
                            }
                        }
                        .padding()
                    }
                }
                .listRowSeparator(.hidden)
            }
            .listStyle(.plain)
            .padding(.top)
        }
    }

I would be grateful if you help to properly filter sections and items.

EDITED:
Changes:

@State private var wordDict: [String:[String]] = [:]

    func loadData() -> [String:[String]] {
        let letters = Set(lists.compactMap( { $0.first } ))
        var dict: [String:[String]] = [:]
        for letter in letters {
            dict[String(letter)] = lists.filter( { $0.first == letter } ).sorted()
        }
        return dict
    }

and load the first data here:

.onAppear {
        wordDict = loadData()
    }

and for TextField I have added:

TextField("Search", text: $searchText)
                    .onChange(of: searchText) {
                        wordDict = getFilteredWords(query: $0)
                    }

But when I clear the textfield of backspace the List won't update anymore

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

笑咖 2025-02-14 04:49:24

一种方法是您可以这样进行过滤:

  1. 创建一个函数,该函数返回过滤列表,
  2. 将函数的结果渲染到屏幕上。
  3. 恢复变化的结果。

这是您在以下假设下的特定需求的功能:

  1. 您没有向我们展示WordDict的结构,因此从您的代码片段中,我认为它是这样的结构:[string:[string]] 。 (例如“ a”:[“ Apple”,“ atari”])
  2. 您只想显示条目包含searchText的部分的部分。如果您想具有更高级的查询功能,该方法将相同,您只需要对下面的功能进行较小的调整。
func getFilteredWords(query: String) -> [String: [String]] {
    wordDict.mapValues({ entry in
        entry.filter({ word in word.lowercased().contains(query.lowercased()) })
    }).filter({ entry in !entry.value.isEmpty})
}

该函数有什么作用?

该函数采用searchText,并将您的WordDict的所有条目与它进行比较。如果WordDict条目包括searchText的一部分,则保留。否则将其丢弃,因此后来未呈现。比较是病例不敏感的。之后,所有没有任何条目的类别也被丢弃,这些类别也可以防止您的搜索显示所有类别,即使它们不包含任何条目。

如何使用它?

  1. 现在您已经将上述功能添加到视图结构。
  2. WordDict在您的List> List视图中使用getFilteredWords(QUERY:searchText)

One approach would be that you could do the filtering like this:

  1. Create a function which returns the filtered list
  2. Render the results of the function to the screen.
  3. Rerender the results on change.

Here is the function for your specific need under the following assumptions:

  1. You didn't show us the structure of wordDict, so from your code snippet I assume that it is structured like this: [String: [String]]. (e.g. "A": ["apple", "Atari"])
  2. You only want to show the sections where the entries contain part of the searchText. If you want to have more advanced querying capabilities the method would be the same, you just have to make minor adjustments to the function below.
func getFilteredWords(query: String) -> [String: [String]] {
    wordDict.mapValues({ entry in
        entry.filter({ word in word.lowercased().contains(query.lowercased()) })
    }).filter({ entry in !entry.value.isEmpty})
}

What does the function do?

The function takes the searchText and compares all of the entries of your wordDict with it. If the wordDict entry includes the part of searchText somewhere, it is kept. Otherwise it is discarded and therefore later not rendered. The comparison is case insensitive. After that, all of the categories without any entries are also discarded which prevents that your search still shows all categories even if they don't contain any entries.

How to use it?

  1. Now you have do add the function above to your view struct.
  2. Replace the wordDict instances in your ListView with getFilteredWords(query: searchText).
焚却相思 2025-02-14 04:49:24

这是一种可能

  1. 具有过滤项目的动态属性(也可以在视图模型中)
@State private var filteredWords: [String: String] = [:]
  1. 迭代ui,而不是直接
ForEach(Array(filteredWords.keys).sorted(by: <), id: \.self) { character in
    ///Section
    Section(header: Text("\(character)").font(Font.system(size:18, weight: .bold, design: .serif)).foregroundColor(.primary)) {
        ///List
        ForEach(filteredWords[character] ?? [""], id: \.self) { word in

  1. 在初始加载存储存储上加载的单词加载的单词,并将其分配给过滤
func load() {
   // ...
   self.wordDict = loadedData
   // assuming possible re-load apply existed filter
   self.filteredWords = applyFilter(data: loadedData, pattern: self.searchText)
}
  1. 后的applated apply extrict effter to搜索字段更改
TextField("Search", text: $searchText)
  .onChange(of: searchText) {
     self.filteredWords = applyFilter(data: self.wordDict, pattern: $0)
  }

或使用视图模型和发布者进行访问(示例

Here is a possible way

  1. have a dynamic property of filtered items (it can be also in view model)
@State private var filteredWords: [String: String] = [:]
  1. iterate UI over that filtered items instead of loaded words directly
ForEach(Array(filteredWords.keys).sorted(by: <), id: \.self) { character in
    ///Section
    Section(header: Text("\(character)").font(Font.system(size:18, weight: .bold, design: .serif)).foregroundColor(.primary)) {
        ///List
        ForEach(filteredWords[character] ?? [""], id: \.self) { word in

  1. on initial load store loaded words and assign them to filtered
func load() {
   // ...
   self.wordDict = loadedData
   // assuming possible re-load apply existed filter
   self.filteredWords = applyFilter(data: loadedData, pattern: self.searchText)
}
  1. apply filter to originally loaded words on search field changes
TextField("Search", text: $searchText)
  .onChange(of: searchText) {
     self.filteredWords = applyFilter(data: self.wordDict, pattern: $0)
  }

or with view model and publishers to debounce (example)

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文