Locking buttons until next day - swift

Is there anyway to lock buttons until the following day? For example, my app has coupons in the form of buttons and when you click the button "Coupon 1" it toggles to say "USED + the current date". I want the user to be able to only use 2 coupons per ViewController/page per dag, and am wondering if its possible to lock the other coupons once two are pressed by the user, and the coupons unlock the following day. I don't want the buttons to disappear, just want them to be locked. Below is the code for one of my buttons/coupons. It would be ideal if a lock actually appeared in the corner of the remaining buttons after 2 are pressed.
var didClick : Bool = false
#IBAction func special1BTNpressed(_ sender: UIButton) {
if !didClick {
didClick = true
let date = Date()
let formatter = DateFormatter()
formatter.dateFormat = "MM/dd/yyyy"
let result = formatter.string(from: date)
sender.setTitle("USED " + result, for: .normal)

You can store the value of the date of which you did the action and then later compare it with today
func canPerformActionForSpecialBTN() -> Bool {
let timeInterval = UserDefaults.standard.double(forKey: "ActionDateForSpecialBTN")
guard timeInterval.isNormal else { return true }
let date = Date(timeIntervalSince1970: timeInterval)
guard Calendar.current.isDateInToday(date) else { return true }
return false
}
func performSpecialBTNAction() {
guard canPerformActionForSpecialBTN() else { return }
defer { UserDefaults.standard.set(Date().timeIntervalSince1970, forKey: "ActionDateForSpecialBTN") }
//implement action here
}
and maybe in the viewDidAppear you can enable or disable the button
button.isEnabled = !canPerformActionForSpecialBTN()

Related

How to get initial value of datePicker Swift

I want to get initial value of timePicker, value changes when I am just scrolling time. Please watch photos it will be more clear to understand what I want.
https://imgur.com/a/3Hg69uR
#IBAction func datePickerChanged(_ sender: Any) {
let dateFormatter = DateFormatter()
dateFormatter.timeStyle = .short
dateFormatter.dateFormat = "HH:mm"
let strDate = dateFormatter.string(from: datePicker.date)
datePickerLb.text = strDate
}
All you need is to update the label inside your viewDidLoad method. I would move the date formatter declaration out of that method to avoid creating a new one every time the value changes. Note that you should use timeStyle or dateFormat but not both. When displaying dates to the end user you should always respect the devices locale and settings so you should choose timeStyle in this case:
let dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.timeStyle = .short
return dateFormatter
}()
override func viewDidLoad() {
super.viewDidLoad()
// your code
// you can update the label here
// datePickerLb.text = dateFormatter.string(from: datePicker.date)
// or manually send the valueChanged action to force the update at view did load
datePicker.sendActions(for: .valueChanged)
}
#IBAction func datePickerChanged(_ datePicker: UIDatePicker) {
datePickerLb.text = dateFormatter.string(from: datePicker.date)
}
The answer by #Leo is practical. One thing to add is that if you wanted the date updated while picker it is rolling then, unfortunately, it is impossible with UIDatePicker itself. This is because UIDatePicker is not a subclass of UIPickerView and it manages a UIPickerView internally. The the solution here might be use a custom UIPickerView. Then for that UIPickerView you can implement it's delegate method like this:
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
switch component {
case 0:
currentTime.hours = pickerView.selectedRow(inComponent: 0)
return "\(row) Hour"
case 1:
currentTime.minutes = pickerView.selectedRow(inComponent: 1)
return "\(row) Minute"
case 2:
currentTime.seconds = pickerView.selectedRow(inComponent: 2)
return "\(row) Second"
default:
return ""
}
}
currentTime var should, for example, have a didSet to update some view elements when it changes.

How to get a button state to save after signing out and sign back in?

I have a save button that when tapped, will save the text in a label (quoteLabel) to a user account in Firebase. The button will then hide and the unsave button will no longer be hidden so that the user can unsave if desired. Both of these buttons are able to post and delete data as desired, however, if I sign-out of the app and sign back in, the buttons are reset to their original status. How do I get the button to stay in the last state it was set to? I can't seem to get this to work. Please let me know if more information is needed, I'm still new to all this.
Edit: Updated with suggestions from below. I still can't seem to get this to work properly. When I tap just one button, they all end up being saved as tapped when I sign back in. I added how I assigned a key as well.
import Foundation
import UIKit
import FirebaseDatabase
import FirebaseAuth
class QuotesCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var quoteLabel: UILabel!
#IBOutlet weak var save: UIButton!
#IBOutlet weak var unsave: UIButton!
#IBAction func saveButton(_ sender: UIButton) {
UserDefaults.standard.set(true, forKey: uuid)
save.isHidden = true
unsave.isHidden = false
var ref: DatabaseReference?
ref = Database.database().reference()
if quoteLabel.text == "The best time to plant a tree was 20 years ago - The next best time is today - Unknown"{
guard let user = Auth.auth().currentUser?.uid else { return }
ref!.child("users").child(Auth.auth().currentUser!.uid).child("Quote").child("Quote1").setValue(quoteLabel.text!)
}
}
#IBAction func UnsaveButton(_ sender: UIButton) {
UserDefaults.standard.set(false, forKey: uuid)
save.isHidden = false
unsave.isHidden = true
var ref: DatabaseReference?
ref = Database.database().reference()
if quoteLabel.text == "The best time to plant a tree was 20 years ago - The next best time is today - Unknown"{
ref!.child("users").child(Auth.auth().currentUser!.uid).child("Quote").child("Quote1").removeValue()
}
}
func setup(with quote: Quotes){
let saveButtonShowing = UserDefaults.standard.bool(forKey: uuid)
if(saveButtonShowing){ // Retrieves the state variable from the hard drive and sets the button visibility accordingly
save.isHidden = true
unsave.isHidden = false
} else {
save.isHidden = false
unsave.isHidden = true
}
quoteLabel.text = quote.quote
}
}
This is how I tried to add a key to each quote:
import UIKit
import Foundation
let uuid = UUID().uuidString
struct Quotes {
let quote: String
let identifier: String
}
let quotes: [Quotes] = [
Quotes(quote: "The best time to plant a tree was 20 years ago - The next best time is today - Unknown", identifier: uuid),
Quotes(quote: "Everytime you spend money, you're casting a vote for the type of world you want - Anna Lappe",identifier: uuid),
Quotes(quote: "Buy less, choose well, make it last - Vivienne Westwood", identifier: uuid),
Quotes(quote: "The future depends on what we do in the present - Mahatma Gandhi", identifier: uuid),
]
The problem is that the .isHidden property of the UIButton's is a state variable, meaning it is saved in RAM. This means that once you exit the application and reopen it, your app will startup again with the default state variable settings.
If you wish to persist the state of these variables, you'll need to save the button states to the phone's hard drive. UserDefaults makes this easy.
#IBAction func saveButton(_ sender: UIButton) {
UserDefaults.standard.set(true, forKey: "saveButtonHidden") // Saves the state to the hard drive
save.isHidden = true
unsave.isHidden = false
var ref: DatabaseReference?
ref = Database.database().reference()
if quoteLabel.text == "The best time to plant a tree was 20 years ago - The next best time is today - Unknown"{
guard let user = Auth.auth().currentUser?.uid else { return }
ref!.child("users").child(Auth.auth().currentUser!.uid).child("Quote").chi.ld("Quote1").setValue(quoteLabel.text!)
}
}
#IBAction func UnsaveButton(_ sender: UIButton) {
UserDefaults.standard.set(false, forKey: "saveButtonHidden") // Saves the state to the hard drive
save.isHidden = false
unsave.isHidden = true
var ref: DatabaseReference?
ref = Database.database().reference()
if quoteLabel.text == "The best time to plant a tree was 20 years ago - The next best time is today - Unknown"{
ref!.child("users").child(Auth.auth().currentUser!.uid).child("Quote").child("Quote1").removeValue()
}
if quoteLabel.text as! NSObject == ref!.child("users").child(Auth.auth().currentUser!.uid).child("Quote").child("Quote1") {
save.isHidden = true
unsave.isHidden = false
}
}
func setup(with quote: Quotes){
let saveButtonShowing = UserDefault.standard.bool(forKey: "saveButtonHidden")
if(saveButtonHidden){ // Retrieves the state variable from the hard drive and sets the button visibility accordingly
save.isHidden = true
unsave.isHidden = false
} else {
save.isHidden = false
unsave.isHidden = true
}
quoteLabel.text = quote.quote
}
You need to store the info on local device.
the best way is to store the info on firebase according to the user info.
I think it's no problem to show the code here.

how to limit button clicked by time passed swift

I need a button where the user clicks to let everyone know they are attending a location, but I only want it to be able to be clicked up to 3 times per 24hrs depending on the contents of the button, as there is a tableView with one button per cell
I haven't tried anything as I have never used time in swift still new to it
#IBAction func myButtonClicked(_ sender: Any) {
DataService.ds.REF_BARS.child(imageTitleLabel.text!).child("GoingCount").observeSingleEvent(of: .value, with: { (snapshot) in
print(snapshot.value as! String)
self.countString = snapshot.value as! String
self.countInt = Int(self.countString)! + 1
DataService.ds.REF_BARS.child(self.imageTitleLabel.text!).updateChildValues(["GoingCount": String(self.countInt)])
})
}
if you can add the pre or post condition of user clicks in or around that button
You can do this for 1 button
if let storedDate = UserDefaults.standard.object(forKey:"StoredDate") as? Date {
let toDate = Calendar.current.date(byAdding: .day, value:1, to:storedDate)
let cliks = UserDefaults.standard.integer(forKey:"NumOfClicks")
if Date() <= toDate {
if cliks < 3 {
// do what you need and increase NumOfClicks by 1
}
else {
// no more clicks this day
}
}
else {
// date exceed may be reset the date and cliks to 1
}
}
else {
// first attempt
UserDefaults.standard.set(Date(),forKey:"StoredDate")
UserDefaults.standard.set(1,forKey:"NumOfClicks")
// do what you need once from here
}
For an array handling you can think of every key above as an array instead , in storing/retrieve like [Date]/[Int]

How to reflect select result of UISegmentedControl in Firebase'S Database

Hello I'm newbie in programming (Swift/Firebase) and developing application. Now is trying to make a user profile to be connected to database. I could do it for TextFields data, but cannot do the same with UISegmentedControl for Male/Female switch.
#IBAction func saveProfileButton(_ sender: Any) {
if nameTextField.text! == "" {
print("enter something")
} else {
addUser()
}
}
#objc func valuChange() {
switch genderSegControl.selectedSegmentIndex {
case 0:
let zero = String("Male")
case 1:
let one = String("Female")
default:
return
}
}
func addUser() {
let genderChanged = genderSegControl?.addTarget(self, action: #selector(MainViewController.valuChange), for: UIControlEvents.valueChanged)
let userGender = String(describing: genderChanged!)
let uidUser = Auth.auth().currentUser?.uid
let user = ["...": ..., "userGender": userGender as! String]
refUsers.child(uidUser!).setValue(user)
}
but when I check Firebase's Database nodes it shows "userGender: Optional(())" and only. I tried in many different ways but couldn't get it to be shown like "userGender: Male" or "userGender: Female".
Would appreciate any advice.
Create outlet for UISegementControl in your ViewController, say genderControl.
In addUser() function, use the selectedIndex of the UISegementControl to determine the value as follows, say index 0 is for MALE & 1 is for FEMALE then:
let gender = genderControl.selectedIndex == 0 ? "Male" : "Female"
Now set this value gender in user dictionary, i.e.
let user = ["...": ..., "userGender": gender]

Swift deleting table view cell when timer expires

I have tried hard to find a solution but I'm stuck. have a custom table view with timer in each cell. when the timer expires the cell should get deleted even if the cell is Offscreen it should get deleted and should not be displayed.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! iDealCell
// cell.SponsorLogo.image = UIImage(named: "back.png")!
cell.SponsorName.text = iDeals[indexPath.row].SponsorName;
cell.Distance.text = iDeals[indexPath.row].Distance;
cell.Type.text = iDeals[indexPath.row].Type;
cell.iDealTimer.font = UIFont(name: "DBLCDTempBlack", size: 18.0)
onHourFromNow = NSDate(timeInterval: 10, sinceDate: timeNow)
let TimeDiffInSec = NSCalendar.currentCalendar().components(.Second, fromDate: timeNow, toDate: onHourFromNow, options: []).second
cell.TimeDiffInSec = TimeDiffInSec
cell.kickOffCountdown()
cell.backgroundColor = UIColor.clearColor()
cell.delegate = self
return cell
}
in cell class, three functions to initialise and run the timer
func kickOffCountdown(){
self.setCountDown()
Timer.invalidate()
Timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(iDealCell.setCountDown), userInfo: nil, repeats: true)
}
func secondsToHoursMinutesSeconds (seconds : Int) -> (String, String, String) {
let hours = seconds / 3600
let minutes = (seconds % 3600) / 60
let seconds = (seconds % 3600) % 60
let hoursString = hours < 10 ? "0\(hours)" : "\(hours)"
let minutesString = minutes < 10 ? "0\(minutes)" : "\(minutes)"
let secondsString = seconds < 10 ? "0\(seconds)" : "\(seconds)"
return (hoursString, minutesString, secondsString)
}
func setCountDown() {
if(TimeDiffInSec > 0) {
let (h,m,s) = secondsToHoursMinutesSeconds(TimeDiffInSec)
self.iDealTimer.text = "\(h):\(m):\(s)"
TimeDiffInSec = TimeDiffInSec - 1
}
else{
self.iDealTimer.text = "EXPIRED"
if let delegate = self.delegate {
delegate.DeleteiDealID(1)
}
Timer.invalidate()
}
}
Any help will be greatly appreaciated
You can do simple thing whenever your time have been expire you can remove those values from your array of dictionary whatever you used for row count.
Simple thing here is your all table cell depends on your row count remover particular cell with by removing particular array object.
example :
if timerExpire == true {
array.removeAtIndex(5)
self.table.reloadData()
}
This is a tricky problem, because you want:
Table rows to be deleted even if they are offscreen at the time the timer pops.
New rows can be added while the old rows are "ticking".
The first point means that you do not want the timer to be kept in the cell. It is the wrong place anyway, because cells get reused and you'd have a nightmare invalidating and restarting timers.
The second point means that the row number you want to delete at the time the timer is started could be different than the row number you delete when the timer pops. You may start your timer for row 5 to be deleted in 5 seconds, but in the meantime row 4 gets deleted, making the former row 5 now row 4. When the former row 5's timer pops, row 4 needs to be deleted from the table.
Here is the approach I suggest:
Give each row in your table a unique ID. This will just be a simple count that is maintained by your UITableViewController class.
var nextID = 0
Maintain a list of active ID's that correspond to the rows that are currently in your table. Add this property to your UITableViewController:
var activeIDs = [Int]()
Add a dictionary to your table that maps a NSTimer to an ID. Add this to your UITableViewController:
var timerIDmap: [NSTimer: Int]()
When you create a new row in your table:
let newID = nextID
activeIDs.append(newID)
nextID += 1
In cellForRowAtIndexPath, be sure to store the ID in a property of the cell.
cell.cellID = activeIDs[indexPath.row]
When you create a timer, you need to store the timer and its corresponding cell ID in the timerIDmap. Since you'll do this in the custom cell, the cell needs to have a weak reference to the tableViewController that holds it:
// add this property to your cell
weak var myTableVC: UITableViewController?
and assign that property in cellForRowAtIndexPath:
cell.myTableVC = self
so that when you create the timer:
let timer = NSTimer.scheduledTimerWithTimerInterval(...
myTableVC?.timerIDmap[timer] = cellID
When your timer ticks, you need to decrement the time left on that timer. That means the time left should also be kept in your model. Add this dictionary to your UITableViewController:
var timeLeft = [Int: Int]() // maps CellID to time left
that means that when you create the timer in the first place, you will store timeLeft in this dictionary
myTableVC?.timeLeft[cellID] = 50 // some appropriate value
OK, so now in your handleCountdown routine which should be implemented in your UITableViewController:
func handleCountdown(timer: NSTimer) {
let cellID = timerIDMap[timer]
// find the current row corresponding to the cellID
let row = activeIDs.indexOf(cellID)
// decrement time left
let timeRemaining = timeLeft[cellID] - 1
timeLeft[cellID] = timeRemaining
if timeRemaining == 0 {
timer.invalidate
timerIDmap[timer] = nil
activeIDs.removeAtIndex(row)
tableView.deleteRowsAtIndexPaths(NSIndexPath(row: row, section: 0), withRowAnimation: ...
} else {
tableView.reloadRowsAtIndexPaths(NSIndexPath(row: row, section: 0), withRowAnimation: ...
}
}
This leaves very little work for your custom cell. It should merely take the time left on the timer and format it for display. In cellForRowAtIndexPath, tell the cell how much time is left on the timer:
cell.timeLeft = timeLeft[activeIDs[indexPath.row]]
The number of items in your table is the same as the number of items in activeIDs, so in tableView:numberOfRowsInSection return
return activeIDs.count
I think, this will be better way:
Inside tableView:cellForRowAtIndexPath calculate (or fetch) time and add it's value to label in cell.
Remove timer from cell.
Add timer in main class (where tableView placed) in viewDidAppear (or inside block where you fetch data), that will every second call method, that check and remove expired objects (or you can apply filter) and fire tableView.reloadData() (or delete needed rows animated).
In viewDidDisappear invalidate timer.
I have been trying these solutions but I dont think they are viable towards the goal. Just wanted to let people know. If you have found something that works or have found the same can you please let us know
This is the solution I have come up with. Its not a perfect solution by any means however, it does solve the problem.
In the viewcontroller:
func handleDate(timer: Timer) {
DispatchQueue.main.async() {
if self.posts.count < 1 {
print("Empty")
timer.invalidate()
} else {
let calendar = Calendar.current
let date = Date()
let componentsCurrent = calendar.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date)
var components = DateComponents()
components.hour = componentsCurrent.hour
components.minute = componentsCurrent.minute
components.second = componentsCurrent.second
components.year = componentsCurrent.year
components.month = componentsCurrent.month
components.day = componentsCurrent.day
let currentTime = calendar.date(from: components)!
for post in self.posts {
let cellID = post.postID
let row = self.postsInFeed.index(of: cellID)
let endDate = TimeInterval(post.time)
if currentTime.timeIntervalSince1970 >= endDate {
self.tableView.beginUpdates()
timer.invalidate()
print(post.postID)
print("Deleting tableview row")
self.postsInFeed.removeFirst()
self.posts.removeFirst()
let store: Dictionary<String, Any> = ["caption": post.caption, "mediaURL": post.imageUrl as Any, "likes": post.likes, "user_ID": FriendSystem.system.CURRENT_USER_ID, "time": post.time]
let firebasePost = FriendSystem().GROUP_REF.child(self.group.groupID).child("storage").child(post.postID)
firebasePost.setValue(store)
FriendSystem().GROUP_REF.child(self.group.groupID).child("posts").child(cellID).removeValue()
self.tableView.deleteRows(at: [IndexPath(row: row!, section: 0)] , with: UITableViewRowAnimation.fade)
self.tableView.endUpdates()
self.tableView.reloadData()
}
}
}
}
}
In the tableviewcell:
func tick(timer: Timer) {
guard let expiresAt = endDate else {
return
}
let calendar = NSCalendar(calendarIdentifier: NSCalendar.Identifier.gregorian)
if let components = calendar?.components([.hour, .minute, .second], from: NSDate() as Date, to: expiresAt, options: []) {
currentTime = formatDateComponents(components: components as NSDateComponents)
self.timerLbl.text = currentTime
if Date() >= endDate! {
timer.invalidate()
}
}
}
func formatDateComponents(components: NSDateComponents) -> String {
let hours = components.hour
let minutes = components.minute
let seconds = components.second
return "\(hours):\(minutes):\(seconds)"
}