SwiftUI - Share SwiftData View with Widget

166 Views Asked by At

I have a SwiftData model called Diem. I want my App and my WidgetExtension to share DiemSharedView (already having both App and Widget as targets). However, DiemSharedView just doesn't show up on my Simulator's widget.

@Model final class Diem: Codable, Hashable, Identifiable {
    @Attribute(.unique) var name: String
    var date: Date
    var detail: String?
    
    var id: String { name }

    
    func daysDiff() -> Int {
        return Calendar.current.dateComponents([.day], from: Date(), to: date).day!
    }
    
    init(name: String, date: Date) {
        self.name = name
        self.date = date
    }
    
    // For Widget intent
    init(entity: DiemEntity) {
        self.name = entity.name
        self.date = entity.date
        self.detail = entity.detail
    }
    
    // Manually conform SwiftData model to Codable from GPT-4 (Bing Chat)
    enum CodingKeys: CodingKey {
        case name, date
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encode(name, forKey: .name)
        try container.encode(date, forKey: .date)
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        name = try container.decode(String.self, forKey: .name)
        date = try container.decode(Date.self, forKey: .date)
    }
}
struct DiemSharedView: View {
    var diem: Diem
    
    var body: some View {
        ...
    }
}

struct DiemWidget: Widget {
    // the defaults
}

struct DiemWidgetEntry: TimelineEntry {
    var date: Date
    let diem: Diem?
}

struct DiemWidgetEntryView : View {
    var entry: DiemWidgetEntry

    var body: some View {
        if let diem: Diem = entry.diem {
            DiemSharedView(diem: diem)  // ISSUE: blank on Simulator
        } else {
            DiemSharedView(diem: .placeholder)  // works (displays when adding the widget on Simulator)
        }
    }
}
/*
Reference: https://developer.apple.com/documentation/swiftui/backyard-birds-sample
See the LICENSE.txt file for this sample’s licensing information.
*/

struct DiemWidgetIntent: WidgetConfigurationIntent {
    static var title: LocalizedStringResource = "Select diem"
    static var description = IntentDescription("Keeps track of a diem.")

    @Parameter (title: "Diem")
    var diem: DiemEntity?
    
    init(diem: DiemEntity) {
        self.diem = diem
    }

    init() {}
    
    static var parameterSummary: some ParameterSummary {
        Summary {
            \.$diem
        }
    }
}

extension DiemWidgetIntent {
    static var christmas: DiemWidgetIntent {
        let intent = DiemWidgetIntent()
        intent.diem = .init(from: .christmas)
        return intent
    }
    
    static var independenceDay: DiemWidgetIntent {
        let intent = DiemWidgetIntent()
        intent.diem = .init(from: .independenceDay)
        return intent
    }
}

struct DiemEntity: AppEntity, Identifiable, Hashable {
    var name: String
    var date: Date
    var detail: String?
    
    var id: String {
        name
    }
    
    func daysDiff() -> Int {
        return Calendar.current.dateComponents([.day], from: Date(), to: date).day!
    }

    init(name: String, date: Date) {
        self.name = name
        self.date = date
    }

    init(from diem: Diem) {
        name = diem.name
        date = diem.date
    }

    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(title: "\(name)")
    }

    static var typeDisplayRepresentation = TypeDisplayRepresentation(name: "Diem")
    static var defaultQuery = DiemEntityQuery()
}

struct DiemEntityQuery: EntityQuery, Sendable {
    func entities(for identifiers: [DiemEntity.ID]) async throws -> [DiemEntity] {
        let modelContext = ModelContext(try! ModelContainer(for: Schema([Diem.self])))
        let diems = try modelContext.fetch(FetchDescriptor<Diem>(predicate: #Predicate { identifiers.contains($0.id) }))
        return diems.map { DiemEntity(from: $0) }
    }
    
    func suggestedEntities() async throws -> [DiemEntity] {
        let modelContext = ModelContext(try! ModelContainer(for: Schema([Diem.self])))
        let diems = try modelContext.fetch(FetchDescriptor<Diem>())
        return diems.map { DiemEntity(from: $0) }
    }
}
/*
Reference: https://developer.apple.com/documentation/swiftui/backyard-birds-sample
See the LICENSE.txt file for this sample’s licensing information.
*/
struct DiemWidgetProvider: AppIntentTimelineProvider {
    func placeholder(in context: Context) -> DiemWidgetEntry {
        DiemWidgetEntry(date: Date(), configuration: DiemWidgetIntent())
    }

    func snapshot(for configuration: DiemWidgetIntent, in context: Context) async -> DiemWidgetEntry {
        DiemWidgetEntry(date: Date(), configuration: configuration)
    }
    
    func timeline(for configuration: DiemWidgetIntent, in context: Context) async -> Timeline<DiemWidgetEntry> {
        var entries: [DiemWidgetEntry] = []

        // Generate a timeline consisting of five entries an hour apart, starting from the current date.
        let currentDate = Date()
        for hourOffset in 0 ..< 5 {
            let entryDate = Calendar.current.date(byAdding: .hour, value: hourOffset, to: currentDate)!
            let entry = DiemWidgetEntry(date: entryDate, configuration: configuration)
            entries.append(entry)
        }

        return Timeline(entries: entries, policy: .atEnd)
    }
}

All the previews work. DiemEntityQuery works.

0

There are 0 best solutions below