I am trying to populate my daily events in to tableview. I have made the connections. (datasource, delegate and tableview) But events value is returning 0.
Sorry if this has been asked else where. I need a little help.
Here is my code.
import UIKit
import EventKit
import EventKitUI
class MainViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
var eventStore: EKEventStore?
var events = [EKEvent]()
var startDate = NSDate()
var endDate = NSDate()
var cellIdentifier = "cell"
#IBOutlet var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
automaticallyAdjustsScrollViewInsets = false
self.tableView.dataSource = self
self.tableView.delegate = self
self.tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: cellIdentifier);
//self.tableView.reloadData()
self.eventStore = EKEventStore()
fetchEvents { (arr:[EKEvent]) in
print(arr)
self.events = arr
//reload table
}
self.tableView.reloadData()
}
func fetchEvents(completed: ([EKEvent]) -> ()) {
eventStore!.requestAccessToEntityType(EKEntityType.Event, completion: {
granted, error in
let startDt = NSDate(timeIntervalSinceNow: -24 * 60 * 60);
let predicate = self.eventStore!.predicateForEventsWithStartDate(startDt, endDate: NSDate(), calendars: nil)
let events = self.eventStore!.eventsMatchingPredicate(predicate)
completed(events)
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func onLoadListButtonClicked(sender: AnyObject)
{
self.performSegueWithIdentifier("LoadTheList", sender: nil)
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
//MARK: UITableViewDataSource
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
//TEST PRINT EVENT COUNT
print("event count (print) \(events.count)")
return events.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "cell"
let cell:UITableViewCell! = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath)
let events:EKEvent! = self.events[indexPath.row]
cell.textLabel!.text = events.title
cell.detailTextLabel!.text = events.startDate.description
print("event cell returned")
return cell
}
}
Issue is your start date is NSDate(timeIntervalSinceNow: 24 * 60 * 60) that is one day ahead and your end date is current date; in short, you are checking event starts tomorrow and ends today ;)
Just use following method to get events started yesterday and ends today. For further use just change the startDt to any previous date.
func fetchEvents(completed: (NSMutableArray) -> ()) {
eventStore!.requestAccessToEntityType(EKEntityType.Event, completion: {
granted, error in
let startDt = NSDate(timeIntervalSinceNow: -24 * 60 * 60);
let predicate = self.eventStore!.predicateForEventsWithStartDate(startDt, endDate: NSDate(), calendars: nil)
let events = NSMutableArray(array: self.eventStore!.eventsMatchingPredicate(predicate))
completed(events)
})
}
Edit
Call this as:
fetchEvents { (arr:NSMutableArray) in
print(arr)
//reload your table
}
As per your comment:
I think its a breakpoint issue but I did not put a breakpoint. It's so
strange. imgur.com/a/nQGo1 imgur.com/a/MUb4i – bourbon 2 mins ago
I had looked at your code, in that remove viewWillAppear method and add self.eventStore = EKEventStore() above the fetchEvents function call in viewDidLoad.
One more important point assign arr to self.events and then reload the table. I have just changed the return type of method use following:
Method:
func fetchEvents(completed: ([EKEvent]) -> ()) {
eventStore!.requestAccessToEntityType(EKEntityType.Event, completion: {
granted, error in
let startDt = NSDate(timeIntervalSinceNow: -24 * 60 * 60);
let predicate = self.eventStore!.predicateForEventsWithStartDate(startDt, endDate: NSDate(), calendars: nil)
let events = self.eventStore!.eventsMatchingPredicate(predicate)
completed(events)
})
}
Method call:
fetchEvents { (arr:[EKEvent]) in
print(arr)
self.events = arr
//reload table
}
Related
I have a tableview with basic cellforrow function. I have constructed a timer function as well for my todo list app.
I am trying to create an app that has a running countdown timer in each cell of a tableview, using standard subtitle cells. The timer name is displayed in the textfield and the running timer is displayed in the subtitle.
The idea here is to calculate the remaining time for a task, when a user sets a specific deadline date for the chosen task.
I managed to implement this feature succesfully but the problem I have is when I set the detailtextlabel of a cell (subtitle) to the remaining time left (which displays remaining days/hours/mins/secs ) it does not refresh.
To summarise i want to create a countdown timer feature of a task to show it counting down every second. I want it to load on viewdidload.
is my timer correct? or in the wrong place. I thought it would make sense to have the timer refresh the whole table then if it was inn cellforRow function.
I have tried a timer in the cellforrow function, with no joy. I then made a separate function for the timer to refresh the tableview with reloadData() inside it. This function is called in viewDidLoad. the countdown and everything works, but my label just doesn't update unless I click a cell or add a new todo task.
override func viewDidLoad() {
super.viewDidLoad()
func startTimer() {
self.countDownTimer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(reloadTableForTimer), userInfo: nil, repeats: true)
}
}
#objc func reloadTableForTimer() {
DispatchQueue.main.async { self.tableView.reloadData() }
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
if item.done == true {
cell.detailTextLabel?.text = "Complete!"
}else {
var deadlineDatesAll = item.deadlineDate
if deadlineDatesAll == "" {
cell.detailTextLabel?.text = "NO DEADLINE SET"
} else {
// insert code for data disection and then to timer to display each countdown:
retrieveDateComponants(deadlineDateChosen: deadlineDatesAll.description)
var dataComponants:Array = retrieveDateComponants(deadlineDateChosen: deadlineDatesAll.description)
var returnedDueDateString = updateTime(yearExtract: dataComponants[0], monthExtract: dataComponants[1], dayExtract: dataComponants[2], hourExtract: dataComponants[3], minExtract: dataComponants[4], secsExtract: dataComponants[5])
cell.detailTextLabel?.text = "Due In: \(returnedDueDateString)"
}
}
cell.accessoryType = item.done == true ? .checkmark : .none
}else {
cell.textLabel?.text = "No Item Found"
}
return cell
}
//UPDATE countdowns function:
#objc func updateTime(yearExtract:String, monthExtract:String, dayExtract:String,hourExtract:String,minExtract:String, secsExtract:String) -> (String) {
var deadlineTimeDueString : String = ""
let futureDate: Date = {
var future = DateComponents(
year: Int(yearExtract),
month: Int(monthExtract),
day: Int(dayExtract),
hour: Int(hourExtract),
minute: Int(minExtract),
second: Int(secsExtract) )
return Calendar.current.date(from: future)!
}()
var countdownComp: DateComponents {
return Calendar.current.dateComponents([.day, .hour, .minute, .second], from: Date(), to: futureDate)
}
let countdown = countdownComp //only compute once per call
let days = countdown.day!
let hours = countdown.hour!
let minutes = countdown.minute!
let seconds = countdown.second!
print((String(format: "OUTPUT TEST: %02d:%02d:%02d:%02d", days, hours, minutes, seconds)))
if seconds < 0 {
deadlineTimeDueString = String("OVERDUE")
} else if seconds > 0 {
deadlineTimeDueString = String(format: "%02d Days, %02d Hours, %02d Mins & %02d Secs", days, hours, minutes, seconds)
}
return deadlineTimeDueString
}
Everything functions well, except countdown time doesn't refresh label unless I manually click another cell or same one. Remaining time works and updates every second.
any help would be appreciated
Thanks MFK
I might suggest letting the cells update their own labels, but so that they update all at the same time, have the controller post notification when cells should be update.
So, you can create a notification for your timer:
extension Notification.Name {
static let timerFired = Notification.Name(rawValue: Bundle.main.bundleIdentifier! + ".timer")
}
The view controller would then post this notification:
class ViewController: UITableViewController {
var todos = ...
private weak var timer: Timer?
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
NotificationCenter.default.post(name: .timerFired, object: nil)
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
timer?.invalidate()
}
}
The UITableViewDataSource could then pass the “to do” to the cell:
// MARK: - UITableViewDataSource
extension ViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return todos.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ToDoCell", for: indexPath) as! ToDoCell
cell.configure(for: todos[indexPath.row])
return cell
}
}
The cell can then tie this all together, observing the notification and updating the text appropriately:
class ToDoCell: UITableViewCell {
private var todo: ToDo?
private var token: NSObjectProtocol?
private static let formatter: DateComponentsFormatter = {
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full
formatter.allowedUnits = [.day, .hour, .minute, .second]
formatter.maximumUnitCount = 2
return formatter
}()
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
addObservers()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
addObservers()
}
deinit {
removeObservers()
}
func configure(for todo: ToDo) {
self.todo = todo
textLabel?.text = todo.name
if todo.done {
removeObservers()
detailTextLabel?.textColor = .black
detailTextLabel?.text = "Done!"
accessoryType = .checkmark
} else {
addObservers()
updateTime()
accessoryType = .none
}
}
}
private extension ToDoCell {
func addObservers() {
guard token == nil else { return }
token = NotificationCenter.default.addObserver(forName: .timerFired, object: nil, queue: .main) { [weak self] _ in
self?.updateTime()
}
}
func removeObservers() {
if let token = token {
NotificationCenter.default.removeObserver(token)
}
}
func updateTime() {
guard let date = todo?.deadline else {
detailTextLabel?.text = "No deadline specified"
detailTextLabel?.textColor = .black
return
}
let now = Date()
if date < now {
detailTextLabel?.text = "Past due " + (ToDoCell.formatter.string(from: date, to: now) ?? "")
detailTextLabel?.textColor = .red
} else {
detailTextLabel?.text = ToDoCell.formatter.string(from: now, to: date)
detailTextLabel?.textColor = .black
}
}
}
Note, I’m using DateComponentsFormatter with maximumUnitCount of 2, so that I don’t see annoying level of detail. So rather than “7 days, 12 hours, 42 minutes, 8 seconds”, I’ll just see “7 days, 12 hours”, but it will show it where the seconds might be relevant (e.g. less than 1 hour away):
But the details are less important than the general design principle of pulling a lot of the cell-specific code into the UITableViewCell subclass rather than putting it in the view controller.
first i come from France so sorry for my english.
Second, I'm new in developpement and i have develop a code for add data and after show them with Core Data. it works.ok
but after i want update but i have a problem i don't know why i can't update my value. There is an error : "fatal error: unexpectedly found nil while unwrapping an Optional value"
i have try many solution since 1 week, but can't find the problem. Thanks if someone can help me ! even a little help :)
this is my code (swift 2.3) :
for show in table view :
import UIKit
import CoreData
class ProduitTableViewController: UITableViewController {
#IBOutlet var table: UITableView!
var produits = [NSManagedObject]()
func refreshStories(refreshControl: UIRefreshControl) {
produits.removeAll()
fetchData()
self.table.reloadData()
refreshControl.endRefreshing()
}
override func viewDidLoad() {
super.viewDidLoad()
self.fetchData()
self.table.addSubview(self.refreshControl!)
self.refreshControl?.addTarget(self, action: #selector(ProduitTableViewController.refreshStories(_:)), forControlEvents: UIControlEvents.ValueChanged)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func fetchData() {
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = appDelegate.managedObjectContext
//2
let fetchRequest = NSFetchRequest(entityName: "Produits")
let sort = NSSortDescriptor(key:"dateAjout", ascending:true)
fetchRequest.sortDescriptors = [sort]
//3
do {
let results = try managedContext.executeFetchRequest(fetchRequest)
produits = results as! [NSManagedObject]
} catch let error as NSError {
print("Donnees non recu \(error), \(error.userInfo)")
}
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return self.produits.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell")
let produit = produits[indexPath.row]
cell!.textLabel!.text = produit.valueForKey("nom") as? String
/*
let id = produit.valueForKey("id") as? String
let date = produit.valueForKey("date") as? NSDate
let localNotification = UILocalNotification()
localNotification.userInfo = ["id" : id!]
localNotification.soundName = UILocalNotificationDefaultSoundName
localNotification.alertBody = "expiré"
localNotification.fireDate = date
UIApplication.sharedApplication().scheduleLocalNotification(localNotification)
UIApplication.sharedApplication().applicationIconBadgeNumber += 1
*/
return cell!
}
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
let supprimer = UITableViewRowAction(style: .Normal, title: "Suppr.") { action, index in
let appDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let moc = appDelegate.managedObjectContext
// 3
moc.deleteObject(self.produits[indexPath.row])
appDelegate.saveContext()
// 4
self.produits.removeAtIndex(indexPath.row)
tableView.reloadData()
}
supprimer.backgroundColor = UIColor.redColor()
let update = UITableViewRowAction(style: .Normal, title: "Modifier") { action, index in
}
update.backgroundColor = UIColor.blueColor()
return [supprimer]
}
override func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
// the cells you would like the actions to appear needs to be editable
return true
}
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "showDetail" {
if let destination = segue.destinationViewController as? DetailViewController {
let row = table.indexPathForSelectedRow?.row
let produit = produits[row!]
let nom = produit.valueForKey("nom") as? String
let id = produit.valueForKey("id") as? String
let detail = produit.valueForKey("detail") as? String
let date = produit.valueForKey("date") as? NSDate
let time = date
let formatter = NSDateFormatter()
formatter.dateFormat = "dd-MM-YY HH:mm"
let formatteddate = formatter.stringFromDate(time!)
destination.dataNom = nom!
destination.dataId = id!
destination.dataDetail = detail!
destination.dataDate = formatteddate
}
}
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if table.cellForRowAtIndexPath(indexPath) != nil {
self.performSegueWithIdentifier("showDetail", sender: self)
}
}
}
for show detail of the cell :
import CoreData
import UIKit
class DetailViewController: UIViewController {
#IBOutlet var Label: UILabel!
#IBOutlet var Detail: UITextView!
#IBOutlet weak var Date: UILabel!
#IBOutlet weak var Id: UILabel!
var dataNom = ""
var dataDetail = ""
var dataDate = ""
var dataId = ""
override func viewDidLoad() {
super.viewDidLoad()
Label.text = dataNom
Detail.text = dataDetail
Date.text = dataDate
Id.text = dataId
// Do any additional setup after loading the view
}
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject!) {
if segue.identifier == "ModifierDetail" {
if let destination = segue.destinationViewController as? ModifierViewController {
destination.modifierNom = dataNom
destination.modifierId = dataId
destination.modifierDetail = dataDetail
destination.modifierDate = dataDate
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
And the last for update/modify my detail:
import UIKit
import CoreData
class ModifierViewController: UIViewController {
#IBOutlet weak var Nom: UITextField!
#IBOutlet weak var Detail: UITextView!
#IBOutlet weak var Date: UITextField!
var Produits: NSManagedObject!
var managedContext: NSManagedObjectContext!
var modifierNom = ""
var modifierDetail = ""
var modifierDate = ""
var modifierId = ""
override func viewDidLoad() {
super.viewDidLoad()
Nom.text = modifierNom
Detail.text = modifierDetail
Date.text = modifierDate
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func Annuler(sender: UIBarButtonItem) {
navigationController?.popViewControllerAnimated(true)
}
#IBAction func Modifier(sender: UIButton) {
let fetchRequest = NSFetchRequest(entityName:"Produits")
fetchRequest.predicate = NSPredicate(format: "nom = %#", modifierNom)
do {
let list = try managedContext.executeFetchRequest(fetchRequest) as! [Produit]
if list.count == 0 // Check notificationId available then not save
{
let newManagedObject = NSEntityDescription.insertNewObjectForEntityForName("Produits", inManagedObjectContext: managedContext)
newManagedObject.setValue(modifierNom, forKey: "nom")
}
// success ...
} catch let error as NSError {
// failure
print("Fetch failed: \(error.localizedDescription)")
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}
i have create this other file:
import Foundation
import CoreData
#objc(Produit)
class Produit: NSManagedObject {
#NSManaged var nom:String!
}
As you mentioned in the comment, this line causes the crash:
let list = try managedContext.executeFetchRequest(fetchRequest) as! [Produit]
That line is not safe, because you don't know, if casting to [Prodiut]will always be successful.
In general, you should never force-cast (as!) or force-unwrap (!) something when you don't know 1000%, that it will succeed.
To cast safely, you can use guard:
guard let list = try managedContext.executeFetchRequest(fetchRequest) as! [Produit] else {
//do some error handling here and then return
return
}
After that, you can safely use list.
It's really important to understand, what optionals are and how to handle them safely without crashes.
I hope someone can help because this is probably a simple problem, but my Parse query within a query prints the object I am looking for, but cannot append it to the array I need it in to retrieve it in a UITableView.
The error is "fatal error: index out of range" when I dequeue the results of the query in a cell.
Here's the code:
import UIKit
import Parse
import Bolts
class MessagesTableVC: UITableViewController {
var usernames = [String]()
var sentDate = [NSDate]()
var details = [String]()
var userImage = [PFFile]()
#IBAction func backToProfile(sender: AnyObject) {
self.performSegueWithIdentifier("messagesToProfile", sender: self)
}
override func viewDidLoad() {
super.viewDidLoad()
let messagesQuery = PFQuery(className: "Messages")
messagesQuery.whereKey("recipientId", equalTo: PFUser.currentUser()!.objectId!)
messagesQuery.includeKey("senderId")
messagesQuery.orderByDescending("createdAt")
messagesQuery.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
if error != nil {
print(error)
}
if error == nil {
if let objects = objects {
self.usernames.removeAll(keepCapacity: true)
self.sentDate.removeAll(keepCapacity: true)
self.details.removeAll(keepCapacity: true)
self.userImage.removeAll(keepCapacity: true)
for object in objects {
self.sentDate.append(object.createdAt! as NSDate)
if (object["item"] != nil) {
self.details.append(object["item"] as! String)
} else {
self.details.append(object["request"] as! String)
}
let senderObject = (object["senderId"] as! PFUser)
let senderId = (senderObject.objectId! as String)
print(senderId)
// Query for sender info
let userQuery = PFUser.query()
userQuery?.whereKey("objectId", equalTo: senderId)
userQuery?.getFirstObjectInBackgroundWithBlock({ (object, error) in
self.usernames.append((object!["username"] as! String))
//self.userImage.append(object!["profilePicture"] as! PFFile)
})
}
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
//self.search.resignFirstResponder()
}
}
}
})
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return sentDate.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("messageCell", forIndexPath: indexPath) as! MessageTableViewCell
//print(usernames[indexPath.row])
cell.senderUsername.text = usernames[indexPath.row]
cell.itemOrPreview.text = details[indexPath.row]
let date = sentDate[indexPath.row]
let formatter = NSDateFormatter()
formatter.dateStyle = NSDateFormatterStyle.LongStyle
formatter.timeStyle = .ShortStyle
let dateString = formatter.stringFromDate(date)
cell.sentDate.text = dateString
//userImage[indexPath.row].getDataInBackgroundWithBlock { (data, error) in
// if let downloadedItemImage = UIImage(data: data!) {
// cell.senderImage?.image = downloadedItemImage
//}
//}
return cell
}
override func viewWillAppear(animated: Bool) {
tableView.reloadData()
}
}
As I may guess, you might get inconsistent number of elements in sentDate and usernames because you append to usernames via asynchronous getFirstObjectInBackgroundWithBlock method, so, by the time you call reloadData on tableView, all user names might not be yet added to usernames. You might have less items in usernames then in sentDate by the time your tableView callback fires and in numberOfItems you return number of items in sentDate.
In order to fix that you need at first refactor your code, it has a lot of places things might go wrong. I won't give any specific advise, but, seems, you might want to wait before you get all the data before you reload your tableView.
import UIKit
class FeedTableViewController: UITableViewController {
var navBar:UINavigationBar=UINavigationBar()
let font = UIFont(name: "Baskerville", size: 15)
var feedData:NSMutableArray = NSMutableArray()
required init(coder aDecoder: NSCoder){
super.init(coder: aDecoder)
}
#IBAction func likeButton(sender: AnyObject) {
if var votes:Int? = quote!.objectForKey("votes") as? Int {
votes!++
}
}
#IBAction func loadData(sender: AnyObject?) {
feedData.removeAllObjects()
var findFeedData:PFQuery = PFQuery(className: "userQuotes")
findFeedData.findObjectsInBackgroundWithBlock{
(objects:[AnyObject]?, error:NSError?)->Void in
if error == nil{
if let objs = objects{
for object in objs{
let quote:PFObject = object as! PFObject
self.feedData.addObject(quote)
// let user:PFUser = (object as! NSArray).lastObject as! PFUser
}
//println(self.feedData)
let array:NSArray = self.feedData.reverseObjectEnumerator().allObjects
self.feedData = NSMutableArray(array: array)
NSOperationQueue.mainQueue().addOperationWithBlock({
self.tableView.reloadData()
})
}
}
}
}
override func viewDidAppear(animated: Bool) {
self.loadData( nil )
}
override func viewDidLoad() {
super.viewDidLoad()
self.title = "Quotezilla"
// 3
//self.navigationItem.setRightBarButtonItem(rightSearchBarButtonItem, animated: true)
// Uncomment the following line to preserve selection between presentations
// self.clearsSelectionOnViewWillAppear = false
// Uncomment the following line to display an Edit button in the navigation bar for this view controller.
// self.navigationItem.rightBarButtonItem = self.editButtonItem()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
// #warning Potentially incomplete method implementation.
// Return the number of sections.
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete method implementation.
// Return the number of rows in the section.
return feedData.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:QuoteTableViewCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! QuoteTableViewCell
let quote:PFObject = self.feedData.objectAtIndex(indexPath.row) as! PFObject
cell.contentTextView!.font = font
cell.timeStampLabel!.font = font
cell.publisherLabel!.font = font
cell.contentTextView.alpha = 0
cell.timeStampLabel.alpha = 0
cell.publisherLabel.alpha = 0
cell.contentTextView.text = quote.objectForKey("content") as! String
//cell.publisherLabel.text = quote.objectForKey("publisher") as? String
/* func loadLikes(){
if var votes:Int? = quote.objectForKey("votes") as? Int {
votes!++
}
}*/
var dateFormatter:NSDateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "EEEE, MMM d, h:mm a"
cell.timeStampLabel.text = dateFormatter.stringFromDate(quote.createdAt!)
var votes:Int? = quote["votes"] as? Int
if votes == nil {
votes = 0
}
cell.likesLabel?.text = "\(votes!)"
var myObject = quote["publisher"] as? PFObject
myObject?.fetchIfNeeded()
if let foundUser = myObject as? PFUser{
cell.publisherLabel.text = foundUser.username
UIView.animateWithDuration(0.7, animations: {
cell.contentTextView.alpha = 1
cell.timeStampLabel.alpha = 1
cell.publisherLabel.alpha = 1
})
}
return cell
}
So what I am essentially attempting to do is create a likes or votes button. As you see in the code I have a likeButton action that is supposed to auto-increment the likes section in parse. I display the current likes that I have filled into the rows in Parse itself in the cellForRowAtIndexPath function. The problem is that I cannot call quote.objectForKey("votes"), because I initialize it later. I have been poring over this problem and cannot find a way to make the votes update in parse through the likeButton action.
You must live with life on the network. That means your table won't have certain data available when the App starts. Handle a missing object or missing key within a particular cell gracefully and just use some kind of placeholder value. When the parse callback executes, you are already correctly forcing a refresh.
OK So BIG EDIT
This class needed a lot of work. I'm not even going to spell out every change here, but it's basically a complete Parse.com tutorial at this point.
This code compiles cleanly but I can't be sure of everything in your context. In particular do you have a 'likesButton' on every table row as part of your custom table cell view? I'm assuming that.
class FeedTableViewController: UITableViewController {
var navBar = UINavigationBar()
let font = UIFont(name: "Baskerville", size: 15)
var feedData = [PFObject]()
static let cellID = "cell"
// NOTE! See how this tag is set below
#IBAction func likeButton(sender: UIButton) {
let quote = feedData[sender.tag]
if let votes = quote.objectForKey("votes") as? Int {
quote.setObject(votes + 1, forKey: "votes")
}
else {
// CHALLENGE FOR YOU: handle the case of no votes attribute
}
// UPDATE the local UI
tableView.reloadRowsAtIndexPaths([NSIndexPath(forRow: sender.tag, inSection: 0)],
withRowAnimation: .None)
// CHALLENGE FOR YOU: UPDATE Parse...start a new question if necessary
}
#IBAction func loadData(sender: AnyObject?) {
feedData.removeAll()
PFQuery(className: "userQuotes").findObjectsInBackgroundWithBlock {
[unowned self]
(objects: [AnyObject]?, error: NSError?) -> Void in
if let objs = objects {
for object in objs {
self.feedData.append(object as! PFObject)
}
self.feedData = self.feedData.reverse()
}
NSOperationQueue.mainQueue().addOperationWithBlock { self.tableView.reloadData() }
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.loadData(nil)
self.title = "Quotezilla"
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return feedData.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(FeedTableViewController.cellID, forIndexPath: indexPath) as! QuoteTableViewCell
cell.likesButton!.tag = indexPath.row // See how tag works with the above
cell.contentTextView!.font = font
cell.timeStampLabel!.font = font
cell.publisherLabel!.font = font
cell.contentTextView.alpha = 0.0
cell.timeStampLabel.alpha = 0.0
cell.publisherLabel.alpha = 0.0
let q = feedData[indexPath.row]
if let content = q.objectForKey("content") as? String {
cell.contentTextView.text = content
}
else {
cell.contentTextView.text = "Content not found!"
}
var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "EEEE, MMM d, h:mm a"
cell.timeStampLabel.text = dateFormatter.stringFromDate(q.createdAt!)
let votes = (q.objectForKey("votes") as? Int) ?? 0
cell.likesLabel?.text = "\(votes)"
let myObject = q.objectForKey("publisher") as? PFObject
myObject?.fetchInBackgroundWithBlock {
[unowned self]
(object: PFObject?, error: NSError?) in
NSOperationQueue.mainQueue().addOperationWithBlock {
if let foundUser = object as? PFUser {
cell.publisherLabel.text = foundUser.username
UIView.animateWithDuration(0.7) {
cell.contentTextView.alpha = 1.0
cell.timeStampLabel.alpha = 1.0
cell.publisherLabel.alpha = 1.0
}
}
else {
cell.publisherLabel.text = "Publisher not found!"
}
}
}
return cell
}
}
I've been following "iOS8 Swift Programming Cookbook"'s section on EventKit and calendars, and I've learned a lot (especially since I'm new to programming). But the next step that I want to take is populating a tableview that I have an outlet to in my ViewController with the event data so as to have a tableview list of upcoming events. Can anyone tell me how to do this?
Here's what I have so far:
import UIKit
import EventKit
import EventKitUI
class TodayViewController: UIViewController, UITableViewDataSource {
var events: AnyObject = []
#IBOutlet weak var tableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
requestCalendarAccess()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func requestCalendarAccess() {
let eventStore = EKEventStore()
switch EKEventStore.authorizationStatusForEntityType(EKEntityTypeEvent){
case .Authorized:
readEvents()
case .Denied:
displayAccessDenied()
case .NotDetermined:
eventStore.requestAccessToEntityType(EKEntityTypeEvent, completion:
{[weak self] (granted: Bool, error: NSError!) -> Void in
if granted{
self!.readEvents()
} else {
self!.displayAccessDenied()
}
})
case .Restricted:
displayAccessRestricted()
}
}
func displayAccessDenied(){
println("Access to the event store is denied.")
}
func displayAccessRestricted(){
println("Access to the event store is restricted.")
}
func readEvents(){
/* Instantiate the event store */
let eventStore = EKEventStore()
let icloudSource = sourceInEventStore(eventStore,
type: EKSourceTypeCalDAV,
title: "iCloud")
if icloudSource == nil{
println("You have not configured iCloud for your device.")
return
}
let calendar = calendarWithTitle("Work",
type: EKCalendarTypeCalDAV,
source: icloudSource!,
eventType: EKEntityTypeEvent)
if calendar == nil{
println("Could not find the calendar we were looking for.")
return
}
/* The event starts from today, right now */
let startDate = NSDate()
/* The end date will be 1 day from today */
let endDate = startDate.dateByAddingTimeInterval(24 * 60 * 60)
/* Create the predicate that we can later pass to the
event store in order to fetch the events */
let searchPredicate = eventStore.predicateForEventsWithStartDate(
startDate,
endDate: endDate,
calendars: [calendar!])
/* Fetch all the events that fall between
the starting and the ending dates */
let events = eventStore.eventsMatchingPredicate(searchPredicate)
as [EKEvent]
if events.count == 0 {
println("No events could be found")
} else {
// Go through all the events and print them to the console
for event in events{
println("Event title = \(event.title)")
println("Event start date = \(event.startDate)")
println("Event end date = \(event.endDate)")
}
}
}
func sourceInEventStore(
eventStore: EKEventStore,
type: EKSourceType,
title: String) -> EKSource?{
for source in eventStore.sources() as [EKSource]{
if source.sourceType.value == type.value &&
source.title.caseInsensitiveCompare(title) ==
NSComparisonResult.OrderedSame{
return source
}
}
return nil
}
func calendarWithTitle(
title: String,
type: EKCalendarType,
source: EKSource,
eventType: EKEntityType) -> EKCalendar?{
for calendar in source.calendarsForEntityType(eventType).allObjects
as [EKCalendar]{
if calendar.title.caseInsensitiveCompare(title) ==
NSComparisonResult.OrderedSame &&
calendar.type.value == type.value{
return calendar
}
}
return nil
}
func tableView(tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
return events.count
}
func tableView(tableView: UITableView,
cellForRowAtIndexPath
indexPath: NSIndexPath) -> UITableViewCell {
let cell =
tableView.dequeueReusableCellWithIdentifier("cell")
as UITableViewCell
cell.textLabel!.text = /*what goes here?*/
return cell
}
Right now my events are printing to the console perfectly, but I'm not sure how to get from there to the tableview in my view controller. Any help is appreciated!
at first your event data should be accessible in you table delegate
func readEvents(){
...
let events = eventStore.eventsMatchingPredicate(searchPredicate)
as [EKEvent]
...
}
but events are NOT !!!
you fetch your data just locally in you function readEvents
eventhough you declare a store for events in your class, you never filled it
class TodayViewController: UIViewController, UITableViewDataSource {
var events: AnyObject = []
...
}
to fill the data in you class variable just remove 'redeclaration'
...
var events: [EKEvent] = []
func readEvents(){
...
events = eventStore.eventsMatchingPredicate(searchPredicate)
as [EKEvent]
...
}
The function
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath:NSIndexPath) -> UITableViewCell
gets passed the parameter cellForRowAtIndexPath which contains the index of the row inside your tableview. You can access that index using indexPath.row, which you should use to access your events-Array.
So for example your function could look like this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell") as UITableViewCell
var event = self.events[indexPath.row]
cell.textLabel!.text = event.eventIdentifier
return cell
}
I don't know exactly how the EKEvent Class looks like, but Apple's Documentation of that class says there is the eventIdentifier of type String and so you can take that to test it with your tableview.
Hope you enjoy programming! :)