I am working on an iOS project in Swift. I used Moya framework for API handling and parsing. It works perfectly. But when I try to parse varibles other than string it shows me en error:
"Missing argument for parameter 'transformation' in call"
Here is my mapper class
import Mapper
class MyMapperClaa:Mappable {
var dateVariable: NSDate?
required init(map: Mapper) throws{
try dateVariable = map.from("date")
}
}
Created an extension for Date and its worked for me
extension Date:Convertible
{
public static func fromMap(_ value: Any) throws -> Date {
guard let rawDate = value as? String else {
throw MapperError.convertibleError(value: value, type: Date.self)
}
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
if let date = dateFormatter.date(from: rawDate) {
return date
} else {
throw MapperError.convertibleError(value: value, type: Date.self)
}
}
}
sorry, you are using this lib: https://github.com/lyft/mapper. from example there:
private func extractDate(object: Any?) throws -> Date {
guard let rawDate = object as? String else {
throw MapperError.convertibleError(value: object, type: Date.self)
}
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "your date format"
if let date = dateFormatter.date(from: rawDate) {
return date
} else {
throw MapperError.convertibleError(value: object, type: Date.self)
}
}
struct DateModel: Mappable {
let date: Date
init(map: Mapper) throws {
try date = map.from("date", transformation: extractDate)
}
}
Related
I have some decoders for different types.
In this function decoders for date are creating.
private static var __once: () = {
//possible formats
let formatters = [
"yyyy-MM-dd",
"yyyy-MM-dd'T'HH:mm:ssZZZZZ",
"yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ",
"yyyy-MM-dd'T'HH:mm:ss'Z'",
"yyyy-MM-dd'T'HH:mm:ss.SSS",
"yyyy-MM-dd HH:mm:ss"
].map { (format: String) -> DateFormatter in
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = format
formatter.timeZone = TimeZone(secondsFromGMT: 0)
return formatter
}
// Decoder for Date
Decoders.addDecoder(clazz: Date.self) { (source: AnyObject, instance: AnyObject?) -> Decoded<Date> in
if let sourceString = source as? String {
for formatter in formatters {
if let date = formatter.date(from: sourceString) {
return .success(date) //in this state date = 2022-03-26 05:45:00 +0000 (not nil)
}
}
}
...some code
}
Decoder calls here. It work right way for another types, but "date" turns to nil after returning from decoder function.
static func decodeOptional<T>(clazz: T.Type, source: AnyObject?) -> Decoded<T?> {
if let source = source, !(source is NSNull) {
switch Decoders.decode(clazz: clazz, source: source, instance: nil) { //this function call decoder for date from above.
case let .success(value): return .success(value) // but date in this statement turns no nil
case let .failure(error): return .failure(error)
}
} else {
return .success(nil)
}
}
static func decode<T>(clazz: T.Type, source: AnyObject, instance: AnyObject?) -> Decoded<T> {
initialize()
...somecode...//decoder with key "Date"
let key = "\(T.self)"
if let decoder = decoders[key], let value = decoder(source, instance) as? Decoded<T> {
return value
} else {
return .failure(.typeMismatch(expected: String(describing: clazz), actual: String(describing: source)))
}
}
Returning type:
public enum Decoded<ValueType> { ////ValueType = Date
case success(ValueType)
case failure(DecodeError)
}
Why Date object turns from real value to nil after returning from function?
I would like to receive the date value in the api value as utc. I looked up the stackoverflow.
There was a similar case, but we couldn't solve it because it was different from me.
The server (POSTMAN(db) stores the value "b_date": 1602813891.
link >> Dateformatter issue in swift
mycode
var ViewItem: BoardView?
func DetailViewList() {
DispatchQueue.main.async {
self.txtUserName.text = String(ViewItem.userId ?? "")
self.txtCount.text = String(ViewItem.b_count ?? 0)
}
}
func utcToLocal(utcDate: String, dateFormat: String) -> String {
let dfFormat = DateFormatter()
dfFormat.dateFormat = dateFormat
dfFormat.timeZone = TimeZone(abbreviation: "UTC")
let dtUtcDate = dfFormat.date(from: utcDate)
dfFormat.timeZone = TimeZone.current
dfFormat.dateFormat = dateFormat
txtDate.text = Int(ViewItem?.b_date) // ERROR [Cannot invoke initializer for type 'Int' with an argument list of type '(Int?)'] , Overloads for 'Int' exist with these partially matching parameter lists: (CGFloat), (Double), (Float), (Float80), (Int64), (Word), (__shared NSNumber)
return dfFormat.string(from: dtUtcDate!)
}
jsonData
struct BoardView: Codable {
var b_date: Int?
var b_count: Int?
var userId: String?
}
Essentially I have a tableview populated using JSON data, the tableview contains sections that groups the data using allowdate from the JSON.
allowdate as seen below is contains a date but is not formatted numerically instead looks like: March 26th 2020 so it makes difficult to control the order it is displayed in the tableview.
In the function fetchJSON I do:
self.structure.sort { $1. allowdate < $0.allowdate }
But this does not work correctly, and fails to put a date for example in January above one in March.
var sections = [TableSection]()
var structure = [TableStructure]()
private func fetchJSON() {
guard let url = URL(string: "\(URL.url)example"),
let value = name.addingPercentEncoding(withAllowedCharacters: .urlQueryValueAllowed)
else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.httpBody = "item1=\(value)&item2=\(value)".data(using: .utf8)
URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data else { return }
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
self.structure.sort { $1. allowdate < $0.allowdate }
let res = try decoder.decode([TableStructure].self, from: data)
let grouped = Dictionary(grouping: res, by: { $0. allowdate })
let keys = grouped.keys.sorted()
self.sections = keys.map({TableSection(date: $0, items: grouped[$0]!)})
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
catch {
print(error)
}
}.resume()
}
JSON:
[
{
"person": "Jack",
"allowdate": "March 26th 2020",
"ready_time": "10:00 am"
}
]
To decode this JSON I am using the following structure:
struct TableSections {
let date : String
var items : [TableStructure]
}
struct TableStructure: Decodable {
let person: String
let allowdate: String
let ready_time: String
enum CodingKeys : String, CodingKey {
case person, allowdate, ready_time
}
}
Check this URL:
https://nsdateformatter.com
It will help you you to try different DateFormatter.
Now for your scenario, Try changing the struct like this:
struct TableStructure: Decodable {
let person: String
let allowdate: Date?
let ready_time: String
enum CodingKeys : String, CodingKey {
case person, allowdate, ready_time
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
self.person = try values.decode(String.self, forKey: .person)
self.ready_time = try values.decode(String.self, forKey: .ready_time)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMMM d'th',yyyy"
let allowDateStringValue = try values.decode(String.self, forKey: .allowdate)
self.allowdate = dateFormatter.date(from: allowDateStringValue)
}
}
and you can sort the array as:
structure.sort { (firstStructure, secondStructure) -> Bool in
if let firstDate = firstStructure.allowDate, let secondDate = secondStructure.allowDate {
return firstDate.timeIntervalSince1970 < secondDate.timeIntervalSince1970
}
return false
}
If you don't want to change your struct format then you can go like this:
struct TableStructure: Decodable {
let person: String
let allowdate: String
let ready_time: String
enum CodingKeys : String, CodingKey {
case person, allowdate, ready_time
}
}
and while sorting :
var dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMMM d'th',yyyy"
structure.sort { (firstStructure, secondStructure) -> Bool in
if let firstDate = dateFormatter.date(from: firstStructure.allowDate),
let secondDate = dateFormatter.date(from: secondStructure.allowDate) {
return firstDate.timeIntervalSince1970 < secondDate.timeIntervalSince1970
}
return false
}
I tried this in a playground. It seems to work with different ordinals:
import UIKit
let json = """
[
{
"person": "Jack",
"allowdate": "March 26th 2020",
"ready_time": "10:00 am"
},
{
"person": "Jill",
"allowdate": "January 1st 2020",
"ready_time": "1:00 pm"
},
{
"person": "Don",
"allowdate": "January 10th 2020",
"ready_time": "1:25 pm"
}
]
"""
struct TableStructure: Decodable {
// These first three come frm the json
let person: String
let allowdate: String
let readyTime: String
// We'll calculate this one later
let compareDate: Date
// This date formatter will read in dates like "January 10th 2020"
// So to use this, we will need to pull the ordinal letters off first
static let dateFormatter: DateFormatter = {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MMM dd yyyy"
return dateFormatter
}()
enum DecodeError: Error {
case compareDateError
}
enum CodingKeys: String, CodingKey {
case person
case allowdate
case readyTime
}
// We decode the three key/values that are passed down, and we calculate a date that we can use to compare
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
person = try values.decode(String.self, forKey: .person)
readyTime = try values.decode(String.self, forKey: .readyTime)
allowdate = try values.decode(String.self, forKey: .allowdate)
if let date = TableStructure.comparingDate(from: allowdate) {
compareDate = date
} else {
throw DecodeError.compareDateError
}
}
// We pull the ordinal letters off of the date, and are left with something cleaner
// A regex could make this much simpler, but me and regex's, we don't get along so great
static func comparingDate(from dateString: String) -> Date? {
var datePurgedArray = dateString.split(separator: " ")
if datePurgedArray.count == 3 {
datePurgedArray[1] = datePurgedArray[1].filter("0123456789.".contains)
let newDateString = datePurgedArray.joined(separator:" ")
print(newDateString)
return TableStructure.dateFormatter.date(from: newDateString)
} else {
return nil
}
}
}
if let jsonData = json.data(using: .utf8) {
print("json Data is \(jsonData)")
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
let array = try decoder.decode([TableStructure].self, from: jsonData).sorted { $0.compareDate < $1.compareDate }
print("array is \(array)")
} catch {
print(error.localizedDescription + "could not decode array")
}
} else {
print("could not get data from json")
}
The string date format cannot be sorted reliably.
You have to decode the date string as Date. That's what the date decoding strategies are designed for.
There is one problem: DateFormatter doesn't support literal ordinal date strings like 1st, you have to remove st, nd, rd, th. The most efficient way is replacingOccurrences with regularExpression option.
Declare the struct (once again the CodingKeys are not needed and name the struct members camelCased)
struct TableStructure: Decodable {
let person: String
let allowdate: Date
let readyTime: String
}
and add the strategy
do {
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .custom({ decoder -> Date in
let container = try decoder.singleValueContainer()
let dateString = try container.decode(String.self)
let trimmedString = dateString.replacingOccurrences(of: "(st|nd|rd|th) ", with: " ", options: .regularExpression)
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.dateFormat = "MMMM dd yyyy"
guard let result = formatter.date(from: trimmedString) else { throw DecodingError.dataCorruptedError(in: container, debugDescription: "Wrong date format")}
return result
})
self.structure.sort { $1. allowdate < $0.allowdate }
let res = try decoder.decode([TableStructure].self, from: data)
let grouped = Dictionary(grouping: res, by: { $0. allowdate })
let keys = grouped.keys.sorted()
self.sections = keys.map({TableSection(date: $0, items: grouped[$0]!)})
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
Consider that a Date is printed with a default date format. If you need a custom date format you have to add a second date formatter
I am trying to map my data to Model.
Where I am using Firestore snapshot listener, to get data.
here, I am getting data and mapping to "User" model that;
do{
let user = try User(dictionary: tempUserDic)
print("\(user.firstName)")
}
catch{
print("error occurred")
}
Here is my Model:
struct User {
let firstName: String
// var lon: Double = 0.0
// var refresh:Int = 0
// var createdOn: Timestamp = Timestamp()
}
//Testing Codable
extension User: Codable {
init(dictionary: [String: Any]) throws {
self = try JSONDecoder().decode(User.self, from: JSONSerialization.data(withJSONObject: dictionary))
}
private enum CodingKeys: String, CodingKey {
case firstName = "firstName"
}
}
Correct me if I am wrong.
Crashing because I am getting "Timestamp" in data.
Data getting from listener :
User Dictionary:
[\"firstName\": Ruchira,
\"lastInteraction\": FIRTimestamp: seconds=1576566738 nanoseconds=846000000>]"
How to map "Timestamp" to Model?
Tried "CodableFirstore" https://github.com/alickbass/CodableFirebase
An approach is to create an extension to type Dictionary that coverts a dictionary to any other type, but automatically modifies Date and Timestamp types to writeable JSON strings.
This is the code:
extension Dictionary {
func decodeTo<T>(_ type: T.Type) -> T? where T: Decodable {
var dict = self
// This block will change any Date and Timestamp type to Strings
dict.filter {
$0.value is Date || $0.value is Timestamp
}.forEach {
if $0.value is Date {
let date = $0.value as? Date ?? Date()
dict[$0.key] = date.timestampString as? Value
} else if $0.value is Timestamp {
let date = $0.value as? Timestamp ?? Timestamp()
dict[$0.key] = date.dateValue().timestampString as? Value
}
}
let jsonData = (try? JSONSerialization.data(withJSONObject: dict, options: [])) ?? nil
if let jsonData {
return (try? JSONDecoder().decode(type, from: jsonData)) ?? nil
} else {
return nil
}
}
}
The .timestampString method is also declared in an extension for type Date:
extension Date {
var timestampString: String {
Date.timestampFormatter.string(from: self)
}
static private var timestampFormatter: DateFormatter {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
dateFormatter.timeZone = TimeZone(identifier: "UTC")
return dateFormatter
}
}
Usage, like in the case of the question:
let user = tempUserDict.decodeTo(User.self)
I solved this by converting the FIRTimestamp fields to Double (seconds) so the JSONSerialization could parse it accordingly.
let items: [T] = documents.compactMap { query in
var data = query.data() // get a copy of the data to be modified.
// If any of the fields value is a `FIRTimestamp` we replace it for a `Double`.
if let index = (data.keys.firstIndex{ data[$0] is FIRTimestamp }) {
// Convert the field to `Timestamp`
let timestamp: Timestamp = data[data.keys[index]] as! Timestamp
// Get the seconds of it and replace it on the `copy` of `data`.
data[data.keys[index]] = timestamp.seconds
}
// This should not complain anymore.
guard let data = try? JSONSerialization.data(
withJSONObject: data,
options: .prettyPrinted
) else { return nil }
// Make sure your decoder setups the decoding strategy to `. secondsSince1970` (see timestamp.seconds documentation).
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .secondsSince1970
return try? decoder.decode(T.self, from: data)
}
// Use now your beautiful `items`
return .success(items)
I am trying to save a Date into a textfield and have that save into CoreData. I have the textfield set up and am able to use the date picker just fine with the NSDateFormatter but I am having trouble with getting it to save into the textfield into CoreData.
extension NSDate{
var stringValue: String{
return self.toString()
}
func toString() -> String {
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let str = formatter.stringFromDate(self)
return str
}
}
extension String{
var dateValue: NSDate?{
return self.toDate()
}
func toDate() -> NSDate? {
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
if let date = formatter.dateFromString(self) {
return date
}else{
// if format failed, Put some code here
return nil // an example
}
}
}
add this befor your class or another swift file,
then change textFieldDDate.NSDate = ddate to:
textFieldDDate.text = ddate.stringValue
you can only use text(String!) with UITextField,also only NSDate in your newItem.ddate.
change newItem.ddate = textFieldDDate.text to
newItem.ddate = textFieldDDate.text.dateValue
I see var ddate = data.valueForKey("ddate"), I guess it is type of NSDate? maybe you need change it to String, it can't be just use as!(?) String,if I am right, you need use my code of extension NSDate{} to change it too.
I checked your codes, just find some lines maybe it is save data to coreData:
if segue.identifier == "update" {
var selectedItem: NSManagedObject = myDivelog[self.tableView.indexPathForSelectedRow()!.row] as! NSManagedObject
let ADLVC: AddDiveLogViewController = segue.destinationViewController as! AddDiveLogViewController
ADLVC.divenumber = selectedItem.valueForKey("divenumber") as! String
ADLVC.ddate = selectedItem.valueForKey("ddate") as! NSDate
ADLVC.divelocation = selectedItem.valueForKey("divelocation") as! String
ADLVC.existingItem = selectedItem
}
am I right? I get this link of an answer of how to save a Data to CoreData for you. because maybe something wrong in there.
here it is https://stackoverflow.com/a/26025022/5113355