Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhancement/issue 122 filter for requests #133

Open
wants to merge 9 commits into
base: master
Choose a base branch
from

Conversation

Intout
Copy link

@Intout Intout commented Mar 12, 2023

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

Simulator.Screen.Recording.-.iPhone.14.Pro.-.2023-03-12.at.12.58.23.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

  • 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

Wormholy Filter 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:
Scheme Screenshot

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.

Scheme FilterTypeViewController Screenshot

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.

Mert Tecimen added 9 commits February 15, 2023 22:21
- Added Interface to Resuable views for static reuse identifier.
- Added Filter View and filter category cells.
- Changed filter data type to array that contains filters, category info stored in the filter struct.
- Added a method that gets specified filter types and creates a filter model.
- FilterModel conformed Comparable protocol.
- Added filterModels and filterSave method to Storage.
- Added notification observer to filter updates.
- FilterType view and Request View controller observes filter updated with notficationCenter.
- Sorted filter names before display.
- Added new model that contains filter models as collection and certain queries for filter collection.
- Changed existing filter request method to filter by search text and selected filter models.
@Intout
Copy link
Author

Intout commented Mar 15, 2023

What do you think @pmusolino @gmoraleda?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

1 participant