I was trying to implement a filter to my List
in my app. Actually I just used the same code when I was searching. It works really well but after the filtering, when I start to search with the filtered results. It gives me non-filtered results. I'm going to explain with screenshots:
This is the screenshot of filtered result. I do the filtering with "Filter" button on the right side. I filtered the results only contains "Halkalı" and the results are true.
And this the screenshot of the situation that I have when I was filtering. When I start to search with filtered results, it destroys the filtered results. I didn't put "Halkalı" value for "EN 10305-5" and "DIN Norm Sample" but it shows that too. And when I cancel the search, it shows all the results without filter.
Actually, I tried to do something but I failed. I would like to have your help as a newbie in SwiftUI. I'm going to put my codes below for inspection. Thank you all in advance.
Codes in Main Page:
// Created by Caner Altuner on 31.07.2023.
//
import SwiftUI
struct NormCatalogView: View {
//To access to the model files
@State private var normsPlant: [NormModel] = allNorms
@State private var normsSearch: [NormModel] = allNorms
//The variable for search
@State private var searchText: String = ""
@State private var filterPlant: String = ""
//Boolean for the alert
@State private var showAlert = false
var body: some View {
NavigationView {
VStack {
List {
//ForEach ile bu işlemi daha basit bir yolla structlar içinde yer alan elemanları liste içine getirebiliyoruz
ForEach(normsSearch) {norm in
//Normları bölümlere göre ayırlmak için Section elementini kullanıyoruz
Section(header: Text(norm.title)) {
ForEach(norm.elements) { element in
//Sonraki sayfaya geçişi sağlayan bölüm
NavigationLink(destination: NormDetailsView(chosenNormElement: element)) {
//İsimleri listeye aktardığımız yer
Text(element.name)
}
}
}
}
//En üstteki başlığın belirlenmesi ve stilinin ayarlanması
}.toolbar(content: {
ToolbarItem(placement: .navigationBarTrailing) {
Button("Filter") {
showAlert = true
}.alert("Filter by Production Plant", isPresented: $showAlert) {
Button("Filter as All Production Plants", role: .cancel) {
filterPlant = "All"
filterByPlant(filterPlant)
}
Button("Filter as Halkalı") {
filterPlant = "Halkalı"
filterByPlant(filterPlant)
}
Button("Filter as Gemlik") {
filterPlant = "Gemlik"
filterByPlant(filterPlant)
}
Button("Filter as Vobarno") {
filterPlant = "Vobarno"
filterByPlant(filterPlant)
}
Button("Filter as Houston") {
filterPlant = "Houston"
filterByPlant(filterPlant)
}
} message: {
Text("How would you like to filter your search?")
}
}
}).navigationBarTitle("Borkatalog").navigationBarTitleDisplayMode(.inline)
.searchable(text: $searchText, prompt: "Search in Norms").onChange(of: searchText) { search in
//Aramanın gerçekleştiği bölüm
searchNorms(search)
}
}
}
}
//Search Function
func searchNorms(_ searchText: String) {
if searchText == "" {
normsSearch = allNorms
return
}
var temp = allNorms
for i in temp.indices {
temp[i].elements = temp[i].elements.filter { $0.name.localizedStandardContains(searchText)
}
}
normsSearch = temp.filter { !$0.elements.isEmpty }
}
//Filter by Plant
func filterByPlant(_ filterPlant: String) {
if filterPlant == "All" {
normsPlant = allNorms
return
}
var temp = allNorms
for i in temp.indices {
temp[i].elements = temp[i].elements.filter { $0.productionPlant.localizedStandardContains(filterPlant) }
}
normsPlant = temp.filter { !$0.elements.isEmpty }
print(normsPlant)
}
}
struct NormCatalogView_Previews: PreviewProvider {
static var previews: some View {
NormCatalogView()
}
}
Norms.swift (Files that contains structs):
struct NormModel : Identifiable {
var id = UUID()
var title : String
var elements : [NormElements]
}
struct NormElements : Identifiable {
var id = UUID()
var name : String
var productionPlant : String
}
let en103052 = NormElements(name: "EN 10305-2", productionPlant: "Halkalı")
let en103053 = NormElements(name: "EN 10305-3", productionPlant: "Halkalı, Gemlik")
let en103055 = NormElements(name: "EN 10305-5", productionPlant: "Gemlik, Vobarno")
let ornekDinNorm = NormElements(name: "DIN Norm Sample", productionPlant: "Vobarno")
let enNorms = NormModel(title: "EN Normları", elements:[en103052, en103053, en103055])
let dinNorms = NormModel(title: "DIN Normları", elements: [ornekDinNorm])
let allNorms = [enNorms, dinNorms]
You are nearly there! searchNorms
should do a further filter on normsPlant
, instead of filtering on allNorms
.
func searchNorms(_ searchText: String) {
if searchText == "" {
normsSearch = normsPlant // <---
return
}
var temp = normsPlant // <---
for i in temp.indices {
temp[i].elements = temp[i].elements.filter { $0.name.localizedStandardContains(searchText)
}
}
normsSearch = temp.filter { !$0.elements.isEmpty }
}
You should also update what is currently displayed when you filterByPlant
, by updating normsSearch
:
func filterByPlant(_ filterPlant: String) {
defer {
searchText = ""
normsSearch = normsPlant
}
// the rest of filterByPlant...
}
Actually, I'd suggest having a single search function, and hence, a single @State
array. The search function would take into account both searchText
and filterPlant
.
@State private var normsSearch: [NormModel] = allNorms
@State private var searchText: String = ""
@State private var filterPlant: String = "All"
...
.searchable(text: $searchText, prompt: "Search in Norms")
.onChange(of: searchText) { _ in
searchNorms()
}
// Do this to avoid calling the search function after every assignment to filterPlant
.onChange(of: filterPlant) { _ in
searchNorms()
}
...
// combining the old searchNorms and filterByPlant
func searchNorms() {
var temp = allNorms
for i in temp.indices {
temp[i].elements = temp[i].elements.filter {
(searchText == "" || $0.name.localizedStandardContains(searchText)) &&
(filterPlant == "All" || $0.productionPlant.localizedStandardContains(filterPlant))
}
}
normsSearch = temp.filter { !$0.elements.isEmpty }
}