I'm trying to fetch data from API according to a tutorial on YouTube and I does exactly as the video but the preview somehow crashed. But when I commented out the Api().getPosts , the preview is able to resume again. If that line of code is wrong how can I write it instead?
User Interface code:
import SwiftUI
struct ContentView: View {
#State var posts: [Post] = []
var body: some View {
VStack {
List(posts) { post in
Text(post.title)
}
.onAppear{
Api().getPosts { (posts) in
self.posts = posts
}
}
}//:VSTACK
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Api Service code:
import SwiftUI
struct Post: Codable, Identifiable {
var id = UUID()
var title: String
var body: String
}
class Api {
func getPosts(completion: #escaping([Post]) -> ()) {
guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else { return
print("Something occured!")
}
//CALL
URLSession.shared.dataTask(with: url) { data, response, error in
let posts = try! JSONDecoder().decode([Post].self, from: data!)
DispatchQueue.main.async {
completion(posts)
}
print(posts)
}//:URLSESSION
.resume()
}
}
use this code for your Post model:
struct Post: Codable, Identifiable {
// let id = UUID() // <-- or this
var id: Int // <-- here
var title: String
var body: String
}
Related
I'm learning swift and I wanted to pull some data from a django model to work with, so I was following a tutorial on YouTube. I copied the code on YouTube and I got his Breaking Bad API (https://breakingbadapi.com/api/quotes) to display on my simulator, however when I subbed in the URL to my API, I returned an empty list and my simulator displays only a blank screen.
I've tried using both http://127.0.0.1:8000/api/main_course/
and http://127.0.0.1:8000/api/main_course/?format=json
From my terminal I get 200 OK:
[14/Sep/2022 21:28:48] "GET /api/main_course/ HTTP/1.1" 200 1185
Here's my code:
import SwiftUI
struct Entree: Codable {
var id: Int
var menu: String
var name: String
var descripton: String
}
struct ContentView: View {
#State private var entrees = [Entree]()
var body: some View {
List(entrees, id: \.id) {entree in
Text(entree.name)
Text("Run")
}
.task {
await loadData()
print(entrees)
}
}
func loadData() async {
// create URL
guard let url = URL(string: "http://127.0.0.1:8000/api/main_course/") else {
print("URL Invalid")
return
}
// fetch data from that URL
do {
let (data, _) = try await URLSession.shared.data(from: url)
// decode that data
if let decodedResponse = try? JSONDecoder().decode([Entree].self, from: data) {
entrees = decodedResponse
}
}
catch {
print("Data invalid")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
It seems that it is on the conversion.
Try using the same keys.
struct Entree: Codable {
var quoteId: Int
var quote: String
var author: String
var series: String
enum CodingKeys: String, CodingKey {
case quoteId = "quote_id"
}
}
I'm getting a strange error with my test project, nw_protocol_get_quic_image_block_invoke dlopen libquic failed Error. ContentView.swift below, App.swift hasn't been touched. It's supposed to just grab JSON from a local web server, and display it in a list. Does anyone know what is going on here? I've been googling but haven't found a solution.
import SwiftUI
struct MyAPI {
static public var address = "http://localhost:9000"
}
struct Post: Codable, Identifiable {
public var id: Int
public var username: String
public var link: String
}
class FetchPosts: ObservableObject {
// 1.
#Published var posts = [Post]()
init() {
let url = URL(string: MyAPI.address)!
// 2.
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let postsData = data {
// 3.
let decodedData = try JSONDecoder().decode([Post].self, from: postsData)
DispatchQueue.main.async {
self.posts = decodedData
print(self.posts)
}
} else {
print("No data")
}
} catch {
print("Error")
}
}.resume()
}
}
struct ContentView: View {
#ObservedObject var fetch = FetchPosts()
var body: some View {
VStack {
Text("Test")
// 2.
List(fetch.posts) { post in
VStack(alignment: .leading) {
Text(post.username)
Text("\(post.link)") // print boolean
.font(.system(size: 11))
.foregroundColor(Color.gray)
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
This error is not related to your request and just console noise. Add NSAppTransportSecurity to your info.plist, add key NSAllowsArbitraryLoads and set it to true. Also, if your app is for Mac, allow outgoing connections in Signing and Capabilites
I am new to SwiftUI and only used UIKit before. I tried to use JSON to show a title but all tutorial videos work with lists. I dont want to use any list with JSON which shows all data. Only want to fetch for example the second or a specific array for title.
How can I remove the list in SwiftUI?
My View:
struct ContentView: View {
#ObservedObject var networkManager = NetworkManager()
var body: some View {
NavigationView {
List(networkManager.posts) { post in
HStack {
Text(String(post.points))
Text(post.title)
}}
.navigationBarTitle("H4X0R NEWS")
}
.onAppear {
self.networkManager.fetchData()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
NetworkManager:
class NetworkManager: ObservableObject {
#Published var posts = [Post]()
func fetchData() {
if let url = URL(string: "https://hn.algolia.com/api/v1/search?tags=front_page") {
let session = URLSession(configuration: .default)
let task = session.dataTask(with: url) { (data, response, error) in
if error == nil {
let decoder = JSONDecoder()
if let safeData = data {
do {
let results = try decoder.decode(Results.self, from: safeData)
DispatchQueue.main.async {
self.posts = results.hits
}
} catch {
print(error)
}
}
}
}
task.resume()
}
}
}
And my struct files for Json:
struct Results: Decodable {
let hits: [Post]
}
struct Post: Decodable, Identifiable {
var id: String {
return objectID
}
let objectID: String
let points: Int
let title: String
}
I dont want to use any list with JSON which shows all data. Only want
to fetch for example the second or a specific array for title.
You can use a computed property to access the specific element (and its title) from the posts array:
struct ContentView: View {
#ObservedObject var networkManager = NetworkManager()
// return the title of the second item in the `posts` array
var title: String {
guard networkManager.posts.count >= 2 else {
// decide what to do when data is not yet loaded or count is <= 1
return "Loading..."
}
return networkManager.posts[1].title
}
var body: some View {
NavigationView {
Text(title)
.navigationBarTitle("H4X0R NEWS")
}
.onAppear {
self.networkManager.fetchData()
}
}
}
I am very new to SwiftUi and I am trying to view the json data and I am currently working on retreiving the weather data from the openweathermap.org which is a free api to retrieve current weather. I am getting Error parsing Weather Json message. I am not sure what I am doing wrong!! Any help would be greatly appreciated and I have been stuck on this for a day. I referred many blogs and tutorials on how to use the Published var and ObservableObject I am not able to fix the problem.
This is my swift file
struct WeatherData {
public var Id: Int
public var main: String
public var weather: [Weather]
public var icon: String
}
extension WeatherData: Decodable, Identifiable {
var id: Int {return Id}
}
struct WeatherView: View {
#ObservedObject var fetch = FetchWeather()
var body: some View {
VStack {
List(fetch.weatherData) {
wthr in
VStack(alignment: .leading){
Text("\(wthr.id)")
Text("\(wthr.weather[0].description)")
Text("\(wthr.icon)")
.font(.system(size:11))
.foregroundColor(Color.gray)
}
}
}
}
}
struct Weather: Decodable {
let description: String
}
struct WeatherView_Previews: PreviewProvider {
static var previews: some View {
WeatherView()
}
}
class FetchWeather: ObservableObject {
#Published var weatherData = [WeatherData] ()
init() {
load()
}
func load() {
let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=London&appid=myapikey")!
URLSession.shared.dataTask(with: url) {
(data, response, error) in
do {
if let wthData = data {
let decodedData = try JSONDecoder().decode([WeatherData].self, from: wthData)
DispatchQueue.main.sync {
self.weatherData = decodedData
}
}
else {
print("No json Data available")
}
}catch {
print("Error parsing Weather Json")
}
}.resume()
}
}
Try this code. I have signed up to get the api and corrected the Model, ViewModel and View accordingly. I have not added the image loader for icon strings.
import SwiftUI
struct Weather: Decodable{
var description: String
var icon :String
}
struct MainData: Decodable {
var temp: Double
var pressure: Int
var humidity: Int
var temp_min: Double
var temp_max: Double
}
struct WeatherData: Decodable, Identifiable {
var id: Int
var main: MainData
var weather: [Weather]
var name: String
}
struct WeatherView: View {
#ObservedObject var fetch = FetchWeather()
var body: some View {
VStack(alignment: .leading) {
Text("Current Weather").font(.title).padding()
List(fetch.weatherData) { wthr in
HStack {
VStack(alignment: .leading){
Text("\(wthr.name)")
Text("\(wthr.weather[0].description)")
.font(.system(size:11))
.foregroundColor(Color.gray)
}
Spacer()
VStack(alignment: .trailing){
Text("\(wthr.main.temp-273.15, specifier: "%.1f") ÂșC")
}
Text("\(wthr.weather[0].icon)") // Image from "https://openweathermap.org/img/w/\(wthr.weather[0].icon).png"
.foregroundColor(Color.gray)
}
}
}
}
}
class FetchWeather: ObservableObject {
#Published var weatherData = [WeatherData]()
private let baseURL = "https://api.openweathermap.org/data/2.5/weather?q="
private let cities = [ "London", "Mumbai", "New+york", "Vatican+City" ]
private let api = "&appid="+"e44ebeb18c332fff46ab956bb38f9e07"
init() {
for city in self.cities {
self.load(self.baseURL+city+self.api)
}
}
func load(_ urlString: String) {
if let url = URL(string: urlString) {
URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
if let wthData = data {
let decodedData = try JSONDecoder().decode(WeatherData.self, from: wthData)
DispatchQueue.main.sync {
self.weatherData.append(decodedData)
}
}
else {
print("No json Data available")
}
} catch let error as NSError{
print(error.localizedDescription)
}
}.resume()
} else {
print("Unable to decode URL")
}
}
}
So I'm trying to access my data by id using a url such as http://localhost:8000/albums/whateverid.
First I gain the ids
class Webservice {
func getAllPosts(completion: #escaping ([Post]) -> ()) {
guard let url = URL(string: "http://localhost:8000/albums")
else {
fatalError("URL is not correct!")
}
URLSession.shared.dataTask(with: url) { data, _, _ in
let posts = try!
JSONDecoder().decode([Post].self, from: data!); DispatchQueue.main.async {
completion(posts)
}
}.resume()
}
}
struct Post: Codable, Hashable, Identifiable {
let id: String
let title: String
let path: String
let description: String
}
Set the variables to the data from class Webservice
final class PostListViewModel: ObservableObject {
init() {
fetchPosts()
}
#Published var posts = [Post]()
private func fetchPosts() {
Webservice().getAllPosts {
self.posts = $0
print("posts \(self.posts)")
}
}
}
And this is how I'm trying to grab album by id by using the id I fetched from the code above
I create a class that when a id is inserted will give me the album data back by id
class SecondWebService: Identifiable {
var id:String = ""
init(id: String?) {
self.id = id!
}
func getAllPostsById(completion: #escaping ([PostById]) -> ()) {
guard let url = URL(string: "http://localhost:8000/albums/\(id)")
else {
fatalError("URL is not correct!")
}
URLSession.shared.dataTask(with: url) { data, _, _ in
let posts = try!
JSONDecoder().decode([PostById].self, from: data!); DispatchQueue.main.async {
completion(posts)
}
}.resume()
}
}
Variables
struct PostById: Codable, Hashable, Identifiable {
let id: String
let name: String?
let path: String
}
Here's where I try to insert the id from class PostListViewModel into my class SecondWebService to get the data back set my variables to that data
final class PostListViewByIdModel: ObservableObject {
#ObservedObject var model = PostListViewModel()
init() {
fetchPostsById()
}
#Published var postsById = [PostById]()
private func fetchPostsById() {
for post in model.posts {
SecondWebService(id: post.id).getAllPostsById {
self.postsById = $0
print("postById \(post)")
}
}
}
}
For some reason above when I try to print nothing will display because I believe posts in model.posts isn't getting read
When I use it here in List() it works but not in init:
struct ContentView: View {
#ObservedObject var model = PostListViewModel()
init() {
for post in model.posts {
print(post)
}
}
var body: some View {
NavigationView {
List(model.posts) { post in
VStack{
Text("Title: ").bold()
+ Text("\(post.title)")
NavigationLink(destination: Album(post: post)) {
ImageView(withURL: "http://localhost:8000/\(post.path.replacingOccurrences(of: " ", with: "%20"))")
}
Text("Description: ").bold()
+ Text("\(post.description)")
}
}
}
}
}
I'm very curious on why nothing is printing when I use model.posts in my for loop. Only when I use it in the SwiftUI functions does it work.
I'm very curious on why nothing is printing when I use model.posts in my for loop
It is because of asynchronous nature of the following call
private func fetchPosts() {
Webservice().getAllPosts {
so in init
#ObservedObject var model = PostListViewModel() // just created
init() {
for post in model.posts { // posts are empty because
// `Webservice().getAllPosts` has not finished yet
print(post)
}
Update: You need to call second service after first one got finished, here is possible approach (only idea - cannot test)
struct ContentView: View {
#ObservedObject var model = PostListViewModel()
#ObservedObject var model2 = PostListViewByIdModel()
// delete init() - it is not needed here
...
var body: some View {
NavigationView {
List(model.posts) { post in
...
}
.onReceive(model.$posts) { posts in // << first got finished
self.model2.fetchPostsById(for: posts) // << start second
}
.onReceive(model2.$postsById) { postById in
// do something here
}
}
}
and updated second service
final class PostListViewByIdModel: ObservableObject {
#Published var postsById = [PostById]()
func fetchPostsById(for posts: [Post]) { // not private now
for post in model.posts {
SecondWebService(id: post.id).getAllPostsById {
self.postsById = $0
print("postById \(post)")
}
}