In old Swift versions (Swift 5.2.x or earlier) when conforming an enumeration to Comparable
protocol you would need to declare its rawValue as Int
instead of String
otherwise it wouldn't make any sense since enumerations are not lexicographically sorted in general. In Swift 5.3 or later If you would like it to be Synthesized automatically you can't declare any rawValue type. You can check this post at Swift evolution about Synthesized Comparable conformance for enum types
Enumeration types which opt-in to a synthesized Comparable conformance
would compare according to case declaration order, with later cases
comparing greater than earlier cases. Only enum types with no
associated values and enum types with only Comparable associated
values would be eligible for synthesized conformances. The latter kind
of enums will compare by case declaration order first, and then
lexicographically by payload values. No enum types with raw values
would qualify.
Swift 5.3 or later
enum Kind: Comparable {
case movie, tv, trailer, genre, article
}
Once you have done that you can simply sort your collection using the custom sort I have pointed as duplicate for this question:
extension MutableCollection where Self: RandomAccessCollection {
mutating func sort<T: Comparable>(_ predicate: (Element) -> T, by areInIncreasingOrder: (T, T) -> Bool = (<)) {
sort { areInIncreasingOrder(predicate($0),predicate($1)) }
}
}
extension Sequence {
func sorted<T: Comparable>(_ predicate: (Element) -> T, by areInIncreasingOrder: (T,T)-> Bool = (<)) -> [Element] {
sorted { areInIncreasingOrder(predicate($0),predicate($1)) }
}
}
Playground testing:
struct Item {
let id: Int
let name: String
let kind: Kind
}
let items: [Item] = [
.init(id: 1, name: "D", kind: .tv),
.init(id: 2, name: "B", kind: .movie),
.init(id: 3, name: "F", kind: .trailer),
.init(id: 4, name: "H", kind: .genre),
.init(id: 5, name: "J", kind: .article),
.init(id: 6, name: "C", kind: .tv),
.init(id: 7, name: "A", kind: .movie),
.init(id: 8, name: "E", kind: .trailer),
.init(id: 9, name: "G", kind: .genre),
.init(id: 10, name: "I", kind: .article)]
items.sorted(\.kind) // [{id 2, name "B", movie}, {id 7, name "A", movie}, {id 1, name "D", tv}, {id 6, name "C", tv}, {id 3, name "F", trailer}, {id 8, name "E", trailer}, {id 4, name "H", genre}, {id 9, name "G", genre}, {id 5, name "J", article}, {id 10, name "I", article}]
edit/update
I don't know if there is a simpler way to accomplish this kind of sort (I would love to get some feedback into this) but You can sort your items by name, group them by kind and then transpose your items. You would need to make your enumeration CaseIterable and declare its rawValue as Int starting from zero. So add those helpers to your project:
extension Collection where Element: RandomAccessCollection, Element.Indices == Range<Int> {
func transposed() -> [[Element.Element]] {
(0..<(max(\.count)?.count ?? .zero)).map {
index in compactMap { $0.indices ~= index ? $0[index] : nil }
}
}
}
extension Sequence {
func max<T: Comparable>(_ predicate: (Element) -> T) -> Element? {
self.max(by: { predicate($0) < predicate($1) })
}
}
And then:
enum Kind: Int, CaseIterable {
case movie = 0, tv, trailer, genre, article
}
let grouped: [[Item]] = items.reduce(into: .init(repeating: [], count: Kind.allCases.count)) { result, item in
result[item.kind.rawValue].append(item)
}
let transposed = grouped.map{$0.sorted(\.name)}.transposed()
print(transposed) // [[Item(id: 7, name: "A", kind: Kind.movie), Item(id: 6, name: "C", kind: Kind.tv), Item(id: 8, name: "E", kind: Kind.trailer), Item(id: 9, name: "G", kind: Kind.genre), Item(id: 10, name: "I", kind: Kind.article)], [Item(id: 2, name: "B", kind: Kind.movie), Item(id: 1, name: "D", kind: Kind.tv), Item(id: 3, name: "F", kind: Kind.trailer), Item(id: 4, name: "H", kind: Kind.genre), Item(id: 5, name: "J", kind: Kind.article)]]