The goal of my code is to call a function every 5 seconds, which it succeeds in doing so. The issue is that when I declare my seconds variable every time, it does not change even though the start date should not be changing.
func timedUpdate()
{
let initialBank = 2000
let dateOfInstallation = UserDefaults.standard.object(forKey: "installDate")
let seconds = date.timeIntervalSince(dateOfInstallation as! Date) / 8.64
let bestStreak = (UserDefaults.standard.integer(forKey: "bestStreak"))
let days = seconds / 86400
print(seconds/8.64)
print(UserDefaults.standard.object(forKey: "bestStreak")!)
if (Int(days) > bestStreak)
{
UserDefaults.standard.set(days, forKey: "bestStreak")
}
bestStreakLabel.text = "\(Int(bestStreak)) Days"
currentStreakLabel.text = "\(Int(days)) Days"
currentStreakBar.progress = CGFloat(Float(days)/Float(bestStreak))
bestStreakBar.progress = 1
getTodaysSteps { [weak self] steps in
self?.bankTotalLabel.text = "\(Int(steps) - Int(seconds/8.64) + Int(initialBank))"
self?.circleLabel.progress = Double(((Double(steps) - Double(seconds) + Double(initialBank))) / 10000)
if (Int(steps) - Int(seconds) + Int(initialBank) < 0)
{
// Will Eventually need a popup screen here to say you lost and streak is reset to 0
self?.resetApp()
}
}
}
When I print the value of seconds, it is always the same decimal, very close to 0. Thank you in advance for the help!
Related
I am trying to write a code where I have got two time[hh:min] data(String type). Need to just compare but the challenge is my code undergones some validations before returning the final values. so the assertion fails sometimes stating expected value is [17:04] but actual is [17:05]. Is there any way where we can use concept of Threshold that upto few minutes (say 2 mins) the comparison will still be valid?
Step one is do not store a thing as something that it is not. If these are times, they should be stored as times. Strings are for representation to the users; underlying storage is for reality.
So now let's store our times as date components:
let t1 = DateComponents(hour:17, minute:4)
let t2 = DateComponents(hour:17, minute:5)
Now it's easy to find out how far apart they are:
let cal = Calendar(identifier: .gregorian)
if let d1 = cal.date(from: t1),
let d2 = cal.date(from: t2) {
let diff = abs(d1.timeIntervalSince(d2))
// and now decide what to do
}
You first need to seprate your string to an array, and then you can compare.
/* That two arrays are A1 and A2 */
let minute1 = Int(A1[0])*60+Int(A1[1])
let minute2 = Int(A2[0])*60+Int(A2[1])
This may help you. I think that #Sweeper did not understand that it is a time, not a date.
You can convert your string to minutes, subtract one from another and check if the absolute value is less than the threshold:
extension String {
var time24hToMinutes: Int? {
guard count == 5, let hours = Int(prefix(2)), let minutes = Int(suffix(2)), Array(self)[2] == ":" else { return nil }
return hours * 60 + minutes
}
func time24hCompare(to other: String, threshold: Int = 2) -> Bool {
guard let lhs = time24hToMinutes, let rhs = other.time24hToMinutes else { return false }
return abs(lhs-rhs) < threshold
}
}
Testing:
"17:02".time24hCompare(to: "17:04") // false
"17:03".time24hCompare(to: "17:04") // true
"17:04".time24hCompare(to: "17:04") // true
"17:05".time24hCompare(to: "17:04") // true
"17:06".time24hCompare(to: "17:04") // false
I am currently building an Instagram clone.
I am quit new so please forgive me if this question is answered easily.
I just want to have a specific score attached to each post.
I already managed to give each new post a score 0f 0 to start with and each time its liked it increases by 100, disliked it decreases by 100.
for every comment it grows by 50 points.
Because I want to order them smartly and want it to show different posts at the top over time I wanted to include a third variable which influences the score.
I want it to decrease the score by -10 each hour since its been uploaded.
The increase and decrease is done in my function incrementLikes() /incrementComments() .
I know that I can't modify the value of score for the time since its uploaded there, but I don't know where else.
My date extension (prob where I can do it?)
extension Date {
func timeAgoDisplay() -> String {
let secondsAgo = Int(Date().timeIntervalSince(self))
let minute = 60
let hour = 60 * minute
let day = hour * 24
let week = day * 7
let month = week * 4
let quotient : Int
let unit: String
if secondsAgo < minute {
quotient = secondsAgo
unit = "Sekunde"
} else if secondsAgo < hour {
quotient = secondsAgo / minute
unit = "Minute"
} else if secondsAgo < day {
quotient = secondsAgo / hour
unit = "Stunde"
} else if secondsAgo < week {
quotient = secondsAgo / day
unit = "Tage"
} else if secondsAgo < month {
quotient = secondsAgo / week
unit = "Woche"
} else {
quotient = secondsAgo / month
unit = "Monat"
}
return "Vor \(quotient) \(unit)\(quotient == 1 ? "" : "n")"
}
}
my function in homeTableViewCell where I set the date
func updateView() {
captionLabel.userHandleLinkTapHandler = { label,string, range in
let mention = String(string.characters.dropFirst())
API.User.observeUserByUsername(username: mention.lowercased(), completion: { (user) in
self.delegate?.goToProfileUserViewController(userId: user.id!)
})
}
guard let count = post?.commentCount else {return}
if count == 0 {
commentButton.setTitle("Schreibe den ersten Kommentar", for: UIControlState.normal)
}else if count == 1 {
commentButton.setTitle("Sieh dir den ersten Kommentar an", for: UIControlState.normal)
} else {
commentButton.setTitle("Alle \(count) Kommentare ansehen", for: UIControlState.normal)
}
let timeAgoDisplay = post?.creationDate?.timeAgoDisplay()
timeLabel.text = timeAgoDisplay
}
thanks for your help :)
The idea of updating the score every hour using a time is based on the idea that the app is constantly running, which seems flawed.
You could instead have something like
var score: Int {
return likes*100 - dislikes*100 + comments*50 - hoursSinceUpload()*10
}
where hoursSinceUpload is computed by something like (see Getting the difference between two NSDates in (months/days/hours/minutes/seconds) for reference)
func hoursSinceUpload() -> Int {
return Calendar.current.dateComponents([.hour], from: uploadDate, to: Date()).hour
}
I have an app that I'm building in Swift that needs to determine if a store/restaurant is currently open. It queries a database that I have control of, so I can set the open hours however I'd like. Currently I have an openTime/closeTime column for each day of the week set as a timestamp. For example: MonOpen = 11:00, MonClose = 19:00.
How can I use swift to determine if the place of business is currently open? I'm imagining something like if currentTime > MonOpen & currentTime < MonClose {...
An example of this is the iOS Starbucks app. If you go to locations, each location is listed with an "Open until 22:00" or "Open until 23:00."
It's just a matter of playing with the timezone, whether you use the user system's timezone or let them choose another one in the app's settings:
let tz = NSTimeZone.defaultTimeZone()
let now = NSCalendar.currentCalendar().componentsInTimeZone(tz, fromDate: NSDate())
if now.weekDay == 2 && now.hour > MonOpen && now.hour < MonClose {
// The store is open
}
I would like to take another crack at this question since we now have Swift 5.1 and most businesses have more complex opening times than just in hours.
import Foundation
// There might be an enum in Swift
// that I did not bother to lookup
enum Days : Int {
case Sun = 1
case Mon = 2
case Tue = 3
case Wed = 4
case Thu = 5
case Fri = 6
case Sat = 7
}
func isOfficeOpenNow(weekSchedule: [Days: (Int, Int)]) -> Bool {
let tz = NSTimeZone.default
let now = NSCalendar.current.dateComponents(in: tz, from: Date())
guard let weekday = now.weekday,
let today = Days(rawValue: weekday),
let hour = now.hour,
let minute = now.minute else {
return false
}
guard let todayTuple = weekSchedule[today] else {
return false // no key, means closed
}
let opensAt = todayTuple.0
let closesAt = todayTuple.1
assert(opensAt < closesAt, "Your schedule is setup wrong.")
let rightNowInMinutes = hour * 60 + minute
return rightNowInMinutes > opensAt &&
rightNowInMinutes < closesAt
}
To use this just define a dictionary for each day.
Key is the dayofweek. Could have used a string "Mon","Tue",etc
but then you will need a mapping or DateFormatter
Value is a tuple (int, int) for (open, close) in minutes
Using minutes, as many stores have more complex open
closing times than just hours
let schedule = [
Days.Mon: (9*60+30, 22*60+30),
Days.Tue: (9*60+30, 23*60+05),
Days.Wed: (9*60+30, 22*60+30),
Days.Thu: (9*60+30, 22*60+30),
Days.Fri: (9*60+30, 22*60+30),
]
if isOfficeOpenNow(weekSchedule: schedule) {
print("Store open")
} else {
print("Store closed")
}
If a specific week is a holiday, just update the schedule for the week and all is good with the world.
If a day is closed, just remove the key from the schedule.
Here's a struct I've written to convert an NSTimeInterval into a walltime-based dispatch_time_t:
public struct WallTimeKeeper {
public static func walltimeFrom(spec: timespec)->dispatch_time_t {
var mutableSpec = spec
let wallTime = dispatch_walltime(&mutableSpec, 0)
return wallTime
}
public static func timeStructFrom(interval: NSTimeInterval)->timespec {
let nowWholeSecsFloor = floor(interval)
let nowNanosOnly = interval - nowWholeSecsFloor
let nowNanosFloor = floor(nowNanosOnly * Double(NSEC_PER_SEC))
println("walltimekeeper: DEBUG: nowNanosFloor: \(nowNanosFloor)")
var thisStruct = timespec(tv_sec: Int(nowWholeSecsFloor),
tv_nsec: Int(nowNanosFloor))
return thisStruct
}
}
I've been trying to test the accuracy of it in a Playground, but my results are confusing me.
Here's the code in my Playground (with my WallTimeKeeper in the Sources folder):
var stop = false
var callbackInterval: NSTimeInterval?
var intendedTime: NSDate?
var intendedAction: ()->() = {}
func testDispatchingIn(thisManySeconds: NSTimeInterval){
intendedTime = NSDate(timeIntervalSinceNow: thisManySeconds)
intendedAction = stopAndGetDate
dispatchActionAtDate()
loopUntilAfterIntendedTime()
let success = trueIfActionFiredPunctually() //always returns false
}
func dispatchActionAtDate(){
let timeToAct = dateAsDispatch(intendedTime!)
let now = dateAsDispatch(NSDate())
/*****************
NOTE: if you run this code in a Playground, comparing the above two
values will show that WallTimeKeeper is returning times the
correct number of seconds apart.
******************/
dispatch_after(timeToAct, dispatch_get_main_queue(), intendedAction)
}
func loopUntilAfterIntendedTime() {
let afterIntendedTime = intendedTime!.dateByAddingTimeInterval(1)
while stop == false && intendedTime?.timeIntervalSinceNow > 0 {
NSRunLoop.currentRunLoop().runMode(NSDefaultRunLoopMode,
beforeDate: afterIntendedTime)
}
}
func trueIfActionFiredPunctually()->Bool{
let intendedInterval = intendedTime?.timeIntervalSinceReferenceDate
let difference = intendedInterval! - callbackInterval!
let trueIfHappenedWithinOneSecondOfIntendedTime = abs(difference) < 1
return trueIfHappenedWithinOneSecondOfIntendedTime
}
func dateAsDispatch(date: NSDate)->dispatch_time_t{
let intendedAsInterval = date.timeIntervalSinceReferenceDate
let intendedAsStruct = WallTimeKeeper.timeStructFrom(intendedAsInterval)
let intendedAsDispatch = WallTimeKeeper.walltimeFrom(intendedAsStruct)
return intendedAsDispatch
}
func stopAndGetDate() {
callbackInterval = NSDate().timeIntervalSinceReferenceDate
stop = true
}
testDispatchingIn(3)
...so not only doestrueIfActionFiredPunctually() always returns false, but the difference value--intended to measure the difference between the time the callback fired and the time it was supposed to fire--which in a successful result should be really close to 0, and certainly under 1--instead comes out to be almost exactly the same as the amount of time the callback was supposed to wait to fire.
In summary: an amount of time to wait is defined, and an action is set to fire after that amount of time. When the action fires, it creates a timestamp of the moment it fired. When the timestamp is compared to the value it should be, instead of getting close to zero, we get close to the amount of time we were supposed to wait.
In other words, it appears as if the action passed to dispatch_after is firing immediately, which it absolutely shouldn't!
Is this something wrong with Playgrounds or wrong with my code?
EDIT:
It's the code. Running the same code inside a live app gives the same result. What am I doing wrong?
I figured it out. It's a head-smacker. I'll leave it up in case anyone is having the same problem.
I was using NSDate().timeIntervalSinceReferenceDate to set my walltimes.
Walltimes require NSDate().timeIntervalSince1970!
The dispatch_after tasks all fired instantly because they thought they were scheduled for over forty years ago!
Changing everything to NSDate().timeIntervalSince1970 makes it work perfectly.
Moral: don't use walltimes unless you're sure your reference date is 1970!
The question is related to calculating an increase in currency.
Loop over this n times, and let's say you start with $50k and your multiplier is 2. Something like b * 2 + a
This is the correct result:
$50,000.00
$100,000.00
$250,000.00
$600,000.00
$1,450,000.00
$3,500,000.00
$8,450,000.00
$20,400,000.00
$49,250,000.00
So just to be clear, the question is about efficiency in swift, not simply how to calculate this. Are there any handy data structures that would make this faster? Basically I was just looping through how many years (n) adding 2 (200%) and incrementing a couple temp variables to keep track of the current and previous values. It feels like there has got to be a much better way of handling this.
$50k base
$50k * 2 + 0 (previous value) = $100k
$100k * 2 + $50k = $250k
$250k * 2 + $100k = $600k
etc.
Code:
let baseAmount = 50000.0
let percentReturn = 200.0
let years = 10
// Calc decimal of percent.
var out: Double = 0.0
var previous: Double = 0.0
let returnPercent = percentReturn * 0.01
// Create tmp array to store values.
var tmpArray = [Double]()
// Loop through years.
for var index = 0; index < years; ++index
{
if index == 0
{
out = baseAmount
tmpArray.append(baseAmount)
}
else if index == 1
{
out = (out * returnPercent)
tmpArray.append(out)
previous = baseAmount
}
else
{
let tmp = (tmpArray.last! * returnPercent) + previous
previous = tmpArray.last!
tmpArray.append(tmp)
}
}
println(tmpArray)
Here are some ideas for improving efficiency:
Initialize your array to the appropriate size (it isn't dynamic; it is always the number of years)
Remove special cases (year 0 and 1 calculations) from the for-loop
Code:
func calculate(baseAmount: Double, percentReturn: Double, years: Int) -> [Double] {
// I prefer to return an empty array instead of nil
// so that you don't have to check for nil later
if years < 1 {
return [Double]()
}
let percentReturnAsDecimal = percentReturn * 0.01
// You know the size of the array, no need to append
var result = [Double](count: years, repeatedValue: 0.0)
result[0] = baseAmount
// No need to do this in the loop
if years > 1 {
result[1] = baseAmount * percentReturnAsDecimal
}
// Loop through years 2+
for year in 2 ..< years {
let lastYear = result[year - 1]
let yearBeforeLast = result[year - 2]
result[year] = (lastYear * percentReturnAsDecimal) + yearBeforeLast
}
return result
}
Efficiency in terms of speed I found this to be the fastest implementation of your algorithm:
let baseAmount = 50000.0
let returnPercent = 2.0
let years = 10
// you know the size of the array so you don't have to append to it and just use the subscript which is much faster
var array = [Double](count: years, repeatedValue: 0)
var previousValue = 0.0
var currentValue = baseAmount
for i in 0..<years {
array[i] = currentValue
let p2 = currentValue
currentValue = currentValue * returnPercent + previousValue
previousValue = p2
}
print(array)