ios – How to use TimelineProvider to provide array of Workouts


I’m building an iOS widget that will display a user’s upcoming workout for the current day.

The frontend of my app uses Angular/Ionic and it currently sets the widget data any time a user adds a workout to their calendar by using the capacitor-widgetsbridge-plugin, utilising the sharedUserDefaults table:

// widget.service.ts
import { WidgetsBridgePlugin } from 'capacitor-widgetsbridge-plugin';

async setWidgetData() {
    this.widgetData = {
        // set widget data here
    };

    await WidgetsBridgePlugin.setItem({
      key: this.key,
      value: JSON.stringify(this.widgetData),
      group: this.appGroup
    });
}

The TimelineProvider sets the next user workout like so:

// appWidget.iOS
func getTimeline(for configuration: ConfigurationIntent, in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
        var entries: [SimpleEntry] = []
        
        let sharedDefaults = UserDefaults.init(suiteName: "group.app.name")
        
        let decoder = JSONDecoder()
        decoder.dateDecodingStrategy = .iso8601

        if let jsonString = sharedDefaults?.string(forKey: "nextWorkout"),
            let jsonData = jsonString.data(using: .utf8),
            let decodedWorkout = try? decoder.decode(Workout.self, from: jsonData) {
            
            var thumbImage: UIImage?
            let dispatchGroup = DispatchGroup()
            
            // Use a dispatch group to wait for the image data to be loaded
            dispatchGroup.enter()
            if let thumbUrlString = decodedWorkout.thumbUrl,
               let thumbUrl = URL(string: thumbUrlString) {
                URLSession.shared.dataTask(with: thumbUrl) { data, _, _ in
                    defer { dispatchGroup.leave() }
                    if let data = data {
                        thumbImage = UIImage(data: data)
                    }
                }.resume()
            } else {
                dispatchGroup.leave()
            }
            
            dispatchGroup.notify(queue: .main) {
                let entry = SimpleEntry(date: Date(), nextWorkout: decodedWorkout, thumbImage: thumbImage, configuration: configuration)
                entries.append(entry)
                let timeline = Timeline(entries: entries, policy: .atEnd)
                completion(timeline)
            }
        } else {
            // Handle the case where there's no next workout data available
            let placeholderEntry = SimpleEntry(date: Date(), nextWorkout: Workout.noDataFound(), thumbImage: nil, configuration: configuration)
            entries.append(placeholderEntry)
            let timeline = Timeline(entries: [placeholderEntry], policy: .atEnd)
            completion(timeline)
        }
    }

struct SimpleEntry: TimelineEntry {
    let date: Date
    let nextWorkout: Workout
    let thumbImage: UIImage?
    let configuration: ConfigurationIntent
}

struct Workout: Codable {
    let name: String
    let date: Date
    let time: String?
    let thumbUrl: String?
}

The problem with this is that if a user doesn’t complete their workout for a given day, Monday for example, there is nothing in place to set the widget data for the following day when the current one passes (i.e. Monday’s data will still be shown on Tuesday if a user hasn’t completed their workout).

I want to be able to pass an array of timeline entries containing the user’s workouts for the entire week ahead. I’m relatively new to Swift and Widget development so things haven’t quite clicked in my head yet.

So far, I’ve created an additional key in the sharedUserDefaults table on the frontend called upcomingWorkouts which contains an array of scheduled workouts (excluding today’s workout). The nextWorkout key in the sharedUserDefaults table just contains the current day workout. I’m having trouble figuring out where to go from here.

Any insight on how best to implement the desired behaviour would be incredible.

Latest articles

spot_imgspot_img

Related articles

Leave a reply

Please enter your comment!
Please enter your name here

spot_imgspot_img