Add TodayDate function to the history of today app - ios

I'm making a history of today app in IOS.
the code is as follows:
import Foundation
import UIKit
final class HistoryAPICaller {
static let shared = HistoryAPICaller()
struct Constants
{
static let topHeadLinesURL = URL(string:"http://history.muffinlabs.com/date/6/3")
static let searchUrlString = "http://history.muffinlabs.com/date/6/3"
}
private init() {}
public func getTopStories(completion: #escaping (Result<[Events], Error>) -> Void) {
guard let html = Constants.topHeadLinesURL else {
return
}
let task = URLSession.shared.dataTask(with: html) { data, _, error in
if let error = error {
completion(.failure(error))
}
else if let data = data {
do {
let result = try JSONDecoder().decode(HistoryAPIResponse.self, from: data)
print("Articles: \(result.data.Events.count)")
completion(.success(result.data.Events))
}
catch {
completion(.failure(error))
}
}
}
task.resume()
}
public func search(with query: String, completion: #escaping (Result<[Events], Error>) -> Void) {
guard !query.trimmingCharacters(in: .whitespaces).isEmpty else {
return
}
let urltring = Constants.searchUrlString + query
guard let html = URL(string:urltring) else {
return
}
let task = URLSession.shared.dataTask(with: html) { data, _, error in
if let error = error {
completion ( .failure(error))
}
else if let data = data {
do {
let result = try JSONDecoder().decode(HistoryAPIResponse.self, from: data)
print("Articles: \(result.data.Events.count)")
completion(.success(result.data.Events))
}
catch {
completion (.failure(error))
}
}
}
task.resume()
}
}
struct HistoryAPIResponse: Codable {
let data: HistoryDataAPIResponse }
struct HistoryDataAPIResponse: Codable {
let Events: [Events]
}
struct Events: Codable {
let year: String
let text: String
let html: String
let no_year_html: String
let links: [Links]
}
struct Links: Codable {
let link: String
}
Now I want to add the current date function to the code, so instead of constantly showing the historic event of June 3rd
"http://history.muffinlabs.com/date/6/3" ,
it will be something like
"http://history.muffinlabs.com/date/(currentMonth)/(currentDay)"
Any assistance will be greatly appreciated.

You can use the dateComponents method to retrieve the current day and month, define this function is your HistoryAPICaller class:
func getSearchUrlString() -> String
{
let dayAndMonth = Calendar.current.dateComponents([.day, .month], from: Date())
guard let day = dayAndMonth.day, let month = dayAndMonth.month else { return "" }
return "http://history.muffinlabs.com/date/\(month)/\(day)"
}
Then edit your search method to retrieve the URL from it:
public func search(with query: String, completion: #escaping (Result<[Events], Error>) -> Void) {
guard !query.trimmingCharacters(in: .whitespaces).isEmpty else {
return
}
let urltring = getSearchUrlString() + query
guard let html = URL(string:urltring) else {
return
}
...

Related

Cannot find 'ModelA' in scope using generic types

I have a call api function and parameter using generic types.
And I alse craete codable data model.
Why the function parameter don't get my custom struct model and get the error Cannot find 'ModelA' in scope.
Is my T type error?
I don't know how to fix it.
Thanks.
struct ResponseHeader :Codable {
let returnCode : String?
let returnMsg : String?
}
struct ModelA :Codable {
let responseHeader : ResponseHeader?
let responseBody : ResponseBody?
struct ResponseBody: Codable {
let name : String?
let age : String?
let email: String?
}
}
enum APIRouter: String {
case apiA = "http://localhost:3000/ApiA"
case apiB = "http://localhost:3000/ApiB"
case apiC = "http://localhost:3000/ApiC"
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.callApi(apiRouter: .apiA, model: ModelA) //Error. Cannot find 'ModelA' in scope
}
func callApi<T: Codable>(apiRouter: APIRouter, model: T.Type) {
let urlString = URL(string: apiRouter.rawValue)
if let url = urlString {
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error == nil else { return }
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
if let data = data {
do {
let response = try decoder.decode(model.self, from: data)
print(response)
} catch {
print(error)
}
} else {
print("Error")
}
}
task.resume()
}
}
}
Add self at the end.
This generic function takes as an argument an Instance Type of model so you have to pass ModelA.self.
self.callApi(apiRouter: .apiA, model: ModelA.self) //Here

Is there a way to assign a variable of Swift enum type to a variable of NSObject type?

In my program code which is posted below, I need to assign a variable of Swift enum type to a variable of NSObject type. However, the compiler doesn't allow this. I know this is not allowed. So, I wonder whether there is a way to change the enum somehow so that it can be assigned to an NSObject variable. Thank you!
Photo+CoreDataClass.swift
import Foundation
import CoreData
#objc(Photo)
public class Photo: NSManagedObject {
}
Photo+CoreDataProperties.swift
import Foundation
import CoreData
extension Photo {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Photo> {
return NSFetchRequest<Photo>(entityName: "Photo")
}
#NSManaged public var datetaken: String?
#NSManaged public var datetakengranularity: NSObject?
#NSManaged public var datetakenunknow: String?
#NSManaged public var farm: Int32
#NSManaged public var heightZ: Int32
#NSManaged public var photoID: String?
#NSManaged public var isfamily: Int32
#NSManaged public var isfriend: Int32
#NSManaged public var ispublic: Int32
#NSManaged public var owner: String?
#NSManaged public var secret: String?
#NSManaged public var server: String?
#NSManaged public var title: String?
#NSManaged public var urlZ: String?
#NSManaged public var widthZ: Int32
}
extension Photo : Identifiable {
}
FlickrPhoto.swift
import Foundation
// MARK: - Photo
struct FlickrPhoto: Codable {
let photoID, owner, secret, server: String
let farm: Int
let title: String
let ispublic, isfriend, isfamily: Int
let datetaken: String
let datetakengranularity: Datetakengranularity
let datetakenunknown: String
let urlZ: String?
let heightZ, widthZ: Int?
enum CodingKeys: String, CodingKey {
case owner, secret, server, farm, title, ispublic, isfriend, isfamily, datetaken, datetakengranularity, datetakenunknown
case photoID = "id"
case urlZ = "url_z"
case heightZ = "height_z"
case widthZ = "width_z"
}
}
enum Datetakengranularity: Codable {
case integer(Int)
case string(String)
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let x = try? container.decode(Int.self) {
self = .integer(x)
return
}
if let x = try? container.decode(String.self) {
self = .string(x)
return
}
throw DecodingError.typeMismatch(Datetakengranularity.self, DecodingError.Context(codingPath: decoder.codingPath, debugDescription: "Wrong type for Datetakengranularity"))
}
func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
switch self {
case .integer(let x):
try container.encode(x)
case .string(let x):
try container.encode(x)
}
}
}
extension FlickrPhoto: Equatable {
static func == (lhs: FlickrPhoto, rhs: FlickrPhoto) -> Bool {
// Two Photos are the same if they have the same photoID
return lhs.photoID == rhs.photoID
}
}
PhotoStore.swift
import UIKit
import CoreData
class PhotoStore {
private let session: URLSession = {
let config = URLSessionConfiguration.default
return URLSession(configuration: config)
}()
let imageStore = ImageStore()
let persistenContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "Photorama")
container.loadPersistentStores { (description, error) in
if let error = error {
print("Error setting up Core Data (\(error))")
}
}
return container
}()
private func processPhotosRequest (data: Data?, error: Error?) ->
Result<[FlickrPhoto], Error> {
guard let jsonData = data else {
return .failure(error!)
}
//return FlickrAPI.photos(fromJSON: jsonData)
let context = persistenContainer.viewContext
switch FlickrAPI.photos(fromJSON: jsonData) {
case let .success(flickrPhotos):
let _ = flickrPhotos.map { flickrPhoto -> Photo in
let fetchRequest: NSFetchRequest<Photo> = Photo.fetchRequest()
let predicate = NSPredicate(format: "\(#keyPath(Photo.photoID)) == \(flickrPhoto.photoID)")
fetchRequest.predicate = predicate
var photo: Photo!
context.performAndWait {
photo = Photo(context: context)
photo.photoID = flickrPhoto.photoID
photo.owner = flickrPhoto.owner
photo.secret = flickrPhoto.secret
photo.server = flickrPhoto.server
photo.farm = Int32(flickrPhoto.farm)
photo.title = flickrPhoto.title
photo.ispublic = Int32(flickrPhoto.ispublic)
photo.isfriend = Int32(flickrPhoto.isfriend)
photo.isfamily = Int32(flickrPhoto.isfamily)
photo.datetaken = flickrPhoto.datetaken
photo.datetakengranularity = flickrPhoto.datetakengranularity // The compiler reports error here:
// Cannot assign value of type 'Datetakengranularity' to type 'NSObject?'
photo.datetakenunknow = flickrPhoto.datetakenunknown
photo.urlZ = flickrPhoto.urlZ
photo.heightZ = Int32(flickrPhoto.heightZ!)
photo.widthZ = Int32(flickrPhoto.widthZ!)
}
return photo
}
return .success(flickrPhotos)
case let .failure(error):
return .failure(error)
}
}
func fetchAllPhotos (completion: #escaping (Result<[Photo], Error>) -> Void) -> Void {
let fetchRequest: NSFetchRequest<Photo> = Photo.fetchRequest()
let sortByDataTaken = NSSortDescriptor(key: #keyPath(Photo.datetaken), ascending: true)
fetchRequest.sortDescriptors = [sortByDataTaken]
let viewContext = persistenContainer.viewContext
viewContext.perform {
do {
let allPhotos = try viewContext.fetch(fetchRequest)
completion(.success(allPhotos))
} catch {
completion(.failure(error))
}
}
}
func fetchRecentPhotos (completion: #escaping (Result<[FlickrPhoto], Error>) -> Void) {
let url = FlickrAPI.interestingPhotoURL
let request = URLRequest(url: url)
let task = session.dataTask(with: request) {
(data, response, error) in
var result = self.processPhotosRequest(data: data, error: error)
if case .success = result {
do {
try self.persistenContainer.viewContext.save()
} catch {
result = .failure(error)
}
}
OperationQueue.main.addOperation {
completion(result)
}
}
task.resume()
}
func fetchImage (for photo: Photo, completion: #escaping (Result<UIImage, Error>) -> Void) {
let photoKey = photo.photoID
if let image = imageStore.image(forKey: photoKey!) {
OperationQueue.main.addOperation {
completion(.success(image))
}
return
}
guard let photoURL = photo.urlZ else { return }
guard let requestURL = URL(string: photoURL) else {
completion(.failure(PhotoError.missingImageURL))
return
}
let request = URLRequest(url: requestURL)
let task = session.dataTask(with: request) {
(data, response, error) in
let result = self.processImageRequest(data: data, error: error)
if case let .success(image) = result {
self.imageStore.setImage(image, forKey: photoKey!)
}
OperationQueue.main.addOperation {
completion(result)
}
}
task.resume()
}
private func processImageRequest (data: Data?, error: Error?) -> Result<UIImage, Error> {
guard let imageData = data,
let image = UIImage(data: imageData) else {
// Couldn't create an image
if data == nil {
return .failure(error!)
} else {
return .failure(PhotoError.imageCreationError)
}
}
return .success(image)
}
}
enum PhotoError: Error {
case imageCreationError
case missingImageURL
}
A snapshot of the Photorama.xcdatamodeld interface is shown below:
More code will be provided if necessary. Thanks very much!
You could do it using hacky enum byte serialization stored as NSData property in your NSObject.
enum Test {
case testCase
case testCase2
}
func bytes<T>(of value: T) -> [UInt8]{
var value = value
let size = MemoryLayout<T>.size
return withUnsafePointer(to: &value, {
$0.withMemoryRebound(to: UInt8.self,
capacity: size,
{
Array(UnsafeBufferPointer(start: $0, count: size))
})
})
}
let testCase: Test = .testCase
let testCase2: Test = .testCase2
var testBytes = bytes(of: testCase)
var testBytes2 = bytes(of: testCase2)
let data = NSData(bytes: &testBytes, length: testBytes.count)
let data2 = NSData(bytes: &testBytes2, length: testBytes2.count)
let testCaseDeserialized = data.bytes.bindMemory(to: Test.self, capacity: 1).pointee
let testCase2Deserialized = data2.bytes.bindMemory(to: Test.self, capacity: 1).pointee
I tried bridging the flickrPhoto.datatakengranularity variable to NSObject via AnyObject like this:
photo.datetakengranularity = (flickrPhoto.datetakengranularity as AnyObject as! NSObject)
And the compiler made no complaints. This may be because in essence AnyObject is equivalent to NSObject and flickrPhoto.datetakengranularity is convertible to AnyObject. I don't know what happened underneath but it worked anyway. I don't know why.
Thanks to all for your concern and support!

JSON throws nil on every request

First time I'm trying to parse JSON data. The output comes out -
Course (success: zero, timestamp: nil, base: nil, date: nil, courses: nil)
Why is there nil everywhere?
I tried to change the value in the "Course" and "Currency" structures but did not lead to success
import UIKit
import JavaScriptCore
struct Course: Decodable {
var success: Bool?
var timestamp: Int?
var base: String?
var date: String?
var rates: Currency?
}
struct Currency: Decodable {
var USD: Float
var AUD: Double
var CAD: Double
var PLN: Double
var MXN: Double
}
class JsonViewContoller: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let urlData: String = "http://data.fixer.io/api/latest?access_key=7ac2982c82da926b787fd2f089b110e5&symbols=USD,AUD,CAD,PLN,MXN&format=1"
guard let url = URL(string: urlData) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
guard error == nil else { return }
do {
let course = try JSONDecoder().decode(Course.self, from: data)
print(Course())
} catch let error {
print(error)
}
}.resume()
}
}
I have run your code you don't need to use two structures to parse the above JSON data. You can easily get Data in a single Structure. I have modified your code as follows:-
import UIKit
struct Course: Codable {
let success: Bool?
let timestamp: Int?
let base, date: String?
let rates: [String: Double]?
}
class JsonViewContoller: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
courseData()
}
fileprivate func courseData(){
let urlData: String = "http://data.fixer.io/api/latest?access_key=7ac2982c82da926b787fd2f089b110e5&symbols=USD,AUD,CAD,PLN,MXN&format=1"
guard let url = URL(string: urlData) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
guard error == nil else { return }
do {
let course = try JSONDecoder().decode(Course.self, from: data)
print(course)
} catch let error {
print(error)
}
}.resume()
}
}

How do I get multiple JSON objects from api call?

I am trying to make an API call to the GitLab API to get the projects that are available to a particular user.
I can get one project of an index of my choosing, put it into a ProjectModel with the projectId and the projectName but I can not figure out how to get all of them into an array of ProjectModels.
By printing then I can see them all being printed in the console but it will not let me append them to an array.
It is in the parseJSON function that I am trying to get a hold of all of the projects.
Does anyone have any suggestions?
This is my manager to fetch the projects:
protocol FetchProjectsManagerDelegate {
func didUpdateProjects(_ fetchProjectsManager: FetchProjectsManager, project: ProjectModel?)
func didFailWithError(error: Error)
}
struct FetchProjectsManager {
let projectsURL = "secret"
var delegate: FetchProjectsManagerDelegate?
func fetchProjects(privateToken: String) {
let privateTokenString = "\(projectsURL)projects?private_token=\(privateToken)"
performRequest(with: privateTokenString)
}
func performRequest(with privateTokenString: String) {
// Create url
if let url = URL(string: privateTokenString) {
// Create URLSession
let session = URLSession(configuration: .default)
// Give the session a task
let task = session.dataTask(with: url) { data, response, error in
if error != nil {
self.delegate?.didFailWithError(error: error!)
return
}
if let safeData = data {
if let project = self.parseJSON(safeData) {
self.delegate?.didUpdateProjects(self, project: project)
}
}
}
// Start the task
task.resume()
}
}
func parseJSON(_ projectData: Data) -> ProjectModel? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode([Project].self, from: projectData)
for project in decodedData {
print(project)
}
let projectId = decodedData[0].id
let projectName = decodedData[0].name
let project = ProjectModel(projectId: projectId, projectName: projectName)
return project
} catch {
delegate?.didFailWithError(error: error)
return nil
}
}
}
This is my project model
struct ProjectModel {
let projectId: Int
let projectName: String
}
Your parseJson method only returns a single project instead of all of them, change it to
func parseJSON(_ projectData: Data) -> [ProjectModel]? {
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode([Project].self, from: projectData)
let projects = decodedData.map { ProjectModel(projectId: $0.id,
projectName: $0.name) }
return projects
} catch {
delegate?.didFailWithError(error: error)
return nil
}
}
and you of course need to update didUpdateProjects so that it takes an array of ProjectModel or call it in a loop

Decoding API data

I'm trying to fetch data from an API but I can't get it right and I don't know the issue here:
struct BTCData : Codable {
let close : Double
let high : Double
let low : Double
private enum CodingKeys : Int, CodingKey {
case close = 3
case high = 4
case low = 5
}
}
func fetchBitcoinData(completion: #escaping (BTCData?, Error?) -> Void) {
let url = URL(string: "https://api.bitfinex.com/v2/candles/trade:30m:tBTCUSD/hist")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let bitcoin = try JSONDecoder().decode([BTCData].self, from: data).first {
print(bitcoin)
completion(bitcoin, nil)
}
} catch {
print(error)
}
}
task.resume()
}
I'd like to be able to access close in every dict and iterate like that:
var items : BTCData!
for idx in 0..<15 {
let diff = items[idx + 1].close - items[idx].close
upwardMovements.append(max(diff, 0))
downwardMovements.append(max(-diff, 0))
}
I get nil. I don't understand how to decode this kind of API where I need to iterate something which is not inside another dict.
EDIT: The above was solved and I'm now struggling to use [BTCData] in another function.
I am trying to use it here :
func fetchBitcoinData(completion: #escaping ([BTCData]?, Error?) -> Void) {
let url = URL(string: "https://api.bitfinex.com/v2/candles/trade:30m:tBTCUSD/hist")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else {
completion(nil, error ?? FetchError.unknownNetworkError)
return
}
do {
let bitcoin = try JSONDecoder().decode([BTCData].self, from: data); completion(bitcoin, nil)
//let close52 = bitcoin[51].close
//print(bitcoin)
//print(close52)
} catch let parseError {
completion(nil, parseError)
}
}
task.resume()
}
class FindArray {
var items = [BTCData]()
func findArray() {
let close2 = items[1].close
print(close2)
}
}
fetchBitcoinData() { items, error in
guard let items = items,
error == nil else {
print(error ?? "Unknown error")
return
}
let call = FindArray()
call.items = items
call.findArray()
}
EDIT 2: Solved it with [BTCData](). var items : [BTCData] = [] works too
To decode an array of arrays into a struct with Decodable you have to use unkeyedContainer. Since there is no dictionary CodingKeys are useless.
struct BTCData : Decodable {
let timestamp : Int
let open, close, high, low, volume : Double
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
timestamp = try container.decode(Int.self)
open = try container.decode(Double.self)
close = try container.decode(Double.self)
high = try container.decode(Double.self)
low = try container.decode(Double.self)
volume = try container.decode(Double.self)
}
}
You don't have to change your JSONDecoder() line.
...
if let bitcoin = try JSONDecoder().decode([BTCData].self, from: data).first {
print(bitcoin)
completion(bitcoin, nil)
}
Just by adding two lines it's even possible to decode the timestamp into a Date value
struct BTCData : Decodable {
let timestamp : Date
let open, close, high, low, volume : Double
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
timestamp = try container.decode(Date.self)
...
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .millisecondsSince1970
if let bitcoin = try decoder.decode([BTCData].self, from: data).first {
print(bitcoin)
completion(bitcoin, nil)
}
To decode the array and get a value at specific index
do {
let bitcoins = try JSONDecoder().decode([BTCData].self, from: data)
let close52 = bitcoins[51].close
print(close52)
...
You need to use JSONSerialization and cast to [[NSNumber]] to get the result needed
UPDATE
Checking this https://docs.bitfinex.com/v2/reference#rest-public-candles I think this is what you are searching for
Try using this
func fetchBitcoinData(completion: #escaping ([BTCData]?, Error?) -> Void) {
let url = URL(string: "https://api.bitfinex.com/v2/candles/trade:30m:tBTCUSD/hist")!
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
do {
if let array = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [[NSNumber]]{
var arrayOfCoinData : [BTCData] = []
for currentArray in array{
arrayOfCoinData.append(BTCData(close: currentArray[2].doubleValue, high: currentArray[3].doubleValue, low: currentArray[4].doubleValue))
}
debugPrint(arrayOfCoinData)
completion(arrayOfCoinData, nil)
}
} catch {
print(error)
completion(nil, error)
}
}
task.resume()
}
Log Result
[BitcoinApiExample.BTCData(close: 7838.8999999999996,...]

Resources