I'm trying to use EventKit with SwiftUI in order to get a simple count of scheduled events on the users iOS calendar. I have added Privacy - Calendars Usage Description to the Info.plist file with a value.
Here's my code. I think something is wrong with the startDate in the predicate... but no matter what I have tried my count of var events:[EKEvent] always comes back as 0. I should add that I have also added a number of events on the simulator's calendar each day I am trying to get this to work. Any help you can provide is greatly appreciated!!!!
import EventKit
import Foundation
import SwiftUI
class EventKitManager: ObservableObject {
var store = EKEventStore()
var events: [EKEvent] = []
init() {
requestAccessToCalendar()
todaysEvents()
}
func requestAccessToCalendar() {
store.requestAccess(to: .event) { success, error in
}
}
func todaysEvents() {
let calendar = Calendar.autoupdatingCurrent
let startDate = Date.now
var onDayComponents = DateComponents()
onDayComponents.day = 1
let endDate = calendar.date(byAdding: onDayComponents, to: .now)!
let predicate = store.predicateForEvents(withStart: startDate, end: endDate, calendars: nil)
events = store.events(matching: predicate)
}
}
import EventKit
import SwiftUI
struct ContentView: View {
#StateObject var store = EventKitManager()
var body: some View {
Text("\(store.events.count)")
}
}
Thanks #jnpdx! I found a solution. The following code works - you have to reinitialize the store in the requestAccessToCalendar call and of course use the #Published wrapper... I can't believe I missed that! : ]
import EventKit
import Combine
import Foundation
import SwiftUI
class EventKitManager: ObservableObject {
var store = EKEventStore()
#Published var events: [EKEvent] = []
init() {
requestAccessToCalendar()
todaysEvents()
}
func requestAccessToCalendar() {
store.requestAccess(to: .event) { success, error in
self.store = EKEventStore()
}
}
func todaysEvents() {
let calendar = Calendar.autoupdatingCurrent
let startDate = Date.now
var onDayComponents = DateComponents()
onDayComponents.day = 1
let endDate = calendar.date(byAdding: onDayComponents, to: .now)!
let predicate = store.predicateForEvents(withStart: startDate, end: endDate, calendars: nil)
events = store.events(matching: predicate)
}
}
Related
Whenever I am using a pre-configured NSFetchRequest like so:
extension Note {
static var requestPrivateDBNotesByDate: NSFetchRequest<Note> {
let request = Note.fetchRequest()
request.sortDescriptors = [NSSortDescriptor(keyPath: \Note.createdDate, ascending: true)]
request.affectedStores = [PersistenceController.shared.privatePersistentStore]
return request
}
to do a #FetchRequest within a SwiftUI view:
#FetchRequest(fetchRequest: Note.requestPrivateDBNotesByDate)
private var notes: FetchedResults<Note>
the SwiftUI view is not updating when I add a Note entity to CoreData:
func addNote(name: String, context: NSManagedObjectContext) {
context.perform {
let note = Note(context: context)
note.displayName = name
note.createdDate = .now
try? context.save()
}
}
If I use a simple #FetchRequest within my SwiftUI view like so:
#FetchRequest(sortDescriptors: [SortDescriptor(\.displayName, order: .forward)]
) private var notes: FetchedResults<Note>
the view updates whenever I add a now Note.
Why is the pre-configured #FetchRequest not updating my SwiftUI view?
Note: I can force a view update by adding context.refresh(chat, mergeChanges: false) after context.save() but then my question would be, why do I need to force a refresh with a pre-configured #FetchRequest while it is not necessary with a simple #FetchRequest.
Is the forced refresh the only/correct way to go?
Am I missing something?
Update:
This is how I get the privatePersistentStore for the affectedStores property in the pre-configured NSFetchRequest.
var privatePersistentStore: NSPersistentStore {
var privateStore: NSPersistentStore?
let descriptions = persistentContainer.persistentStoreDescriptions
for description in descriptions {
if description.cloudKitContainerOptions?.databaseScope == .private {
guard let url = description.url else { fatalError("NO STORE URL!") }
guard let store = persistentContainer.persistentStoreCoordinator.persistentStore(for: url) else { fatalError("NO STORE!") }
privateStore = store
}
}
guard let privateStore else { fatalError("NO PRIVATE STORE!") }
return privateStore
}
you forgot to assign the new note to the store you are fetching from, e.g.
context.assign(to: PersistenceController.shared.privatePersistentStore)
try? context.save()
I want to develop view that's loading data from Health kit (mindfulness time) so I used Timer every 1 minute to get a new data from Health kit, created by Apple watch but onReceive(Timer) are not refreshing a new data (it pass previous data only)
if I Open another app and come back to this app then it's show me a new data
import SwiftUI
struct LoadingView: View {
var healthStore : HealthStore? = HealthStore()
#State private var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
#State var num : Int = 0
#Binding var showModal: Bool
var decription : String
// MARK: - BODY
var body: some View {
VStack (alignment: .center){
Spacer()
WatchView()
Spacer()
Text(decription)
.font(.callout)
.fontWeight(.semibold)
.padding(.horizontal,30)
Spacer()
}
.navigationBarBackButtonHidden(true)
.onReceive(timer) { _ in
if let healthStore = healthStore {
healthStore.requestAuthorization { success in
if success {
healthStore.getDailyMindfulnessTime { time in
print("\(time)")
}
} //: SUCCESS
}
}
}// ON RECEIVE
.onDisappear(perform: {
self.timer.upstream.connect().cancel()
})//ON DISAPPEAR
}
}
Health Store
import Foundation
import HealthKit
class HealthStore {
var healthStore : HKHealthStore?
var query : HKStatisticsCollectionQuery?
var querySampleQuery : HKSampleQuery?
init(){
// to check data is avaliable or not?
if HKHealthStore.isHealthDataAvailable(){
//Create instance of HKHealthStore
healthStore = HKHealthStore()
}
}
// Authorization
func requestAuthorization(compleion: #escaping(Bool)-> Void){
let stepType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)!
let mindfulSampleType = HKSampleType.categoryType(forIdentifier: .mindfulSession)!
guard let healthStore = self.healthStore else { return compleion(false)}
healthStore.requestAuthorization(toShare: [], read: [stepType,mindfulSampleType]) { (success, error) in
compleion(success)
}
}
//Calculate steps count
func calculateSteps(completion : #escaping(HKStatisticsCollection?)->Void){
let stepType = HKQuantityType.quantityType(forIdentifier: HKQuantityTypeIdentifier.stepCount)!
let startDate = Calendar.current.date(byAdding: .day,value: -7, to: Date())
let anchorDate = Date.mondayAt12AM()
let daily = DateComponents(day:1)
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: Date()
, options: .strictStartDate)
//cumulativeSum (Watch+Iphone)
query = HKStatisticsCollectionQuery(quantityType: stepType, quantitySamplePredicate: predicate, options: .cumulativeSum, anchorDate: anchorDate, intervalComponents: daily)
query!.initialResultsHandler = { query, statisticsCollection , error in
completion(statisticsCollection)
}
if let healthStore = self.healthStore, let query = self.query {
healthStore.execute(query)
}
}
// DailyMindfulnessTime
func getDailyMindfulnessTime(completion: #escaping (TimeInterval) -> Void) {
let sampleType = HKSampleType.categoryType(forIdentifier: .mindfulSession)!
let sortDescriptor = NSSortDescriptor(key: HKSampleSortIdentifierEndDate, ascending: false)
let startDate = Calendar.current.startOfDay(for: Date())
let endDate = Calendar.current.date(byAdding: .day, value: 1, to: startDate)
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: endDate, options: .strictStartDate)
querySampleQuery = HKSampleQuery(sampleType: sampleType, predicate: predicate, limit: HKObjectQueryNoLimit, sortDescriptors: [sortDescriptor]) { (_, results, error) in
if error != nil {
print(" HealthKit returned error while trying to query today's mindful sessions. The error was: \(String(describing: error?.localizedDescription))")
}
if let results = results {
var totalTime = TimeInterval()
for result in results {
totalTime += result.endDate.timeIntervalSince(result.startDate)
}
completion(totalTime)
} else {
completion(0)
}
}
if let healthStore = self.healthStore, let querySampleQuery = self.querySampleQuery {
healthStore.execute(querySampleQuery)
}
}
}
extension Date {
static func mondayAt12AM() -> Date{
return Calendar(identifier: .iso8601).date(from: Calendar(identifier: .iso8601).dateComponents([.yearForWeekOfYear,.weekOfYear],from: Date()))!
}
}
first of all you write in your question that you want to update every minute, but currently you update every second. TimeInterval is a typealias for Double and you pass it in your Timer as seconds. So in your case it should be:
#State private var timer = Timer.publish(every: 60, on: .main, in: .common).autoconnect()
Be aware this means onReceive is called after 60 seconds and not immediately.
And I tested your code and it worked fine for me. Unfortunately you didnt include your watch view so I dont know what you are doing in there.
I assumed num is the variable you wanted to update, so you need to call:
num += Int(time)
in your closure for getDailyMindfulnessTime.
If you want to display the time in your WatchView make sure to pass num as a Binding in there.
I am trying to understand if I am following the theory of SwiftUI #Published and #ObservedObject.
Theory
I have a Model that is receiving updates from a server. The model publishes any changes to the data in the model.
My main view observes this list from the model and creates a List view with cell views that pushes to a detail view. The cell views are published.
The detail view observes changes to the cell view.
What I Think Should Happen
When the model updates this would update the list view, which is does.
When the model updates the detail view would update if it was loaded. It does not.
Why doesn't the detail view update when the model updates if there is an #Published and #ObservedObject chain?
ObservableObjects don't nest. You have choices to trigger objectWillChange manually. That's actually a great thing because you can use an EnvironmentObject factory to wire up your app without exposing anything to views and not force everything to update all at once.
If you know it has changed from a callback you can fire it yourself objectWillChange.send().
You can also subscribe to a Publisher (e.g., another ObservableObjects ObjectWillChangePublisher, or some networking pipeline) and trigger the recipient ObservableObject's publisher on value receipt.
Here is a redux-style code example that goes hog wild and ties into every update.
import Foundation
import Combine
open class Republisher: ObservableObject {
public func republish() {
objectWillChange.send()
}
public init () {}
}
class VM: ObservableObject {
private var republishers = Set<AnyCancellable>()
internal var root: RootStore
init(_ root: RootStore, _ repubs: Republisher...) {
self.root = root
root.objectWillChange
.receive(on: DispatchQueue.main)
.sink(receiveValue: { [weak self] _ in
guard let self = self else { return }
self.objectWillChange.send()
})
.store(in: &republishers)
repubs.forEach { repubs in
repubs.objectWillChange
.receive(on: DispatchQueue.main)
.sink(receiveValue: { [weak self] _ in
guard let self = self else { return }
self.objectWillChange.send()
})
.store(in: &republishers)
}
}
deinit { republishers = [] }
}
import Foundation
import Combine
public final class RootStore: Republisher {
private var middlewareCancellables: Set<AnyCancellable> = []
public init(state: RootState,
reducer: #escaping Reducer<RootState, RootAction>,
middleware: Middlewares = []) {
self.state = state
self.reducer = reducer
self.middleware = RootStore.mandatoryWares(and: middleware)
}
public private(set) var state: RootState {
didSet { republish() }
}
...
}
My code below works that if you manually use to datePicker to match the date and time with the users date and time it will print cool. However the code does not work as a alarm. I can set the date picker ahead of the user time and when the user time matches the date pickers timer nothing prints. I just want to be able to select a date/time using date picker and when the users eventually matches the datePickers time just print cool.
import UIKit
var dateFormatter : DateFormatter!
let datePicker2 = UIDatePicker();
let date = Date()
class ViewController: UIViewController {
#IBOutlet var dateLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let datePicker : UIDatePicker = UIDatePicker(frame: CGRect(x: 0,y: 330,width: self.view.frame.size.width,height: 220))
datePicker.datePickerMode = UIDatePickerMode.dateAndTime
self.view.addSubview(datePicker)
datePicker.addTarget(self, action: #selector(ViewController.change(_:)), for: UIControlEvents.valueChanged)
dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-dd hh:mm"
}
#IBAction func change(_ sender : UIDatePicker)
{
let pickerString = dateFormatter.string(from: sender.date)
let nowString = dateFormatter.string(from: Date())
dateLabel.text = pickerString
if pickerString == nowString {
print("cool")
}
}}
This is happening because your if statement is only being called when the picker moves. You need a timer that fires every second looking to see if they match. To use the method below you just need to move the pickerString and nowString up to global variables. If you don't like that you can refactor and use as you want.
var pickerString: String?
var nowString: String?
override func viewDidLoad() {
super.viewDidLoad()
let _ = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { (timer) in
if pickerString == nowString {
print("cool")
}
}
#buildSucceeded
The following code gives an error.
The error occurs on "var businessIndes: Index!"
It keeps asking me to add < Any>! then once I do it tells me to remove < Any>!
What's weird is that this is supposed to be a term used by Algolia search, so I do not see what would be causing this. Any help would be appreciated. It's probably something small and obvious but I cannot seem to figure it out.
import Foundation
import UIKit
import Firebase
import AlgoliaSearch
import SwiftyJSON
import AFNetworking
class ExploreVC: UIViewController, UITableViewDelegate, UITableViewDataSource,UISearchResultsUpdating, UISearchBarDelegate, UISearchControllerDelegate {
let client = Client(appID: "APP-ID", apiKey: "API-KEY")
override func viewDidLoad() {
}
#IBOutlet weak var tableView: UITableView!
var searchController: UISearchController!
var businessSearch = [Business]()
var businessIndex: Index!
let query = Query()
var searchId = 0
var displayedSearchId = -1
var loadedPage: UInt = 0
var nbPages: UInt = 0
To solve this, specify which pod you would like to use.
e.g.
AlgoliaSearch.Index!