Cannot convert value of type '() -> _' to specified type 'NewsAPIResponse' - swift

I thought my completionHandler returned a value of the type 'NewsAPIResponse', but I don't know how to assign it to a static var. Why is my code throwing an error? Is it not converting? I have checked all other areas of code for debugging. I am not sure whether the problem stems from how I created the completionHandler or not. Any insights would be greatly appreciated.
struct Article {
let site: String
let title: String
let date: Date
let tickers: String
let image: String
var siteText: String {
site
}
var titleText: String {
title
}
var tickerText: String {
tickers
}
var dateText: String {
"\(relativeDateFormatter.localizedString(for: date, relativeTo:Date()))"
}
var imageURL: URL? {
URL(string: image)
}
}
extension Article: Codable{}
extension Article: Identifiable{
var id: String{title}
}
struct NewsAPIResponse : Decodable{
let content: [Article]
}
extension Article {
func fetchData(completionHandler: #escaping (NewsAPIResponse) -> Void) {
let url = URL(string: "My API KEY")
var request = URLRequest(url: URL!)
request.addValue("application/json", forHTTPHeaderField: "Accept")
let task = URLSession.shared.dataTask(with: url!) { data, response, error in
guard error == nil else {
print(error!)
return
}
guard let data = data else {
print("Data is empty")
return
}
let jsonDecoder = JSONDecoder()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
jsonDecoder.dateDecodingStrategy = .formatted(dateFormatter)
let apiResponse = try!jsonDecoder.decode(NewsAPIResponse.self, from: data)
completionHandler(apiResponse)
}
task.resume()
}
static var previewData: NewsAPIResponse = { //error is thrown here
let response = fetchData(){[weak self](apiResponse) in
}
return response
}
}

Related

How to bind data to ViewModel for showing it on UI in MVVM?

In my app I am using MVVM pattern.
Below is my Model.
struct NewsModel: Codable {
let status: String
let totalResults: Int
let articles: [Article]
}
struct Article: Codable {
let source: Source
let author: String?
let title: String
let articleDescription: String?
let url: String
let urlToImage: String?
let publishedAt: Date
let content: String?
enum CodingKeys: String, CodingKey {
case source, author, title
case articleDescription = "description"
case url, urlToImage, publishedAt, content
}
}
struct Source: Codable {
let id: String?
let name: String
}
Below is my ViewModel. Which is used for show the data from API.
struct NewsArticleViewModel {
let article: Article
var title:String {
return self.article.title
}
var publication:String {
return self.article.articleDescription!
}
var imageURL:String {
return self.article.urlToImage!
}
}
Below is my API request class.
class Webservice {
func getTopNews(completion: #escaping (([NewsModel]?) -> Void)) {
guard let url = URL(string: "https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=2bfee85c94e04fc998f65db51ec540bb") else {
fatalError("URL is not correct!!!")
}
URLSession.shared.dataTask(with: url) {
data, response, error in
guard let data = data, error == nil else {
DispatchQueue.main.async {
completion(nil)
}
return
}
let news = try? JSONDecoder().decode([NewsModel].self, from: data)
DispatchQueue.main.async {
completion(news)
}
}.resume()
}
}
After receiving response from my API I want to show it on screen. For this I added below ViewModel.
class NewsListViewModel: ObservableObject {
#Published var news: [NewsArticleViewModel] = [NewsArticleViewModel]()
func load() {
fetchNews()
}
private func fetchNews() {
Webservice().getTopNews {
news in
if let news = news {
//How to bind this data to NewsArticleViewModel and show it on UI?
}
}
}
}
Please let me know. What I have to write there for showing it on UI.
According to the documentation of newsapi.org your request will return one NewsModel object not an array. So change your Webservice class to:
class Webservice {
//Change the completion handler to return an array of Article
func getTopNews(completion: #escaping (([Article]?) -> Void)) {
guard let url = URL(string: "https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=2bfee85c94e04fc998f65db51ec540bb") else {
fatalError("URL is not correct!!!")
}
URLSession.shared.dataTask(with: url) {
data, response, error in
guard let data = data, error == nil else {
DispatchQueue.main.async {
completion(nil)
}
return
}
// decode to a single NewsModel object instead of an array
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
let news = try? decoder.decode(NewsModel.self, from: data)
DispatchQueue.main.async {
// completion with an optional array of Article
completion(news?.articles)
}
}.resume()
}
}
You would need to map those received values to NewsArticleViewModel types. For example:
Webservice().getTopNews { articles in
if let articles = articles {
self.news = articles.map{NewsArticleViewModel(article: $0)}
}
}
And remove let news: NewsModel from the NewsArticleViewModel struct as it is not needed.
Edit:
It seems:
let publishedAt: Date
is throwing an error. Jsondecoder fails to interpret the string to a date. Change your Webservice. IĀ“ve updated it in my answer.
You could remove the legacy MVVM pattern and do it in proper SwiftUI like this:
struct ContentView: View {
#State private var articles = [Article]()
var body: some View {
NavigationView {
List(articles) { article in
Text(article.title)
}
.navigationTitle("Articles")
}
.task {
do {
let url = URL(string: "https://newsapi.org/v2/top-headlines?country=us&category=business&apiKey=2bfee85c94e04fc998f65db51ec540bb")!
let (data, _) = try await URLSession.shared.data(from: url)
articles = try JSONDecoder().decode([Article].self, from: data)
} catch {
articles = []
}
}
}
}

How to troubleshoot API Call JSON in SwiftUI

I'm making an API call to a Rails server to fetch an array of objects and then display those objects in a SwiftUI view.
When I make this same API call in Postman, it works fine. I get the response.
When I make this same call in my SwiftUI project, I don't appear to be saving that response to my Models properly or I'm running into an error otherwise. My server appears to be sending the data fine. The view loads, but with a blank List and just the navigationTitle of "Your Projects"
Looking for guidance on how to check if my response array is storing data and how to troubleshoot. The view loads the data from this array and it appears to be empty.
I used quicktype.io to map the model structure out from the server provided JSON in Postman.
Here's the relevant portion of the Model:
import Foundation
struct ProjectFetchRequest: Decodable {
let request: [ProjectResponseObjectElement]
}
// MARK: - ProjectResponseObjectElement
struct ProjectResponseObjectElement: Codable, Identifiable {
let id = UUID()
let project: Project
let projectType: ProjectType
let inspirations: [JSONAny]
}
// MARK: - Project
struct Project: Codable {
let name: String
let id: Int
let projectType, timeframe, description: String
let currentProgress: Int
let zipcode, status, createdAt, visibility: String
let city, state: String
let title: String
let showURL: String
let thumbnailURL: String
let ownedByLoggedinUser, hasBids, isPublished: Bool
}
// MARK: - ProjectType
struct ProjectType: Codable {
let generalConstructions, landscapes: [GeneralConstruction]?
}
// MARK: - GeneralConstruction
struct GeneralConstruction: Codable {
let id: Int
}
typealias ProjectResponseObject = [ProjectResponseObjectElement]
Here's the API call:
import Foundation
final class Projectservice {
static let shared = Projectservice()
private init() {}
func fetchProjects(completed: #escaping (Result<[ProjectResponseObjectElement], AuthenticationError>) -> Void) {
guard let url = URL(string: "https://example.com/api/v1/projects") else {
completed(.failure(.custom(errorMessage:"URL unavailable")))
return
}
guard let Accesstoken = UserDefaults.standard.string(forKey: "access-token") else { return }
guard let client = UserDefaults.standard.string(forKey: "client") else { return }
guard let uid = UserDefaults.standard.string(forKey: "userEmail") else { return }
print(Accesstoken)
print(client)
print(uid)
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue(Accesstoken, forHTTPHeaderField: "access-token")
request.addValue(client, forHTTPHeaderField: "client")
request.addValue(uid, forHTTPHeaderField: "uid")
request.addValue("Bearer", forHTTPHeaderField: "Tokentype")
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data, error == nil else { return }
guard let projectResponse = try? JSONDecoder().decode(ProjectFetchRequest.self, from: data) else { return }
completed(.success(projectResponse.request))
print(projectResponse)
}.resume()
}
}
Here's the view:
import SwiftUI
struct ProjectsView: View {
#State private var projectObjects: [ProjectResponseObjectElement] = []
var body: some View {
NavigationView{
List(projectObjects){ projectObject in
ProjectRowView(project: projectObject.project)
}
.navigationTitle("Your Projects")
.foregroundColor(.primary)
}.navigationViewStyle(StackNavigationViewStyle())
.onAppear {
fetchProjects()
}
}
func fetchProjects() {
Projectservice.shared.fetchProjects { result in
DispatchQueue.main.async {
switch result {
case .success(let projectObjects):
self.projectObjects = projectObjects
case .failure(let error):
print(error.localizedDescription)
}
}
}
}
}
I needed to declare the top level array struct in the URLSession.
import Foundation
final class Projectservice {
static let shared = Projectservice()
private init() {}
func fetchProjects(completed: #escaping (Result<[ProjectResponseObjectElement], AuthenticationError>) -> Void) {
guard let url = URL(string: "https://example.com/api/v1/projects") else {
completed(.failure(.custom(errorMessage:"URL unavailable")))
return
}
guard let Accesstoken = UserDefaults.standard.string(forKey: "access-token") else { return }
guard let client = UserDefaults.standard.string(forKey: "client") else { return }
guard let uid = UserDefaults.standard.string(forKey: "userEmail") else { return }
print(Accesstoken)
print(client)
print(uid)
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue(Accesstoken, forHTTPHeaderField: "access-token")
request.addValue(client, forHTTPHeaderField: "client")
request.addValue(uid, forHTTPHeaderField: "uid")
request.addValue("Bearer", forHTTPHeaderField: "Tokentype")
URLSession.shared.dataTask(with: request) { (data, response, error) in
guard let data = data, error == nil else { return }
do {
let projectResponse = try JSONDecoder().decode([ProjectResponseObjectElement].self, from: data)
completed(.success(projectResponse))
} catch {
print(error)
}
}.resume()
}
}

how can I init struct: Codable without default value

I want to get Json from API, and use Codable protocol, but when I init published var, I get an error.
struct Search: Codable {
let result: [String]
}
class SearchViewModel: ObservableObject {
#Published var data = Search()
func loadData(search: String) {
var urlComps = URLComponents(string: getUrl)
let queryItems = [URLQueryItem(name: "result", value: search)]
urlComps!.queryItems = queryItems
let url = urlComps!.url!.absoluteString
guard let Url = URL(string: url) else { return }
URLSession.shared.dataTask(with: Url) { (data, res, err) in
do {
if let data = data {
let decoder = JSONDecoder()
let result = try decoder.decode(Search.self, from: data)
DispatchQueue.main.async {
self.data = result
}
} else {
print("there's no DatašŸ˜­")
}
} catch (let error) {
print("Error!")
print(error.localizedDescription)
}
}.resume()
}
}
Change
#Published var data:Search?

How to make an API call with Swift?

So I'm practising trying to make API calls with Swift. The code is as below:
struct Example: Codable {
let userID: String
let ID: String
let title: String
let completed: String
}
func getJson(completion: #escaping (Example)-> ()) {
let urlString = "https://jsonplaceholder.typicode.com/todos/1"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) {data, res, err in
if let data = data {
let decoder = JSONDecoder()
if let json: Example = try? decoder.decode(Example.self, from: data) {
completion(json)
}
}
}.resume()
}
}
getJson() { (json) in
print(json.ID)
}
However, I am unable to print out anything when getJson is called.
The example API I used is found here.
The tutorial that I used to help me write the code is found here.
Modified your variable name and data type exactly as your API response.
struct Example: Codable {
let userId: Int
let id: Int
let title: String
let completed: Bool
}
func getJson(completion: #escaping (Example)-> ()) {
let urlString = "https://jsonplaceholder.typicode.com/todos/1"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) {data, res, err in
if let data = data {
let decoder = JSONDecoder()
do {
let json: Example = try! decoder.decode(Example.self, from: data)
completion(json)
}catch let error {
print(error.localizedDescription)
}
}
}.resume()
}
}
getJson() { (json) in
print(json.id)
}
You can also use CodingKey and can change your response during the init period.
struct Example: Codable {
var userID: Int
var ID: Int
var title: String
var completed: Bool
enum CodingKeys: String, CodingKey {
case userID = "userId"
case ID = "id"
case title = "title"
case completed = "completed"
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
userID = try values.decode(Int.self, forKey: .userID)
ID = try values.decode(Int.self, forKey: .ID)
title = try values.decode(String.self, forKey: .title)
completed = try values.decode(Bool.self, forKey: .completed)
title = "Title: \(title)"
}
}
func getJson(completion: #escaping (Example)-> ()) {
let urlString = "https://jsonplaceholder.typicode.com/todos/1"
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) {data, res, err in
if let data = data {
do {
let json: Example = try JSONDecoder().decode(Example.self, from: data)
completion(json)
}catch let error {
print(error.localizedDescription)
}
}
}.resume()
}
}
getJson() { (json) in
print("ID: \(json.ID) \(json.title)")
}
import UIKit
class ApiManager: NSObject {
static func postApiRequest(url: String,parameters:[String: Any],completion: #escaping([String: Any])-> Void) {
let session = URLSession.shared
let request = NSMutableURLRequest(url: NSURL(string: url)! as URL)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
do{
request.httpBody = try JSONSerialization.data(withJSONObject: parameters, options: JSONSerialization.WritingOptions())
let task = session.dataTask(with: request as URLRequest as URLRequest, completionHandler: {(data, response, error) in
if let response = response {
let nsHTTPResponse = response as! HTTPURLResponse
let statusCode = nsHTTPResponse.statusCode
print ("status code = \(statusCode)")
}
if let error = error {
print ("\(error)")
}
if let data = data {
do{
let jsonResponse = try JSONSerialization.jsonObject(with: data, options: JSONSerialization.ReadingOptions())
print ("data = \(jsonResponse)")
}catch _ {
print ("OOps not good JSON formatted response")
}
}
})
task.resume()
}catch _ {
print ("Oops something happened buddy")
}
}
}

Why can't I change the instance variable inside this session.dataTask()?

I tried to directly get the data recieved from a URLsession updated into the instance variables. Tried the code below in playgroud, I can see until the self.cityName = weatherdecoded.name the code seems working fine, but the self.cityName which intended to be the instance's variable didn't got updated. The results are nils. Hope to understand the reason, what is the mistake i made. Thanks!
import UIKit
class WeatherManager {
var cityName: String?
var conditionID: Int?
var temp: Double?
func fetchData(cityName: String) {
let session = URLSession(configuration: .default)
let urlStr = "https://api.openweathermap.org/data/2.5/weather?units=metric&appid=8da179fa1c83749056ec6a5385cabb04&q=" + cityName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
print(urlStr)
let url = URL(string: urlStr)!
let weatherDataSession = session.dataTask(with: url, completionHandler: getdata(data:response:error:))
weatherDataSession.resume()
}
func getdata(data: Data?, response: URLResponse?, error: Error?) {
if error != nil {
print(error!)
return
}
if let safedata = data {
let decoder = JSONDecoder()
do {
let weatherdecoded = try decoder.decode(Weatherdata.self, from: safedata)
self.cityName = weatherdecoded.name
self.conditionID = weatherdecoded.weather[0].id
self.temp = weatherdecoded.main.temp
} catch {
print(error)
}
}
}
}
struct Weatherdata: Decodable {
let weather : [Weather]
let main: Main
let name: String
}
struct Weather: Decodable {
let id: Int
let description: String
}
struct Main: Decodable {
let temp: Double
}
let weathermanager = WeatherManager()
weathermanager.fetchData(cityName: "beijing")
print(weathermanager.cityName)
print(weathermanager.conditionID)
print(weathermanager.temp)
The simple reason is the fact that you are printing the values before the request actually completes. You have to use a completion handler:
class WeatherManager {
func fetchObject<T: Decodable>(urlPath: String, onCompletion: #escaping (Result<T, Error>) -> Void) {
let session = URLSession(configuration: .default)
let url = URL(string: urlPath)!
let task = session.dataTask(with: url) { data, _, error in
if let error = error {
onCompletion(.failure(error))
return
}
let decoder = JSONDecoder()
do {
let decoded = try decoder.decode(T.self, from: data ?? Data())
onCompletion(.success(decoded))
} catch {
onCompletion(.failure(error))
}
}
task.resume()
}
func fetchWeather(cityName: String, onCompletion: #escaping (Result<Weatherdata, Error>) -> Void) {
let urlPath = "https://api.openweathermap.org/data/2.5/weather?units=metric&appid=8da179fa1c83749056ec6a5385cabb04&q=" + cityName.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
fetchObject(urlPath: urlPath, onCompletion: onCompletion)
}
}
struct Weatherdata: Decodable {
let weather : [Weather]
let main: Main
let name: String
}
struct Weather: Decodable {
let id: Int
let description: String
}
struct Main: Decodable {
let temp: Double
}
let weathermanager = WeatherManager()
weathermanager.fetchWeather(cityName: "beijing") { result in
switch result {
case .success(let weatherData):
print(weatherData.name)
print(weatherData.weather[0].id)
print(weatherData.main.temp)
case .failure(let error):
print(error)
}
}