I am trying to make a POST request using OAuthSwift but I have been stuck. Can someone point me in the right direction?
On ViewController1 we have a list of users, when you tap on a user, you get to ViewController2 which has the details for the user. If you tap a cell, you get an action sheet which you can change a user status. Once you are done, you tap 'save' and the save button should make a post request to the API.
I am lost on how to do this, here is what I have so far.
The Model for GET Request
struct DailyInjuryIllnessPlayer: Hashable, Codable {
var playerId: Int
var playerName: String?
var numberOfStatusMissingDays: Int
var entries: [Entry]
func hash(into hasher: inout Hasher) {
hasher.combine(playerId)
}
static func == (lhs: DailyInjuryIllnessPlayer, rhs: DailyInjuryIllnessPlayer) -> Bool {
return lhs.playerId == rhs.playerId
}
}
struct Entry: Codable {
var injuryIllnessId: Int
var injuryIllnessName: String?
var statusDate: Date
var modifiedBy: String?
var status: String?
var playerStatusId: Int
var parentEventId: Int?
}
The Model for the POST Request:
struct CreateDailyInjuryIllnessNote: Encodable {
var teamId: UInt32
var dailyInjuryIllnessStatus: [DailyInjuryIllnessStatus]
}
struct DailyInjuryIllnessStatus: Encodable {
var iIeventId: UInt32
var effectiveDate: Date
var eventStatusId: UInt32
var playerId: UInt32
}
When a user taps on the user from ViewController1, they segue to ViewController 2 where they can see the details of the user, which would be the [Entry] array from the GET Request.
View Controller2:
The user taps a cell (Entry Object) and they get an action sheet which will change the variable status to whatever title they selected in the action sheet (available, or unavailable).
Next, the user can tap save to send the updated status results to the RESTFUL API.
This is where I am stuck, I am not sure what to do, if you cannot help with the code can you please point me to a tutorial or the steps in pseudocode to help out?
Here is what I tried:
In ViewController2
private extension DailyInjuryIllnessAPI.Model.Request.CreateDailyInjuryIllnessNote {
static func from(playerId: Int?, statusNote: DailyInjuryIllnessAPI.Model.Request.CreateDailyInjuryIllnessNote) {
guard let playerId = playerId else { return nil }
let teamId = statusNote.teamId
// Extract the values and add to dailyInjuryIllnessStatusArray
for statusValues in statusNote.dailyInjuryIllnessStatus {
let iIeventId = statusValues.iIeventId
let effectiveDate = statusValues.effectiveDate
let eventStatusId = statusValues.eventStatusId
let playerId = statusValues.playerId
}
}
}
In the model for the POST request:
func createDailyInjuryIllnessNote(forTeamID teamId: Int, createDailyInjuryIllnessStatus: DailyInjuryIllnessAPI.Model.Request.CreateDailyInjuryIllnessNote, completion: #escaping ((Result<DailyInjuryIllnessPlayer, API.APIError>) -> Void)) {
guard let createDailyInjuryIllnessNoteData = try? API.jsonEncoder.encode(createDailyInjuryIllnessStatus) else {
completion(.failure(.encoding(error: "Could not encode DailyInjuryIllnessAPi.Model.Request.CreateDailyInjuryIllnessNote: \(createDailyInjuryIllnessStatus)")))
return
}
let baseURL = self.configuration.baseURL
let endPoint = baseURL.appendingPathComponent("team/\(teamId)/daily-injury-illness-status")
var headers: OAuthSwift.Headers = [:]
headers["Content-Type"] = "application/json"
API.shared.httpClient.post(endPoint, headers: headers, body: createDailyInjuryIllnessNoteData) { (result) in
switch result {
case .success(let response):
do {
let note = try API.jsonDecoder.decode(DailyInjuryIllnessPlayer.self, from: response.data)
completion(.success(note))
} catch (let error) {
completion(.failure(.decoding(error: error.localizedDescription)))
}
case .failure(let error):
completion(.failure(.unknown(message: error.localizedDescription)))
}
}
}
If anyone can help it would be greatly appreciated.
Related
I am trying to display the data from API . Here is the API Link .https://coinmap.org/api/v1/venues/ . I want to display the properties of the Vanues Array fields into IOS app . I created model by using Quick type . I use the Map like self.vanues = respone.results.map{$0} but still same result Here is the model .
import Foundation
// MARK: - Welcome
struct Coin: Codable {
let venues: [Venue]
}
// MARK: - Venue
struct Venue: Codable {
let id: Int
let lat, lon: Double
let category, name: String
let createdOn: Int
let geolocationDegrees: String
enum CodingKeys: String, CodingKey {
case id, lat, lon, category, name
case createdOn = "created_on"
case geolocationDegrees = "geolocation_degrees"
}
}
I convert that to list by using another swift file . Here is the code .
import Foundation
struct VanueResponse: Decodable {
let results: [Venue]
}
Here is my Network Manager .
import Foundation
class NetworkManager {
func getCoins(from url: String, completion: #escaping (Result<VanueResponse, NetworkError>) -> Void ) {
guard let url = URL(string: url) else {
completion(.failure(.badURL))
return
}
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
completion(.failure(.other(error)))
return
}
if let data = data {
//decode
do {
let response = try JSONDecoder().decode(VanueResponse.self, from: data)
completion(.success(response))
} catch let error {
completion(.failure(.other(error)))
}
}
}
.resume()
}
}
Here is the presenter class.
import Foundation
class VenuePresenter : VanueProtocol{
// creating instance of the class
private let view : VanueViewProtocol
private let networkManager: NetworkManager
private var vanues = [Venue]()
var rows: Int{
return vanues.count
}
// initilanize the class
init(view:VanueViewProtocol , networkmanager:NetworkManager = NetworkManager()){
self.view = view
self.networkManager = networkmanager
}
func getVanue(){
let url = "https://coinmap.org/api/v1/venues/"
networkManager.getCoins(from: url) { result in
switch result {
case.success(let respone):
self.vanues = respone.results
DispatchQueue.main.async {
self.view.resfreshTableView()
}
case .failure(let error):
DispatchQueue.main.async {
self.view.displayError(error.localizedDescription)
print(Thread.callStackSymbols)
}
}
}
}
func getId(by row: Int) -> Int {
return vanues[row].id
}
func getLat(by row: Int) -> Double {
return vanues[row].lat
}
func getCreated(by row: Int) -> Int {
return vanues[row].createdOn
}
func getLon(by row: Int) -> Double? {
return vanues[row].lon
}
}
I put the break point and find this in console windows .
Here is the screenshot when I run the Applications .
The Decoding Error is clear:
The key in the root dictionary is venues (not results) so the proper struct is Coin.
In getCoins replace both occurrences of VanueResponse with Coin
One API key can only make 100 requests per day. So one API key can't handle a lot of requests per day. There are other ways to solve this problem, but I would like to solve this problem by entering various API keys. For example, if the first API key makes 100 requests and the request value returns as an error, I want to add a function that automatically moves to the second API key.
Can you tell me how to make it with Rxswift?
I would appreciate any help you can provide.
The code is as below.
private func loadTopNews() {
let resource = Resource<ArticleResponse>(url: URL(string: "https://newsapi.org/v2/top-headlines?country=\(selectedLanguagesCode[0])&sortBy=%20popularity&apiKey=\(apiKey[0])")!)
URLRequest.load(resource: resource)
.subscribe(onNext: { articleResponse in
let topArticle = articleResponse.articles.first
self.articleVM = ArticleViewModel(topArticle!)
}).disposed(by: disposeBag)
}
struct Resource<T: Decodable> {
let url: URL
}
extension URLRequest {
static func load<T>(resource: Resource<T>) -> Observable<T> {
return Observable.just(resource.url)
.flatMap { url -> Observable<Data> in
let request = URLRequest(url: url)
return URLSession.shared.rx.data(request: request)
}.map { data -> T in
return try JSONDecoder().decode(T.self, from: data)
}
}
}
struct ArticleResponse: Decodable {
let articles: [Article]
}
struct Article: Decodable {
let title: String
let publishedAt: String
let urlToImage: String?
let url: String
}
struct ArticleListViewModel {
let articlesVM: [ArticleViewModel]
}
extension ArticleListViewModel {
init(_ articles: [Article]) {
self.articlesVM = articles.compactMap(ArticleViewModel.init)
}
}
extension ArticleListViewModel {
func articleAt(_ index: Int) -> ArticleViewModel {
return self.articlesVM[index]
}
}
struct ArticleViewModel {
let article: Article
init(_ article: Article) {
self.article = article
}
}
extension ArticleViewModel {
var title: Observable<String> {
return Observable<String>.just(article.title)
}
var publishedAt: Observable<String> {
return Observable<String>.just(article.publishedAt)
}
var urlToImage: Observable<String> {
return Observable<String>.just(article.urlToImage ?? "NoImage")
}
var url: Observable<String> {
return Observable<String>.just(article.url)
}
}
I wrote an article covering this very thing (albeit in a different context): RxSwift and Handling Invalid Tokens
The above article will help if you are making multiple requests at the same time and need to restart all of them with the new token. It might be overkill in this specific case.
To solve this problem, you need:
A function that will build a resource with a given api key
An Observable that emits a different API key whenever it's subscribed to.
Once you have those two pieces, you can just retry your subscription until one of the keys works.
Solution
I suggest you use the above as clues and try to solve the problem yourself. Then you can check your answer against the solution below...
For item 1, I see that you need two arguments to create a resource. So I suggest making a function factory that will produce a function that takes an apiKey. Like this:
func makeResource(selectedLanguagesCode: String) -> (String) -> Resource<ArticleResponse> {
{ apiKey in
Resource<ArticleResponse>(url: URL(string: "https://newsapi.org/v2/top-headlines?country=\(selectedLanguagesCode)&sortBy=%20popularity&apiKey=\(apiKey)")!)
}
}
Note that this function is not part of the class. It doesn't need self.
For item 2, we need a function that takes the array of apiKeys and produces an Observable that will emit a different key each time it's subscribed to:
Something like this should work:
func produceApiKey(apiKeys: [String]) -> Observable<String> {
var index = 0
return Observable.create { observer in
observer.onNext(apiKeys[index % apiKeys.count])
observer.onCompleted()
index += 1
return Disposables.create()
}
}
Again, this function doesn't need self so it's not part of the class.
Now that you have these two elements, you can use them in your loadTopNews() method. Like this:
private func loadTopNews() {
produceApiKey(apiKeys: apiKey)
.map(makeResource(selectedLanguagesCode: selectedLanguagesCode[0]))
.flatMap(URLRequest.load(resource:))
.retry(apiKey.count - 1)
.subscribe(onNext: { articleResponse in
let topArticle = articleResponse.articles.first
self.articleVM = ArticleViewModel(topArticle!)
})
.disposed(by: disposeBag)
}
I want to send a post request to my API but I am having some issues with mapping.
The user starts on VCA and sees a list of users that is retrieved via GET request, the user can tap on a cell and see the details (VCB). on VCB, you can tap a cell again, and you get an action sheet which will update that status to available or unavailable. The issue I have is to populate the dailyInjuryStatus array in the POST request.
Get request model:
struct DailyInjuryIllnessPlayer: Hashable, Codable {
var playerId: Int
var playerName: String?
var numberOfStatusMissingDays: Int
var entries: [Entry]
func hash(into hasher: inout Hasher) {
hasher.combine(playerId)
}
static func == (lhs: DailyInjuryIllnessPlayer, rhs: DailyInjuryIllnessPlayer) -> Bool {
return lhs.playerId == rhs.playerId
}
}
struct Entry: Codable {
var injuryIllnessId: Int
var injuryIllnessName: String?
var statusDate: Date
var modifiedBy: String?
var status: String?
var playerStatusId: Int
var parentEventId: Int?
}
The model for the POST
struct CreateDailyInjuryIllnessNote: Encodable {
var teamId: UInt32
var dailyInjuryIllnessStatus: [DailyInjuryIllnessStatus]
}
struct DailyInjuryIllnessStatus: Encodable {
var iIeventId: UInt32
var effectiveDate: Date
var eventStatusId: UInt32
var playerId: UInt32
}
The code I am using for the request (Extracting the values from the screen to send to the API):
private extension DailyInjuryIllnessAPI.Model.Request.CreateDailyInjuryIllnessNote {
static func from(playerId: Int?, statusNote: DailyInjuryIllnessPlayer) -> Self? {
guard let playerId = playerId else { return nil }
let teamId = getTeamId(from: statusNote)
// Here is the issue, I can populate teamId, but I cannot populate dailyInjuryIllnessStatus.
return .init(teamId: UInt32(teamId), dailyInjuryIllnessStatus: <#T##[DailyInjuryIllnessAPI.Model.Request.DailyInjuryIllnessStatus]#>)
}
private static func getTeamId(from model: DailyInjuryIllnessPlayer) -> Int {
let answer = model.playerId
return answer
}
private static func getEventId(from model: DailyInjuryIllnessPlayer) -> Int {
var answer = 1
for eventId in model.entries {
answer = eventId.injuryIllnessId
}
return answer
}
private static func getEffectiveDate(from model: DailyInjuryIllnessPlayer) -> Date {
var answer = Date()
for date in model.entries {
answer = date.statusDate
}
return answer
}
private static func getEventStatusId(from model: DailyInjuryIllnessPlayer) -> Int {
var answer = 1
for statusId in model.entries {
answer = statusId.playerStatusId
}
return answer
}
private static func getPlayerId(from model: DailyInjuryIllnessPlayer) -> Int {
var answer = model.playerId
return answer
}
}
The issue as stated in the code is at the return .init Swift expects it to be like the model which has a teamId and an array of DailyInjuryIllnessStatus. How can I put the methods I used to extract those values into the array
I'm building a SwiftUI app that retrieves an array of movies by genre from The Movie Database API.
Once the user selects a movie, I make a second API to get details for that specific movie. I'm using #Published to notify the view of changes however I am getting the I get the error "Missing argument for parameter 'from' in call" whenever I call an instance of the Model.
Here's the Model:
import Foundation
// MARK: - MovieList
struct MovieList: Codable {
let page: Int
let totalResults: Int
let totalPages: Int
let movie: [Movie]
enum CodingKeys: String, CodingKey {
case page
case totalResults = "total_results"
case totalPages = "total_pages"
case movie = "results"
}
}
// MARK: - Movie
struct Movie: Codable {
let popularity: Double
let voteCount: Int
let video: Bool
let posterPath: String?
let id: Int
let adult: Bool
let backdropPath: String?
let title: String
let voteAverage: Double
let overview: String
let releaseDate: String?
let runTime: Int?
enum CodingKeys: String, CodingKey {
case popularity
case voteCount = "vote_count"
case video
case posterPath = "poster_path"
case id, adult
case backdropPath = "backdrop_path"
case title
case voteAverage = "vote_average"
case overview
case releaseDate = "release_date"
case runTime = "runtime"
}
}
And here's the View Model:
import Foundation
class DetailViewModel: ObservableObject {
#Published var fetchedMovie = Movie() // getting error here
func getMovieDetails(id: Int) {
WebService().getMovieDetails(movie: id) { movie in
if let movieDetails = movie {
self.fetchedMovie = movieDetails
}
}
}
}
And here's the network call:
func getMovieDetails(movie: Int, completion: #escaping (Movie?) -> ()) {
guard let url = URL(string: "https://api.themoviedb.org/3/movie/\(movie)?api_key=5228bff935f7bd2b18c04fc3633828c0") else {
fatalError("Invalid URL")
}
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let task = session.dataTask(with: url) { data, response, error in
// Check for errors
guard error == nil else {
print ("error: \(error!)")
return
}
// Check that data has been returned
guard let data = data else {
print("No data")
return
}
do {
let decoder = JSONDecoder()
let movieDetails = try decoder.decode(Movie.self, from: data)
DispatchQueue.main.async {
completion(movieDetails)
}
} catch let err {
print("Err", err)
}
}
// execute the HTTP request
task.resume()
}
}
And the View code:
import SwiftUI
struct MovieDetailView: View {
#ObservedObject private var detailVM = DetailViewModel() // error here: Missing argument for parameter 'movie' in call
var movie: DetailViewModel
var body: some View {
VStack {
URLImage(url: "\(movie.backdropURL)")
.aspectRatio(contentMode: .fit)
Text("\(detailVM.movieRunTime) mins")
Text(movie.movieOverview)
.padding()
Spacer()
}.onAppear {
self.detailVM.getMovieDetails(id: self.movie.id)
}
.navigationBarTitle(movie.movieTitle)
}
}
struct MovieDetailView_Previews: PreviewProvider {
static var previews: some View {
MovieDetailView(movie: DetailViewModel(movie: Movie.example))
}
}
Any help would be greatly appreciated.
You cant initialise Movie object like this ... it needs Decoder object or all member wise intialization ---
You can define your function like this
class DetailViewModel: ObservableObject {
#Published var fetchedMovie : Movie?
func getMovieDetails(id: Int) {
WebService().getMovieDetails(movie: id) { movie in
if let movieDetails = movie {
self.fetchedMovie = movieDetails
}
}
}
}
I am trying to parse the data and display on the screen but i am getting " Value of type 'EmployeeData' has no member 'employee_name' "
What i am missing ?
I created my struct, parsed data and tried to divide into two parts. first part will be related with listing, second part is all data.
struct EmployeeData: Codable {
var data: Employee
var status: String
}
struct Employee: Codable {
var employee_name: String
var employee_salary: String
var employee_age: String
}
class WebServices {
func getData(completion: #escaping (EmployeeData?) -> ()){
guard let url = URL(string:"http://dummy.restapiexample.com/api/v1/employees")
else { fatalError("There is error!") }
URLSession.shared.dataTask(with: url) { (data, response,error) in
guard let data = data, error == nil else {
DispatchQueue.main.async{
completion(nil)
}
return
}
let empleyees = try? JSONDecoder().decode(EmployeeData.self, from: data)
DispatchQueue.main.async {
completion(empleyees)
}
}.resume()
}
}
class MVDesingnListView: ObservableObject {
}
struct MVDesignCellView {
let employeeDatas: EmployeeData
init(employeeDatas: EmployeeData) {
self.employeeDatas = employeeDatas
}
var employee_name: String {
self.employeeDatas.employee_name
}
}
The compiler is all right. Your struct EmployeeData has no member employee_name.
You need to go to the employee first, to get her name:
var employee_name: String {
self.employeeDatas.data.employee_name
}
should do the job.