I have created filter UI and filter logic for the responses as requested in Issue 122.
I tried to take a dynamic approach without affecting existing logic and models; filter models are created by predetermined categories (code and method in current version) and from observed request model.
I did use popover view that anchored to filter button on SearchBar. View height is dynamic and consists of 2 level:
You can access from this Figma link for the design.
Level 1 Categories
Categories for the filter types are listed in TableView and navigates on tap. In this example code and method are categories
Level 2 Types (Values)
Types for the corresponding category are listed in TableView and updates collection of FilterModel on Storage for the selected type. Multiple types can be selected.
Quantity of the requests that conforms to corresponding types are displayed and dynamically updated.
Logic
You can access this logic flow with this Figma link
Model
Filters are represented with FilterModel.
mainly consist of value, quantity, category and selectionStatus; name variable can be string desc for value variable.
Category and value variables are used for comparing two types.
Value is any Equatable type and filter types under same category are expected to share the same Equatable type.
On ViewModel array of FilterModel gets stored in the FilterCollectionModel.
Parsing and filtered methods handled in this model.
Flow
A new FilterModel gets created with a didSet observer for Requests. Created array of filterModel gets saved or updated then filterChange notification gets posted.
On ViewController, filterChange notification being observed. On post, FilterCollectionModel gets created from filter data and existing request data gets updated via filter data and search text.
When user selects a filter type from FilterTypeViewController, cell UI gets updated on didSelectRowAt and after animation update gets requestested for selected filter, filter selectionStatus gets updated with "updateFilterModel" method.
How to add new category and types for category?
While explaining the logic, I will give example with scheme parameter of the requests.
Category
Category is Enum type with String desc for each case. We have to add new case to Enum and case array on FilterViewController.
FilterModel.swift
enum FilterCategory: CaseIterable{
case code, method, scheme
var description: String{
switch self {
case .code:
return "Code"
case .method:
return "Method"
case .scheme:
return "Scheme"
}
}
}
FilterViewController.swift
class FilterViewController: UIViewController {
private static var cellHeight: CGFloat = 50
private var filterModel: [FilterModel] = []
private var filterCategories: [FilterCategory] = [.code, .method, .scheme]
Final result for adding category:
Category Types
For category types saving, updating and UI methods are handled dynamically, but we have add conditions and Equatable types for filter model creation and data filtering methods.
Scheme is a String type so we have to add a [String: Int] dictionary to "createFilterModel" method on Storage. String key is for the value of the filter type and Int value of dict represents the quantity of requests that conforms to the filter type.
We have to use request.scheme parameter of the request and add condition with optional checking (scheme can be nil unlike method and code values of the request) to create or update dict value.
Then we have to create FilterModel with scheme category, quantity (as value of dict) and scheme value (as key of dict) and add it to filter array to save.
Final result of "createFilterModel" method:
func createFilterModel(from requests: [RequestModel]){
var codeDict: [Int: Int] = [:]
var methodDict: [String: Int] = [:]
var schemeDict: [String: Int] = [:]
var filterArray: [FilterModel] = []
for request in requests {
if request.code == 0{
continue
}
if codeDict[request.code] != nil{
codeDict[request.code]! += 1
} else {
codeDict[request.code] = 1
}
if methodDict[request.method] != nil {
methodDict[request.method]! += 1
} else {
methodDict[request.method] = 1
}
if let scheme = request.scheme, schemeDict[scheme] != nil{
schemeDict[scheme]! += 1
} else if let scheme = request.scheme{
schemeDict[scheme] = 1
}
}
for codeKey in codeDict.keys{
filterArray.append(.init(filterCategory: .code, value: codeKey, count: codeDict[codeKey] ?? 1))
}
for methodKey in methodDict.keys{
filterArray.append(.init(filterCategory: .method, value: methodKey, count: methodDict[methodKey] ?? 1))
}
for schemeKey in schemeDict.keys{
filterArray.append(.init(filterCategory: .scheme, value: schemeKey, count: schemeDict[schemeKey] ?? 1))
}
Storage.shared.saveFilters(filters: filterArray)
}
We can see the scheme types in FilterTypeViewController but selecting type doesn't affect the logic yet.
To effect the logic we have to:
Add selected scheme variable to FilterCollectionModel as array of String type. There is already a generic type for getting selected collection of FilterModel values for given category named "getSelectedFilterCollection(by:)"
Final result of FilterCollectionModel:
open class FilterCollectionModel{
var filterCollection: [FilterModel]
var selectedFilterCollection: [FilterModel]{
filterCollection.filter{ filterModel -> Bool in
filterModel.selectionStatus == .selected
}
}
var selectedMethodFilterCollection: [String]{
getSelectedFilterCollection(by: .method) as! [String]
}
var selectedCodeFilterCollection: [Int]{
getSelectedFilterCollection(by: .code) as! [Int]
}
var selectedSchemeFilterCollection: [String]{
getSelectedFilterCollection(by: .scheme) as! [String]
}
init(filterCollection: [FilterModel]) {
self.filterCollection = filterCollection
}
/// Returns collection of any Equatable from current filter collection that matches with given filter category.
/// - Parameter filterCategory: ``FilterCategory`` type that filter collection element must conform.
/// - Returns: Filtered filter colelction values as array.
func getFilterCollection(by filterCategory: FilterCategory) -> [any Equatable]{
return filterCollection.filter{ filterModel -> Bool in
filterModel.filterCategory == filterCategory
}.map{ filterModel -> any Equatable in
filterModel.value
}
}
/// Returns collection of any Equatable from current filter collection that matches with given filter category and selected status.
/// - Parameter filterCategory: ``FilterCategory`` type that filter collection element must conform.
/// - Returns: Filtered filter colelction values as array.
private func getSelectedFilterCollection(by filterCategory: FilterCategory) -> [any Equatable]{
return filterCollection.filter{ filterModel -> Bool in
filterModel.filterCategory == filterCategory && filterModel.selectionStatus == .selected
}.map{ filterModel -> any Equatable in
filterModel.value
}
}
}
new we can add scheme conditions to filterByFilterModels on RequestsViewController handle filtering of the data.
add new variable named "schemeArray" that stores values of scheme FilterModel array. If selected FilterModels are exists for scheme store those values in variable otherwise store all of the array of scheme FilterModel values.
Add condition in .filter function for to check if request.scheme values is in the array of scheme values that we created.
Final result of "filterByFilterModels"
func filterByFilterModels(filterCollection: FilterCollectionModel?, requests: [RequestModel]) -> [RequestModel]{
guard let filterCollection = filterCollection else{
return requests
}
if filterCollection.selectedFilterCollection.isEmpty{
return requests
}
// If no selected filter exists for category, contain all of the category filters.
let codeArray: [Int] = filterCollection.selectedCodeFilterCollection.isEmpty ? filterCollection.getFilterCollection(by: .code) as! [Int] : filterCollection.selectedCodeFilterCollection
let methodArray: [String] = filterCollection.selectedMethodFilterCollection.isEmpty ? filterCollection.getFilterCollection(by: .method) as! [String] : filterCollection.selectedMethodFilterCollection
let schemeArray: [String] = filterCollection.selectedSchemeFilterCollection.isEmpty ? filterCollection.getFilterCollection(by: .scheme) as! [String] : filterCollection.selectedSchemeFilterCollection
return requests.filter{ request -> Bool in
methodArray.contains(request.method) && codeArray.contains(request.code) && schemeArray.contains(request.scheme ?? "")
}
}
P.S. I have added some lightweight extensions to library classes to ease the development of the UI part.
Hello
I have created filter UI and filter logic for the responses as requested in Issue 122.
I tried to take a dynamic approach without affecting existing logic and models; filter models are created by predetermined categories (code and method in current version) and from observed request model.
I am looking forward to your feedback.
UI
https://user-images.githubusercontent.com/49282941/224537491-c9842ec7-3825-4fa7-9355-86292180dfea.mp4
I did use popover view that anchored to filter button on SearchBar. View height is dynamic and consists of 2 level:
You can access from this Figma link for the design.
Level 1 Categories
Level 2 Types (Values)
Logic
You can access this logic flow with this Figma link
Model
Filters are represented with FilterModel.
On ViewModel array of FilterModel gets stored in the FilterCollectionModel.
Flow
A new FilterModel gets created with a didSet observer for Requests. Created array of filterModel gets saved or updated then filterChange notification gets posted.
On ViewController, filterChange notification being observed. On post, FilterCollectionModel gets created from filter data and existing request data gets updated via filter data and search text.
When user selects a filter type from FilterTypeViewController, cell UI gets updated on didSelectRowAt and after animation update gets requestested for selected filter, filter selectionStatus gets updated with "updateFilterModel" method.
How to add new category and types for category?
While explaining the logic, I will give example with scheme parameter of the requests.
Category
Category is Enum type with String desc for each case. We have to add new case to Enum and case array on FilterViewController. FilterModel.swift
FilterViewController.swift
Final result for adding category:
Category Types
For category types saving, updating and UI methods are handled dynamically, but we have add conditions and Equatable types for filter model creation and data filtering methods.
We can see the scheme types in FilterTypeViewController but selecting type doesn't affect the logic yet.
To effect the logic we have to:
Final result of FilterCollectionModel:
new we can add scheme conditions to filterByFilterModels on RequestsViewController handle filtering of the data.
Final result of "filterByFilterModels"
P.S. I have added some lightweight extensions to library classes to ease the development of the UI part.