I am trying to figure out a way to allow a user to change current date represented as a label.
The change/action button is a Stepper
The label is defaulting to the current date and when they click the stepper they should be able to change the date to tomorrow, next day, and so on...when they click the + on the stepper. Then the opposite for clicking - on the stepper.
class DateTestViewController: UIViewController {
#IBOutlet weak var stepperOutlet: UIStepper!
#IBOutlet weak var dateLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
let now = NSDate()
var daysToAdd: Double = 1
var newDate = now.dateByAddingTimeInterval(60*60*24*daysToAdd)
dateLabel.text = "\(newDate)"
}
#IBAction func stepperAction(sender: AnyObject) {
var unitsValue =
dateLabel.text = "\(unitsValue)"
}
}
Can someone please point me in the right direction to solve this issue.
Thanks,
Addison
update: Xcode 7.3 • Swift 2.2
You can use NSCalendar method dateByAddingUnit to add or subtract the value from your UIStepper from your date as follow:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var strDate: UILabel!
var selectedDate:NSDate!
override func viewDidLoad() {
super.viewDidLoad()
selectedDate = NSDate() // sets the initial date
strDate.text = selectedDate.formatted
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func stepAction(sender: UIStepper) {
selectedDate = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!.dateByAddingUnit(.Day, value: Int(sender.value), toDate: NSDate(), options: [])!
strDate.text = selectedDate.formatted
}
}
extension NSDate {
var formatted: String {
let df = NSDateFormatter()
df.dateFormat = "MMMM dd, yyyy - EEEE"
df.locale = NSLocale(localeIdentifier: "en_US_POSIX")
return df.stringFromDate(self)
}
}
Related
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.
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.
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).
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var yearTextField: UITextField!
#IBOutlet weak var monthTextField: UITextField!
#IBOutlet weak var dayTextField: UITextField!
#IBOutlet weak var messageLabel: UILabel!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func daysButtonPressed(sender: AnyObject) {
messageLabel.text = "The button is pressed!"
var dateComponets = NSDateComponents()
dateComponets.day = 2
dateComponets.month = 10
dateComponets.year = 1999
var calendar = NSCalendar(identifier: NSGregorianCalendar )
var birthDate = calendar?.dateFromComponents(dateComponets)
var currentDate = NSDate.date()
}
}
I was compiling the code and suddenly it shows me this error: date()' is unavailable: use object construction 'NSDate()' and also it shows me another error thats is: " 'NSCalendar?' does not have a member named 'dateFromComponents' " anyone can help me please!! I am new at swift and also to stack overflow
Use NSDate() instead of NSDate.date().
replace the ? after calendar with !.
calendar!.dateFromComponents(dateComponets)
Krys's suggestions were correct but also you left out some characters that will give you an issue
var dateComponets = NSDateComponents()
Should be:
var dateComponents = NSDateComponents()
Same with
var birthDate = calendar?.dateFromComponents(dateComponets)
Should be
var birthDate = calendar?.dateFromComponents(dateComponents)
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()