How to add mutiple progress views to a timer? - ios

here is all of the code that I am using for this project. I have four progress views and when the first end an alarm goes off and then the next start eminently and then goes to the next and then to the fourth one where the timer ends after that and the timer goes off one more time and then the timers stop.
import UIKit
import AVFoundation
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
#IBAction func btnPressed4(_ sender: Any) {
let currentDateTime = Date()
let formatter = DateFormatter()
formatter.timeStyle = .short
let dateTimeString = formatter.string(from: currentDateTime)
timePrint4.text = dateTimeString
btnPressed4.titleLabel?.textColor = UIColor.white
}
#IBAction func btnPressed3(_ sender: Any) {
let currentDateTime = Date()
let formatter = DateFormatter()
formatter.timeStyle = .short
let dateTimeString = formatter.string(from: currentDateTime)
timePrint3.text = dateTimeString
btnPressed3.titleLabel?.textColor = UIColor.white
}
#IBAction func btnPressed2(_ sender: UIButton) {
let currentDateTime = Date()
let formatter = DateFormatter()
formatter.timeStyle = .short
let dateTimeString = formatter.string(from: currentDateTime)
timePrint2.text = dateTimeString
btnPressed2.titleLabel?.textColor = UIColor.green
}
#IBAction func btnPressed1(_ sender: UIButton) {
let currentDateTime = Date()
let formatter = DateFormatter()
formatter.timeStyle = .short
let dateTimeString = formatter.string(from: currentDateTime)
timePrint1.text = dateTimeString
}
#IBOutlet weak var btnPressed1: UIButton!
#IBOutlet weak var btnPressed2: UIButton!
#IBOutlet weak var btnPressed3: UIButton!
#IBOutlet weak var btnPressed4: UIButton!
#IBOutlet weak var timePrint1: UILabel!
#IBOutlet weak var timePrint2: UILabel!
#IBOutlet weak var timePrint4: UILabel!
#IBOutlet weak var timePrint3: UILabel!
#IBOutlet weak var titleLabel: UILabel!
#IBOutlet weak var progressBar1: UIProgressView!
#IBOutlet weak var progressBar2: UIProgressView!
#IBOutlet weak var progressBar3: UIProgressView!
#IBOutlet weak var progressBar4: UIProgressView!
let start = 5
var timer = Timer()
var player: AVAudioPlayer!
var totalTime = 0
var secondsPassed = 0
#IBAction func startButtonPressed(_ sender: UIButton) {
let startB = sender.titleLabel?.text
totalTime = start
progressBar1.progress = 0.0
secondsPassed = 0
titleLabel.text = "coffee timer"
btnPressed1.titleLabel?.textColor = UIColor.white
timer = Timer.scheduledTimer(timeInterval: 1.0, target:self, selector: #selector(updateTimer), userInfo:nil, repeats: true)
}
#objc func updateTimer() {
if secondsPassed < totalTime {
secondsPassed += 1
progressBar1.progress = Float(secondsPassed) / Float(totalTime)
print(Float(secondsPassed) / Float(totalTime))
} else {
timer.invalidate()
titleLabel.text = "check coffee"
btnPressed1.titleLabel?.textColor = UIColor.green
let url = Bundle.main.url(forResource: "alarm_sound", withExtension: "mp3")
player = try! AVAudioPlayer(contentsOf: url!)
player.play()
}
}
}

There's lots of different ways to handle this. Here is an outline of a possible approach:
Have an instance variable currentProgress, of type Int? If it's nil, none of your progress indicators is running.
Have an instance var stepCompletedInterval: Double
Create a struct to hold the different views you use to manage a progress step:
struct ProgressInfo {
let aButton: UIButton
let aLabel: UILabel
let aProgressIndicator: UIProgressIndicator
let secondsForThisStep: Int
}
Create an array of ProgressInfo, (let's call it progressInfoArray) and populate it with the buttons, labels, and progress indicators you have above. Also give each entry a value for secondsForThisStep.
When the user starts the process, set currentProgress to 0 (The first progress indicator.)
Now, for each step, set a stepCompletedInterval to the current time plus progressInfoArray[currentProgress].secondsForThisStep (The time at which the current step should complete.
Also start a 1-second repeating timer. Each time the timer fires, check to see if the current time is greater than stepCompletedInterval. If it is, increment currentProgress if there are still more steps to complete, and repeat the "now for each step" part above.
That is a rough outline of how you might go about it. I'm not completely clear on what your start buttons and btnPressed1 to btnPressed4 are supposed to do, so I'll leave that for you.
You'll need to adapt the approach I've outlined to your needs. It isn't code, it's an approach. I'm not going to give you code.

Related

IBOutlet UIView causing current VC to freeze before UIUpdates are made

Problem:
UIView updates cause MainViewController to freeze until UI changes are updated. Can I avoid the freezing of the MainViewController using dispatchAsync or is this completely set up wrong?
I have a MainViewController that has a UIView connected as an IBOutlet via storyboard.
class MainViewController: UITableViewController {
#IBOutlet weak var headerView: MettaHeaderView!
func loadCustomView() {
DispatchQueue.main.async {
self.headerView.populateViews()
}
}
}
The UIView Class is set up like this
class MettaHeaderView: UIView {
//MARK: - Properties
weak var delegate: MettaTableVC!
let dataStore = DataStore.shared
//MARK: - Outlets
#IBOutlet weak var cardView: CurvedUIView!
#IBOutlet weak var dayStack: UIStackView!
#IBOutlet weak var plusBtn: UIButton!
#IBOutlet weak var segmentedControl: UISegmentedControl!
#IBOutlet weak var streakLbl: UILabel!
#IBOutlet weak var todayImg: UIImageView!
#IBOutlet weak var todayLbl: UILabel!
#IBOutlet weak var yesterdayImg: UIImageView!
#IBOutlet weak var yesterdayLbl: UILabel!
#IBOutlet weak var twoDayAgoImg: UIImageView!
#IBOutlet weak var twoDayAgoLbl: UILabel!
#IBOutlet weak var threeDayAgoImg: UIImageView!
#IBOutlet weak var threeDayAgoLbl: UILabel!
#IBOutlet weak var fourDayAgoImg: UIImageView!
#IBOutlet weak var fourDayAgoLbl: UILabel!
#IBOutlet weak var fiveDayAgoImg: UIImageView!
#IBOutlet weak var fiveDayAgoLbl: UILabel!
#IBOutlet weak var sixDayAgoImg: UIImageView!
#IBOutlet weak var sixDayAgoLbl: UILabel!
//MARK: - Override Methods
override func layoutMarginsDidChange() {
super.layoutMarginsDidChange()
self.layoutIfNeeded()
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.endEditing(true)
}
//MARK: - Methods
func initializeHeader(with delegate: MettaTableVC) {
self.delegate = delegate
styleCardView()
let font = UIFont.systemFont(ofSize: 12)
let selectedFont = UIFont.systemFont(ofSize: 12, weight: .medium)
segmentedControl.setTitleTextAttributes([NSAttributedString.Key.font: font],
for: .normal)
segmentedControl.setTitleTextAttributes([NSAttributedString.Key.font: selectedFont],
for: .selected)
}
func styleCardView() {
self.layoutIfNeeded()
self.layoutSubviews()
cardView.curvedPercent = 0.17
setFontSize()
}
func populateViews() {
let totalDays = self.totalDays()
let dayString = totalDays == 1 ? "Day" : "Days"
self.streakLbl.text = "\(totalDays) \(dayString) with a Session"
self.setDayLbls()
self.setDayImages(with: dataStore.sessions)
}
func totalDays() -> Int {
let grouped = dataStore.sessions.group { $0.dateString }
return grouped.count
}
func setStreak(with sessions: [MeditationSession]) {
streakLbl.text = "\(currentStreak(from: sessions)) day streak"
}
func currentStreak(from sessions: [MeditationSession]) -> Int {
var streak = 0
var lastDate: Date?
let formatter = DateFormatter()
formatter.dateFormat = "MM/dd/yyyy"
for sesh in sessions {
let date = sesh.date.dateValue()
// Check if first day check complete yet
if let last = lastDate {
let difference = daysBetween(start: date, end: last)
// Check if the same day
if difference == 0 {
continue
}
// Check if day before
if difference == 1 {
lastDate = date
streak += 1
} else {
return streak
}
// If first go through
} else {
let difference = daysBetween(start: date, end: Date())
// Check if today
if difference == 0 {
lastDate = date
streak += 1
continue
}
// Check if day before
if difference == 1 {
lastDate = date
streak += 1
} else {
return streak
}
}
}
return streak
}
func daysBetween(start: Date, end: Date) -> Int {
let cal = Calendar(identifier: .gregorian)
let date1 = cal.startOfDay(for: start)
let date2 = cal.startOfDay(for: end)
return Calendar.current.dateComponents([.day], from: date1, to: date2).day!
}
func setDayLbls() {
let lbls: [UILabel] = [sixDayAgoLbl, fiveDayAgoLbl, fourDayAgoLbl, threeDayAgoLbl, twoDayAgoLbl, yesterdayLbl, todayLbl]
let formatter = DateFormatter()
formatter.dateFormat = "EEEE"
let todayString = formatter.string(from: Date())
guard let today = Day(rawValue: todayString.lowercased()) else { return }
let week = past7Days(from: today)
for (index, val) in week.enumerated() {
lbls[index].text = val
}
}
func setFontSize() {
let labels: [UILabel] = [sixDayAgoLbl, fiveDayAgoLbl, fourDayAgoLbl, threeDayAgoLbl, twoDayAgoLbl, yesterdayLbl, todayLbl]
labels.forEach {
let size: CGFloat = ($0.superview?.frame.width)! * 0.42
$0.font = $0.font.withSize(size)
}
}
func past7Days(from day: Day) -> [String] {
var dayStrings: [String] = []
var currentIndex: Int = day.order
let days = Day.allCasesOrdered
for _ in 0..<7 {
print("here")
let day = days[currentIndex]
dayStrings.append(day.letter)
if currentIndex > 0 {
currentIndex -= 1
} else {
currentIndex = 6
}
}
return dayStrings.reversed()
}
func setDayImages(with sessions: [MeditationSession]) {
let dayImgViews: [UIImageView] = [todayImg, yesterdayImg, twoDayAgoImg, threeDayAgoImg, fourDayAgoImg, fiveDayAgoImg, sixDayAgoImg]
var date: Date = Date()
for index in 0...6 {
print(index)
let filtered = sessions.filter { $0.dateString == date.dateString() }
dayImgViews[index].image = filtered.count == 0 ? #imageLiteral(resourceName: "Lotus_Monochromatic") : #imageLiteral(resourceName: "Lotus")
date = Calendar.current.date(byAdding: .day, value: -1, to: date)!
}
}
Calling the function below in the MainViewController freezes the entire VC until the function completes. Is there a way I can avoid this or fix my problem?
self.headerView.populateViews()
Freeze:
Meaning the entire screen is frozen until that function completes.
So I have found that the issue is because of this function but im not sure why the freeze is happening:
func setDayImages(with sessions: [MeditationSession]) {
let dayImgViews: [UIImageView] = [headerView.todayImg, headerView.yesterdayImg, headerView.twoDayAgoImg, headerView.threeDayAgoImg, headerView.fourDayAgoImg, headerView.fiveDayAgoImg, headerView.sixDayAgoImg]
var date: Date = Date()
for index in 0...6 {
print(index)
let filtered = sessions.filter { $0.dateString == date.dateString() }
dayImgViews[index].image = filtered.count == 0 ? #imageLiteral(resourceName: "Lotus_Monochromatic") : #imageLiteral(resourceName: "Lotus")
date = Calendar.current.date(byAdding: .day, value: -1, to: date)!
}
}
The function outlined at the end func setDayImages(with sessions: [MeditationSession]) inserts images inside UIImageViews, which means each image needs to be loaded into memory as a Bitmap, this is quite a heavy operation and you're running it on the Main thread in your MainViewController.
My solution would be to load the images into UIImage() outside of the main thread and then assign them on the main thread later. And see if that improves performance. And in general it is advised that calculations unrelated to the UI be performed outside of the main thread using GCD (DispatchQueue.global(.userInitiated).async for example).
This is my best guess at least without additional performance diagnostics.

Decrease the value of the variable containing the current time with UISlider

I don't know how to make the current time value (returned by the dateFormatter variable) change when scrolling the slider.
I tried to convert the dateFormatter variable to double but without success.
var c: Double
c = Double(dateFormatter.string(from: Date())
I would like to decrease the value of the time -1 hour for each shift of the slider thumb.
The changeTime function has to take care of changing the time value when moving the slider.
#IBOutlet weak var sliderTime: UISlider!
#IBOutlet var labelTime: UILabel!
var timer = Timer()
var dateFormatter = DateFormatter()
override func viewDidLoad()
{
dateFormatter.timeStyle = .short
labelTime.text = dateFormatter.string(from: Date())
timer = Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(updateTimeLabel), userInfo: nil, repeats: true);
}
#objc func updateTimeLabel() {
labelTime.text = dateFormatter.string(from: Date())
}
#IBAction func changeTime(_ sender: Any) {
}
Swift 5
Whenever you update the date, decrease the number of hours dedicated by slider from the current date, try this:
class ViewController: UIViewController
{
#IBOutlet weak var simpleSlider: UISlider!
#IBOutlet weak var dateLabel: UILabel!
var timer = Timer()
var currentSliderStep: Int = 0
var dateFormatter = DateFormatter()
override func viewDidLoad()
{
dateFormatter.timeStyle = .short
labelTime.text = dateFormatter.string(from: Date())
timer = Timer.scheduledTimer(timeInterval: 60, target: self, selector: #selector(updateTimeLabel), userInfo: nil, repeats: true);
}
#objc func updateTimeLabel()
{
let newDate = Calendar.current.date(byAdding: .hour, value: -currentSliderStep, to: Date())!
labelTime.text = dateFormatter.string(from: newDate)
}
#IBAction func sliderMoved(sender: AnyObject)
{
let value = simpleSlider.value
let step: Float = 1
let roundedValue = round(value / step) * step
currentSliderStep = Int(roundedValue)
}
}
Note: With the Slider, the "Primary Action Triggered" event is triggered when it is adjusted. This is the perfect event to handle (link this event to sliderMoved).

Hide keyboard in view after inputting values?

I have a small calculator built into 3 different views. On all the calcuulators, I would like to have the keyboard disappear upon either tapping anywhere on the screen or upon pressing the button to calucate a value. Ive researched several sources and have become confused on how to implement any code for this. My calculator code is below. Thanks in advance!
class Calculators: UIViewController {
#IBOutlet var fluidIn: UITextField!
#IBOutlet var timeIn: UITextField!
#IBOutlet var dripIn: UITextField!
#IBOutlet var dripResult: UILabel!
#IBOutlet var weight: UITextField!
#IBOutlet var etomidate: UILabel!
#IBOutlet var succ: UILabel!
#IBOutlet var roc: UILabel!
#IBOutlet var ketamine: UILabel!
#IBOutlet var lbsIn: UITextField!
#IBOutlet var KgIn: UITextField!
#IBOutlet var KgOut: UILabel!
#IBOutlet var LbsOut: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func calculate(sender: UIButton) {
let Number2 = Double(weight.text!)
let etommult = (0.3)
let etomresult = Number2! * etommult
etomidate.text = "\(etomresult)"
etomidate.text = NSString(format:"%2.1f mg",etomresult)as String;
let Number3 = Double(weight.text!)
let succmult = (2.0)
let succresult = Number3! * succmult
succ.text = "\(succresult)"
succ.text = NSString(format: "%2.1f mg",succresult)as String;
let Number4 = Double(weight.text!)
let rocmult = (1.0)
let rocresult = Number4! * rocmult
roc.text = "\(rocresult)"
roc.text = NSString(format: "%2.1f mg",rocresult)as String;
let Number5 = Double(weight.text!)
let fentmult = (2.0)
let fentresult = Number5! * fentmult
ketamine.text = "\(fentresult)"
ketamine.text = NSString(format: "%2.1f mg",fentresult)as String;
}
#IBAction func KgConv(_ sender: Any) {
let kg = Double(lbsIn.text!)
let kgmult = (2.2)
let kgresult = kg! / kgmult
KgOut.text = "\(kgresult)"
KgOut.text = NSString(format: "%2.1f kg",kgresult)as String;
}
#IBAction func LbsConv(_ sender: Any) {
let lbs = Double(KgIn.text!)
let lbsmult = (2.2)
let lbsresult = lbs! * lbsmult
LbsOut.text = "\(lbsresult)"
LbsOut.text = NSString(format: "%2.1f kg",lbsresult)as String;
}
#IBAction func dripCalc(_ sender: Any) {
let cc = Double(fluidIn.text!)
let min = Double(timeIn.text!)
let fct = Double(dripIn.text!)
let dripRes = (cc! * fct!) / min!
dripResult.text = "\(dripRes)"
dripResult.text = NSString(format: "%2.1f gtts/min",dripRes)as String;
}
Call the below code whenever and wherever you want to hide the keyboard in viewController.
view.endEditing(true)

Retain countdown timer value when switching between view controllers

I'm working on a trip countdown app that also has weather forecast built in.
Things are working pretty well, but when I switch from countdown timer to weather forecast, then come back, the countdown timer is reset.
Here's my countdown view controller code:
import UIKit
class CountdownVC: UIViewController {
var timer = Timer()
let userCalendar = Calendar.current
let requestedComponent: Set<Calendar.Component> = [.day,.hour,.minute,.second]
var departureDateTime: Date?
#IBOutlet weak var imageView: UIImageView!
let images = [
UIImage(named: "MK.png")!,
UIImage(named: "SSE1.png")!,
UIImage(named: "TofT.png"),
UIImage(named: "TofL1.png")!]
var index = 0
let animationDuration: TimeInterval = 0.25
let switchingInterval: TimeInterval = 15
func animateImageView()
{
CATransaction.begin()
CATransaction.setAnimationDuration(animationDuration)
CATransaction.setCompletionBlock {
DispatchQueue.main.asyncAfter(deadline: .now() + self.switchingInterval) {
self.animateImageView()
}
}
let transition = CATransition()
transition.type = kCATransitionFade
imageView.layer.add(transition, forKey: kCATransition)
imageView.image = images[index]
imageView.contentMode = .scaleAspectFill
imageView.alpha = 0.5
CATransaction.commit()
index = index < images.count - 1 ? index + 1 : 0
}
#IBOutlet weak var departureDateTimePicker: UIDatePicker!
#IBAction func departureDateTimePicker(_ sender: Any) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd/yy H:mm:ss"
departureDateTimePicker.addTarget(self, action: #selector(handler), for: UIControlEvents.valueChanged)
}
#IBOutlet weak var selectDepartureDateTimeLabel: UILabel!
#IBOutlet weak var daysLabel: UILabel!
#IBOutlet weak var hoursLabel: UILabel!
#IBOutlet weak var minutesLabel: UILabel!
#IBOutlet weak var secondsLabel: UILabel!
func handler(sender: UIDatePicker) {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd/yy H:mm:ss"
let departureDateTimeString = dateFormatter.string(from: departureDateTimePicker.date)
departureDateTime = dateFormatter.date(from: departureDateTimeString)
}
func printTime() {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd/yy H:mm:ss"
let startTime = Date()
let endTime = departureDateTime
let timeDifference = userCalendar.dateComponents(requestedComponent, from: startTime, to: endTime!)
let startTimeDouble: Double = startTime.timeIntervalSinceReferenceDate
var endTimeDouble: Double?
endTimeDouble = (endTime?.timeIntervalSinceReferenceDate)
if endTimeDouble! > startTimeDouble {
daysLabel.text = "\(timeDifference.day!) Days"
hoursLabel.text = "\(timeDifference.hour!) Hours"
minutesLabel.text = "\(timeDifference.minute!) Minutes"
secondsLabel.text = "\(timeDifference.second!) Seconds"
} else {
timer.invalidate()
daysLabel.text = ""
hoursLabel.text = ""
minutesLabel.text = ""
secondsLabel.text = ""
}
}
func runTimer() {
let timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(printTime), userInfo: nil, repeats: true)
timer.fire()
}
#IBOutlet weak var startBtn: UIButton!
#IBAction func startBtnPressed(_ sender: Any) {
handler(sender: departureDateTimePicker)
printTime()
runTimer()
selectDepartureDateTimeLabel.text = "Time to departure"
departureDateTimePicker.isHidden = true
startBtn.isHidden = true
}
#IBOutlet weak var resetBtn: UIButton!
#IBAction func resetBtnPressed(_ sender: Any) {
timer.invalidate()
selectDepartureDateTimeLabel.text = "Select departure date & time"
departureDateTimePicker.isHidden = false
startBtn.isHidden = false
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM/dd/yy H:mm:ss"
let startTime = Date()
let endTime = startTime
let timeDifference = userCalendar.dateComponents(requestedComponent, from: startTime, to: endTime)
daysLabel.text = "\(timeDifference.day!) Days"
hoursLabel.text = "\(timeDifference.hour!) Hours"
minutesLabel.text = "\(timeDifference.minute!) Minutes"
secondsLabel.text = "\(timeDifference.second!) Seconds"
}
#IBAction func goToWeatherBtnPressed(_ sender: Any) {
performSegue(withIdentifier: "goToWeatherVC", sender: self)
}
override func viewDidLoad() {
super.viewDidLoad()
super.viewDidLoad()
imageView.image = images[index+1]
animateImageView()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

Swift code; unable to dismiss keypad using standard method

I've been working on a Swift tutorial.
In the tutorial the authors neglected to explain how to dismiss the keyboard on the simulator.
I tried the standard method that I'm aware of; resignFirstResponder( )
However I keep getting an error; cannot convert a string into a bool.
I haven't been able to find a workable answer on Stack overflow to date.
Any ideas?
I've included the code below.
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var yearTextField: UITextField!
#IBOutlet weak var dayTextField: UITextField!
#IBOutlet weak var monthTextField: UITextField!
#IBOutlet weak var messageLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.yearTextField.delegate = self
self.dayTextField.delegate = self
self.monthTextField.delegate = self
}
// Suggested fix not working?
// func textFieldShouldReturn(textField: UITextField!) -> Bool {
// textField.resignFirstResponder()
// return true;
// }
#IBAction func daysButtonPressed(sender: AnyObject) {
println("Days Button Pressed")
messageLabel.text = "Button pressed"
messageLabel.resignFirstResponder()
var dateComponents = NSDateComponents()
dateComponents.day = dayTextField.text.toInt()! // 28
dateComponents.month = monthTextField.text.toInt()! // 3
dateComponents.year = yearTextField.text.toInt()! // 1956
var calendar = NSCalendar(identifier: NSGregorianCalendar)
var birthDate = calendar.dateFromComponents(dateComponents) //NSDate
var currentDate = NSDate.date()
println(birthDate)
println("Current Date \(currentDate)")
var durationDateComponets = calendar.components(NSCalendarUnit.CalendarUnitDay,
fromDate: birthDate!,
toDate: currentDate,
options: nil)
var numberOfDaysAlive = durationDateComponets.day
//Format with commas
var numberFormatter = NSNumberFormatter()
numberFormatter.usesGroupingSeparator = true
var dayString = numberFormatter.stringFromNumber(numberOfDaysAlive)
messageLabel.text = "Days alive: \(dayString)"
// self.messageLabel.resignFirstResponder()
}
}
The keyboard is up because one of your UITextField's is the first responder. Since you don't know which one, call resignFirstResponder() on each of them.
In daysButtonPressed() instead of calling:
messageLabel.resignFirstResponder()
You need to call:
dayTextField.resignFirstResponder()
monthTextField.resignFirstResponder()
yearTextField.resignFirstResponder()

Resources