fetch array data from API in SwiftUI? - ios

This is Model for My API and we will use this model in future. can Anyone tell me this is correct or Not. Im a native user of SwiftUI.
public struct Temperatures {
public let bookDetails: BookDetails
public init(bookDetails: BookDetails) {
self.bookDetails = bookDetails
}
}
public struct BookDetails {
public let data: [Datum]
public init(data: [Datum]) {
self.data = data
}
}
public struct Datum : Hashable, Identifiable {
public let id = UUID()
public let title: String
public let published: String
public let url: String
public init( title: String, published: String, url: String) {
self.title = title
self.published = published
self.url = url
}
}
And this is ViewModel
And i cant fetch the data of data[]
View Model For Prime Book And Showing Details
class PrimeBookVM: ObservableObject{
#Published var datas = [Datum]()
init(){
let source = "https://********/api/book-search?is_prime"
let url = URL(string: source)!
let session = URLSession(configuration: .default)
session.dataTask(with: url){
(data, _, err) in
if err != nil{
print(err?.localizedDescription ?? "Hello Error")
return
}
let json = try!JSON(data: data!)
for i in json["data"]{
let published = i.1["published"].stringValue
let title = i.1["title"].stringValue
let url = i.1["url"].stringValue
DispatchQueue.main.async {
self.datas.append(Datum(title: title, published: published, url: url))
}
}
}
.resume()
}
}
This is my View and try to fetch the detail of data array in api.
struct PrimeBooksView: View{
#StateObject var list = PrimeBookVM()
var body: some View{
ScrollView(.horizontal){
HStack{
ForEach(list.datas, id: \.self){ item in
VStack(alignment: .leading){
WebImage(url: URL(string: item.url)!, options: .highPriority, context: nil)
.resizable()
.frame(width: 180, height: 230)
Text(item.title)
.multilineTextAlignment(.leading)
.font(.system(size: 16))
.foregroundColor(Color("default"))
Text(item.published)
.font(.system(size: 12))
.fontWeight(.light)
.foregroundColor(Color("default"))
}
.padding(.all,4)
.background(Color.white).cornerRadius(8)
.shadow(color: .gray, radius: 1)
}
.padding(.all,1)
}
}
}
}
Thank You So much in Advance for Help.

Try this example code, with a working set of data model structs, and an updated getData() function
to fetch the data from the server. You still need to check the server documentation,
to determine which properties are optional.
import Foundation
import SwiftUI
struct ContentView: View {
var body: some View {
PrimeBooksView()
}
}
class PrimeBookVM: ObservableObject {
#Published var datas = [Datum]()
init() {
getData()
}
func getData() {
guard let url = URL(string: "https://alibrary.in/api/book-search?is_prime") else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
if let data = data {
do {
let results = try JSONDecoder().decode(ApiResponse.self, from: data)
DispatchQueue.main.async {
self.datas = results.bookDetails.data
}
}
catch {
print(error)
}
}
}.resume()
}
}
struct PrimeBooksView: View{
#StateObject var list = PrimeBookVM()
var body: some View{
ScrollView(.horizontal){
HStack {
ForEach(list.datas, id: \.self){ item in
VStack(alignment: .leading){
AsyncImage(url: URL(string: item.url)) { image in
image
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 180, height: 230)
} placeholder: {
ProgressView()
}
Text(item.title)
.multilineTextAlignment(.leading)
.font(.system(size: 16))
Text(item.published)
.font(.system(size: 12))
.fontWeight(.light)
}
.padding(4)
.background(Color.white).cornerRadius(8)
.shadow(color: .gray, radius: 1)
}
}
}
}
}
public struct ApiResponse: Codable {
let bookDetails: BookDetails
let bookSearch: String?
let uploadTypeID: Int
let stackID: String
let data: Int
enum CodingKeys: String, CodingKey {
case bookDetails, bookSearch
case uploadTypeID = "upload_type_id"
case stackID = "stack_id"
case data
}
}
public struct BookDetails: Codable {
let currentPage: Int
let data: [Datum]
let firstPageURL: String
let from, lastPage: Int
let lastPageURL, nextPageURL, path: String
let perPage: Int
let prevPageURL: String?
let to, total: Int
enum CodingKeys: String, CodingKey {
case data, from, path, to, total
case currentPage = "current_page"
case firstPageURL = "first_page_url"
case lastPage = "last_page"
case lastPageURL = "last_page_url"
case nextPageURL = "next_page_url"
case perPage = "per_page"
case prevPageURL = "prev_page_url"
}
}
public struct Datum : Hashable, Identifiable, Codable {
public let id = UUID() // <-- could be Int
public let title: String
public let published: String
public let url: String
public init( title: String, published: String, url: String) {
self.title = title
self.published = published
self.url = url
}
enum CodingKeys: String, CodingKey {
case title, published, url
}
}

Related

how to make A SwiftUI Image Gallery/Slideshow With Auto Scrolling in SwiftUI?

How can I make a slider when my Data is coming from API? I am using
this(below code) for static images work fine but whenever I try to
use API data then my code does not work.
How to Set the Marquee in this images.
This is My code
public struct MagazineModel: Decodable {
public let magzineBanners: [MagzineBanner]
}
public struct MagzineBanner: Decodable, Identifiable {
public let id: Int
public let url: String
}
This is My View Model
//View Model for Magazines and showing Details
class MagazineBannerVM: ObservableObject{
#Published var datas = [MagzineBanner]()
let url = "ApiUrl"
init() {
getData(url: url)
}
func getData(url: String) {
guard let url = URL(string: url) else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
if let data = data {
do {
let results = try JSONDecoder().decode(MagazineModel.self, from: data)
DispatchQueue.main.async {
self.datas = results.magzineBanners
}
}
catch {
print(error)
}
}
}.resume()
}
}
struct MagazineBannerView: View{
#ObservedObject var list = MagazineBannerVM()
public let timer = Timer.publish(every: 2, on: .main, in: .common).autoconnect()
#State var currentIndex = 0
#State var totalImages = 2
var body: some View{
ScrollView(.horizontal) {
GeometryReader { proxy in
TabView(selection: $currentIndex) {
HStack{
ForEach(list.datas, id: \.id){ item in
Group{
AsyncImage(url: URL(string: item.url)){ image in
image
.resizable()
.frame(width:UIScreen.main.bounds.width, height: 122)
}placeholder: {
Image("logo_gray").resizable()
.frame(width:UIScreen.main.bounds.width, height: 122)
}
}
}
}
}
.tabViewStyle(PageTabViewStyle())
.onReceive(timer, perform: { _ in
withAnimation{
currentIndex = currentIndex < totalImages ? currentIndex + 1: 0
}
})
}
}
}
}
I want to change images after every 2 seconds and every images has
full width as the screen width
And it is showing the half of screen width and showing both images in
single view

Show API Data in a View without ID

I have a small weather project and I got stuck in the phase where I have to show the results from the API in a view. The API is from WeatherAPI.
I mention that the JSON file doesn't have an id and I receive the results in Console.
What is the best approach for me to solve this problem?
Thank you for your help!
This is the APIService.
import Foundation
class APIService: ObservableObject {
func apiCall(searchedCity: String) {
let apikey = "secret"
guard let url = URL(string: "https://api.weatherapi.com/v1/current.json?key=\(apikey)&q=\(searchedCity)&aqi=no") else {
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-type")
let task = URLSession.shared.dataTask(with: request) { data, _, error in
guard let data = data, error == nil else {
return
}
do {
let response = try JSONDecoder().decode(WeatherModel.self, from: data)
print(response)
print("SUCCESS")
}
catch {
print(error)
}
}
task.resume()
}
}
This is the ForecastModel
import Foundation
struct WeatherModel: Codable {
var location: Location
var current: Current
}
struct Location: Codable {
var name: String
}
struct Current: Codable, {
var temp_c: Decimal
var wind_kph: Decimal
}
Here is the view where I want to call.
import SwiftUI
struct WeatherView: View {
#StateObject var callApi = APIService()
#Binding var searchedCity: String
var body: some View {
NavigationView {
ZStack(alignment: .leading) {
Color.backgroundColor.ignoresSafeArea(edges: .all)
VStack(alignment: .leading, spacing: 50) {
comparation
temperature
clothes
Spacer()
}
.foregroundColor(.white)
.font(.largeTitle)
.onAppear(perform: {
self.callApi.apiCall(searchedCity: "\(searchedCity)")
})
}
}
}
}
struct WeatherView_Previews: PreviewProvider {
#State static var searchedCity: String = ""
static var previews: some View {
WeatherView(searchedCity: $searchedCity)
}
}
fileprivate extension WeatherView {
var comparation: some View {
Text("Today is ")
.fontWeight(.medium)
}
var temperature: some View {
Text("It is ?C with ? and ? winds")
.fontWeight(.medium)
}
var clothes: some View {
Text("Wear a ?")
.fontWeight(.medium)
}
}

SwiftUI load JSON file and filter it once on view load

I'm trying to have a list of supported countries in my application, filter may come from a WS later on or it may stay as an array of country-codes within my code.
I'm new at SwiftUI and what I'm trying to do is filter a JSON file with all the country-names, their phone codes and their country-code, and after playing around with some code I've come to this:
struct CountryCodePicker: View {
let includedCountries = ["MX", "US", "CA"]
var countryCode = Locale.current.regionCode ?? "MX"
let allCountries: [Country] = load("countryCodes.json")
#State var countries: [Country]
#State var showingCountriesList: Bool = false
#State private var dialCode: String = "+1"
#State var phoneNumber: String
#State var isActive: Bool = false
func getDialCode() -> String {
countries = allCountries.filter {includedCountries.contains($0.code)}
print(countries)
for country in countries where country.code == countryCode {
return country.dialCode
}
return self.dialCode
}
var body: some View {
HStack {
Button(action: {
self.showingCountriesList.toggle()
}) {
HStack() {
Image(countryCode, bundle: FlagKit.assetBundle)
.renderingMode(.original)
.resizable()
.frame(width: 36, height: 24)
.aspectRatio(contentMode: .fit)
.offset(x: 0, y: -6)
Text(getDialCode())
.baselineOffset(12)
.foregroundColor(ColorManager.Grey500)
.titleStyle()
}
}.sheet(isPresented: $showingCountriesList) {
CountriesList(countryList: self.countries)
}
UnderscoredTextField(phoneNumber: "")
}
}
}
struct CountryCodePicker_Previews: PreviewProvider {
static var previews: some View {
CountryCodePicker(countries: [Country(name: "United States", dialCode: "+1", code: "US")], phoneNumber: "12345678")
}
}
That produces this output
And the Country model looks like this:
struct Country: Codable {
let name: String
let dialCode: String
let code: String
enum CodingKeys: String, CodingKey {
case name = "name"
case dialCode = "dial_code"
case code = "code"
}
init(name: String, dialCode: String, code: String) {
self.name = name
self.dialCode = dialCode
self.code = code
}
}
I know it contains some extra views, but you get the idea of where this program is going, I'm getting a purple warning on the first line of getDialCode() stating:
Modifying state during view update, this will cause undefined behavior.
This, I believe is because it may be working now, but it may behave differently on some devices under certain circumstances.
Could anyone explain what would be the correct way of doing the country filtering? I need to send this filtered array later on to a modal view in case the user has to change their country when tapping the flag.
I fixed it by moving:
let allCountries: [Country] = load("countryCodes.json")
let includedCountries = ["MX", "US", "CA"]
func filterCountries() -> [Country] {
return allCountries.filter{includedCountries.contains($0.code)}
}
And
CountryCodePicker(countries: self.filterCountries())
To my ContentView
And my CountryCodePicker now became:
struct CountryCodePicker: View {
#State var countries: [Country]
#State private var countryCode = Locale.current.regionCode ?? "US"
#State private var dialCode: String = "+1"
#State private var showingCountriesList: Bool = false
func getDialCode() -> String {
for country in countries where country.code == countryCode {
return country.dialCode
}
return self.dialCode
}
var body: some View {
HStack {
Button(action: {
self.showingCountriesList.toggle()
}) {
HStack() {
Image(countryCode, bundle: FlagKit.assetBundle)
.renderingMode(.original)
.resizable()
.frame(width: 36, height: 24)
.aspectRatio(contentMode: .fit)
.offset(x: 0, y: -6)
Text(getDialCode())
.baselineOffset(12)
.foregroundColor(ColorManager.Gray200)
.titleStyle()
}
}.sheet(isPresented: $showingCountriesList) {
CountriesList(countryList: self.countries)
}
UnderscoredTextField(phoneNumber: "")
}
}
}

SwiftUI List doesn't appear

Good morning,
I have an issue with my SwiftUI list.
After receiving the data from my JSON correctly and having it as a string afterwards, the informations doesn't seem to appear in my list view.
struct Lists: View{
#State private var Countries = [Country]()
var body: some View {
List(Countries, id: \.id) { item in
VStack(alignment: .leading) {
Text(item.Country)
.font(.headline)
Text(item.Country)
}
}.onAppear(perform: loadData)
}
func loadData() {
guard let url = URL(string: "https://api.covid19api.com/summary") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
let jsonData = (try! String(contentsOf: url))
/*print(jsonData)*/
URLSession.shared.dataTask(with: request) { data, response, error in
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
if let jsonData = data {
do {
let decodedResponse = try decoder.decode(AllCountries.self, from: jsonData)
print(decodedResponse.Countries)
} catch let error as NSError {
/*print("json error: \(error.localizedDescription)")*/
print("json error: \(error)")
}
}
}.resume()
}
Here are my structs for the object:
struct AllCountries: Decodable {
var Countries: [Country]
}
struct AllCountries: Decodable {
var Countries: [Country] }
struct Country: Decodable, Identifiable {
let id = UUID()
var Country, CountryCode, Slug: String
var NewConfirmed, TotalConfirmed, NewDeaths, TotalDeaths: Int
var NewRecovered, TotalRecovered: Int
var Date: Date
}
enum CodingKeys: String, CodingKey {
case Country = "Country"
case CountryCode = "CountryCode"
case Slug = "Slug"
case NewConfirmed = "NewConfirmed"
case TotalConfirmed = "TotalConfirmed"
case NewDeaths = "NewDeaths"
case TotalDeaths = "TotalDeaths"
case NewRecovered = "NewRecovered"
case TotalRecovered = "TotalRecovered"
case Date = "Date"
}
}
Here is the beginning of the result of the "data" when I print it:
[_IOS_Project.Country(id: EB629D42-8278-444C-878E-A6EAC46BD5D6, Country: "Afghanistan", CountryCode: "AF", Slug: "afghanistan", NewConfirmed: 546, TotalConfirmed: 28424, NewDeaths: 21, TotalDeaths: 569, NewRecovered: 330, TotalRecovered: 8292, Date: 2020-06-21 19:37:01 +0000), _IOS_Project.Country(id: 8DDDCA84-CE99-4374-A487-096BFDF8A467, Country: "Albania", CountryCode: "AL", Slug: "albania", NewConfirmed: 53, TotalConfirmed: 1891, NewDeaths: 1, TotalDeaths: 43, NewRecovered: 12, TotalRecovered: 1126, Date: 2020-06-21 19:37:01 +0000),
Could somebody point me to the right direction on this issue?
Thanks in advance :)
There seems to be a few problems with your code.
Firstly the naming of your variables. Variable names in Swift begin with a lowercase, structs and classes begin with uppercase.
Secondly you aren't assigning the response from the your URLRequest to the countries state variable, this is the main problem.
Thirdly, your pasted code doesn't seem to be formatted correctly.
I put your code into a fresh project and with a few tweaks I got it to work.
struct ContentView: View {
#State private var countries = [AllCountries.Country]()
var body: some View {
List(countries, id: \.id) { item in
VStack(alignment: .leading) {
Text(item.country)
.font(.headline)
Text(item.country)
}
}.onAppear(perform: loadData)
}
func loadData() {
guard let url = URL(string: "https://api.covid19api.com/summary") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
let decoder = JSONDecoder()
decoder.dateDecodingStrategy = .iso8601
if let jsonData = data {
do {
let decodedResponse = try decoder.decode(AllCountries.self, from: jsonData)
print(decodedResponse.countries)
// You should update this on the main thread
DispatchQueue.main.async {
// this assigns the values you received to the state variable countries
self.countries = decodedResponse.countries
}
} catch let error as NSError {
print("json error: \(error)")
}
}
}.resume()
}
}
Notice in the loadData function that I assign the response from the URLRequest to the countries variable. Because this is a #State variable it causes the screen to reload when it changes. You weren't doing this so your UI had no idea that it needed to update.
I also updated your variable names, so that they are lowercased.
struct AllCountries: Decodable {
var countries: [Country]
enum CodingKeys: String, CodingKey {
case countries = "Countries"
}
struct Country: Decodable, Identifiable {
let id = UUID()
var country, countryCode, slug: String
var newConfirmed, totalConfirmed, newDeaths, totalDeaths: Int
var newRecovered, totalRecovered: Int
var date: Date
enum CodingKeys: String, CodingKey {
case country = "Country"
case countryCode = "CountryCode"
case slug = "Slug"
case newConfirmed = "NewConfirmed"
case totalConfirmed = "TotalConfirmed"
case newDeaths = "NewDeaths"
case totalDeaths = "TotalDeaths"
case newRecovered = "NewRecovered"
case totalRecovered = "TotalRecovered"
case date = "Date"
}
}
}

Trouble displaying data from Contentful in SwiftUI

I'm trying to display data from Contentful into my SwiftUI app but I'm hitting an issue.
The goal is to display a list of movies and have them tappable. When I select a movie I want to get the data for that movie, so the title & movie trailer for example.
But in my selectable row I'm getting Use of undeclared type 'movie' and in my movies View I'm getting Use of undeclared type 'fetcher'
Here's what I have tried below:
import SwiftUI
import Combine
import Contentful
struct Movie: Codable, Identifiable, FieldKeysQueryable, EntryDecodable {
static let contentTypeId: String = "movies"
// FlatResource Memberes.
let id: String
var updatedAt: Date?
var createdAt: Date?
var localeCode: String?
var title: String
var movieId: String
var movieTrailer: String
enum FieldKeys: String, CodingKey {
case title, movieId, movieTrailer
}
enum CodingKeys: String, CodingKey {
case id = "id"
case title = "title"
case movieId = "movieId"
case movieTrailer = "movieTrailer"
}
}
public class MovieFetcher: ObservableObject {
#Published var movies = [Movie]()
init() {
getArray(id: "movies") { (items) in
items.forEach { (item) in
self.movies.append(Movie(id: item.id, title: item.title, movieId: item.movieId, movieTrailer: item.movieTrailer))
}
}
}
func getArray(id: String, completion: #escaping([Movie]) -> ()) {
let client = Client(spaceId: spaceId, accessToken: accessToken, contentTypeClasses: [Movie.self])
let query = QueryOn<Movie>.where(contentTypeId: "movies")
client.fetchArray(of: Movie.self, matching: query) { (result: Result<ArrayResponse<Movie>>) in
switch result {
case .success(let array):
DispatchQueue.main.async {
completion(array.items)
}
case .error(let error):
print(error)
}
}
}
}
struct moviesView : View {
#ObservedObject var fetcher = MovieFetcher()
#State var selectMovie: Movie? = nil
#Binding var show: Bool
var body: some View {
HStack(alignment: .bottom) {
if show {
ScrollView(.horizontal) {
Spacer()
HStack(alignment: .bottom, spacing: 30) {
ForEach(fetcher.movies, id: \.self) { item in
selectableRow(movie: item, selectMovie: self.$selectMovie)
}
}
.frame(minWidth: 0, maxWidth: .infinity)
}
.padding(.leading, 46)
.padding(.bottom, 26)
}
}
}
}
struct selectableRow : View {
var movie: Movie
#Binding var selectedMovie: Movie?
#State var initialImage = UIImage()
var urlString = "\(urlBase)\(movie.movieId).png?"
var body: some View {
ZStack(alignment: .center) {
if movie == selectedMovie {
Image("")
.resizable()
.frame(width: 187, height: 254)
.overlay(
RoundedRectangle(cornerRadius: 13)
Image(uiImage: initialImage)
.resizable()
.cornerRadius(13.0)
.frame(width: 182, height: 249)
.onAppear {
guard let url = URL(string: self.urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
guard let image = UIImage(data: data) else { return }
RunLoop.main.perform {
self.initialImage = image
}
}.resume()
}
} else {
Image(uiImage: initialImage)
.resizable()
.cornerRadius(13.0)
.frame(width: 135, height: 179)
.onAppear {
guard let url = URL(string: self.urlString) else { return }
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard let data = data else { return }
guard let image = UIImage(data: data) else { return }
RunLoop.main.perform {
self.initialImage = image
}
}.resume()
}
}
}
.onTapGesture {
self.selectedMovie = self.movie
}
}
}
I suppose it was intended
struct moviesView : View {
#ObservedObject var fetcher = MovieFetcher()
#State var selectMovie: Movie? = nil // type is Movie !!
...
and here
struct selectableRow : View {
var movie: Movie
#Binding var selectedMovie: Movie? // type is Movie !!
The good practice is to use Capitalized names for types and lowerCased types for variables/properties, following this neither you nor compiler be confused.

Resources