Home > database >  How can i wait for data to be loaded from API and then create events in CalendarKit library
How can i wait for data to be loaded from API and then create events in CalendarKit library

Time:11-18

I have a problem with fetching data from API. The DayViewCalendar is creating View before events data is fetched from API.

My main view is in SwiftUI

struct CalendarScreen: View {
    @StateObject private var viewModel: ViewModel = ViewModel()
    var body: some View {
        NavigationView {
            ZStack(alignment: .trailing) {
                CalendarKitDisplayView(viewModel: viewModel)
            }
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
}

I have a ViewModel which is fetching events data from API

import Combine
import Foundation

extension NSNotification.Name {
    static let onEventLoaded = Notification.Name("onEventLoaded")
}
extension CalendarScreen {
    class ViewModel: ObservableObject {
        let calendarService = CalendarService()
        @Published var calendarEvents: [CalendarEvent]
        var cancellable: AnyCancellable?
        init() {
            self.calendarEvents = [CalendarEvent()]
        }
        func fetchCalendarEvents() {
            cancellable = calendarService.getEvents()
                .sink(
                    receiveCompletion: { _ in },
                    receiveValue: {
                        calendarEvents in self.calendarEvents = calendarEvents
                        NotificationCenter.default.post(name: .onEventLoaded, object: nil)
                    })
        }
    }
}

Calendar Service is just a service for singletion of repository

import Foundation
import Combine
struct CalendarService {
    private var calendarRepository = CalendarRepository()
    func getEvents() -> AnyPublisher<[CalendarEvent], Error> {
        return calendarRepository.getEvents()
    }
}

And calendarRepository is just simple URL Request for my API

import Combine
struct CalendarRepository {
  private let agent = Agent()
  private let calendarurl = "\(api)/calendars_events"
  func getEvents() -> AnyPublisher<[CalendarEvent], Error>{
    let urlString = "\(calendarurl)"
    let url = URL(string: urlString)!
    var request = URLRequest(url: url)
    request.httpMethod = "GET"
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")
    request.addValue("application/json", forHTTPHeaderField: "Accept")
    request.addValue("Bearer \(AuthManager.shared.token)", forHTTPHeaderField: "Authorization")
    return agent.run(request)
  }
}

Agent is handling the request

class Agent {
    let session = URLSession.shared
    
    var cancelBag: Set<AnyCancellable> = []

    func run<T: Decodable>(_ request: URLRequest) -> AnyPublisher<T, Error> {
        return session
            .dataTaskPublisher(for: request)
            .decode(type: T.self, decoder: JSONDecoder())
            .receive(on: DispatchQueue.main)
            .eraseToAnyPublisher()
    }
    }

Everything is going in CalendarViewController from CalendarKit library which stands as follow:

import SwiftUI
import UIKit

class CalendarViewController: DayViewController {
    convenience init(viewModel: CalendarScreen.ViewModel) {
        self.init()
        self.viewModel = viewModel
    }
    var viewModel = CalendarScreen.ViewModel()
    var refresh: Bool = false
    override func viewDidLoad() {
        super.viewDidLoad()
        subscribeToNotification()
    }
    func subscribeToNotification() {
        NotificationCenter.default.addObserver(
            self, selector: #selector(eventChanged(_:)), name: .onDataImported, object: nil)
    }
    @objc func eventChanged(_ notification: Notification) {
        print("notification")
        reloadData()
    }
    override func eventsForDate(_ date: Date) -> [EventDescriptor] {
        // HOW CAN I WAIT FOR THIS LINE TO FINISH FETCH DATA FROM API
        viewModel.fetchCalendarEvents()
        //
        let calendarKitEvents = viewModel.calendarEvents.filter {
            dateTimeFormat.date(from: $0.start) ?? Date() >= date
                && dateTimeFormat.date(from: $0.end) ?? Date() <= date
        }.map { item in
            let event = Event()
            event.dateInterval = DateInterval(
                start: self.dateTimeFormat.date(from: item.start) ?? Date(),
                end: self.dateTimeFormat.date(from: item.end) ?? Date())
            event.color = UIColor(InvoiceColor(title: item.title))
            event.isAllDay = false
            event.text = item.title
            return event
        }
        return calendarKitEvents
    }
    let dateTimeFormat: DateFormatter = {
        let df = DateFormatter()
        df.locale = Locale(identifier: "pl")
        df.timeZone = TimeZone(abbreviation: "CET")
        df.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
        return df
    }()
}

And the SwiftUI and UIKit is bridged by UIViewControllerRepresntable

import SwiftUI
import UIKit

struct CalendarKitDisplayView: UIViewControllerRepresentable {
    @ObservedObject var viewModel: CalendarScreen.ViewModel
    func makeUIViewController(context: Context) -> DayViewController {
        let dayViewCalendar = CalendarViewController(viewModel: viewModel)
        return dayViewCalendar
    }
    func updateUIViewController(_ uiViewController: DayViewController, context: Context) {

    }
}

And the entity CalendarEvent which is coded to CalendarKit event

public struct CalendarEvent: Codable, Identifiable {
  public var id: Int = 0
  var title: String = ""
  var start: String = ""
  var end: String = ""
  var note: String?
}

My goal is to wait for viewModel.fetchCalendarEvents() to fetch data from API and then start other tasks.

 override func eventsForDate(_ date: Date) -> [EventDescriptor] {
        // HOW CAN I WAIT FOR THIS LINE TO FINISH FETCH DATA FROM API
        viewModel.fetchCalendarEvents()
        //

I tried to implement NotificationCenter with variable refresh but when i added and changed functions To the CalendarViewController variable var refresh: Bool = false and push notification to ViewModel

        func fetchCalendarEvents() {
            cancellable = calendarService.getEvents()
                .sink(
                    receiveCompletion: { _ in },
                    receiveValue: {
                        calendarEvents in self.calendarEvents = calendarEvents
                        NotificationCenter.default.post(name: .eventChanged, object: nil)
                    })
        }

After that i added subscribe to event in init() function in my CalendarViewController and #selector as follow

    @objc func eventChanged(_ notification: Notification) {
        print("notification")
        refresh = true
        reloadData()
    }

I tried to add but it stay in infinite loop and variable never change

    override func eventsForDate(_ date: Date) -> [EventDescriptor] {
        
        viewModel.fetchCalendarEvents()
        while refresh == true {
        }
    }

I was thinking about using conclusion or completion handler but i am new in Swift programming and dont really know how it should looks like.

CodePudding user response:

Using a completion handler your function should look like this:

func fetchCalendarEvents(_ completion: @escaping () -> Void) {
    cancellable = calendarService.getEvents()
        .sink(
            receiveCompletion: { _ in },
            receiveValue: {
                calendarEvents in self.calendarEvents = calendarEvents
                NotificationCenter.default.post(name: .eventChanged, object: nil)
                completion()
            })
}

And when calling it:

fetchCalendarEvents {
    //finished, run some code.
}
  • Related