Skip to content

Instantly share code, notes, and snippets.

@jghankins
Created April 3, 2026 17:56
Show Gist options
  • Select an option

  • Save jghankins/42b8b658f949b59438bc0847575df60e to your computer and use it in GitHub Desktop.

Select an option

Save jghankins/42b8b658f949b59438bc0847575df60e to your computer and use it in GitHub Desktop.
Multi-Day Event Support: Integration Guide for iOS

Multi-Day Event Support: Integration Guide

Overview

Multi-day events (e.g., IL/MD CCW classes spanning 2 days, 18 hours total) are now fully supported. Each day can have different times and venues (classroom vs range). The data model was already in place -- this update adds admin UX for bulk creation and exposes structured multi-day data through the API.

Data Model (unchanged)

Event (duration_type: "multi_day" | "single_day")
  └── EventInstance (a "Session" -- e.g., "Mar 25-26 Session")
        ├── EventOccurrence (Day 1 -- classroom, 9am-5pm)
        └── EventOccurrence (Day 2 -- range, 8am-12pm)
  • An Event can have multiple Instances (sessions at different date ranges)
  • Each Instance has multiple Occurrences (the individual days)
  • Each Occurrence has: starts_at, ends_at, day_label, venue_id
  • Registration is per-Instance (registering for one session covers all its days)

How Existing Mobile Clients Are Protected

Header-Gated API Fields

New fields are only returned when the client sends the x-supports-multi-day request header. This follows the same pattern as x-supports-native-checkout.

Without the header (existing clients):

{
  "id": 1,
  "title": "IL CCW 16-Hour Course",
  "duration_type": "multi_day",
  "instances": [
    {
      "id": 10,
      "label": "Mar 25 - Mar 26, 2026",
      "occurrences": [
        { "id": 100, "starts_at": "...", "ends_at": "...", "day_label": "Day 1 - Classroom" },
        { "id": 101, "starts_at": "...", "ends_at": "...", "day_label": "Day 2 - Range" }
      ]
    }
  ]
}

The duration_type and day_label fields were already present in the API. Existing clients already receive and can ignore these fields. No existing fields were changed or removed.

With x-supports-multi-day header (updated clients):

{
  "id": 1,
  "title": "IL CCW 16-Hour Course",
  "duration_type": "multi_day",
  "is_multi_day": true,
  "total_days": 2,
  "attendance_required": true,
  "multi_day_notice": "All scheduled days must be attended to complete this training.",
  "instances": [
    {
      "id": 10,
      "label": "Mar 25 - Mar 26, 2026",
      "day_count": 2,
      "date_range": {
        "starts_at": "2026-03-25T14:00:00Z",
        "ends_at": "2026-03-26T17:00:00Z"
      },
      "occurrences": [
        { "id": 100, "starts_at": "...", "ends_at": "...", "day_label": "Day 1 - Classroom", "venue": { ... } },
        { "id": 101, "starts_at": "...", "ends_at": "...", "day_label": "Day 2 - Range", "venue": { ... } }
      ]
    }
  ]
}

What This Means

  • Old app versions continue working identically
  • No silent JSON decode failures (no new required fields on existing models)
  • The duration_type field was already in the response, so old clients that display it will show "multi_day" as-is
  • Instance labels now auto-generate as date ranges (e.g., "Mar 25 - Mar 26, 2026") which old clients will display naturally

What the Mobile App Needs to Do

1. Add the Request Header

In the API client, add the header to event detail requests:

// In your API request builder
request.addValue("true", forHTTPHeaderField: "x-supports-multi-day")

2. Update the Event Model

Add the new optional fields to the event detail model:

struct EventDetail: Codable {
    // ... existing fields ...

    // New multi-day fields (optional -- only present with header)
    let isMultiDay: Bool?
    let totalDays: Int?
    let attendanceRequired: Bool?
    let multiDayNotice: String?

    enum CodingKeys: String, CodingKey {
        // ... existing keys ...
        case isMultiDay = "is_multi_day"
        case totalDays = "total_days"
        case attendanceRequired = "attendance_required"
        case multiDayNotice = "multi_day_notice"
    }
}

3. Update the Instance Model

struct EventInstance: Codable {
    // ... existing fields ...

    // New multi-day fields (optional)
    let dayCount: Int?
    let dateRange: DateRange?

    struct DateRange: Codable {
        let startsAt: Date
        let endsAt: Date

        enum CodingKeys: String, CodingKey {
            case startsAt = "starts_at"
            case endsAt = "ends_at"
        }
    }

    enum CodingKeys: String, CodingKey {
        // ... existing keys ...
        case dayCount = "day_count"
        case dateRange = "date_range"
    }
}

4. Update the UI

Event Detail Screen

  • When isMultiDay == true, show a banner: "This is a multi-day event. All scheduled days must be attended."
  • Show the multiDayNotice string if present

Instance Selection (Registration)

  • For multi-day instances, show all occurrences under the instance card:
    ┌─────────────────────────────────────┐
    │ ○ Mar 25 - Mar 26, 2026 (2 days)   │
    │   Day 1 - Classroom                 │
    │   Wed, Mar 25 · 9:00 AM - 5:00 PM  │
    │   Main Training Center              │
    │                                     │
    │   Day 2 - Range                     │
    │   Thu, Mar 26 · 8:00 AM - 12:00 PM │
    │   Outdoor Range                     │
    └─────────────────────────────────────┘
    

Schedule Display

  • Show day_label before each occurrence date when present
  • Group occurrences by instance with the instance label as section header

5. Important Notes

  • All new fields are optional. The app must handle nil gracefully.
  • Registration flow is unchanged. Users still register for an Instance. The multi-day display is purely informational -- registering for a session covers all its days.
  • No new API endpoints. Everything goes through the existing GET /api/v1/events/:slug endpoint.
  • The duration_type field already exists in the response. You can use event.durationType == "multi_day" as a local check even without the new header, but the additional structured fields (dayCount, dateRange, etc.) require the header.

Rollout Strategy

  1. Phase 1 (done): Server-side support deployed. Old clients unaffected.
  2. Phase 2: Add x-supports-multi-day header and new model fields to the iOS app. All new fields are optional, so this is backward-compatible with older server versions (fields will just be nil).
  3. Phase 3: Add multi-day UI enhancements to event detail and registration screens.
  4. Phase 4: Ship app update. No server changes needed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment