Right now I'm working on a time clock app that allows the user to punch in/out of work time. But I'm having trouble figuring out how to make that function possible.
In my model I have:
struct TimeLog {
var punchInTime: CFAbsoluteTime
var punchOutTime: CFAbsoluteTime
var timeWorked: Double
init (pInTime: CFAbsoluteTime, pOutTime: CFAbsoluteTime) {
self.punchInTime = pInTime
self.punchOutTime = pOutTime
self.timeWorked = pOutTime - pInTime
}
}
And in my view controller:
class ViewController: UIViewController {
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 punchInButtonPressed(sender: AnyObject) {
// not sure what actions are needed here to start the "aTimeLog" variable
}
#IBAction func punchOutButtonPressed(sender: AnyObject) {
// not sure what actions are needed here to complete the "aTimeLog" variable
}
I'm trying to complete this variable:
var aTimeLog = TimeLog(pInTime: //get the punch in time here, pOutTime: //get the punch out time here)
And once the variable "aTimeLog" is complete (the punchOutButton is pressed) I want to display a log of all of my "timeWorked" variables.
Forgive me in advance. You might be able to tell I'm just learning programming and Swift.
Personally, I'd keep a variable in NSUserDefaults - storing the punch in time. Then, when the punch out button is tapped, it fetches the dateTime variable from NSUserDefaults, does the needed calculation and returns the result. This caters for if a user closes the app (punch in time is remembered).
This creates an NSUserDefaults object
var punchInTime : NSUserDefaults = NSUserDefaults.standardUserDefaults()
#IBAction func punchInButtonPressed(sender: AnyObject) {
Saves the current time
punchInTime.setObject(NSDate(), forKey: "punchInTime")
punchInTime.synchronize()
}
#IBAction func punchOutButtonPressed(sender: AnyObject) {
fetches the saved time
punchInTime.objectForKey("punchInTime") as NSDate
creates a variable holding the current time so you can compare the two
var currentTime : NSDate = NSDate()
currentTime.timeIntervalSinceDate(punchInTime) // returns seconds
Now you can save the start time, end time and total working time inside, say, a dictionary with the key as the date maybe...
}
Then when you want to display all the days worked, simply loop through the dictionary :)
...hope I understood correctly
As one of the possible solutions, you could alter your init method as follows:
initWithStartTime (pInTime: CFAbsoluteTime)
{
self.punchInTime = pInTime
}
Then add a method like:
func workedTimeWithEndTime(pOutTime: CFAbsoluteTime) -> CFAbsoluteTime
{
return pOutTime - self.punchInTime
}
Finally you'll probably want to add "var timeLog: TimeLog!" into your view controller and initialise it in punchInButtonPressed with start time.
You should give your ViewController class a ref to a TimeLog class (why did you choose a struct? works too but just curious).
var timeLog: TimeLog?
Second of all you want to initialise it with a fresh one every time you punch in
#IBAction func punchInButtonPressed(sender: AnyObject) {
let now = CFAbsoluteTimeGetCurrent()
self.timeLog = TimeLog(pInTime: now)
}
So you initialise only with the pInTime, and the rest of the times are set to 0 / null. Next when you punch out you add the pOutTime and you can calculate the timeWorked.
#IBAction func punchOutButtonPressed(sender: AnyObject) {
if (self.timeLog == nil) {
return
}
self.timeLog!.punchOut(CFAbsoluteTimeGetCurrent());
println(self.timeLog!.timeWorked)
}
Since we don't use the constructor like you've created it and it needs a new function we need to change your class (use class unless you know why you want a struct).
class TimeLog {
var punchInTime: CFAbsoluteTime
var punchOutTime: CFAbsoluteTime
var timeWorked: Double
init (pInTime: CFAbsoluteTime) {
self.punchInTime = pInTime
self.punchOutTime = 0
self.timeWorked = 0
}
func punchOut(pOutTime: CFAbsoluteTime) {
self.punchOutTime = pOutTime
self.timeWorked = self.punchOutTime - self.punchInTime
}
}
Try this:
// run this on first action/tap
let start = Date()
// then, run this on the second time/interaction, to get the negative elapsed time
// for example: for 0.8 seconds this will return -0.8694559335708618
print("Elapsed time: \(start.timeIntervalSinceNow) seconds")
Related
I'm new to iOS development, but I'm having an issue with one of the views that I'm working on. I have a UIDatePicker that can either be hidden or visible depending on the state of a UISwitch. It seems that the associated #IBAction does not trigger when the view starts out hidden. It does work when the date picker starts out visible, so the IBAction is working.
Here's a simplified version of my code:
import UIKit
class StatusEditorViewController: UIViewController {
#IBOutlet var expiryPicker: UIDatePicker!
#IBOutlet var enableExpirySwitch: UISwitch!
var editingObject: StoredStatus?
private var pickerIsVisible = false
private var expiresIn: TimeInterval?
override func viewDidLoad() {
// Set a default value
expiryPicker.countDownDuration = TimeInterval(3600)
// If this view got passed an object to edit, use that for expiresIn
if let status = editingObject {
if let expires = status.expiresIn.value {
expiresIn = TimeInterval(expires)
}
}
// Hide the picker and turn off the "enable expiry" switch if we don't
// have a value yet. We'll show the picker once the switch has been pressed
pickerIsVisible = expiresIn != nil
enableExpirySwitch.isOn = expiresIn != nil
updatePicker()
}
func updatePicker() {
expiryPicker?.isHidden = !pickerIsVisible
}
#IBAction func expiryDidUpdate(_ sender: UIDatePicker) {
expiresIn = sender.countDownDuration
print(expiresIn!)
}
#IBAction func expirySwitchDidUpdate(_ sender: UISwitch) {
pickerIsVisible = sender.isOn
updatePicker()
// If the user just turned on the switch, we want to make sure we store the
// initial value already, in case the user navigated away
if (sender.isOn && expiresIn == nil) {
expiresIn = expiryPicker.countDownDuration
}
}
}
I'm not sure what's going wrong. I tried manually attaching a target (e.g. self.expiryPicker.addTarget(self, action: #selector(setExpiryValue), for: .allEditingEvents))
once the view becomes available, but that didn't work either.
I hope someone can tell me what I'm doing wrong. I'm guessing there's something fundamental that I'm doing wrong, but so far no search on Google or SO has led me to the answer.
Thanks in advance
f.w.i.w, I'm running XCode 11.7, with Swift 5, with a deployment target of iOS 13.7
I am new to Swift and Xcode, but I have been reading on here and watching videos on YouTube to guide me along with starting my app. I can't seem to get my button to save its state once the app is closed and re-opened. I used UserDefault To Save Button State as an example, but following it still did not get the button state saved.
I set the state with the Interface Builder and so far have the below code:
#IBAction func ownedButton(_ sender UIButton) {
sender.isSelected = !sender.isSelected
UserDefaults.standard.set(sender.isSelected, forKey: "isSaved")
UserDefaults.standard.synchronize()
}
Clicking the button will keep it selected until clicked again, so it is partially working. It looks like I need some code to in the viewDidLoad section, but I haven't been able to figure out what it should be.
Thank you for any help!
A habit from my Objective-C days is to write a wrapper around the properties in UserDefaults. This way, everything is compile-time checked and the use of strings as keys is minimized:
// Properties.swift
import Foundation
fileprivate var standardDefaults = UserDefaults.standard
class Properties {
static func registerDefaults() {
standardDefaults.register(defaults: [
kIsButton1Selected: false,
kIsButton2Selected: true
])
}
fileprivate static let kIsButton1Selected = "isButton1Selected"
static var isButton1Selected: Bool {
get { return standardDefaults.value(forKey: kIsButton1Selected) as! Bool }
set { standardDefaults.set(newValue, forKey: kIsButton1Selected) }
}
fileprivate static let kIsButton2Selected = "isButton2Selected"
static var isButton2Selected: Bool {
get { return standardDefaults.value(forKey: kIsButton2Selected) as! Bool }
set { standardDefaults.set(newValue, forKey: kIsButton2Selected) }
}
}
Then in your View Controller:
override func viewDidLoad() {
// Always call registerDefaults before you use UserDefaults
// for the first time in your app
Properties.registerDefaults()
button1.isSelected = Properties.isButton1Selected
button2.isSelected = Properties.isButton2Selected
}
#IBAction func ownedButton(_ sender: UIButton) {
sender.isSelected = !sender.isSelected
switch sender {
case button1:
Properties.isButton1Selected = sender.isSelected
case button2:
Properties.isButton2Selected = sender.isSelected
default:
break
}
}
You can replace the switch with key-value observing but remember to remove the KVO on deinit.
I have an app which contains several viewControllers. On the viewDidAppear() of the first VC I call a set of functions which populate some arrays with information pulled from a database and then reload table data for a tableView. The functions all work perfectly fine and the desired result is achieved every time. What I am concerned about is how often viewDidAppear() is called. I do not think (unless I am wrong) it is a good idea for the refreshing functions to be automatically called and reload all of the data every time the view appears. I cannot put it into the viewDidLoad() because the tableView is part of a tab bar and if there are some modifications done to the data in any of the other tabs, the viewDidLoad() will not be called when tabbing back over and it would need to reload at this point (as modifications were made). I thought to use a set of variables to check if any modifications were done to the data from any of the other viewControllers to then conditionally tell the VDA to run or not. Generally:
override func viewDidAppear(_ animated: Bool) {
if condition {
//run functions
} else{
//don't run functions
}
}
The issue with this is that the data can be modified from many different viewControllers which may not segue back to the one of interest for the viewDidAppear() (so using a prepareForSegue wouldn't work necessarily). What is the best way to 'check' if the data has been modified. Again, I figured a set of bool variables would work well, but I want to stay away from using too many global variables. Any ideas?
Notification Center
struct NotificationName {
static let MyNotificationName = "kMyNotificationName"
}
class First {
init() {
NotificationCenter.default.addObserver(self, selector: #selector(self.notificationReceived), name: NotificationName.MyNotificationName, object: nil)
}
func notificationReceived() {
// Refresh table view here
}
}
class Second {
func postNotification() {
NotificationCenter.default.post(name: NotificationName.MyNotificationName, object: nil)
}
}
Once postNotification is called, the function notificationReceived in class First will be called.
Create a common global data store and let all the view controllers get their data from there. This is essentially a global singleton with some accompanying functions. I know you wanted to do this without global variables but I think you should consider this.
Create a class to contain the data. Also let it be able to reload the data.
class MyData {
static let shared = MyData()
var data : SomeDataType
func loadData() {
// Load the data
}
}
Register to receive the notification as follows:
static let dataChangedNotification = Notification.Name("DataChanged")
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
}
override func viewDidLoad() {
super.viewDidLoad()
// Establish a way for call activity to notify this class so it can update accordingly
NotificationCenter.default.addObserver(self, selector: #selector(handleDataChangedNotification(notification:)), name: "DataChanged", object: nil)
}
func handleDataChangedNotification(notification: NSNotification) {
// This ViewController was notified that data was changed
// Do something
}
func getDataToDisplay() {
let currentData = MyData.shared.data
// do something
}
// Any view controller would call this function if it changes the data
func sendDataChangeNotification() {
let obj = [String]() // make some obj to send. Pass whatever custom data you need to send
NotificationCenter.default.post(name: type(of: self).dataChangedNotification, object: obj)
}
I am new to OS X app development. I manage to built the NSComboBox (Selectable, not editable), I can get it indexOfSelectedItem on action button click, working fine.
How to detect the the value on change? When user change their selection, what kind of function I shall use to detect the new selected index?
I tried to use the NSNotification but it didn't pass the new change value, always is the default value when load. It is because I place the postNotificationName in wrong place or there are other method should use to get the value on change?
I tried searching the net, video, tutorial but mostly written for Objective-C. I can't find any answer for this in SWIFT.
import Cocoa
class NewProjectSetup: NSViewController {
let comboxRouterValue: [String] = ["No","Yes"]
#IBOutlet weak var projNewRouter: NSComboBox!
#IBAction func btnAddNewProject(sender: AnyObject) {
let comBoxID = projNewRouter.indexOfSelectedItem
print(“Combo Box ID is: \(comBoxID)”)
}
#IBAction func btnCancel(sender: AnyObject) {
self.dismissViewController(self)
}
override func viewDidLoad() {
super.viewDidLoad()
addComboxValue(comboxRouterValue,myObj:projNewRouter)
self.projNewRouter.selectItemAtIndex(0)
let notificationCenter = NSNotificationCenter.defaultCenter()
notificationCenter.addObserver(
self,
selector: “testNotication:”,
name:"NotificationIdentifier",
object: nil)
NSNotificationCenter.defaultCenter().postNotificationName("NotificationIdentifier", object: projNewRouter.indexOfSelectedItem)
}
func testNotication(notification: NSNotification){
print("Found Combo ID \(notification.object)")
}
func addComboxValue(myVal:[String],myObj:AnyObject){
let myValno: Int = myVal.count
for var i = 0; i < myValno; ++i{
myObj.addItemWithObjectValue(myVal[i])
}
}
}
You need to define a delegate for the combobox that implements the NSComboBoxDelegate protocol, and then use the comboBoxSelectionDidChange(_:) method.
The easiest method is for your NewProjectSetup class to implement the delegate, as in:
class NewProjectSetup: NSViewController, NSComboBoxDelegate { ... etc
Then in viewDidLoad, also include:
self.projNewRouter.delegate = self
// self (ie. NewProjectSetup) implements NSComboBoxDelegate
And then you can pick up the change in:
func comboBoxSelectionDidChange(notification: NSNotification) {
print("Woohoo, it changed")
}
I have a CoreData entity named 'Studio' with an attribute named 'name' with an NSManagedObject subclass created.
My app designed for a simple process, enter a name into a text box, and press 'save' and the name is saved into Studio.name - Press 'Update' and a text label is refreshed to show the value of Studio.name
However, it is not functioning as expected, if I, for example, enter the name 'Stack' and save the update I see 'Stack' in the text label, if i then enter 'Overflow' save/update the label reads 'Overflow', If i update it a third time to 'Swift' save/update the label again reads 'Stack'.
From there updates will give one of the three values seemingly at random.
Force quitting the app and relaunching it shows that the data is being saved to Core Data as pressing the update button will return a random previous value.
My question is, how does this happen with a string? (Shouldn't it only hold one value at a time?)
How can I correct this so it will only hold a single value and any subsequent values simply overwrite the previous value?
My code follows.
import UIKit
import CoreData
class ViewController: UIViewController {
#IBOutlet weak var studioBox: UITextField!
#IBOutlet weak var nameLabel: UILabel!
#IBAction func saveData(sender: AnyObject) {
var studio = writeStudioData()
studio.name = studioBox.text
}
#IBAction func Update(sender: AnyObject) {
var studio = getStudioData()
nameLabel.text = studio.name
}
func getStudioData() -> Studio {
let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
let managedContext = appDelegate.managedObjectContext!
let request = NSFetchRequest(entityName: "Studio")
request.returnsObjectsAsFaults = false
let result = managedContext.executeFetchRequest(request, error: nil) as [Studio]
return result[0]
}
func writeStudioData () -> Studio {
let appDelegate =
UIApplication.sharedApplication().delegate as AppDelegate
let managedContext = appDelegate.managedObjectContext!
let entityDescription = NSEntityDescription.entityForName("Studio", inManagedObjectContext: managedContext)
let result = Studio(entity: entityDescription!, insertIntoManagedObjectContext: managedContext)
return result
}
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.
}
}
You don't have one object, you are creating a new object every time. As you aren't including a sort descriptor with your fetch request, the order you get them, and thus the corresponding name, is unspecified, meaning it could be any of them.
You could either perform a fetch first in writeStudioData to see if there's already an object, only creating one if there isn't, or you could create one object and keep it around in a property.