SwifUI : Is my downloaded JSON saved to coreData? - json

I've been messing around with CoreData and this time I'm trying to save my downloaded JSON to core data. I don't get any error until I try to display the list and it would return empty.
Any suggestions would be much appreciated!
Model
struct ArticleData: Identifiable, Decodable {
let id : String
let type : String
let attributes: ArticleAttributee
struct ArticleAttributee: Codable {
let name: String
let card_artwork_url: String
...
}
AttributeArticle+CoreDataProperties.swift
extension AttributeArticle {
#nonobjc public class func fetchRequest() -> NSFetchRequest<AttributeArticle> {
return NSFetchRequest<AttributeArticle>(entityName: "AttributeArticle")
}
#NSManaged public var card_artwork_url: String?
#NSManaged public var content_type: String?
...
public var wrapperCard_artwork_url : String {
card_artwork_url ?? "Unknown"
}
...
}
Download JSOn and load to Core Data
class Article {
static func loadDataFromJSON(completion: #escaping ([ArticleData]) -> ()) {
let stringURL = "https://api.jsonbin.io/b/5ed679357741ef56a566a67f"
guard let url = URL(string: stringURL) else { return }
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) {
data, response, error in
guard let data = data else {
print("No data in response \(error?.localizedDescription ?? "No data response")")
return
}
if let decoderLoadedUser = try? JSONDecoder().decode([ArticleData].self, from: data) {
completion(decoderLoadedUser)
}
}.resume()
}
static func loadDataToCD(moc: NSManagedObjectContext) {
loadDataFromJSON { (articles) in
DispatchQueue.main.async {
var tempArticles = [AttributeArticle]()
for article in articles {
let newArticle = AttributeArticle(context: moc)
newArticle.name = article.attributes.name
newArticle.card_artwork_url = article.attributes.card_artwork_url
... so on
...
tempArticles.append(newArticle)
}
do {
try moc.save()
} catch let error {
print("Could not save data: \(error.localizedDescription)")
}
}
}
}
}
my list :
#Environment(\.managedObjectContext) var moc
#FetchRequest(entity: AttributeArticle.entity(), sortDescriptors: []) var articles: FetchedResults<AttributeArticle>
VStack {
List() {
ForEach(articles, id: \.id) { article in
NavigationLink(destination: ArticleDetailView(articles: article)){
ArticleRowView(articles: article)
}
}
}
}.onAppear {
if self.articles.isEmpty {
print("Articles is empty \(self.articles)")
Article.loadDataToCD(moc: self.moc)
}
Sorry for the long post and thank you for your helps!

Related

swiftUI / Xcode 12 fetch data from server

I'm very new to swiftUI and have been working through the landscapes app tutorial.
I have been trying to switch the data source from a bundled JSON file to a remote JSON source but have so far been lost on how to integrate what I've learnt about the URLSession with the tutorials load code.
Apple's code:
final class ModelData: ObservableObject {
#Published var landmarks: [Landmark] = load("landmarkData.json")
// #Published var landmarks: [Landmark] = apiCall.getLocations(locations)
}
func load<T: Decodable>(_ filename: String) -> T {
let data: Data
guard let file = Bundle.main.url(forResource: filename, withExtension: nil)
else {
fatalError("Couldn't find \(filename) in main bundle.")
}
do {
data = try Data(contentsOf: file)
} catch {
fatalError("Couldn't load \(filename) from main bundle:\n\(error)")
}
do {
let decoder = JSONDecoder()
return try decoder.decode(T.self, from: data)
} catch {
fatalError("Couldn't parse \(filename) as \(T.self):\n\(error)")
}
}
What I have to load from the remote source:
struct Location: Codable, Identifiable {
let id = UUID()
let country: String
let name: String
}
class apiCall {
func getLocations(completion:#escaping ([Location]) -> ()) {
guard let url = URL(string: "https://overseer.cyou/heritage/heritageData.json") else { return }
URLSession.shared.dataTask(with: url) { (data, _, _) in
let locations = try! JSONDecoder().decode([Location].self, from: data!)
print(locations)
DispatchQueue.main.async {
completion(locations)
}
}
.resume()
}
}
Can anyone show me how I go about doing this, ideally from a complete beginners point of view?
// framework support
import SwiftUI
import Combine
// List view setup
struct LocationsView: View {
#ObservedObject var viewModel = LocationModel()
var body: some View {
List(viewModel.locations) { location in
HStack {
VStack(alignment: .leading) {
Text(location.name)
.font(.headline)
Text(location.country)
.font(.subheadline)
}
}
}
}
}
// Location model
struct Location: Codable, Identifiable {
var id = UUID()
let country: String
let name: String
let locationId: Int = 0
enum CodingKeys: String, CodingKey {
case locationId = "id"
case country
case name
}
}
// Location view model class
class LocationModel: ObservableObject {
#Published var locations: [Location] = []
var cancellationToken: AnyCancellable?
init() {
getLocations()
}
}
extension LocationModel {
func getLocations() {
cancellationToken = self.request("https://overseer.cyou/heritage/heritageData.json")?
.mapError({ (error) -> Error in
print(error)
return error
})
.sink(receiveCompletion: { _ in },
receiveValue: {
self.locations = $0
})
}
// API request
private func request(_ path: String) -> AnyPublisher<[Location], Error>? {
guard let url = URL(string: path)
else { return nil }
let request = URLRequest(url: url)
return apiCall.run(request)
.map(\.value)
.eraseToAnyPublisher()
}
}
// API setup
struct apiCall {
struct Response<T> {
let value: T
let response: URLResponse
}
static func run<T: Decodable>(_ request: URLRequest) -> AnyPublisher<Response<T>, Error> {
return URLSession.shared
.dataTaskPublisher(for: request)
.tryMap { result -> Response<T> in
let value = try JSONDecoder().decode(T.self, from: result.data)
return Response(value: value, response: result.response)
}
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
}

Why is my JSON code in Swift not parsing?

Disclaimer: Very basic question below. I am trying to learn the basics of IOS development.
I'm currently trying to parse data from an API to a SwiftUI project and am not able to successfully do so.
The code goes as follows:
import SwiftUI
struct Poem: Codable {
let title, author: String
let lines: [String]
let linecount: String
}
class FetchPoem: ObservableObject {
// 1.
#Published var poems = [Poem]()
init() {
let url = URL(string: "https://poetrydb.org/random/1")!
// 2.
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let poemData = data {
// 3.
let decodedData = try JSONDecoder().decode([Poem].self, from: poemData)
DispatchQueue.main.async {
self.poems = decodedData
}
} else {
print("No data")
}
} catch {
print("Error")
}
}.resume()
}
}
struct ContentView: View {
#ObservedObject var fetch = FetchPoem()
let joined = fetch.poem.lines.joined(separator: "\n")
var body: some View {
Text(fetch.poem.title)
.padding()
Text( \(joined) )
.padding()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The build currently fails. It's throwing me the following errors:
Initializer 'init(_:)' requires that 'Binding<Subject>' conform to 'StringProtocol'
Referencing subscript 'subscript(dynamicMember:)' requires wrapper 'ObservedObject<FetchPoem>.Wrapper'
Value of type 'FetchPoem' has no dynamic member 'poem' using key path from root type 'FetchPoem'
Moreover, I am attempting to append the array "Lines" into one main String variable "Joined". However, I am not sure this works... The error is "String interpolation can only appear inside a string literal". Would love some help if anyone knows...
Any ideas? All help is appreciated.
** Edited Code - Q2
import SwiftUI
struct Poem: Codable, Hashable {
let title, author: String
let lines: [String]
let linecount: String
}
class FetchPoem: ObservableObject {
#Published var poems = [Poem]()
func getPoem() {
let url = URL(string: "https://poetrydb.org/random/1")!
// 2.
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let poemData = data {
// 3.
let decodedData = try JSONDecoder().decode([Poem].self, from: poemData)
DispatchQueue.main.async {
self.poems = decodedData
}
} else {
print("No data")
}
} catch {
print("Error")
}
}.resume()
}
}
struct ContentView: View {
#ObservedObject var fetch = FetchPoem()
var body: some View {
VStack {
if let poem = fetch.poems.first {
Button("Refresh") {getPoem}
Text("\(poem.author): \(poem.title)").bold()
Divider()
ScrollView {
VStack {
ForEach(poem.lines, id: \.self) {
Text($0)
}
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Most probably you wanted this (because poems is an array) - tested with Xcode 12.1 / iOS 14.1
struct Poem: Codable, Hashable {
let title, author: String
let lines: [String]
let linecount: String
}
// ...
struct ContentView: View {
#ObservedObject var fetch = FetchPoem()
var body: some View {
List {
ForEach(fetch.poems, id: \.self) {
Text("\($0.author): \($0.title)")
}
}
}
}
... , and next might be wrapping that into NavigationView/NavigationLink with shown each poem in desination view.
Of showing lines as follows:
var body: some View {
VStack {
if let poem = fetch.poems.first {
Text("\(poem.author): \(poem.title)").bold()
Divider()
ScrollView {
VStack {
ForEach(poem.lines, id: \.self) {
Text($0)
}
}
}
}
}
}

Decode JSON using Codable and then populate my SwiftUI

I am new to Swift and SwiftUI. I am currently teaching myself how to code (loving it!) through hackingwithswift.com I am currently on Day 60 and I am stuck and not sure what to do from here.
The challenge is to decode some information using Codable and populate SwiftUI.
I created a struct to match the JSON, but when I go to run the app, I keep getting my error "Fetch Failed: Unknown Error" and therefore my UI won't update.
Would someone glance at my code and provide any pointers on where I am going wrong and possibly why? Thank you so much for any suggestions and help, it is much appreciated! Code is posted below.
Cody
import SwiftUI
struct Response: Codable {
var results: [User]
}
struct User: Codable, Identifiable {
let id: String
let isActive: Bool
let name: String
let age: Int
let company: String
let email: String
let address: String
let about: String
let registered: String
let tags: [String]
struct FriendRole: Codable {
let id: String
let name: String
}
let friend: [FriendRole]
}
struct ContentView: View {
#State private var results = [User]()
var body: some View {
List(results, id: \.id) { item in
VStack(alignment: .leading) {
Text(item.name)
.font(.headline)
Text(item.address)
}
}
.onAppear(perform: loadData)
}
func loadData() {
guard let url = URL(string: "https://www.hackingwithswift.com/samples/friendface.json") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
DispatchQueue.main.async {
self.results = decodedResponse.results
}
return
}
}
print("Fetch Failed: \(error?.localizedDescription ?? "Unkown Error").")
}.resume()
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I'm also new to swiftUI this is how I managed to make it work by using Observable Objects
Here's the structures Since in the json file there is a [ in the very beginning you do not have to create a struct that has a User array you could create a struct and then create a variable of that struct type as an array
here's how I did it
I created separate files for instance for the structures i had a different file for them
here's what I have for my structure file
import Foundation
struct User : Codable, Identifiable {
let id : String
let isActive : Bool
let name : String
let age : Int
let company : String
let email : String
let address : String
let about : String
let registered : String
let tags = [String]()
let friends = [Friends]()
}
struct Friends : Codable {
let id : String
let name : String
}
I created another file for the observable object class
class JsonChannel : ObservableObject {
#Published var retVal = [User]()
func getInfo () {
guard let url = URL(string: "https://www.hackingwithswift.com/samples/friendface.json") else {return}
URLSession.shared.dataTask(with: url) { (data, resp, err) in
if let data = data {
DispatchQueue.main.async {
do {
self.retVal = try JSONDecoder().decode([User].self, from: data)
}
catch {
print(error)
}
}
}
}.resume()
}
}
and here's what i have for my contentView file
import SwiftUI
struct ContentView : View {
#ObservedObject var info = JsonChannel()
var body: some View {
VStack {
Button(action: {
self.info.getInfo()
}) {
Text("click here to get info")
}
List {
ForEach (self.info.retVal) { item in
VStack {
Text("\(item.name)")
Text("\(item.address)")
}
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

SwiftUI - JSON from GitHub REST API fails to parse

I am attempting to create an app in SwiftUI using GitHub's REST API. I am attempting to only retrieve only the login name and profile picture of a given user's followers and then populate a List.
FollowersView.swift
import SwiftUI
struct Follower: Codable, Hashable {
public var login: String
public var avatar_url: String
}
struct Response: Codable {
var followers: [Follower] = [Follower]()
}
struct FollowersView: View {
#EnvironmentObject var followerInfo: FollowerInfo
#State var followers: [Follower] = [Follower]()
init() {
UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: UIColor.white]
}
var body: some View {
NavigationView {
ZStack {
Color.black.edgesIgnoringSafeArea(.all)
List(followers, id: \.self) { follower in
HStack(spacing: 10) {
Image(decorative: "\(follower.avatar_url).png")
.resizable()
.frame(width: 75, height: 75)
HStack(spacing: 5) {
Image(systemName: "person")
Text("\(follower.login)").fontWeight(.heavy)
}
}
}
.onAppear(perform: loadData)
}
.navigationBarTitle("\(followerInfo.followerUsername)")
}
}
func loadData() {
guard let url = URL(string: "https://api.github.com/users/\(followerInfo.followerUsername)/followers") else {
print("Invalid URL")
return
}
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { data, response, error in
if let data = data {
if let decodedResponse = try? JSONDecoder().decode(Response.self, from: data) {
DispatchQueue.main.async {
self.followers = decodedResponse.followers
}
return
}
}
print("Fetch failed: \(error?.localizedDescription ?? "Unknown Error")")
}.resume()
}
}
struct FollowersView_Previews: PreviewProvider {
static var previews: some View {
FollowersView()
}
}
The code gets to print("Fetch failed: \(error?.localizedDescription ?? "Unknown Error")") and prints "Unknown Error" before crashing. Upon inspecting the data that comes back, it comes up with nothing, at least not that I can tell anyway. (I'm not entirely accustomed to the Xcode debugger). I double checked the API response in a browser and the fields in the response in the browser and the names of the properties in the Codable struct match. So, I'm not exactly sure what's going on here. Any suggestions on how I can fix this issue?
If I understood your problem correctly. Update your Codable as shown below:
import Foundation
// MARK: - FollowerElement
struct FollowerElement: Codable {
let login: String
let id: Int
let nodeID: String
let avatarURL: String
let gravatarID: String
let url, htmlURL, followersURL: String
let followingURL, gistsURL, starredURL: String
let subscriptionsURL, organizationsURL, reposURL: String
let eventsURL: String
let receivedEventsURL: String
let type: String
let siteAdmin: Bool
enum CodingKeys: String, CodingKey {
case login, id
case nodeID = "node_id"
case avatarURL = "avatar_url"
case gravatarID = "gravatar_id"
case url
case htmlURL = "html_url"
case followersURL = "followers_url"
case followingURL = "following_url"
case gistsURL = "gists_url"
case starredURL = "starred_url"
case subscriptionsURL = "subscriptions_url"
case organizationsURL = "organizations_url"
case reposURL = "repos_url"
case eventsURL = "events_url"
case receivedEventsURL = "received_events_url"
case type
case siteAdmin = "site_admin"
}
}
typealias Follower = [FollowerElement]
You can remove properties you don't need from above code.
Now update your dataTask method
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Fetch failed: \(error.localizedDescription)")
} else {
if let data = data {
do {
let follower = try JSONDecoder().decode(Follower.self, from: data)
DispatchQueue.main.async {
print(follower.count)
}
} catch {
print(error)
}
}
}
}.resume()

Empty List from JSON in SwiftUI

I'm having some trouble fetching data from the PiHole API;
This is the JSON format (from the url http://pi.hole/admin/api.php?summary):
{
"domains_being_blocked": "1,089,374",
"dns_queries_today": "34,769",
"ads_blocked_today": "11,258",
"ads_percentage_today": "32.4",
"unique_domains": "9,407",
"queries_forwarded": "17,972",
"queries_cached": "5,539",
"clients_ever_seen": "35",
"unique_clients": "23",
"dns_queries_all_types": "34,769",
"reply_NODATA": "1,252",
"reply_NXDOMAIN": "625",
"reply_CNAME": "10,907",
"reply_IP": "21,004",
"privacy_level": "0",
"status": "enabled",
"gravity_last_updated": {
"file_exists": true,
"absolute": 1588474361,
"relative": {
"days": "0",
"hours": "14",
"minutes": "18"
}
}
}
This is my code:
ContentView.swift
import SwiftUI
struct NetworkController {
static func fetchData(completion: #escaping (([PiHole.Stat]) -> Void)) {
if let url = URL(string: "http://pi.hole/admin/api.php?summary") {
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let data = data {
let stat = try? JSONDecoder().decode(PiHole.self, from: data)
completion(stat?.stats ?? [])
}
}.resume()
}
}
}
class ContentViewModel: ObservableObject {
#Published var messages: [PiHole.Stat] = []
func fetchData() {
NetworkController.fetchData { messages in
DispatchQueue.main.async {
self.messages = messages
}
}
}
}
struct ContentView: View {
#ObservedObject var viewModel = ContentViewModel()
var body: some View {
List {
ForEach(viewModel.messages, id: \.self) { stat in
Text(stat.domains_being_blocked)
}
}.onAppear{
self.viewModel.fetchData()
}
}
}
Data.swift
struct PiHole: Decodable {
var stats: [Stat]
struct Stat: Decodable, Hashable {
var domains_being_blocked: String
var ads_percentage_today: String
var ads_blocked_today: String
var dns_queries_today: String
}
}
Everything seems okay, no errors, yet when I run it, the simulator only shows an empty list
In Playground I can retrieve those data just fine:
import SwiftUI
struct PiHoleTest: Codable {
let domains_being_blocked: String
let ads_blocked_today: String
}
let data = try! Data.init(contentsOf: URL.init(string: "http://pi.hole/admin/api.php?summary")!)
do {
let decoder: JSONDecoder = JSONDecoder.init()
let user: PiHoleTest = try decoder.decode(PiHoleTest.self, from: data)
print("In Blocklist \(user.domains_being_blocked)")
print("Blocked Today: \(user.ads_blocked_today) ")
} catch let e {
print(e)
}
The Output:
In Blocklist 1,089,374
Blocked Today: 11,258
What am I doing wrong? Or better, is there another way to fetch these stats?
Thanks in Advance!
The issue was related to the structure. Your JSON decoded were not an array. So PiHole struct was unnecessary. I can tested and this code is working now.
import SwiftUI
struct NetworkController {
static func fetchData(completion: #escaping ((Stat) -> Void)) {
if let url = URL(string: "http://pi.hole/admin/api.php?summary") {
let request = URLRequest(url: url)
URLSession.shared.dataTask(with: request) { (data, response, error) in
do {
if let data = data {
let stat = try JSONDecoder().decode(Stat.self, from: data)
DispatchQueue.main.async() {
completion(stat)
}
return
} else {
print("Error Found")
}
} catch let error as NSError {
print(error.localizedDescription)
}
}.resume()
}
}
}
class ContentViewModel: ObservableObject {
#Published var stat: Stat? = nil
func fetchData() {
NetworkController.fetchData { stat in
self.stat = stat
}
}
}
struct TestView: View {
#ObservedObject var viewModel = ContentViewModel()
var body: some View {
List {
Text(viewModel.stat?.domains_being_blocked ?? "No Data")
Text(viewModel.stat?.ads_blocked_today ?? "No Data")
Text(viewModel.stat?.ads_percentage_today ?? "No Data")
Text(viewModel.stat?.dns_queries_today ?? "No Data")
}.onAppear{
self.viewModel.fetchData()
}
}
}
struct Stat: Decodable, Hashable {
var domains_being_blocked: String
var ads_percentage_today: String
var ads_blocked_today: String
var dns_queries_today: String
}