0

I need to force a specific order into the resulting Event Status array but it's a bit beyond my experience. How would I force the following order into the sections array:

  1. Live
  2. Active
  3. Completed
  4. Archived

Currently the are appended into any random order of the options above

//Retrieving list of Event Status from event documents
let groups = Dictionary(grouping: self.retrievedEvents) { (event) -> String in
       return event.eventStatus
}

//Grouping events in arrays based on the event status of the event
self.sections = groups.map { (eventStatus, events) in
EventStatus(eventStatus: eventStatus, events: events)
}

Current results for group:

["active": [Gallery.Event, Gallery.Event], "archived": [Gallery.Event, Gallery.Event, Gallery.Event, Gallery.Event], "live": [Gallery.Event, Gallery.Event], "completed": [Gallery.Event]]

Current results for sections:

[Gallery.EventStatus(eventStatus: Optional("active"), events: [Gallery.Event, Gallery.Event]), Gallery.EventStatus(eventStatus: Optional("archived"), events: [Gallery.Event, Gallery.Event, Gallery.Event, Gallery.Event]), Gallery.EventStatus(eventStatus: Optional("live"), events: [Gallery.Event, Gallery.Event])]

Event Model (Updated)

class Event {
var adminUser = ""
var byId = ""
var eventCreated:Timestamp?
var eventId = ""
var eventName = ""
var eventStart = ""
var eventStatus: EventStatusTypes = .active
}

Updated model with enum:

enum EventStatusTypes: String, CaseIterable, Comparable {
case live
case completed
case active
case created
case archived

static func < (lhs: EventStatusTypes, rhs: EventStatusTypes) -> Bool {
    return allCases.firstIndex(of: lhs)! < allCases.firstIndex(of: rhs)!
}
}

'

struct EventStatus {
var eventStatus = ""
var events: [Event]
}

'

Current having issues with eventStatus:

init?(snapshot:DocumentSnapshot){

self.eventId = snapshot.get("eventId") as? String ?? "No event Id"
self.byId = snapshot.get("byId") as? String ?? "No uid"
self.adminUser = snapshot.get("adminUser") as? String ?? "No admin user"
**self.eventStatus = snapshot.get(eventStatus.rawValue) as? EventStatusTypes ?? EventStatusTypes(rawValue: EventStatusTypes.active.rawValue)!**


}
akash23a
  • 127
  • 10

2 Answers2

1

As Alexander-ReinstateMonica has suggested, you can read up on how to do this in a general way at this link:

Sorting a Swift array by ordering from another array

For your specific case, the code below shows a way to do it if you're still struggling. Note that I had to make up your classes/enums/structs since you did not supply the actual definitions. The sorting code should work in your case though, with very minimal alterations:

struct Gallery {
    struct Event: CustomStringConvertible {
        var description: String {
            return "Gallery.Event"
        }
        let name: String
        init() {
            name = ""
        }

    }
    struct EventStatus {
        let eventStatus: String
        let events: [Gallery.Event]
    }
}

extension Array where Element == Gallery.EventStatus {

    func reorderByEventStatus(customOrdering: [String]? = nil) -> [Gallery.EventStatus] {

        var defaultOrder = ["live","active","completed","archived"]

        if customOrdering != nil {
            defaultOrder = customOrdering!
        }

        return self.sorted { (a, b) -> Bool in

            if let first = defaultOrder.firstIndex(of: a.eventStatus), let second = defaultOrder.firstIndex(of: b.eventStatus) {
                return first < second
            }
            return false
        }
    }
}

var sections : [Gallery.EventStatus] = [Gallery.EventStatus(eventStatus: "active", events: [Gallery.Event(), Gallery.Event()]), Gallery.EventStatus(eventStatus: "archived", events: [Gallery.Event(), Gallery.Event(), Gallery.Event(), Gallery.Event()]), Gallery.EventStatus(eventStatus: "live", events: [Gallery.Event(), Gallery.Event()])]

for section in sections {
    print(section)
}

print()
sections = sections.reorderByEventStatus()
print()

for section in sections {
    print(section)
}

Output:

EventStatus(eventStatus: "active", events: [Gallery.Event, Gallery.Event])
EventStatus(eventStatus: "archived", events: [Gallery.Event, Gallery.Event, Gallery.Event, Gallery.Event])
EventStatus(eventStatus: "live", events: [Gallery.Event, Gallery.Event])


EventStatus(eventStatus: "live", events: [Gallery.Event, Gallery.Event])
EventStatus(eventStatus: "active", events: [Gallery.Event, Gallery.Event])
EventStatus(eventStatus: "archived", events: [Gallery.Event, Gallery.Event, Gallery.Event, Gallery.Event])
Wattholm
  • 849
  • 1
  • 4
  • 8
0

I would recommend having Event be an enum, which is more robust and allows for some nice optimisations. Here is a simple example of what you can implement (I tested this in playgrounds).

struct Gallery {
    struct Event {
        enum Status: String, CaseIterable, Comparable {
            case live
            case active
            case completed
            case archived

            static func < (lhs: Status, rhs: Status) -> Bool {
                return allCases.firstIndex(of: lhs)! < allCases.firstIndex(of: rhs)!
            }
        }

        let status: Status
    }

    struct Section {
        let eventStatus: Event.Status
        let events: [Event]
    }
}

let retrievedEvents: [Gallery.Event] = ...
let sections = Dictionary(grouping: retrievedEvents, by: { $0.status }) // (1)
    .map { Gallery.Section(eventStatus: $0.0, events: $0.1) } // (2)
    .sorted { $0.eventStatus < $1.eventStatus } // (3)

Thanks to Swift magic Gallery.Event.Status.allCases will be automatically generated for you, which allows us to create a function for easily ordering the enum values. If you want to get fancy you can make it an extension instead:

extension CaseIterable where Self: Equatable {
    static func < (lhs: Self, rhs: Self) -> Bool {
        return allCases.firstIndex(of: lhs)! < allCases.firstIndex(of: rhs)!
    }
}

This way you can easily group the events by status (1), map the key-value tuple into an array of Sections (2), and then sort the sections by status (3).

Guy Kogus
  • 7,251
  • 1
  • 27
  • 32
  • I updated the original post with my event model and eventstatus struct and trying to fit the enum into the event model itself but can't seem to get the hang of it. With this extra information, do you believe your solution still works. I assume all of of the grouping, mapping and sorting happens in the actual VC prior to being loaded into my tableview. Should I add the enum as a part of the Event model or outside of it? Many thanks! – akash23a Nov 26 '19 at 18:11
  • In this case I would declare the enum raw type as `Int` and sort by `rawValue`. – vadian Nov 26 '19 at 18:32
  • @akash23a it doesn't really matter if you declare the enum inside the `Event` model (which should be a `struct`, btw, not a `class`) or outside it. Once you declare it as I demonstrate (and not as `Int` type, as was suggested) you can create the values by calling `event.eventStatus = Event.Status(rawValue: somestringValue)`. – Guy Kogus Nov 27 '19 at 00:12
  • @GuyKogus I'm pretty close. 1) . Added the enum outside the Event struct file (changed from class, thx) 2 added the 'status' param on my Feed VC along with the grouping, mapping and sorting code. However the grouping part throws the following error "Cannot convert value of type 'EventStatusTypes' to closure result type 'String'. EventStatusTypes is your 'Status' and can't figure out why, any ideas? – akash23a Nov 27 '19 at 04:58
  • You may need to extract the string value from the enum value using `eventStatus.rawValue`. It's hard to really diagnose the problem without seeing that code. But ideally you won't be converting in and out of the raw value type except when encoding/decoding. – Guy Kogus Nov 27 '19 at 05:07
  • I updated my eventStatus to be of type EventStatusTypes and defaulted the property to .active but as you may have guessed when using the init() for documentSnapshot to get the array of event documents every event's event status is "active". Is there a way to override the default in the init to actually use the stored eventStatus within the document? I haven't been able to find much on this topic on SO or Youtube. – akash23a Nov 27 '19 at 20:47
  • Can't you just overwrite the `init()` method to do that? Alternatively, you can (and probably should) have all the properties in your structs as `let` and not `var` with no default value. Have a look at what I did in `struct Section` to see what I mean. That way there is no default value, only what is defined when the object is created. – Guy Kogus Nov 27 '19 at 23:44