Filter array by String

Hi,
In the following code, I filter an array, but it only works with the first letter of the name. For Example, when I write "A" in the TextField, the filter shows me "Apple". But if I write "Ap", the filter shows me nothing. Can you help me?

Code Block Swift
List{
       
      ForEach(groupedArray.keys.sorted().filter{
        text.isEmpty || $0.contains(text)
      }, id: \.self){key in
        Section(header: Text(key)) {
          ForEach(groupedArray[key]!, id: \.self){website in
            NavigationLink(
              destination: WebView(website: website, url: URL(string: website.url)),
              label: {
                Text(website.name)
                Spacer()
                if website.image != ""{
                  RemoteImage(url: website.image).frame(width: 25, height: 25, alignment: .center)
                   
                }else{
                  Loader()
                }
              })
          }
        }
      }
    }


Answered by OOPer in 653832022
In your way of creating groupedArray, its keys contains section titles.
["A", "B", ...]

If you want to filter rows by name, you need to move filter to the second ForEach:
Code Block
ForEach(groupedArray.keys.sorted(), id: \.self){key in //<-
Section(header: Text(key)) {
ForEach(groupedArray[key]!.filter{ //<-
text.isEmpty || $0.name.contains(text) //<-
}, id: \.self){website in //<-
NavigationLink(
destination: WebView(website: website, url: URL(string: website.url)),
label: {
Text(website.name)
Spacer()
if website.image != ""{
RemoteImage(url: website.image).frame(width: 25, height: 25, alignment: .center)
}else{
Loader()
}
})
}
}
}


You may want to remove empty sections, but that needs a little more complex code.
Is groupedArray a dictionary ?
Arrays have no keys property.

Could you show groupedArray content ?
And also show the complete code.

As you don't tell, I have to guess.
Are you sure you want to filter on keys and not values ?
Yes, groupArray is a dictionary. The complete code is:

Code Block Swift
struct ResultView: View {
  @State var websites : [Website]
  @Binding var text : String
  @State var groupedArray : [String: [Website]] = [:]
  var body: some View {
   
     
    List{
       
      ForEach(groupedArray.keys.sorted().filter{
        text.isEmpty || $0.contains(text)
      }, id: \.self){key in
        Section(header: Text(key)) {
          ForEach(groupedArray[key]!, id: \.self){website in
            NavigationLink(
              destination: WebView(website: website, url: URL(string: website.url)),
              label: {
                Text(website.name)
                Spacer()
                if website.image != ""{
                  RemoteImage(url: website.image).frame(width: 25, height: 25, alignment: .center)
                   
                }else{
                  Loader()
                }
              })
          }
        }
      }
    }.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height, alignment: .center)
       .navigationBarTitle("Results")
     
    .colorScheme(.light)
    .onAppear {
       
       
       
       
      groupedArray = Dictionary(
        grouping: websites,
        by: {$0.name.first?.uppercased() ?? ""}
      ).mapValues{$0.sorted()}
       
       
       
    }
  }
   
   
}
struct Loader : UIViewRepresentable{
  func makeUIView(context: UIViewRepresentableContext<Loader>) -> UIActivityIndicatorView {
    let indicator = UIActivityIndicatorView(style: .large)
    indicator.startAnimating()
    return indicator
  }
   
  func updateUIView(_ uiView: UIActivityIndicatorView, context: UIViewRepresentableContext<Loader>) {
     
  }
}

I have this method from a tutorial, but there wasn't the filter method so I wrote it behind the keys in the ForEach. I don't know if thats right. The content is from a database but the array looks like this:
Code Block Swift
@State var websites = [Website] = [Website(url: "https://www.apple.com/", name: "Apple", image: "Apple.png" , calls: 0), Website(url: "https://www.amazon.com/", name: "Amazon", image: "Amazon.png" , calls: 0)]


Is that all you need to know?
Accepted Answer
In your way of creating groupedArray, its keys contains section titles.
["A", "B", ...]

If you want to filter rows by name, you need to move filter to the second ForEach:
Code Block
ForEach(groupedArray.keys.sorted(), id: \.self){key in //<-
Section(header: Text(key)) {
ForEach(groupedArray[key]!.filter{ //<-
text.isEmpty || $0.name.contains(text) //<-
}, id: \.self){website in //<-
NavigationLink(
destination: WebView(website: website, url: URL(string: website.url)),
label: {
Text(website.name)
Spacer()
if website.image != ""{
RemoteImage(url: website.image).frame(width: 25, height: 25, alignment: .center)
}else{
Loader()
}
})
}
}
}


You may want to remove empty sections, but that needs a little more complex code.
Hi, Thank you @OOPer. Do you know how to write the little more complex code?

Do you know how to write the little more complex code?

You can try something like this:
Code Block
struct ResultView: View {
@State var websites : [Website]
@Binding var text : String
@State var groupedArray : [String: [Website]] = [:]
var body: some View {
List{
ForEach(filteredKeys(byName: text), id: \.self){key in //<-
Section(header: Text(key)) {
ForEach(groupedArray[key]!.filter{
text.isEmpty $0.name.contains(text)
}, id: \.self) {website in
NavigationLink(
destination: WebView(website: website, url: URL(string: website.url)),
label: {
Text(website.name)
Spacer()
if website.image != ""{
RemoteImage(url: website.image).frame(width: 25, height: 25, alignment: .center)
}else{
Loader()
}
})
}
}
}
}.frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height, alignment: .center)
.navigationBarTitle("Results")
.colorScheme(.light)
.onAppear {
groupedArray = Dictionary(
grouping: websites,
by: {$0.name.first?.uppercased() ?? ""}
).mapValues{$0.sorted()}
}
}
private func filteredKeys(byName text: String) -> [String] {
return groupedArray.filter {(key, value) in
value.contains{
text.isEmpty $0.name.contains(text)
}
}.map {(key, _) in key}.sorted()
}
}

(Sorry, not tested and may need some fixes. And some || are not shown as always, you need to find where to fill in.)
Perfect, it works! Thank you for helping
Filter array by String
 
 
Q