SwiftUI Schedule Countdown Timer - Pause & Start - timer

I'm trying to make Clock(actual time) / Timer (CountDown) to my app.. I know my solution is kind of rough, but everything works kind of ok BUT. When I pause my Countdown and then start it again - In the first time Counter adds time (for like a second) that passed since i press Pause button and afterwards loads my timeCounter value that i saved and continues counting down.. Why? Also if you have better smoother solution I will be very pleased.. THANKS!
import SwiftUI
struct Clock : View {
#State private var nowDate: Date = Date()
#State private var referenceDate: Date = Date()
#State private var display: Bool = true
#State private var updateTimer: Timer?
#State private var timeCounter: Double = 0
#State private var timerRunning: Bool = false
var body: some View {
VStack {
Button(action: {
self.display.toggle()
}) {
Text(display ? "COUNTDOWN" : "TIME")
}
Text(display ? "\(timeString(date: nowDate))" : "\(countDownString(for: referenceDate))")
HStack {
Button(action: {
self.display = false
self.timeCounter = 10
self.referenceDate = Date(timeIntervalSinceNow: self.timeCounter)
}) {
Text("10")
}
}
Button(action: {
if self.timerRunning {
self.referenceDate = Date(timeIntervalSinceNow: self.timeCounter)
self.startTimer()
self.timerRunning.toggle()
} else {
self.updateTimer?.invalidate()
self.timerRunning.toggle()
}
}) {
Text(timerRunning ? "START" : "PAUSE")
}
}
.onAppear {
self.startTimer()
}
}
//MARK: Timer
func startTimer() {
self.updateTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
self.nowDate = Date()
if self.referenceDate.distance(to: self.nowDate) >= 0 {
self.referenceDate = Date(timeIntervalSinceNow: 0)
self.display = true
}
self.timeCounter -= 1
}
}
//MARK: - CountDown Timer
func countDownString(for date: Date) -> String {
let calendar = Calendar(identifier: .gregorian)
let components = calendar
.dateComponents([.hour, .minute, .second],
from: nowDate,
to: referenceDate)
return String(format: "%02d:%02d:%02d",
components.hour ?? 00,
components.minute ?? 00,
components.second ?? 00)
}
//MARK: - Clock Timer
var timeFormat: DateFormatter {
let formatter = DateFormatter()
formatter.dateFormat = "HH:mm:ss"
return formatter
}
func timeString(date: Date) -> String {
let time = timeFormat.string(from: date)
return time
}
}

Ive seen lots of complicated ways to do this on the net.
New in iOS 14:
let closeDate = Date(timeIntervalSinceNow: 60.0)
Text(closeDate, style: .relative)

maybe you need Timer publisher
https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-a-timer-with-swiftui

Related

How to stop multiple instances of AVAudioPlayer?

There are multiple classes in which the AVAudioPlayer? instances are created like so
var player: AVAudioPlayer?
var player2: AVAudioPlayer?
Now is there a way I can stop these multiple instances in a different class , tried to create a function and stop all sounds but it does not work , can any one suggest a way to stop all sounds in app, but also one that I can disable at viewWillappear so its not permanently stop all sound
func stopAllSounds() {
var demoView1 = Demo1ViewController()
var player1 = demoView1.player
var demoView2 = Demo2ViewController()
var player2 = demoView2.player
var demoView3 = Demo3ViewController()
var player3 = demoView3.player
var allPlayer: [AVAudioPlayer?] = [player1, player2, player3]
for stopData in allPlayer {
stopData?.stop()
stopData?.currentTime = 0
}
}
The function I call on viewDidload
func playSound() {
guard let path = Bundle.main.path(forResource: "demosound", ofType:"wav") else {
return }
let url = URL(fileURLWithPath: path)
do {
player = try AVAudioPlayer(contentsOf: url)
player?.volume = 1.0
player?.play()
Utility.avPLayers.append(player!) <—— add the instance
} catch let error {
print(error.localizedDescription)
}
}

Filtering events by date in Swift4

I'm building an application that provides autistic children with a very simple visual calendar based on scheduled events. So far, I've had no major issues and my events are filtering correctly when the first one is added for each recurrence frequency (daily, weekly, monthly, yearly).
However, for some reason, when I attempt to add a second event with a recurrence frequency that matches any events previously added, it will not populate.
I have attempted to debug and narrow down the issue myself to no avail. I'm sure I'm missing something extremely simple. Thank you in advance for any assistance tracking this down.
This function filters the entire list of dates by date:
func filterEvents() {
filteredEvents = []
tempEvents = []
dates = []
removeOldEvents()
generateRecurrances()
extractDates()
var currentIndex = 0
for date in dates {
if filteredEvents.count == 0 {
filteredEvents.append([])
}
else if filteredEvents.count != currentIndex + 1 {
filteredEvents.append([])
}
filteredEvents[currentIndex] = tempEvents.filter({ $0.filterDate() == date })
currentIndex += 1
}
}
This function is used to extract the necessary information to filter by date:
func extractDates() {
for event in tempEvents {
let extractedDate: (month: Int, day: Int, year: Int) = (event.month(), event.day(), event.year())
dates.append(extractedDate)
}
var currentIndex = 0
for date in dates {
if currentIndex != 0 {
if date.month == dates[currentIndex - 1].month && date.day == dates[currentIndex - 1].day && date.year == dates[currentIndex - 1].year {
dates.remove(at: currentIndex)
currentIndex -= 1
}
}
currentIndex += 1
}
}
This function generates recurring future events based on the user's preference when creating the original event:
func generateRecurrances() {
var dateComponent = DateComponents()
var daysToAdd = 0
var weeksToAdd = 0
var monthsToAdd = 0
var yearsToAdd = 0
for event in events {
if event.recurrenceFrequency == 1 {
for _ in 1...60 {
dateComponent.day = daysToAdd
dateComponent.month = 0
dateComponent.year = 0
let newDate = Calendar.current.date(byAdding: dateComponent, to: event.date)
tempEvents.append(Event(name: event.name, date: newDate!, image: event.image, completion: event.requiresCompletion, recurrenceFrequency: event.recurrenceFrequency))
daysToAdd += 1
}
}
else if event.recurrenceFrequency == 2 {
for _ in 1...8 {
dateComponent.day = weeksToAdd
dateComponent.month = 0
dateComponent.year = 0
let newDate = Calendar.current.date(byAdding: dateComponent, to: event.date)
tempEvents.append(Event(name: event.name, date: newDate!, image: event.image, completion: event.requiresCompletion, recurrenceFrequency: event.recurrenceFrequency))
weeksToAdd += 7
}
}
else if event.recurrenceFrequency == 3 {
for _ in 1...2 {
dateComponent.day = 0
dateComponent.month = monthsToAdd
dateComponent.year = 0
let newDate = Calendar.current.date(byAdding: dateComponent, to: event.date)
tempEvents.append(Event(name: event.name, date: newDate!, image: event.image, completion: event.requiresCompletion, recurrenceFrequency: event.recurrenceFrequency))
monthsToAdd += 1
}
}
else if event.recurrenceFrequency == 4 {
for _ in 1...2 {
dateComponent.day = 0
dateComponent.month = 0
dateComponent.year = yearsToAdd
let newDate = Calendar.current.date(byAdding: dateComponent, to: event.date)
tempEvents.append(Event(name: event.name, date: newDate!, image: event.image, completion: event.requiresCompletion, recurrenceFrequency: event.recurrenceFrequency))
yearsToAdd += 1
}
}
else if event.recurrenceFrequency == 0 {
tempEvents.append(event)
}
}
daysToAdd = 0
}
I really don't think this is related but, just for safety, here is the function that is used to automatically remove events with a date prior to the current date:
func removeOldEvents() {
var currentIndex = 0
let now = Event(name: "Now", date: Date(), image: #imageLiteral(resourceName: "Logo"), completion: false, recurrenceFrequency: 0)
for event in events {
if event.year() < now.year() {
events.remove(at: currentIndex)
currentIndex -= 1
}
else if event.month() < now.month() {
events.remove(at: currentIndex)
currentIndex -= 1
}
else if event.day() < now.day() {
events.remove(at: currentIndex)
currentIndex -= 1
}
currentIndex += 1
}
}
This modification to extractDates() has resolved this issue. Thank you for pointing me in the right direction.
func extractDates() {
for event in tempEvents {
dates.append(event.date)
}
var currentIndex = 0
for date in dates {
let newDate = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: date)!
dates[currentIndex] = newDate
print(dates[currentIndex])
currentIndex += 1
}
var tempDates = dates.removingDuplicates()
tempDates = tempDates.sorted()
dates = tempDates
for date in dates {
let calendar = Calendar.current
let year = calendar.component(.year, from: date)
let month = calendar.component(.month, from: date)
let day = calendar.component(.day, from: date)
dateComponents.append((month, day, year))
}
}

Face detection swift vision kit

I am trying Vision kit for iOS 11. I can use Vision and I can find boundbox values face. But I don't know how can I draw a rectangle using this points. I hope so my question is clear.
Hope you were able to use VNDetectFaceRectanglesRequest and able to detect faces. To show rectangle boxes there are lots of ways to achieve it. But simplest one would be using CAShapeLayer to draw layer on top your image for each face you detected.
Consider you have VNDetectFaceRectanglesRequest like below
let request = VNDetectFaceRectanglesRequest { [unowned self] request, error in
if let error = error {
// somthing is not working as expected
}
else {
// we got some face detected
self.handleFaces(with: request)
}
}
let handler = VNImageRequestHandler(ciImage: ciImage, options: [:])
do {
try handler.perform([request])
}
catch {
// catch exception if any
}
You can implement a simple method called handleFace for each face detected and use VNFaceObservation property to draw a CAShapeLayer.
func handleFaces(with request: VNRequest) {
imageView.layer.sublayers?.forEach { layer in
layer.removeFromSuperlayer()
}
guard let observations = request.results as? [VNFaceObservation] else {
return
}
observations.forEach { observation in
let boundingBox = observation.boundingBox
let size = CGSize(width: boundingBox.width * imageView.bounds.width,
height: boundingBox.height * imageView.bounds.height)
let origin = CGPoint(x: boundingBox.minX * imageView.bounds.width,
y: (1 - observation.boundingBox.minY) * imageView.bounds.height - size.height)
let layer = CAShapeLayer()
layer.frame = CGRect(origin: origin, size: size)
layer.borderColor = UIColor.red.cgColor
layer.borderWidth = 2
imageView.layer.addSublayer(layer)
}
}
More info can be found here in Github repo iOS-11-by-Examples
Here is easy and simple way to draw boxes.
let faceRequest = VNDetectFaceRectanglesRequest(completionHandler:self.faceDetection)
func faceDetection (request: VNRequest, error: Error?) {
guard let observations = request.results as? [VNFaceObservation]
else { print("unexpected result type from VNFaceObservation")
return }
guard observations.first != nil else {
return
}
// Show the pre-processed image
DispatchQueue.main.async {
self.resultImageView.subviews.forEach({ (subview) in
subview.removeFromSuperview()
})
for face in observations
{
let view = self.CreateBoxView(withColor: UIColor.red)
view.frame = self.transformRect(fromRect: face.boundingBox, toViewRect: self.analyzedImageView)
self.analyzedImageView.image = self.originalImageView.image
self.resultImageView.addSubview(view)
}
}
}
//MARK - Instance Methods
func boxView(withColor : UIColor) -> UIView {
let view = UIView()
view.layer.borderColor = withColor.cgColor
view.layer.borderWidth = 2.0
view.backgroundColor = UIColor.clear
return view
}
//Convert Vision Frame to UIKit Frame
func transformRect(fromRect: CGRect , toViewRect :UIView) -> CGRect {
var toRect = CGRect()
toRect.size.width = fromRect.size.width * toViewRect.frame.size.width
toRect.size.height = fromRect.size.height * toViewRect.frame.size.height
toRect.origin.y = (toViewRect.frame.height) - (toViewRect.frame.height * fromRect.origin.y )
toRect.origin.y = toRect.origin.y - toRect.size.height
toRect.origin.x = fromRect.origin.x * toViewRect.frame.size.width
return toRect
}

'inout [int]' (aka 'inout Array<int>') is not convertible to 'Array<Element>'

The code is to store activity data into a bar chart, however, the last line is flagging up this error of which I have never come across.
Code:
var days:[String] = []
var stepsTaken:[Int] = []
let activityManager = CMMotionActivityManager()
let pedoMeter = CMPedometer()
let formatter = NSDateFormatter()
formatter.dateFormat = "d MMM"
dispatch_sync(serialQueue, { () -> Void in
let today = NSDate()
for day in 0...6{
let fromDate = NSDate(timeIntervalSinceNow: Double(-7+day) * 86400)
let toDate = NSDate(timeIntervalSinceNow: Double(-7+day+1) * 86400)
let dtStr = formatter.stringFromDate(toDate)
self.pedoMeter.queryPedometerDataFromDate(fromDate, toDate: toDate) { data, error in
if let data = data {
if(error == nil){
print("\(dtStr) : \(data.numberOfSteps)")
self.days.append(dtStr)
self.stepsTaken.append(Int(data.numberOfSteps))
print("Days :\(self.days)")
print("Steps :\(self.stepsTaken)")
if(self.days.count == 7){
dispatch_sync(dispatch_get_main_queue(), { () -> Void in
let xVals = self.days
var yVals: [BarChartDataEntry] = []
for idx in 0...6 {
yVals.append(BarChartDataEntry(value: Float(self.stepsTaken[idx]), xIndex: idx))
}

Swift array. trying to find the next time in array based on current time

I have the current time and this array of times. I would like to work out which is the closest next time to the current time.
let date = NSDate()
let calendar = NSCalendar.currentCalendar()
let components = calendar.components([.Hour, .Minute], fromDate: date)
let hour = components.hour
let minutes = components.minute
let currentTime = "\(hour)" + ":" + "\(minutes)" //output 15:24
let timesArray = ["5:45", "6:35", "7:00", "7:30", "7:50", "8:20", "8:40", "9:15", "10:10", "11:10", "12:40", "14:15", "14:50", "15:40", "16:10", "17:10", "17:40", "18:40", "19:25", "20:50"]
let date = NSDate()
let timesArray = ["5:45", "6:35", "7:00", "7:30", "7:50", "8:20", "8:40", "9:15", "10:10", "11:10", "12:40", "14:15", "14:50", "15:40", "16:10", "17:10", "17:40", "18:40", "19:25", "20:50"]
// create a method to convert your time to minutes
func stringToMinutes(input:String) -> Int {
let components = input.componentsSeparatedByString(":")
let hour = Int((components.first ?? "0")) ?? 0
let minute = Int((components.last ?? "0")) ?? 0
return hour*60 + minute
}
//create an array with the minutes from the original array
let timesMinutesArray:[Int] = timesArray.map { stringToMinutes($0) }
let dayMinute = stringToMinutes("15:24")
// filter out the times that has already passed
let filteredTimesArray = timesMinutesArray.filter{$0 > dayMinute }
// get the first time in your array
if let firstTime = filteredTimesArray.first {
// find its position and extract it from the original array
let item = timesArray[timesMinutesArray.indexOf(firstTime)!] // "15:40"
}
If you need you can also create an extension to use it anywhere in your code as follow:
extension String {
var hourMinuteValue: (hour:Int,minute:Int) {
guard
let hour = Int(componentsSeparatedByString(":").first ?? ""),
let minute = Int(componentsSeparatedByString(":").last ?? "")
where componentsSeparatedByString(":").count == 2
else { return (0,0) }
return (hour,minute)
}
var dayMinute:Int {
return hourMinuteValue.hour*60 + hourMinuteValue.minute
}
}
let time = "15:24"
let times = ["5:45", "6:35", "7:00", "7:30", "7:50", "8:20", "8:40", "9:15", "10:10", "11:10", "12:40", "14:15", "14:50", "15:40", "16:10", "17:10", "17:40", "18:40", "19:25", "20:50"]
let minutes = times.map{ $0.dayMinute }
let filteredMinutes = minutes.filter{ $0 > time.dayMinute }
if let firstTime = filteredMinutes.first {
let item = times[minutes.indexOf(firstTime)!] // "15:40"
}

Resources