I am coding since January 2019 and this is my first post here.
I am using Swift and Firestore. In my App is a tableView where I display events loaded out of a single Document with an array of events inside as [String: [String:Any]]. If the user wants to get more infos about an event he taps on it. In the background the TableViewController will open a new "DetailEventViewController" with a segue and give it the value of the eventID in the tapped cell.
When the user is on the DetailViewController Screen the app will download a new Document with the EventID as key for the document.
I wanna save this Data out of Firestore in a Struct called Event. For this example just with Event(eventName: String).
When I get all the data I can print it directly out but I can't save it in a variable and print it out later. I really don't know why. If I print the struct INSIDE the brackets where I get the data its working but if I save it into a variable and try to use this variable it says its nil.
So how can I fetch data out of Firestore and save in just a Single ValueObject (var currentEvent = Event? -> currentEvent = Event.event(for: data as [String:Any]) )
I search in google, firebaseDoc and stackoverflow but didn't find anything about it so I tried to save all the singe infos of the data inside a singe value.
// Struct
struct Event {
var eventName: String!
static func event(for eventData: [String:Any]) -> Event? {
guard let _eventName = eventData["eventName"] as? String
else {
print("error")
return nil
}
return Event(eventName: _eventName)
}
// TableView VC this should work
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "ShowEventDetailSegue" {
if let ShowEvent = segue.destination as? DetailEventViewController, let event = eventForSegue{
ShowEvent.currentEventId = event.eventID
}
}
}
// DetailViewController
var currentEvent = Event()
var currentEventId: String?
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
guard let _eventID = currentEventId else {
print("error in EventID")
return}
setupEvent(eventID: _eventID) /* currentEvent should be set here */
setupView(event: currentEvent) /* currentEvent has after "setupEvent" the value of nil */
}
func setupEvent(eventID: String) {
let FirestoreRef = Firestore.firestore().collection("events").document(eventID)
FirestoreRef.getDocument { (document, error) in
if let err = error {
debugPrint("Error fetching docs: \(err)")
SVProgressHUD.showError(withStatus: "Error in Download")
}else {
if let document = document, document.exists {
guard let data = document.data() else {return}
let eventData = Event.event(for: data as [String:Any])
print(eventData)
//here all infos are printed out - so I get them
self.currentEvent = eventData!
//Here is the error.. I can't save the fetched Data in my single current Event
} else {
SVProgressHUD.showError(withStatus: "Error")
}
}
}
}
func setupView(event: Event) {
self.titleLabel.text = event.eventName
}
I expect that the function setupEvents will give the currentEvent in the DetailViewController a SINGLEvalue cause its a SINGLE document not an array. So I can use this single Eventvalue for further actions. Like starting a new segue for a new ViewController and just push the Event there not
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]
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()
I created an app that has a forum and in that forum users can like a post.
When i try to increment the number of likes in parse it seems to increment it because when i print out the value, it prints correctly, but when i refresh parse, it stays at 0.
Here is my likeButton function:
//This gives me the index of the cell in which the like button was tapped
#IBAction func likeButton(_ sender: AnyObject) {
let buttonRow = (sender.tag)!
let query = PFQuery(className: "Posts")
query.whereKey("body", equalTo: messages[buttonRow])
query.whereKey("title", equalTo: titles[buttonRow])
query.findObjectsInBackground { (object, error) in
if error != nil {
print(error)
}else{
if let post = object {
for objects in post {
if let posts = objects as? PFObject {
//I would think this line is the only thing I'd need to execute but it isn't working
posts.incrementKey("Like", byAmount: 1)
let pre = [posts["Like"]!]
//this prints 1 --> meaning it worked
print(pre[0])
//This next line should update posts["Like"] but it doesn't
posts["Like"] = pre[0]
posts.saveInBackground()
}
}
}
}
}
}
It seems like i'm incrementing it, but it is not saving. Please, any help would be greatly appreciated!
Thanks!
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)"
}