For a button, each time it is tapped, how to trigger a code1 (set a specific var to value1) when ‘onTap’ occured less than 60 seconds ago or code2 (set a specific variable to value2) when onTap occured more than 60 seconds ago ?
==> Working solution implemented for duration = 10 seconds
Button(action: {
self.date = Date()
let hour = self.calendar.component(.hour, from: self.date)
let minutes = self.calendar.component(.minute, from: self.date)
let seconds = self.calendar.component(.second, from: self.date)
self.newdate = hour*3600+minutes*60+seconds
if (self.lastdate == 0){
print(" First Tap...")
}else
if (self.newdate-self.lastdate) < 10 {
print("Tap before 10 sec => call Func1")
} else {
print("Tap > 10 seconds => call Func2")
}
self.lastdate = hour*3600+minutes*60+seconds
}) {
Text("One")
}
Here's a complete example that works for an interval of 5 seconds (to make testing quicker).
It uses #State vars to update the Date of the lastTap and message and uses .timeIntervalSince(last) to measure the time in seconds. The first tap is detected when lastTap is still nil.
Changing the message causes the view to redraw and update the message on the screen.
struct ContentView: View {
#State private var lastTap: Date? = nil
#State private var message = ""
var body: some View {
VStack(spacing: 40) {
Text(message)
Button("Tap me") {
let now = Date()
if let last = self.lastTap {
if now.timeIntervalSince(last) > 5 {
self.message = "Greater than 5 seconds"
} else {
self.message = "Less than 5 seconds"
}
} else {
self.message = "First tap"
}
self.lastTap = now
}
}
}
}
var lastTapTimestamp: Date?
#IBAction func buttonTapped(_ sender: UIButton) {
defer { lastTapTimestamp = Date() }
guard let date = lastTapTimestamp else {
callFunc1()
return
}
if Int(Date().timeIntervalSinceReferenceDate) - Int(date.timeIntervalSinceReferenceDate) >= 3600 {
callFunc2()
} else {
callFunc1()
}
}
Related
How make CountdownTimer and how make let elapsed = public var . I want to make the variable constantly decrease life.
For example, as in Tamagotchi - You need to constantly feed the animal so that it does not die.
This timer should continuously run until it reaches 0. When the tamagotchi eats, the value is again added to the variable, which decreases over time.
class Stopwatch: ObservableObject {
/// String to show in UI
#Published private(set) var message = "Not running"
/// Is the timer running?
#Published private(set) var isRunning = false
/// Time that we're counting from
private var startTime: Date? { didSet { saveStartTime() } }
/// The timer
private var timer: AnyCancellable?
init() {
startTime = fetchStartTime()
if startTime != nil {
start()
}
}
}
extension Stopwatch {
func start() {
timer?.cancel() // cancel timer if any
if startTime == nil {
startTime = Date()
}
message = ""
timer = Timer
.publish(every: 0.1, on: .main, in: .common)
.autoconnect()
.sink { [weak self] _ in
guard
let self = self,
let startTime = self.startTime
else { return }
let now = Date()
let elapsed = now.timeIntervalSince(startTime)
guard elapsed < 60 else {
self.stop()
return
}
self.message = String(format: "%0.1f", elapsed)
}
isRunning = true
}
func stop() {
timer?.cancel()
timer = nil
startTime = nil
isRunning = false
message = "Not running"
}
}
private extension Stopwatch {
func saveStartTime() {
if let startTime = startTime {
UserDefaults.standard.set(startTime, forKey: "startTime")
} else {
UserDefaults.standard.removeObject(forKey: "startTime")
}
}
func fetchStartTime() -> Date? {
UserDefaults.standard.object(forKey: "startTime") as? Date
}
}
I have a test project where I get the total number of falls for a user for each day over the course of the week. The initialResultsHandler works perfectly every time, however the statisticsUpdateHandler doesn't always fire off. If you start the app, then go to the health app and insert falls manually, switch back to the test app you should see the total for today update. In reality this works for about the first 3-6 times. After that the statisticsUpdateHandler doesn't get called anymore.
What's also odd is that if you delete data and then go back to the test app, or add data from a time earlier than now, the statisticsUpdateHandler gets called. This leads me to think that it has something to do with the statisticsUpdateHandler end date.
Apples documentation is pretty clear however I’m afraid they might be leaving something out.
If this property is set to nil, the statistics collection query will automatically stop as soon as it has finished calculating the initial results. If this property is not nil, the query behaves similarly to the observer query. It continues to run, monitoring the HealthKit store. If any new, matching samples are saved to the store—or if any of the existing matching samples are deleted from the store—the query executes the update handler on a background queue.
Is there any reason that statisticsUpdateHandler might not be called? I have included a test project below.
struct Falls: Identifiable{
let id = UUID()
let date: Date
let value: Int
var formattedDate: String{
let formatter = DateFormatter()
formatter.setLocalizedDateFormatFromTemplate("MM/dd/yyyy")
return formatter.string(from: date)
}
}
struct ContentView: View {
#StateObject var manager = HealthKitManager()
var body: some View {
NavigationView{
List{
Text("Updates: \(manager.updates)")
ForEach(manager.falls){ falls in
HStack{
Text(falls.value.description)
Text(falls.formattedDate)
}
}
}
.overlay(
ProgressView()
.scaleEffect(1.5)
.opacity(manager.isLoading ? 1 : 0)
)
.navigationTitle("Falls")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
class HealthKitManager: ObservableObject{
let healthStore = HKHealthStore()
let fallType = HKQuantityType.quantityType(forIdentifier: .numberOfTimesFallen)!
#Published var isLoading = false
#Published var falls = [Falls]()
#Published var updates = 0
init() {
let healthKitTypesToRead: Set<HKSampleType> = [fallType]
healthStore.requestAuthorization(toShare: nil, read: healthKitTypesToRead) { (success, error) in
if let error = error{
print("Error: \(error)")
} else if success{
self.startQuery()
}
}
}
func startQuery(){
let now = Date()
let cal = Calendar.current
let sevenDaysAgo = cal.date(byAdding: .day, value: -7, to: now)!
let startDate = cal.startOfDay(for: sevenDaysAgo)
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: now, options: [.strictStartDate, .strictEndDate])
var interval = DateComponents()
interval.day = 1
// start from midnight
let anchorDate = cal.startOfDay(for: now)
let query = HKStatisticsCollectionQuery(
quantityType: fallType,
quantitySamplePredicate: predicate,
options: .cumulativeSum,
anchorDate: anchorDate,
intervalComponents: interval
)
query.initialResultsHandler = { query, collection, error in
guard let collection = collection else {
print("No collection")
DispatchQueue.main.async{
self.isLoading = false
}
return
}
collection.enumerateStatistics(from: startDate, to: Date()){ (result, stop) in
guard let sumQuantity = result.sumQuantity() else {
return
}
let totalFallsForADay = Int(sumQuantity.doubleValue(for: .count()))
let falls = Falls(date: result.startDate, value: totalFallsForADay)
print(falls.value, falls.formattedDate)
DispatchQueue.main.async{
self.falls.insert(falls, at: 0)
}
}
print("initialResultsHandler done")
DispatchQueue.main.async{
self.isLoading = false
}
}
query.statisticsUpdateHandler = { query, statistics, collection, error in
print("In statisticsUpdateHandler...")
guard let collection = collection else {
print("No collection")
DispatchQueue.main.async{
self.isLoading = false
}
return
}
DispatchQueue.main.async{
self.isLoading = true
self.updates += 1
self.falls.removeAll(keepingCapacity: true)
}
collection.enumerateStatistics(from: startDate, to: Date()){ (result, stop) in
guard let sumQuantity = result.sumQuantity() else {
return
}
let totalFallsForADay = Int(sumQuantity.doubleValue(for: .count()))
let falls = Falls(date: result.startDate, value: totalFallsForADay)
print(falls.value, falls.formattedDate)
print("\n\n")
DispatchQueue.main.async{
self.falls.insert(falls, at: 0)
}
}
print("statisticsUpdateHandler done")
DispatchQueue.main.async{
self.isLoading = false
}
}
isLoading = true
healthStore.execute(query)
}
}
I was so focused on the statisticsUpdateHandler and the start and end time that I didn't pay attention to the query itself. It turns out that the predicate was the issue. By giving it an end date, it was never looking for samples outside the the initial predicate end date.
Changing the predicate to this solved the issue:
let predicate = HKQuery.predicateForSamples(withStart: startDate, end: nil, options: [.strictStartDate])
I store a value called month hours in my application that keeps track of the hours a person has used the apps and displays it in a line of text. The text if part of a stack in Swift UI, but I can't figure out how to make the text update once the information has been queried from I've tried quite a few ways of making this work from structs to classes to using #State.
This is just the latest thing I tried that didn't work if anyone can help that would be greatly appreciated.
let db = Firestore.firestore()
class Month {
var monthHours = "0"
func getMonthHours() {
db.addSnapshotListener(. //Im removing the actual query part to keep that private but the print statement below confirms the query is not the issue.
{ (docSnapShot, err) in
if let e = err {
print("There was an error retrieving the monthly hours:\n\(e.localizedDescription)")
} else {
let data = docSnapShot?.data()
if let h = data?[K.FStore.monthHoursField] as? Double {
self.monthHours = String(h.rounded())
print("These are the hours:\n\(self.monthHours)")
}
}
})
}
func getMonth() -> String {
let date = Date()
let formatter = DateFormatter()
formatter.dateFormat = "MMMM yyyy"
let result = formatter.string(from: date)
return result
}
init() {
getMonthHours()
}
}
struct ChartView : View {
#State private var month = Month()
//Struct variables
var body : some View {
ZStack {
Color(UIColor(named: K.BrandColors.grey)!).edgesIgnoringSafeArea(.all)
VStack {
Text("HOURS THIS MONTH \(month.monthHours)")
.font(.system(size: 18))
.fontWeight(.heavy)
}
}
}
This outlines one possible approach. The crux is to deal with the asynchronous function "getMonthHours". You need to wait till it is finished its fetching before you can use the results.
class Month {
var monthHours = "0"
// async fetch the month hours from Firestore, ... error handling todo
static func getMonthHours(handler: #escaping (String) -> Void) {
db.addSnapshotListener{ (docSnapShot, err) in
if let e = err {
print("There was an error retrieving the monthly hours:\n\(e.localizedDescription)")
return handler("") // should return some error here .... todo
} else {
if let data = docSnapShot?.data(),
let h = data?[K.FStore.monthHoursField] as? Double {
// return the result
return handler(String(h.rounded()))
} else {
return handler("") // should return some error here .... todo
}
}
}
}
func getMonth() -> String {
let date = Date()
let formatter = DateFormatter()
formatter.dateFormat = "MMMM yyyy"
let result = formatter.string(from: date)
return result
}
init() { }
}
struct ChartView : View {
#State private var monthHours = ""
var body : some View {
ZStack {
Color(UIColor(named: K.BrandColors.grey)!).edgesIgnoringSafeArea(.all)
VStack {
Text("HOURS THIS MONTH \(monthHours)")
.font(.system(size: 18))
.fontWeight(.heavy)
}
}.onAppear(perform: loadData)
}
}
func loadData() {
// when the fetching is done it will update the view
Month.getMonthHours() { hours in
self.monthHours = hours
}
}
while practicing coding, I just caught up with question: which code will process faster in swift?
Is this one faster:
var aYear = Int(readLine()!)!
func isLeap(year: Int)
{
if aYear%400 == 0
{
print("YES")
}
else if aYear%4 == 0 && !(aYear%100 == 0)
{
print("YES")
}
else
{
print("NO")
}
}
isLeap(year: aYear)
Or this one faster?
var aYear = Int(readLine()!)!
func isLeap(year: Int) {
var leap = "NO"
if year % 4 == 0 {
leap = "YES"
}
if year % 100 == 0 {
leap = "NO"
}
if year % 400 == 0 {
leap = "YES"
}
print(leap)
}
isLeap(year: aYear)
Thank you. Have a great day :)
One way to check function performance is to this helper struct. Add the following to your project:
struct Benchmark {
static func testElapsedTimeFor(_ title: String, operation: #escaping ()->()) {
let startTime = CFAbsoluteTimeGetCurrent()
operation()
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
print("Time elapsed for \(title): \(timeElapsed) s.")
}
// If you would like to get the double instead
static func testElapsedTimeFor(operation: #escaping ()->()) -> Double {
let startTime = CFAbsoluteTimeGetCurrent()
operation()
let timeElapsed = CFAbsoluteTimeGetCurrent() - startTime
return Double(timeElapsed)
}
}
And use it like so:
Benchmark.testElapsedTimeFor("First Function") {
self.isLeap(year: 2019)
}
stackoverflow is my last chance. I hope you can help me. I have a countdown in my app which is set to 2 hours and 30 minutes. When it hits 0 it starts to count up to 30 minutes. After the 30 minutes are over, the countdown is back at 2:30. Everything is working fine except the part when the timer is switching from the countdown part to the countup part. Here is the problem: The timer is going negative when hitting zero.
#objc func startTimer() {
print("Countdown : \(totalTime)")
countdownTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(updateTime), userInfo: nil, repeats: true)
}
#objc func updateTime() {
if (totalTime > 10800) {
totalTime = totalTime - 10800
}
totalTime -= 1
var time = totalTime
//When in session...
if (time > 9000) {
//Calculate CountUP
time = 10800 - time
//if session has started
if(isSessionActive == false) {
isSessionActive = true
lastFiveMinutes = false
self.setSessionStarted()
}
} else {
//If session has ended...
if(isSessionActive == true) {
isSessionActive = false
lastFiveMinutes = false
self.setSessionEnded()
}
//If last 5 minutes have started
if(time < 300 && lastFiveMinutes == false) {
lastFiveMinutes = true
self.setLastFiveMinutes()
}
}
countdownLabel.text = "\(timeFormatted(time))"
//update Participant Count every x second, ONLY if in last 5 Minutes or Session is active
if (lastFiveMinutes == true || isSessionActive == true) {
if (totalTime % 5 == 0) {
setParticipants(n: httpController.getPar())
}
} else {
setParticipants(n: "...")
}
}
The part "var time = totalTime" is not necessary, i know.
In Android the code is exactly the same except the -=1 part, but it is working.
* Update *
Inital Value of totalTime:
#objc var totalTime = 0
And here is the next part where it gets a real value.
#objc func initCountdown() {
var currentSec = 0.0
// Get Current Time from Server
serverTimeReturn { (getResDate) -> Void in
let dFormatter = DateFormatter()
dFormatter.dateStyle = DateFormatter.Style.long
dFormatter.timeStyle = DateFormatter.Style.long
dFormatter.timeZone = NSTimeZone(abbreviation: "GMT")! as TimeZone
let dateGet = dFormatter.string(from: getResDate! as Date)
currentSec = Double((getResDate?.timeIntervalSince1970)!)
print("Formatted Hour : \(dateGet)")
let todaySec = Int(currentSec) % 86400
print("Today Sec Hour : \(todaySec)")
let countDownSec = 10800 - todaySec % 10800
// Check status to init
if(countDownSec > 9000) {
self.isSessionActive = true
self.setSessionStarted()
} else {
self.setSessionEnded()
}
if(countDownSec < 300) {
self.setLastFiveMinutes()
self.lastFiveMinutes = true
}
self.totalTime = countDownSec
}
print("Formatted Hour : \(totalTime)")
startTimer()
}
I don't see any code where you count upwards so I am not surprised it isn't working. I would introduce a boolean property isCountingDown to keep track of how to handle the timer and then have two separate methods for up and down
var isCountingDown: boolean
#objc func initCountdown() {
//existing code
isCountingDown = true
startTimer()
}
#objc func updateTime() {
if isCountingDown {
doCountDown()
} else {
doCountUp()
}
}
And then in doCountDown() and doCountUp() I would check against time is 0 or 30 and update isCountingDown accordingly
func doCountDown {
if (totalTime > 10800) {
totalTime = totalTime - 10800
}
totalTime -= 1
if totalTime == 0 {
isCountingDown = false
//Maybe return here?
}
var time = totalTime
//rest of code
}
func doCountUp() {
totalTime += 1
if totalTime == 30 {
isCountingDown = true
//Maybe return here?
}
//rest of code
}