Combine Timer - setting timer - swiftui

I'm not very familiar with Combine's Timer.publish(every:, on: , in: ). Essentially I'm looking to run a timer to call a func in my ObservableObject class. Is there a better way of doing this, not sure if I'm doing so correctly or if there's a cleaner way of doing the same.
class Example : ObservableObject {
private var cancellables = Set<AnyCancellable>()
var cancellable: AnyCancellable? = nil
#Published var somedate: Date = Date()
init() {
cancellable = Timer
.publish(every: 1, on: .main, in: .common)
.autoconnect()
.sink(receiveValue: { _ in
self.update()
})
}
func update() {
let publisher = Date()
Just(publisher)
.sink(receiveCompletion: { completion in
}, receiveValue: { date in
self.somedate = date
print(self.somedate)
}).store(in: &cancellables)
}
}
The output seems correct.
2021-08-10 22:25:24 +0000
2021-08-10 22:25:25 +0000
2021-08-10 22:25:26 +0000
2021-08-10 22:25:27 +0000
2021-08-10 22:25:28 +0000
2021-08-10 22:25:29 +0000
2021-08-10 22:25:30 +0000
2021-08-10 22:25:31 +0000
2021-08-10 22:25:32 +0000
2021-08-10 22:25:33 +0000
2021-08-10 22:25:34 +0000
2021-08-10 22:25:35 +0000
2021-08-10 22:25:36 +0000

You can significantly reduce complexity in your update function. There's no need to create a new publisher with Just to get the date. Plus, you can already get that in your initial sink:
class Example : ObservableObject {
private var cancellable: AnyCancellable? = nil
#Published var somedate: Date = Date()
init() {
cancellable = Timer
.publish(every: 1, on: .main, in: .common)
.autoconnect()
.sink(receiveValue: { value in
self.update(date: value)
})
}
func update(date: Date) {
self.somedate = date
print(self.somedate)
}
}

Related

SwiftUI combine nil data

I have created a class to perform a network request and parse the data using Combine. I'm not entirely certain the code is correct, but it's working as of now (still learning the basics of Swift and basic networking tasks). My Widget has the correct data and is works until the data becomes nil. Unsure how to check if the data from my first publisher in my SwiftUI View is nil, the data seems to be valid even when there's no games showing.
My SwiftUI View
struct SimpleEntry: TimelineEntry {
let date: Date
public var model: CombineData?
let configuration: ConfigurationIntent
}
struct Some_WidgetEntryView : View {
var entry: Provider.Entry
#Environment(\.widgetFamily) var widgetFamily
var body: some View {
VStack (spacing: 0){
if entry.model?.schedule?.dates.first?.games == nil {
Text("No games Scheduled")
} else {
Text("Game is scheduled")
}
}
}
}
Combine
import Foundation
import WidgetKit
import Combine
// MARK: - Combine Attempt
class CombineData {
var schedule: Schedule?
var live: Live?
private var cancellables = Set<AnyCancellable>()
func fetchSchedule(_ teamID: Int, _ completion: #escaping (Live) -> Void) {
let url = URL(string: "https://statsapi.web.nhl.com/api/v1/schedule?teamId=\(teamID)")!
let publisher = URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: Schedule.self, decoder: JSONDecoder())
//.catch { _ in Empty<Schedule, Error>() }
//.replaceError(with: Schedule(dates: []))
let publisher2 = publisher
.flatMap {
return self.fetchLiveFeed($0.dates.first?.games.first?.link ?? "")
}
Publishers.Zip(publisher, publisher2)
.receive(on: DispatchQueue.main)
.sink(receiveCompletion: {_ in
}, receiveValue: { schedule, live in
self.schedule = schedule
self.live = live
completion(self.live!)
WidgetCenter.shared.reloadTimelines(ofKind: "NHL_Widget")
}).store(in: &cancellables)
}
func fetchLiveFeed(_ link: String) -> AnyPublisher<Live, Error /*Never if .catch error */> {
let url = URL(string: "https://statsapi.web.nhl.com\(link)")!
return URLSession.shared.dataTaskPublisher(for: url)
.map(\.data)
.decode(type: Live.self, decoder: JSONDecoder())
//.catch { _ in Empty<Live, Never>() }
.eraseToAnyPublisher()
}
}
Like I said in the comments, it's likely that the decode(type: Live.self, decoder: JSONDecoder()) returns an error because the URL that you're fetching from when link is nil doesn't return anything that can be decoded as Live.self.
So you need to handle that case somehow. For example, you can handle this by making the Live variable an optional, and returning nil when link is empty (or nil).
This is just to set you in the right direction - you'll need to work out the exact code yourself.
let publisher2 = publisher1
.flatMap {
self.fetchLiveFeed($0.dates.first?.games.first?.link ?? "")
.map { $0 as Live? } // convert to an optional
.replaceError(with: nil)
}
Then in the sink, handle the nil:
.sink(receiveCompletion: {_ in }, receiveValue:
{ schedule, live in
if let live = live {
// normal treatment
self.schedule = schedule
self.live = live
//.. etc
} else {
// set a placeholder
}
})
SwiftUI and WidgetKit work differently. I needed to fetch data in getTimeline for my IntentTimelineProvider then add a completion handler for my TimelineEntry. Heavily modified my Combine data model. All credit goes to #EmilioPelaez for pointing me in the right direction, answer here.

My Object that's Health Store are not refreshing data when it received a new data (in Health kit) in swift app

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.

How to change Event Dot Color in FSCalender

Here is my code. I am trying to change the dot color but I did not find any solution. Thanks
func calendar(_ calendar: FSCalendar, numberOfEventsFor date: Date) -> Int {
return 1;
}
func calendar(_ calendar: FSCalendar, appearance: FSCalendarAppearance, eventColorFor date: Date) -> UIColor? {
return UIColor.red
}
Here is an example image of these event-dots:
This example is taken from here. Basically you just use the given method, check for event-type or something like that and return a color of your favour.
//Used by one of the example methods
var datesWithEvent = ["2015-10-03", "2015-10-06", "2015-10-12", "2015-10-25"]
var datesWithMultipleEvents = ["2015-10-08", "2015-10-16", "2015-10-20", "2015-10-28"]
//Used in one of the example methods
fileprivate lazy var dateFormatter2: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
The complete example is to long for embedding, so i only took 2 example methods. I added the fields from the example, to have an "complete" example of how such a method could look like.
func calendar(_ calendar: FSCalendar, appearance: FSCalendarAppearance, eventColorFor date: Date) -> UIColor? {
//Do some checks and return whatever color you want to.
return UIColor.purple
}
func calendar(_ calendar: FSCalendar, appearance: FSCalendarAppearance, eventDefaultColorsFor date: Date) -> [UIColor]? {
let key = self.dateFormatter2.string(from: date)
if self.datesWithMultipleEvents.contains(key) {
return [UIColor.magenta, appearance.eventDefaultColor, UIColor.black]
}
return nil
}
For better understanding have a look at linked example class in Github. That example is pretty self explanatory.

Repeating Notifications Swift 3

I have notification that works well with only one time item. I need show my notification: 10:00 PM and 10:30 PM. How i can do that? Please tell me
My code:
NotificationManager.swift:
import UIKit
import UserNotifications
class NotificationManager
{
static let shared = NotificationManager()
let center = UNUserNotificationCenter.current()
func registerNotifications()
{
center.requestAuthorization(options: [.sound,.alert], completionHandler: {( granted, error) in })
}
func addNotificationWithCalendarTrigger(hour: Int, minute: Int)
{
let content = UNMutableNotificationContent()
content.title = "Hi"
content.body = "It,s new notification!"
content.sound = UNNotificationSound.default()
var components = DateComponents()
components.hour = hour
components.minute = minute
let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: true)
let request = UNNotificationRequest(identifier: "calendar", content: content, trigger: trigger)
center.add(request) { (error) in
//handle error
}
}
}
ViewController.swift
import UIKit
class ViewController: UIViewController
{
override func viewDidLoad()
{
super.viewDidLoad()
timeForNotifications()
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
}
func timeForNotifications()
{
NotificationManager.shared.addNotificationWithCalendarTrigger(hour: 22, minute: 00)
}
}
might be a bit late but I think this is what you're looking for. You can set the notification to show up at 10:00 with the following code:
var components = DateComponents()
components.hour = 22
components.minute = 00
let trigger = UNCalendarNotificationTrigger(dateMatching: components, repeats: true)
let request = UNNotificationRequest(identifier: "calendar", content: content, trigger: trigger)
What I always do is replace components with date, but I think your option works as well.
var date = DateComponents()
date.hour = 22
date.minute = 00
let trigger = UNCalendarNotificationTrigger(dateMatching: date, repeats: true)
Hope this helped you out!

FSCalendar events in Swift 3

How can events be added to an FSCalendar in swift 3?
Implement the appropriate methods in a class adopting FSCalendarDataSource.
var datesWithEvent = ["2015-10-03", "2015-10-06", "2015-10-12", "2015-10-25"]
var datesWithMultipleEvents = ["2015-10-08", "2015-10-16", "2015-10-20", "2015-10-28"]
fileprivate lazy var dateFormatter2: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
func calendar(_ calendar: FSCalendar, numberOfEventsFor date: Date) -> Int {
let dateString = self.dateFormatter2.string(from: date)
if self.datesWithEvent.contains(dateString) {
return 1
}
if self.datesWithMultipleEvents.contains(dateString) {
return 3
}
return 0
}
Based On FsCalendar Documentation