Reputation: 6922
I am trying to sort an array of Custom Structs by different property values easily.
struct Customer: Comparable, Equatable {
var name: String
var isActive: Bool
var outstandingAmount: Int
var customerGroup: String
}
var customerlist: [Customer] // This is downloaded from our backend.
I want to be able to sort the customerlist array in the UI by all the field values when the user selects the various icons.
I have tried a few methods to sort it using a switch statement - however I am told that the correct way to do this is using Sort Descriptors( which appear to be Objective-C based and mean I need to convert my array to an NSArray. ) I keep getting errors when I try this approach with native Swift structs.
What is the best way to allow the user to sort the above array using Swift?
Eg: below seems very verbose!
func sortCustomers(sortField:ColumnOrder, targetArray:[Customer]) -> [Customer] { //Column Order is the enum where I have specified the different possible sort orders
var result = [Customer]()
switch sortField {
case .name:
result = targetArray.sorted(by: { (cust0: Customer, cust1: Customer) -> Bool in
return cust0.name > cust1.name
})
case .isActive:
result = targetArray.sorted(by: { (cust0: Customer, cust1: Customer) -> Bool in
return cust0.isActive > cust1.isActive
})
case .outstandingAmount:
result = targetArray.sorted(by: { (cust0: Customer, cust1: Customer) -> Bool in
return cust0.outstandingAmount > cust1.outstandingAmount
})
case .customerGroup:
result = targetArray.sorted(by: { (cust0: Customer, cust1: Customer) -> Bool in
return cust0.customerGroup > cust1.customerGroup
})
}
return result
}
Upvotes: 0
Views: 899
Reputation: 42598
I re-packaged the verbose solution to make something nicer. I added a property to ColumnOrder
that returns a ordering closure.
struct Customer {
var name: String
var isActive: Bool
var outstandingAmount: Int
var customerGroup: String
}
enum ColumnOrder {
case name
case isActive
case outstandingAmount
case customerGroup
var ordering: (Customer, Customer) -> Bool {
switch self {
case .name: return { $0.name > $1.name }
case .isActive: return { $0.isActive && !$1.isActive }
case .outstandingAmount: return { $0.outstandingAmount > $1.outstandingAmount}
case .customerGroup: return { $0.customerGroup > $1.customerGroup }
}
}
}
Here is how it's used:
let sortedCustomers = customers.sorted(by: ColumnOrder.name.ordering)
Next, I extended Sequence
to make calling it from an array look good.
extension Sequence where Element == Customer {
func sorted(by columnOrder: ColumnOrder) -> [Element] {
return sorted(by: columnOrder.ordering)
}
}
Final result:
let sortedCustomers = customers.sorted(by: .name)
Upvotes: 2
Reputation: 7756
What I would go with, is using KeyPaths:
func sortCustomers<T: Comparable>(customers: [Customer], with itemPath: KeyPath<Customer, T>) -> [Customer] {
return customers.sorted() {
$0[keyPath: itemPath] < $1[keyPath: itemPath]
}
}
This approach avoids the need for your enum at all, and allows you to just do
let testData = [Customer(name: "aaaa", isActive: false, outstandingAmount: 1, customerGroup: "aaa"),
Customer(name: "bbbb", isActive: true, outstandingAmount: 2, customerGroup: "bbb")];
let testResultsWithName = sortCustomers(customers: testData, with: \Customer.name)
let testResultsWithActive = sortCustomers(customers: testData, with: \Customer.isActive)
// etc
Notice that I switched the >
to a <
. That is the default expectation and will result in "a" before "b", "1" before "2", etc.
Also, you need to add an extension for Bool to be comparable:
extension Bool: Comparable {
public static func <(lhs: Bool, rhs: Bool) -> Bool {
return lhs == rhs || (lhs == false && rhs == true)
}
}
To round out the approach, you can also pass in a comparison function:
func sortCustomers<T: Comparable>(customers: [Customer], comparing itemPath: KeyPath<Customer, T>, using comparitor: (T, T) -> Bool) -> [Customer] {
return customers.sorted() {
comparitor($0[keyPath: itemPath], $1[keyPath: itemPath])
}
}
let testResults = sortCustomers(customers: testData, comparing: \Customer.name, using: <)
This way you can use the normal comparison operators: (<, <=, >, >=) as well as a closure if you want custom sorting.
Upvotes: 2