TEAM LICENSES: Save money and learn new skills through a Hacking with Swift+ team license >>

@ObservedObject Not Updating UIViewRepresentable

Forums > SwiftUI

Kia ora koutou!

I've been working on showing some CoreData using a UICalendarView, but am having trouble getting my data model which is an @ObservedObject to update the decorations on the calendar. My file looks like this:

//
//  CalendarView.swift
//  mInr
//
//  Created by Finn LeSueur on 2/03/23.
//

import SwiftUI

struct CalendarView: UIViewRepresentable {
    let interval: DateInterval
    @ObservedObject var dataModel = DataManager.shared
    @AppStorage("lightAccentColour") var lightAccentColour: Color = .red
    @AppStorage("darkAccentColour") var darkAccentColour: Color = .yellow

    func makeUIView(context: Context) -> UICalendarView {
        let view = UICalendarView()
        view.delegate = context.coordinator
        view.availableDateRange = interval
        view.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
        view.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
        return view
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(parent: self)
    }

    func updateUIView(_ uiView: UICalendarView, context: Context) {
        print("updateUIView called")
        context.coordinator.antiCoagulantDoses = dataModel.antiCoagulantDoses
    }

    class Coordinator: NSObject, UICalendarViewDelegate, UICalendarSelectionSingleDateDelegate {
        var parent: CalendarView
        var antiCoagulantDoses: [AntiCoagulantDose]

        init(parent: CalendarView) {
            self.parent = parent
            self.antiCoagulantDoses = self.parent.dataModel.antiCoagulantDoses
        }

        func calendarView(_ calendarView: UICalendarView, decorationFor dateComponents: DateComponents) -> UICalendarView.Decoration? {
            print("Running calendarView")
            // Don't decorate future dates.
            if dateComponents.date?.timeIntervalSinceNow.sign == FloatingPointSign.plus {
                return nil
            }

            let antiCoagulantDose = antiCoagulantDoses.first(where: { $0.timestamp?.startOfDay == dateComponents.date?.startOfDay })

            let font = UIFont.systemFont(ofSize: 12)
            let configuration = UIImage.SymbolConfiguration(font: font)

            if antiCoagulantDose == nil {
                let image = UIImage(systemName: "exclamationmark.triangle", withConfiguration: configuration)?
                    .withRenderingMode(.alwaysOriginal)
                    .withTintColor(.red)

                return .image(image)
            }

            return .customView {
                let label = UILabel()
                label.text = "\(antiCoagulantDose!.dose)g"
                label.font = UIFont.systemFont(ofSize: UIFont.smallSystemFontSize)
                return label
            }
        }

        func dateSelection(_ selection: UICalendarSelectionSingleDate,
                           didSelectDate dateComponents: DateComponents?) {
        }

        func dateSelection(_ selection: UICalendarSelectionSingleDate,
                           canSelectDate dateComponents: DateComponents?) -> Bool {
            return true
        }
    }
}

I've done a lot of searching about this, and I haven't made much progress. The @ObservedObject is working excellently for my other views (adding/deleting/updating/viewing/rendering a chart) – it's just this one!

I've set it up that I pass in the relevant data and update the coordinator with fresh data when changes occur. I can definitely see changes occuring in my logs:


Updating anticoagulant doses.
Updating anticoagulant doses.
Start: 2022-12-20 08:48:27 +0000. End: 2023-03-31 08:48:27 +0000
Start: 2022-12-20 08:48:27 +0000. End: 2023-03-31 08:48:27 +0000
updateUIView called
updateUIView called

But it seems like calendarView never gets called again – which is odd. If I change the rotation of the device or scroll back many months then it refreshes the decorations, but I can't seem to make it go manually.

Any advice would be much appreciated!

2      

Hi, I think you need to update the decorations in updateUIView with:

uiView.reloadDecorations(forDateComponents: "Your date components", animated: true)

Display decorations for specific dates

2      

Thanks for the reply @Hectorcrdna! I had seen that method around but was not sure how to determine what my date components should be to update. I suppose I could use .era but that would be computationally wasteful and slower than updating just a single date.

Maybe I could see if I could get my dataModel to send the most recent update? Not sure yet.

2      

I am late to the party. I met the same issue and here's my workaround. I don't know if it's a standard way or a hack.

You could define a reference to the view you made inside the makeUIView

struct CalendarView:UIViewRepresentable{

@State var calendarView:UICalendarView?

func makeUIView(context: Context) -> UICalendarView {
         DispatchQueue.main.asyncAfter(deadline: .now()){
         self.calendarView = view
         }

}
}

And then you could use

self.calendarView?.reloadDecorations(forDateComponents: dates, animated: true)

when you need to refresh the calendar view.

2      

Hacking with Swift is sponsored by Superwall.

SPONSORED Superwall lets you build & test paywalls without shipping updates. Run experiments, offer sales, segment users, update locked features and more at the click of button. Best part? It's FREE for up to 250 conversions / mo and the Superwall team builds out 100% custom paywalls – free of charge.

Learn More

Sponsor Hacking with Swift and reach the world's largest Swift community!

Reply to this topic…

You need to create an account or log in to reply.

All interactions here are governed by our code of conduct.

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.