I have the following class:
ChatMessage: Codable {
var id: Int?
var sender: User?
var message: String?
var seen: Int?
var tsp: Date?
}
The tsp is formatted like this: YYYY-MM-DD hh:mm:ss
I would like to "group" messages sent on the same day to end up with something like in this example:
let groupedMessages = [ [ChatMessage, ChatMessage], [ChatMessage, ChatMessage, ChatMessage] ]
I ultimately want to use groupedMessages in a UITableViewController to introduce sections like so:
func numberOfSections(in tableView: UITableView) -> Int {
return groupedMessages.count
// would be 2 int the above
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return chatMessages[section].count
// would be 2 for the first and 3 for the second section
}
What would be the most performant way of getting the sorting done? - i.e. something that also works well once the number of chatMessages to be sorted increases
You could try:
let cal = Calendar.current
let groupedMessages = Dictionary(grouping: self.chatMessages, by: { cal.startOfDay($0.tsp) })
let keys = groupedMessages.keys.sorted(by: { $0 > $1 })
This however would give you a dictionary like:
[
SomeDateHere: [
ChatMessage(id: 0, sender: User?, message: "String", seen: 1),
ChatMessage(id: 0, sender: User?, message: "String", seen: 1)
],
AnotherDateHere: [
ChatMessage(id: 0, sender: User?, message: "String", seen: 1)
]
You could then use keys to return the section count:
return keys.count
And to get the array of messages for each dictionary item like so:
let key = keys[indexPath.section]
let messages = groupedMessages[key].sorted(by: { $0.tsp > $1.tsp })
Or to get just the count:
let key = keys[indexPath.section]
return groupedMessages[key].count
Grouping array by date you could find here How to group array of objects by date in swift?
How to connect this to UITableView you will find here https://www.ralfebert.de/ios-examples/uikit/uitableviewcontroller/grouping-sections/
I would probably start by introducing a new type:
public struct CalendarDay: Hashable {
public let date: Date
public init(date: Date = Date()) {
self.date = Calendar.current.startOfDay(for: date)
}
}
extension CalendarDay: Comparable {
public static func < (lhs: CalendarDay, rhs: CalendarDay) -> Bool {
return lhs.date < rhs.date
}
}
Then I would extend your message:
extension ChatMessage {
var day: CalendarDay {
return CalendarDay(date: tsp)
}
}
(I am assumming tsp is not an optional, there is no reason for it to be optional).
Now, let's define a section:
struct Section {
var messages: [ChatMessage] = []
let day: CalendarDay
init(day: CalendarDay) {
self.day = day
}
}
Let's group easily:
let messages: [ChatMessage] = ...
var sections: [CalendarDay: Section] = [:]
for message in messages {
let day = message.day
var section = sections[day] ?? Section(day: day)
section.messages.append(day)
sections[day] = section
}
let sectionList = Array(sections.values).sorted { $0.day < $1.day }
If your messages are not originally sorted, you might need to also sort messages in every section separately.
let testArray = ["2016-06-23 09:07:21", "2016-06-23 08:07:21", "2016-06-22 09:07:21", "2016-06-22 08:07:21"]
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "YYYY-MM-DD hh:mm:ss"
let groupDic = Dictionary(grouping: testArray) { (pendingCamera) -> DateComponents in
let date = Calendar.current.dateComponents([.day, .year, .month], from: dateFormatter.date(from: pendingCamera)!)
return date
}
print(groupDic)
You can take groupDic and return in numberOfSections and titleForHeaderInSection.
Related
I am building a calendar app and I have developed it so that when you click on a date it will show the event corresponding to that date. I have it so that the date will appear in the tableview and when you click on the event it will then segue to a detail view controller and show the details of that event. I am stuck because I am not sure how to get firebase to load multiple events onto one date or how to show multiple events with the same date.
My Code:
View Did Load:
override func viewDidLoad() {
super.viewDidLoad()
calendar.dataSource = self
calendar.delegate = self
self.calendar.calendarHeaderView.backgroundColor = UIColor.blue
navigationController?.navigationBar.barTintColor = UIColor.blue
calendar.appearance.titleWeekendColor = UIColor.red
retrieveEventsFromDatabase()
print(eventsArray)
}
How I am getting the Data:
func retrieveEventsFromDatabase() {
dbReference = Database.database().reference().child("calendarevents")
dbReference.observe(DataEventType.value, with: { [weak self] (snapshot) in
guard snapshot.childrenCount > 0 else { return }
var events: [EventsDataModel] = []
for event in snapshot.children.allObjects as! [DataSnapshot]
{
let object = event.value as? [String: AnyObject]
let eventName = object?["eventName"]
let eventDate = object?["eventDate"]
let eventColor = object?["eventColor"]
let event = EventsDataModel(eventName: eventName as! String, eventDate: eventDate as! String,eventColor: eventColor as! String)
events.append(event)
self?.eventsArray = events
//print(events)
}
DispatchQueue.main.async {
self?.eventsTable.reloadData()
self?.calendar.reloadData()
}
})
}
How i am showing it in my table and calendar:
func calendar(_ calendar: FSCalendar, didSelect date: Date, at monthPosition: FSCalendarMonthPosition) {
let formatter = DateFormatter()
formatter.dateFormat = "dd-MM-yyyy"
let dateString = self.dateFormatter2.string(from: date)
print("selected dateString = \(dateString)")
tableArray.removeAll()
for event in eventsArray {
if event.eventDate.contains(dateString) {
selectedDateLabel.text = ("The selected date is \(dateString)")
print("The event for this date is \(event.eventName)")
tableArray.append(event)
break;
} else {
selectedDateLabel.text = "select a date to see the events for this day"
print("=select a date to see the events for this day")
}
}
self.eventsTable.reloadData()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let friend = tableArray[indexPath.row].eventName
let cell = eventsTable.dequeueReusableCell(withIdentifier: "eventCell", for: indexPath) as! EventsTitleTableViewCell
cell.eventTitleLabel?.text = friend
return cell
}
Firebase Database:
{
"2021-01-12" : {
"eventColor" : "red",
"eventDate" : "2021-01-12",
"eventName" : "SignUp Day"
},
"2021-01-15" : {
"eventColor" : "red",
"eventDate" : "2021-01-15",
"eventName" : "First Day"
},
"electionday" : {
"eventColor" : "red",
"eventDate" : "2021-02-20",
"eventName" : "Election Day "
},
"groundhogday" : {
"eventColor" : "red",
"eventDate" : "2021-02-20",
"eventName" : "GroundHog Day"
}
}
Picture of my App for more details:
I think that a better and more logical approach would be to store the event, rather than dates, and then every event has a corresponding date.
When the user taps on the day, you fetch all the events that have the date property equal to that date.
So for anybody that is looking at this in the future. I want to answer my own question. For any references I was using this with FSCalendar and Firebase. The answer to my question was that there was a "break;" as seen after the tableArray(append). That was only allowing one of my events to show on the table. When i commented that out, then both the events would show on my table. Thanks!
I am trying to populate a tableview with entries from an XML file. So far I have parsed the XML file to a Struct and written the methods for titleForHeaderInSection and numberOfSections, but am struggling calculate numberOfRows for each section by counting the <holiday> entries in each <calendarevent>. I think my main struggle comes from not understanding how to use [section]. Here's what I've done so far:
I have parsed an xml file that has calendar dates and events like this. You'll see how the first calendarevent has 2 holiday and description entries but the second has 1. :
<calendar>
<calendarevent>
<month>October</month>
<dateevent>2018 10 01</dateevent>
<datenumber>01</datenumber>
<holiday>First Holiday</holiday>
<description>aaaaaaaaaa</description>
<holiday>Second Holiday</holiday>
<description>bbbbbbbbbb</description>
</calendarevent>
<calendarevent>
<month>October</month>
<dateevent>2018 10 10</dateevent>
<datenumber>10</datenumber>
<holiday>Third Holiday</holiday>
<description>ccccccccccc</description>
</calendarevent>
.... and so on
To a struct that looks like this:
struct CalendarDates {
struct CalendarEvents {
var month = ""
var eventdate = ""
var eventdatenumber = ""
var holiday = ""
var description = ""
}
}
Here is my XML Parser code:
class CalendarViewController {
var myCalendarDatesFromStrut = [CalendarDates]()
var myCalendarEventsFromStrut = [CalendarDates.CalendarEvents]()
}
extension CalendarViewController {
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
calendarEventsElementFromXML = elementName
}
func parser(_ parser: XMLParser, foundCharacters string: String) {
let data = string.trimmingCharacters(in: CharacterSet.whitespacesAndNewlines)
if data.count != 0 {
switch calendarEventsElementFromXML
{
case "month": monthsFromXML = data
case "dateevent": eventdatesFromXML = data
case "datenumber": eventdatenumbersFromXML = data
case "holiday": holidaysFromXML = data
default: break
}
}
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
if elementName == "calendarevent" {
var myCalendarDates = CalendarDates.CalendarEvents()
myCalendarDates.month = monthsFromXML
myCalendarDates.eventdate = eventdatesFromXML
myCalendarDates.eventdatenumber = eventdatenumbersFromXML
myCalendarDates.holiday = holidaysFromXML
myCalendarEventsFromStrut.append(myCalendarDates)
}
}
}
So after parsing XML and appending it to the struct, I start getting the values for the tableview.
Each section is made of one <calendarevent> if the <month> is the same as current Date month. The XML has <calendarevent> from other months so I don't want all of them. I check if the current month is == month in XML, if it is, then I count #of dateevents for numberOfSections and format the dateevents to become the titles of those sections.
Then each row in a section represents one <holiday> in the corresponding <calendarevent>.
extension CalendarViewController: UITableViewDelegate, UITableViewDataSource {
//Calculates # of <dateevents> in current month
func numberOfSections(in tableView: UITableView) -> Int {
formatter.dateFormat = "MMMM"
let currentMonthShown = formatter.string(from: selectedDate)
let allEventsInVisibleMonth = myCalendarEventsFromStrut.filter({ $0.month == currentMonthShown }).map {$0.eventdate}.count
return allEventsInVisibleMonth
}
// Prints <dateevent> reformatted as section header
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
formatter.dateFormat = "MMMM"
let currentMonthShown = formatter.string(from: selectedDate)
let allEventsInVisibleMonth = myCalendarEventsFromStrut.filter({ $0.month == currentMonthShown }).map {$0.eventdate}
formatter.dateFormat = "yyyy-MM-dd"
let eventStringsToDate = allEventsInVisibleMonth.map{ formatter.date(from: $0) }
formatter.dateFormat = "yyyy-MM-dd"
formatter.dateStyle = .long
formatter.timeStyle = .none
let eventDatesBackToString = eventStringsToDate.map{ formatter.string(from: $0 as! Date )}
return eventDatesBackToString[section]
}
Now, each row in a section needs to represent one <holiday> in the corresponding <calendarevent> - here's where I'm stuck.
For tableview's numberOfRowsInSection, I need to count # of <holiday> entries are in each <calendarevent> and show that # rows in each section. Then print the in cellForRowAt. Could someone give me a hint for the logic here?
Here is what I tried and where I'm failing specifically:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
formatter.dateFormat = "MMMM"
let currentMonthShown = formatter.string(from: selectedDate)
let allEventsInMonth = myCalendarEventsFromStrut.filter({ $0.month == currentMonthShown }).map {$0.holiday}
return allEventsInMonth.count //This gets me same number of rows in all sections = total # of events in current month.
//I'm struggling to figure out how to get # of events at a particular section, so I can make that the # of rows
}
First of all, the XML may contain multiple holiday element inside a single calendarevent, so you need to update your CalendarEvents to be able to hold multiple holidays.
struct Holiday {
var title: String = ""
var description: String = ""
}
//You should better avoid plural form for something which represents a single object
struct CalendarEvent {
var month: String = ""
var eventdate: String = ""
var eventdatenumber: String = ""
//Better use plural form for the name representing multiple objects
var holidays: [Holiday] = []
}
(I removed the outer struct CalendarDates as I cannot find any reason you need nested types. Also I recommend you to avoid plural form to name something which represents a single object.)
To parse your XML using the structs, you need some more properties to be used in XMLParserDelegate:
class CalendarViewController: UIViewController {
//`myCalendarEventsFromStrut` is too long and `FromStrut` does not make sense
var myCalendarEvents: [CalendarEvent] = []
//Properties needed for parsing your XML
var textCurrentlyParsed: String? = nil
var monthFromXML: String = ""
var dateeventFromXML: String = ""
var datenumberFromXML: String = ""
var holidaysFromXML: [Holiday] = []
//Your table view shows some selected evnets in `myCalendarEvents`,
//To improve response, you should beter keep the filtered result, when `selectedDate` is updated.
var selectedDate: Date? {
didSet {
if let date = selectedDate {
let currentMonthShown = monthFormatter.string(from: date)
allEventsInVisibleMonth = myCalendarEvents.filter({ $0.month == currentMonthShown })
} else {
allEventsInVisibleMonth = [] //Or you prefer `allEventsInVisibleMonth = myCalendarEvents`?
}
}
}
var allEventsInVisibleMonth: [CalendarEvent] = []
//You may have this sort of constants somewhere, this is just an example
let TheCellID = "cell" //Change this according to your actual setups
//
// Date formatters.
// Better keep distinct DateFormaters accoding to the format to avoid simple mistakes
//
let monthFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "MMMM"
return formatter
}()
let dateeventFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy MM dd"
return formatter
}()
let sectionHeaderFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
//...
}
(The code above has some addition to write some example of UITableViewDataSource methods.)
With the properties above you can write your XMLParserDelegate methods as follows:
extension CalendarViewController: XMLParserDelegate {
func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
switch elementName {
case "month", "dateevent", "datenumber", "holiday", "description":
//Reset the text content for the element
textCurrentlyParsed = ""
case "calendarevent":
//Reset all variables which may contain the result of previous element
monthFromXML = ""
dateeventFromXML = ""
datenumberFromXML = ""
holidaysFromXML = []
case "calendar":
//Can be ignored
break
default:
print("Unexpected start tag:", elementName)
break
}
}
func parser(_ parser: XMLParser, foundCharacters string: String) {
//`parser(_:foundCharacters:)` may be called several times for a seemingly single text content,
//So you need to add the `string` to the currently parsed text
textCurrentlyParsed? += string
}
func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
switch elementName {
case "calendarevent":
var calendarEvent = CalendarEvent()
calendarEvent.month = monthFromXML
calendarEvent.eventdate = dateeventFromXML
calendarEvent.eventdatenumber = datenumberFromXML
calendarEvent.holidays = holidaysFromXML
myCalendarEvents.append(calendarEvent)
case "month":
if let parsedText = textCurrentlyParsed?.trimmingCharacters(in: .whitespacesAndNewlines) {
monthFromXML = parsedText
}
case "dateevent":
if let parsedText = textCurrentlyParsed?.trimmingCharacters(in: .whitespacesAndNewlines) {
dateeventFromXML = parsedText
}
case "datenumber":
if let parsedText = textCurrentlyParsed?.trimmingCharacters(in: .whitespacesAndNewlines) {
datenumberFromXML = parsedText
}
case "holiday":
if let parsedText = textCurrentlyParsed?.trimmingCharacters(in: .whitespacesAndNewlines) {
var holiday = Holiday()
holiday.title = parsedText
holidaysFromXML.append(holiday)
}
break
case "description":
if
let parsedText = textCurrentlyParsed?.trimmingCharacters(in: .whitespacesAndNewlines),
case let lastIndexOfHoliday = holidaysFromXML.count - 1, lastIndexOfHoliday >= 0
{
//You need to modify the last entry in `holidaysFromXML`
holidaysFromXML[lastIndexOfHoliday].description = parsedText
}
default:
print("Unexpected end tag:", elementName)
break
}
}
}
(If you have no need to trim the texts, the code above can be simplified, a little.)
With those codes included, your UITableViewDataSource methods would look like this:
extension CalendarViewController: UITableViewDelegate, UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return allEventsInVisibleMonth.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return allEventsInVisibleMonth[section].holidays.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let eventForTheSection = allEventsInVisibleMonth[indexPath.section]
let holidayForTheRow = eventForTheSection.holidays[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: TheCellID, for: indexPath)
//... setup the cell using `eventForTheSection` and `holidayForTheRow`
return cell
}
// Prints <dateevent> reformatted as section header
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let eventForTheSection = allEventsInVisibleMonth[section]
if let eventdateAsDate = dateeventFormatter.date(from: eventForTheSection.eventdate) {
return sectionHeaderFormatter.string(from: eventdateAsDate)
} else {
return "Broken eventdate: \(eventForTheSection.eventdate)"
}
}
}
(NOT tested, you may need some fixes.)
Generally, UITableViewDataSource methods can be called very often, so you should implement them efficiently.
So, creating a filtered array at each call is not recommended.
You should better update allEventsInVisibleMonth only when:
myCalendarEvents is updated (not included in my codes above)
selectedDate is updated (didSet in selectedDate will do it)
A little bit long, but I believe it's worth trying.
I have a json file being imported to my project (https://api.myjson.com/bins/ywv0k). The json attributes are decoded and stored in my struct class "News", which has the same attributes like the json file.
in a second step I populate a table with the string attribute "timestamp" from my struct class "News", which is actually a UNIX time.
My problem now is that I am lost how to change this UNIX time to a string of format "dd/mm/yy HH:mm:ss", since I get an error when I try to put a function
let date = NSDate(timeIntervalSince1970: timestamp) //error since timestamp is currently defined as string. If I make it a long variable, I cannot populate the table with it any more, since the label requires a text with string format.
let dayTimePeriodFormatter = NSDateFormatter()
dayTimePeriodFormatter.dateFormat = "dd/mm/yy HH:mm:ss"
let dateString = dayTimePeriodFormatter.stringFromDate(date)
into the do-encoding-loop as well as when I put it into this table function: func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell.
Swift 4
import UIKit
// structure from json file
struct News: Codable{
let type: String
let timestamp: String // UNIX format, eg. "1531294146340"
let title: String
let message: String
}
class HomeVC: BaseViewController, UITableViewDelegate, UITableViewDataSource {
var myNewsItems: [News] = []
#IBOutlet weak var myNewTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
let nibName = UINib(nibName: "CustomTableViewCell", bundle: nil)
myNewTableView.register(nibName, forCellReuseIdentifier: "tableViewCell")
// JSON Decoding
let url=URL(string:"https://api.myjson.com/bins/ywv0k")
let session = URLSession.shared
let task = session.dataTask(with: url!) { (data, response, error) in
guard let data = data else { return }
do {
let myNewsS = try
JSONDecoder().decode([News].self, from: data)
print(myNewsS)
self.myNewsItems = myNewsS
DispatchQueue.main.async {
self.myNewTableView.reloadData()
}
} catch let jsonErr {
}
}
task.resume()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myNewsItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "tableViewCell", for: indexPath) as!
CustomTableViewCell
// populate table with json content
cell.commonInit(timestamp: myNewsItems[indexPath.row].timestamp, message: myNewsItems[indexPath.row].message)
return cell
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.backgroundColor = UIColor(white: 1, alpha: 0.5)
}
}
First of all the date format is wrong. It has to be "dd/MM/yy HH:mm:ss"
The most efficient solution – if you are responsible for the JSON – send the value for timestamp as Double. Then it's sufficient to declare timestamp
let timestamp: Date // UNIX format, eg. 1531294146340
and add the date decoding strategy
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .millisecondsSince1970
Another solution is to put the date conversion code into the struct
struct News: Codable{
let type: String
let timestamp: String // UNIX format, eg. "1531294146340"
let title: String
let message: String
enum CodingKeys: String, CodingKey { case type, timestamp, title, message}
let dateFormatter : DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/yy HH:mm:ss"
return formatter
}()
var dateString : String {
let timeInterval = TimeInterval(timestamp)!
let date = Date(timeIntervalSince1970: timeInterval / 1000)
return dateFormatter.string(from:date)
}
}
The computed property dateString contains the date string.
Further you could declare type as enum
enum Type : String, Codable {
case organizational, planning
}
struct News: Codable{
let type: Type
...
You should be able to convert the timestamp to date and then format it to specific format and convert back to String to display on UILabel. See if the following helps
func string(from timestamp: String) -> String {
if let timeInterval = TimeInterval(timestamp) {
let date = Date(timeIntervalSince1970: timeInterval)
let formatter = DateFormatter()
formatter.dateFormat = "dd/MM/yy HH:mm:ss"
return formatter.string(from: date)
}
return "" //return empty if somehow the timestamp conversion to TimeInterval (Double) fails
}
1) As a first suggestion, do NOT store date as string for a number or reasons.
(Apple says to use the very basic type... so use a 64bit for UnixTimestamp OR NSDate.. far more flexible, for example performing calcs, difference, localisations and so on... (and far better memory usage.. (Ints do not even use ARC...))
(and use optional for fields.... far more secure..)
2) so use an extension to save as Date (for example)
Let's start from a int unixTimestamp:
(I added a complete sample for a controller...)
//
// ViewController.swift
// sampleDate
//
// Created by ing.conti on 16/08/2018.
// Copyright © 2018 com.ingconti. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
//sample with int...
if let dm =
// Thursday, January 1, 2015 12:00:00 AM GMT+01:00
(1420066800000/1000).fromUnixTimeStamp(){
// note: usual timestamp from server come with milliseincods..
//now You get a date... use and format!
print(dm.description)
}
let testString = "1420066800"
if let n = Int(testString), let dm = n.fromUnixTimeStamp(){
print(dm.description)
}
}
}
extension Int {
func fromUnixTimeStamp() -> Date? {
let date = Date(timeIntervalSince1970: TimeInterval(self))
return date
}
}
so use extension AND change your to use date.
A final note: codable is fine, but not fine for "edge cases"ad apple says in
"Reflecting on Reflection" (https://developer.apple.com/swift/blog/?id=37) sometimes is better to write parser by hand... for a small piece of JSON.
So for example use:
(I rewrote a bit your class...)
typealias Dict = [String : Any]
struct News{
let type: String?
// NO! let timestamp: String // UNIX format, eg. "1531294146340"
let timestamp: Date?
let title: String?
let message: String?
init?(dict : Dict?) {
guard let d = dict else{
return nil
}
if let s = d["timestamp"] as? String, let n = Int(s) {
timestamp = n.fromUnixTimeStamp()
}else{
timestamp = nil // or other "default" ..
}
// go on parsing... other fields..
if let s = d["type"] as? String{
type = s
}else{
type = nil // or other "default" ..
}
if let s = d["title"] as? String {
title = s
}
else{
title = nil // or other "default" ..
}
if let s = d["message"] as? String {
message = s
}else{
message = nil // or other "default" ..
}
}
}
so use in this way:
...
let new = News(dict: dict)
I usually extract data form JSON in this way:
...
guard let json = try? JSONSerialization.jsonObject(with: data, options: []) as? Dict
else{
return
}
guard let dict = json else{
return
}
..
let new = News(dict: dict)
I am trying to sort an array of different NSManagedObjects in Swift. In Objective-C, it would have required just 2 lines of code (a sort descriptor and the actual sort). However, the only way I could figure out how to do it in swift required several lines of code. Is there a better and/or faster way than the code I wrote please?:
var orderMOs = [NSManagedObject]()
orderMOs.append(contentsOf: incidentMOs)
orderMOs.append(contentsOf: transferMOs)
orderMOs.sort (by: {(leftMO, rightMO) -> Bool in
var leftDate: NSDate?
var rightDate: NSDate?
if leftMO is Incident {leftDate = (leftMO as! Incident).createdDate}
else if leftMO is Transfer {leftDate = (leftMO as! Transfer).createdDate}
if rightMO is Incident {rightDate = (rightMO as! Incident).createdDate}
else if rightMO is Transfer {rightDate = (rightMO as! Transfer).createdDate}
if leftDate == nil || rightDate == nil {return true}
return leftDate!.compare(rightDate! as Date) == .orderedDescending
})
You should both your classes conform to a protocol that declares createdDate. Type orderMOs as such. than you won't need the conditional casts.
import Foundation
class A {
init(created createdDate: Date) {
self.createdDate = createdDate
}
let createdDate: Date
}
class B {
init(created createdDate: Date) {
self.createdDate = createdDate
}
var createdDate: Date
}
protocol Created {
var createdDate: Date { get }
}
extension A: Created {}
extension B: Created {}
func createDate(year: Int, month: Int, day: Int) -> Date {
var comps = DateComponents()
comps.year = year
comps.month = month
comps.day = day
return Calendar.current.date(from: comps)!
}
var objects = [Created]()
objects.append(A(created: createDate(year: 2018, month: 2, day: 1)))
objects.append(B(created: createDate(year: 2017, month: 12, day: 1)))
objects.append(B(created: createDate(year: 2018, month: 5, day: 18)))
objects.append(A(created: Date()))
Sort it like
objects.sort { (c1, c2) -> Bool in
return c1.createdDate < c2.createdDate
}
Another thing you can do is to use switch-statement with pattern matching to clean code up a bit.
Why not use KVC here?
if let leftDate = leftMO.value(forKey: "createdDate") as? Date,
let rightDate = rightMO.value(forKey: "createdDate") as? Date {
if leftDate == nil || rightDate == nil {return true}
return leftDate.compare(rightDate) == . orderedDescending
}
i want to ask, when i clicked the button and then println the json data appear like that picture..
i want to ask if it possible to change the output like this
{"telur" : "4" , "roti" : "4"}
{"softball" : "60" , "senam" : "60"}
in the other way, i want to make it one bracket and erase type and amount in it, I'm using jsonserializer though..
the code like this..
menu.swift
class Nutritiondata
{
var type = "type"
var amount = 0
//var date = "Date"
init (type: String, amount: Int)
{
self.type = type
self.amount = amount
//self.date = date
}
}
class Activitiesdata
{
var type = "type"
var amount = 0
//var date = "Date"
init (type: String, amount: Int)
{
self.type = type
self.amount = amount
//self.date = date
}
}
table view
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: CustomCell = tableView.dequeueReusableCellWithIdentifier("cell") as! CustomCell
let menu = arrayOfMenu[indexPath.row]
cell.setCell(menu.type, rightlabeltext: menu.unit, imagename: menu.image)
var data = Nutritiondata(type: menu.type, amount: String(cell.value ).toInt()!)
var json = JSONSerializer.toJson(data)
JSONNutrisi.jsondata = json
return cell
}
var data = Nutritiondata(type: leftlabel.text!, amount: qtytext.text.toInt()!)
var temp:Nutritiondata
temp = data
var idx = 0
if(temp.amount-1 > 0) {
temp.amount -= 1
}
data = Nutritiondata(type: leftlabel.text!, amount: qtytext.text.toInt()!)
var json = JSONSerializer.toJson(data)
var tempJson = JSONSerializer.toJson(temp)
for(var i = 0; i < Nutritionmenu.data.count; i++){
if(Nutritionmenu.data[i] == tempJson){
self.check = true
self.idx = i
}
}
First of all make one struct function which have type and amount
struct Nutritiondata {
let type :String
let amount : Int
func Dict(age : Int) -> Nutritiondata
{
return Nutritiondata(type: type, amount: amount)
}
}
Now take one array and store the data in it. here I am store static data but you store the data from response
let alldata = [Nutritiondata(type: "telur", amount: 4),Nutritiondata(type: "roti", amount: 4),Nutritiondata(type: "softball", amount: 60),Nutritiondata(type: "seman", amount: 60)]
Now you can use the data as below.
alldata[0].type // telur
alldata[0].amount // 4
use this alldata in your cellForRowAtIndexPath delegate method.
let nutrition = alldata[indexPath.row]
you can display in tableview using below code
nutrition.type
nutrition.amount