Im making a simple planner app which sends notifications to users at specific times that events occur.
I have set up a table to store the data and I am storing individual values inside of an array.
I am encountering a problem outputting the NSDates that I have stored inside of my array.
import UIKit
extension NSDate {
convenience init(dateString:String, format:String="h-mm a") {
let formatter = NSDateFormatter()
formatter.timeZone = NSTimeZone.defaultTimeZone()
formatter.dateFormat = format
let d = formatter.dateFromString(dateString)
self.init(timeInterval:0, sinceDate:d!)
}
class MedicineTableViewController: UITableViewController {
//MARK Properties
var medicines = [Medicine]()
override func viewDidLoad() {
super.viewDidLoad()
loadSampleMedicine()
}
func loadSampleMedicine() {
let medicine1 = Medicine(name: "Inhaler", time1: NSDate(dateString: "08:00 a"), time2: NSDate(dateString: "10:00 a"), time3: NSDate(dateString: "02:00 p"), time4: NSDate(dateString: "06:00 p"), time5: NSDate(dateString: "10:00 p"))
medicines.append(medicine1!)
}
// MARK: - Table view data source
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return medicines.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "MedicineTableViewCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath) as! MedicineTableViewCell
let medicine = medicines[indexPath.row]
cell.nameLabel.text = medicine.name
cell.takeAt.text = "Take At:"
cell.time1Label.text = medicine.time1
cell.time2Label.text = medicine.time2
cell.time3Label.text = medicine.time3
cell.time4Label.text = medicine.time4
cell.time5Label.text = medicine.time5
return cell
}
This returns the error "Cannot assign a value of 'NSDate' to a value of type String?"
Is there a way to convert these NSDates into strings?
I have come up with some other possible solution but it involves reworking the whole application so I'd prefer to avoid them if possible.
My possible solution is to rework the data that the user inputs to be a pickerView which has 3 columns one cycling the numbers 01 through to 12, the second 00 to 59 and the third am and pm. and then take the overall string produced and store it in the array. This would allow me to easily print it out since it is just a stored string. Then when I come to the stage at which I am making the notification system I could use the "dateString" function to convert from strings to dates and then program my notifications from that.
So overall I would like to know if I'm able to just print out the NSDates stored in my array or if not if my possible solution would work?
Thanks.
You can use NSDateFormatter. There is a function called stringFromDate. Here is an example.
var date = NSDate() //Or whatever your date is
var stringDate = NSDateFormatter().stringFromDate(date)
Related
Aim :
To be able to display the days selected and the time picked by the user in the same row of the table view. The time should appear at the top and the days selected should appear at the bottom, both in the same row, just like an alarm clock.
Work :
This is the relationship I've got setup :
and this is how I save the days that are selected from a UITable and the time from a UIDatepicker when the save button is tapped :
#IBAction func saveButnTapped(_ sender: AnyObject)
{
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext //creates an object of a property in AppDelegate.swift so we can access it
let bob = Bob(context: context)
//save the time from UIDatePicker to core data
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "HH:mm"
bob.timeToPing = dateFormatter.string(from: timePicked.date)
// save the days selected to core data
for weekday in filteredWeekdays
{
var day = Days(context: context) //create new Days object
day.daysSelected = weekday as NSObject? //append selected weekday
bob.addToTimeAndDaysLink(day) //for every loop add day object to bob object
}
//Save the data to core data
(UIApplication.shared.delegate as! AppDelegate).saveContext()
//after saving data, show the first view controller
navigationController!.popViewController(animated: true)
}
Now that the data is once saved, I get the data :
func getData()
{
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do
{
bobs = try context.fetch(Bob.fetchRequest())
}
catch
{
print("Fetching failed")
}
}
Attempt to get the days selected :
I tried to follow this, the below comments and a formerly deleted answer to this question to do this :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = UITableViewCell()
let bob = bobs[indexPath.row]
cell.textLabel?.text = bob.timeToPing?.description
// retrieve the days that are selected
var daysArray: [Days] = []
daysArray = bob.timeAndDaysLink?.allObjects as! [Days]
for days in daysArray
{
print (days.daysSelected?.description)
cell.textLabel?.text = days.daysSelected! as! String
}
return cell
}
EDIT :
print(daysArray) gives this :
[<Days: 0x6080000a5880> (entity: Days; id: 0xd000000000040000 <x-coredata://30B28771-0569-41D3-8BFB-D2E07A261BF4/Days/p1> ; data: <fault>)]
print(daysArray[0]) gives this :
<Days: 0x6080000a5880> (entity: Days; id: 0xd000000000040000 <x-coredata://30B28771-0569-41D3-8BFB-D2E07A261BF4/Days/p1> ; data: <fault>)
How to save days
let weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
var filteredWeekdays: [String] = []
#NSManaged public var daysSelectedbyUser: NSSet
And then
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
selectedWeekdays()
}
override func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath)
{
selectedWeekdays()
}
func selectedWeekdays()
{
if let selectedRows = tableView.indexPathsForSelectedRows
{
let rows = selectedRows.filter {$0.section == 0}.map{ $0.row}
filteredWeekdays = rows.map{ weekdays[$0] }
print(filteredWeekdays)
}
}
Many thanks!
OK based on your latest comment that the crash occur on this line:
cell.textLabel?.text = days.value(forKey: "daySelected") as! String
It's clearly pointing to the typo you've made in key name. You have: daySelected and should be (based on your core data model) daysSelected, but nevertheless it's not very good approach to use values for your core data entity and also force type like that. To make it better I suggest replacing this line with:
cell.textLabel?.text = days.daysSelected!
This should be already a String since this is a String in CoreData. In case it's optional (should be an optional), you shouldn't force it. I will assume that whenever data will be not there you will just display empty cell, so even better it will be:
cell.textLabel?.text = days.daysSelected ?? ""
This will produce empty string to text, whenever (for some reason) data will be not there.
EDIT
So for additional piece of code you put in your question:
In your CoreData field daysSelected is type of String?, right?
Then you assign timeAndDateLink to NSSet<String>, right? But expected value here should be NSSet<Days>.
So let's edit your input code a bit ( i will put comment on every line):
let bob = Bob(context: context) /* create new Bob object */
for weekday in filteredWeekdays {
var day = Days(context: context) /* create new Days object */
day.daysSelected = weekday /* append selected weekday */
bob.addToTimeAndDaysLink(day) /* for every loop add day object to bob object */
}
I hope everything is clear in above example. You may have a problem with a compiler in that case, because if you choose generate class for entities you will endup with two func with the same name but different parameter (in Swift this should be two different functions, but Xcode sometimes pointing to the wrong one). If you hit that problem try:
let bob = Bob(context: context) /* create new Bob object */
var output: NSMutableSet<Days> = NSMutableSet()
for weekday in filteredWeekdays {
var day = Days(context: context) /* create new Days object */
day.daysSelected = weekday /* append selected weekday */
output.add(day)
}
bob.addToTimeAndDaysLink(output) /* this will assign output set to bob object */
You should also rename your Days entity to Day to avoid future confusion that we have right now, days as array will only be in relation from other entities to this not entity itself.
I don't know why no one uses FetchedResultsController, which is made for fetching NSManagedObjects into tableView, but it doesn't matter I guess...
Problem in this question is that you didn't post here your NSManagedObject class for the variable, so I cannot see which type you set there (Should be Transformable in CoreData model and [String] in NSManagedObject class...)
Ignoring all force unwraps and force casting and that mess (which you should pretty damn well fix as first, then it won't crash at least but just don't display any data...)
Days selected by user is NSSet, which it sure shouldn't be.
Please provide you NSManagedObjectClass in here so I can edit this answer and solve your problem...
I'm building a side project that involves a rather complicated table view. I've decided to take inspiration from Producthunt's app and have a header section for each date. But alas, this simple implementation is a bit harder than I thought it would be. For reference, I've attached photos below to show you what I'm trying to clone. This is from PH's app:
Examples
Implementation
See how there is a Today and Yesterday header section on the top with the date chillin beside it? I've asked peers and some of them suggested to try the [[array]] (array inside of array) approach. Section them out through enums and put them into specific arrays right? This is fine, but after awhile, you can see it getting out of hand. For example, here is some of my code below:
First, there's an enum for the diffrent date cases
enum dateCases {
case today
case yesterday
case twoDaysAgo
case threeDaysAgo
case fourDaysAgo
case fiveDaysAgo
case lastWeek
case lastMonth
case lastYear
case janurary
case feburary
case march
case april
case may
case june
case july
case augest
case september
case october
case november
case december
}
Then there's a struct for each class that needs these arrays in it:
struct dateCaseController {
var todayArray = [Objects]()
var yesterdayArray = [Objects]()
var twoDaysAgoArray = [Objects]()
var threeDaysAgoArray = [Objects]()
var fourDaysAgoArray = [Objects]()
var fiveDaysAgoArray = [Objects]()
var lastWeekArray = [Objects]()
var lastMonthArray = [Objects]()
var lastYearArray = [Objects]()
var januraryArray = [Objects]()
var feburaryArray = [Objects]()
var marchArray = [Objects]()
var aprilArray = [Objects]()
var mayArray = [Objects]()
var juneArray = [Objects]()
var julyArray = [Objects]()
var augestArray = [Objects]()
var septemberArray = [Objects]()
var octoberArray = [Objects]()
var novemberArray = [Objects]()
var decemberArray = [Objects]()
}
The objects all have dates inside of them we can use to analyze and section out the data. Each instance of this class has this struct.
After all of that, there's a function to section out the different objects into their specific arrays:
func anilyzeDateForDateCase(){
for object in objects {
let dateValue = (Logic for counting how many days has passed)
switch dateValue {
case 0:
print("")
case 1:
print("")
case 2:
print("")
case 3:
print("")
case 4:
print("")
case 5:
print("")
case 6:
print("")
ect.....
Conclusion
Thats going to be alot switch cases. You can tell by going this approach, the code will get out of hand quick. Using this approach would be nice if I had a small number of categories, but that is not the case. Not to mention after that, I will have load the table array according to section. I'm fine with doing that, but I'm wondering if this the correct approach to solving this problem? There has to be a simpler solution. Does anyone have experience with an implementation like this? Thank you.
As you correctly deduce, enum is not the way to go here. Apart from anything else, it is hard-coding UI choices into your data structures.
Can I suggest something along these lines:
import Foundation
// Define a `struct` that describes a date range.
// You may want to have convenience inits that don't just specify "days ago".
// You will create an array of these later...
struct DateRange {
var latestDaysAgo: Int
var earliestDaysAgo: Int
var description: String
// The important piece of this is that we can check if any given date
// is in `self` - we will use this later to `filter` by DateRange
func isDateInRange(_ date: Date) -> Bool {
let now = Date()
let oneDayInSeconds = 86400.0 // 60 * 60 * 24
let earliestDate = now.addingTimeInterval(Double(-self.earliestDaysAgo) * oneDayInSeconds)
let latestDate = now.addingTimeInterval(Double(-self.latestDaysAgo) * oneDayInSeconds)
return (date > earliestDate) && (date <= latestDate)
// NB - you REALLY want to do this with `Calendar` arithmetic
// but for clarity of the method, I'm just using 24 hour time differences
}
}
// Now construct your ranges. These can be extended after testing.
// They can also be overlapping.
let ranges: [DateRange] = [
DateRange(latestDaysAgo: 0, earliestDaysAgo: 1, description: "Today"),
DateRange(latestDaysAgo: 1, earliestDaysAgo: 2, description: "Yesterday"),
DateRange(latestDaysAgo: 2, earliestDaysAgo: Int.max, description: "Earlier"),
]
// This is whatever event struct you need - but they include the date
struct Event {
var date: Date
var payload: String // or whatever struct you want
}
// Create some random test data
let events: [Event] = [
Event(date: Date().addingTimeInterval(-100), payload: "abc"),
Event(date: Date().addingTimeInterval(-1000), payload: "abc"),
Event(date: Date().addingTimeInterval(-100000), payload: "abc"),
Event(date: Date().addingTimeInterval(-1000000), payload: "abc"),
]
// Now, the clever piece is combining `flatMap` & `filter` to get your sections:
let sections: [(DateRange, [Event])] = ranges.flatMap({
(dr: DateRange) in
let qualifyingEvents = events.filter({ dr.isDateInRange($0.date) })
return (dr, qualifyingEvents)
})
// Note that the order of ranges in `sections` is the same as the
// order that you chose in `ranges`.
// Check the sections are correct.
for (dr, es) in sections {
print("'\(dr.description)' has \(es.count) entries")
}
/* Output:
'Today' has 2 entries
'Yesterday' has 1 entries
'Earlier' has 1 entries
*/
You can now drive your UITableViewDataSource with sections. For example:
override func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section].1.count
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return sections[section].0.description
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "MyCellID")
let payload = sections[indexPath.section].1[indexPath.row].payload
cell.textLabel?.text = payload // or whatever...
return cell
}
I have an issue with my table view being choppy on scroll while it loads each image inside the function of cellForItemAtIndexPath i've searched through some examples and these are the things i've tried and still have the same issue.
So i have this
var arrRes = [[String:AnyObject]]()
Then inside view did load i make an Alamofire GET request and with swiftyJSON i store the json file to the above dictionary.
if let resData = swiftyJsonVar["events"].arrayObject {
self.arrRes = resData as! [[String:AnyObject]]
}
self.tableview2.reloadData()
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrRes.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("mapCell", forIndexPath: indexPath) as! locationEventsTableViewCell
var dict = arrRes[indexPath.row]
cell.eventTitle.text = dict["eventName"] as? String
let cal = dict["eventStarttime"] as? String
let dateF = NSDateFormatter()
dateF.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
let date:NSDate = dateF.dateFromString(cal!)!
let d = NSDateFormatter()
d.dateFormat = "dd-MM-yyyy HH:mm"
let d1 = d.stringFromDate(date)
cell.eventDate.text = d1
let at = dict["eventStats"]?["attendingCount"] as? Int
let kapa = at?.stringValue
cell.eventAttends.text = kapa
let imageDef : UIImage = UIImage(named: "noimage")!
let priority = DISPATCH_QUEUE_PRIORITY_DEFAULT
dispatch_async(dispatch_get_global_queue(priority, 0)) {
if let theImage = dict["eventCoverPicture"] as? String {
let url = NSURL(string: theImage)
if url != nil {
let data = NSData(contentsOfURL: url!)
dispatch_async(dispatch_get_main_queue()) {
cell.eventImage.image = UIImage(data: data!)
}
} else {
cell.eventImage.image = imageDef
}
}
}
return cell
}
So as you can see i am using the dispatch async function to get the image and even if i have it or not its still choppy.
Has anyone any solution about this? Thanks!
So the problem is that you're calling the images from a URL each time your UITableView is showing. Every time the cell goes off screen and comes back it's calling the method to retrieve the image from the server.
The server calls are being performed while the UI is trying to execute, this includes the scrolling and other visual loads.
Depending on the app, you can download all the images for the UITableView before you load the tableView and store them locally. I would also look into NSCache as that might be better for your app.
The goal is to have UI always be the number one priority. So if there are things that need to be in the UITableView like your eventCoverPicture, load them or call them from memory before you load the UITableView.
This ensures you're making the minimum amount of server calls necessary to reduce user network load.
The UI is interrupted and your users can scroll through their app without this choppiness.
I think your code is right about Async api. it's possibly the NSDateFormatter slowing you down. Date formatter is an heavy API. Use memorization, it would improve the prformance as well.
class MyObject {
// define static variable
private static let formatter: NSDateFormatter = {
let formatter = NSDateFormatter()
formatter.dateFormat = "EEE MMM dd HH:mm:ss Z yyyy"
return formatter
}()
// you could use it like so
func someMethod(date: NSDate) -> String {
return MyObject.formatter.stringFromDate(date)
}
}
This is the craziest error I've encountered ever. I am trying to get the value from a column called nameRetailer in the Orders table, but it keeps getting nil.
Other columns of same String type are returning properly including the status column shown below.
What could lead to this? The spelling is certainly correct. Please help....
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let date = object?.objectForKey("dueDate") as! NSDate
let strDate = dateFormatter.stringFromDate(date)
cell.retailerName.text = object?.objectForKey("nameRetailer") as? String
cell.orderDueDate.text = strDate
cell.orderStatus.text = object?.objectForKey("status") as? String
When I tried to print the value of object?.objectForKey("nameRetailer"), it shows nil in console. In the parse data browser, column has data and was refreshed.
Update: Adding additional code:
The entire class code responsible for the table view:
class OrderViewController: PFQueryTableViewController {
override func queryForTable() -> PFQuery {
let query = PFQuery(className: "Orders")
query.cachePolicy = .CacheElseNetwork
query.orderByAscending("createdAt")
return query
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath, object: PFObject?) -> PFTableViewCell? {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! OrdersTableViewCell
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let date = object?.objectForKey("dueDate") as! NSDate
let strDate = dateFormatter.stringFromDate(date)
cell.retailerName.text = object?.objectForKey("nameRetailer") as? String
cell.orderDueDate.text = strDate
cell.orderStatus.text = object?.objectForKey("status") as? String
print(object)
//let imageFile = object?.objectForKey("image") as PFFile
//cell.cellImageView.image = UIImage(named:"placeholder")
//cell.cellImageView.file = imageFile
//cell.cellImageView.loadInBackground()
return cell
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if indexPath.row + 1 > self.objects?.count
{
return 44
}
let height = super.tableView(tableView, heightForRowAtIndexPath: indexPath)
return height
}
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.row + 1 > self.objects?.count
{
self.loadNextPage()
tableView.deselectRowAtIndexPath(indexPath, animated: true)
self.performSegueWithIdentifier("showDetail", sender: self)
}
}
An image of the table of orders:
and here is the log snapshot of printing PFObject:
And here is the updated snapshots showing the two rows
You have set your cache policy to start by looking up data in the local cache, which may be stale.
Change:
query.cachePolicy = .CacheElseNetwork
to
query.cachePolicy = .NetworkOnly // ignores cache on reading, but saves to cache
or
query.cachePolicy = .IgnoreCache // no cache at all -- this is the default
(or other appropriate value based on your specific context and use case)
In my RealmSwift (0.92.3) under Xcode6.3, how would I
// the Realm Object Definition
import RealmSwift
class NameEntry: Object {
dynamic var player = ""
dynamic var gameCompleted = false
dynamic var nrOfFinishedGames = 0
dynamic var date = NSDate()
}
The current tableView finds the number of objects (i.e. currently all objects) like follows:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if let cnt = RLM_array?.objects(NameEntry).count {
return Int(cnt)
}
else {
return 0
}
}
First question: How would I find the number of objects that have a date-entry after, let's say, the date of 15.06.2014 ?? (i.e. date-query above a particular date from a RealmSwift-Object - how does that work ?). Or in other words, how would the above method find the number of objects with the needed date-range ??
The successful filling of all Realm-Objects into a tableView looks as follows:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("NameCell") as! PlayersCustomTableViewCell
if let arry = RLM_array {
let entry = arry.objects(NameEntry)[indexPath.row] as NameEntry
cell.playerLabel.text = entry.player
cell.accessoryType = entry.gameCompleted ? .None : .None
return cell
}
else {
cell.textLabel!.text = ""
cell.accessoryType = .None
return cell
}
}
Second question: How would I fill into the tableView only the RealmSwift-Objects that have a particular date (i.e. for example filling only the objects that have again the date above 15.06.2014). Or in other words, how would the above method only fill into the tableView the objects with the needed date-range ??
You can query Realm with dates.
If you want to get objects after a date, use greater-than (>), for dates before, use less-than (<).
Using a predicate with a specific NSDate object will do what you want:
let realm = Realm()
let predicate = NSPredicate(format: "date > %#", specificNSDate)
let results = realm.objects(NameEntry).filter(predicate)
Question 1: For the number of objects, just call count: results.count
Question 2: results is an array of NameEntrys after specificNSDate, get the object at indexPath. Example, let nameEntry = results[indexPath.row]
For creating a specific NSDate object, try this answer: How do I create an NSDate for a specific date?