Swift not recognising new elements from JSON in LazyHStack - json

Hey guys so I have a problem with decoding JSON. I decoded the entire string and made a statement in the ViewModel which fetches only elements as such
#Published var posts = [Post]()
var postsid6: [Post] {
var typeid6posts = [Post]()
for post in self.posts { if post.type_id == "6" { typeid6posts.append(post) }
}
return typeid6posts
}
So if it's type_id="6" it fetches them seperetly and I call the function like this
LazyHStack(alignment: .center){
ForEach(viewModel.postsid6) { post in
NavigationLink(destination: StoryDetailsView(post)) {
SingleStoryView(post: post, dateFormatter: viewModel.dateFormatter)
.padding(.bottom, 16)
}
}
}
Problem is for some reason swift fetches only 2 of them and there are 7 in the JSON with type_id="6"
This is the entire ViewModel. Posts are being updated but the typeid6posts isn't adding new ones
//
// PostsViewModel.swift
// EMDC App (iOS)
//
// Created by admin on 19.6.21..
//
import Foundation
class PostsViewModel: ObservableObject{
lazy var dateFormatter: DateFormatter = {
let df = DateFormatter()
df.doesRelativeDateFormatting = true
df.timeStyle = .short
df.dateStyle = .short
return df
}()
struct PostsResponse: Codable{
let items: [Post]
let users: [String : UserModel]
}
#Published var posts = [Post]()
var postsid6: [Post] {
var typeid6posts = [Post]()
for post in self.posts { if post.type_id == "6" { typeid6posts.append(post) }
}
return typeid6posts
}
var videibgt: [Post] {
var videiisat = [Post]()
for post in self.posts { if (post.videourl != nil) == true { videiisat.append(post) } }
return videiisat
}
private let jsonDecoder = JSONDecoder()
func fetchPosts(){
let url = URL(string: "https://cdn.rapttor.com/influencer/data/2/posts.json")!
URLSession.shared.dataTask(with: url) { data, response, error in
guard error == nil else {
return
}
guard let data = data else {
return
}
do{
let decoded = try self.jsonDecoder.decode(PostsResponse.self, from: data)
DispatchQueue.main.async {
let posts = decoded.items
var postsWithUser = [Post]()
for var post in posts{
post.user = decoded.users[post.userId]!
postsWithUser.append(post)
}
self.posts.append(contentsOf: postsWithUser)
print(self.posts.count)
}
}
catch{
print(error)
}
}.resume()
}
func duplicate(){
posts.append(contentsOf: posts)
}
}

Related

Swift - Creating a stack (Text view) from a function that returns an array

I am trying to create a list of text objects out of a function that returns an array of params. Everything seems to be working fine, getting the data, console shows the correct results, except the list itself which remains empty.
The function call:
import UIKit
import SwiftUI
struct SubdomainsList: View {
#State var SubDomains = VTData().funky(XDOMAIN: "giphy.com")
var body: some View {
VStack {
List{
Text("Subdomains")
ForEach(SubDomains, id: \.self) { SuDo in
Text(SuDo)
}
}
}
}
}
struct SubdomainsList_Previews: PreviewProvider {
static var previews: some View {
SubdomainsList()
}
}
The Json handlers:
struct VTResponse: Decodable {
let data: [VT]
}
struct VT: Decodable {
var id: String
}
The class:
class VTData {
func funky (XDOMAIN: String) -> Array<String>{
var arr = [""]
getDATA(XDOMAIN: "\(XDOMAIN)", userCompletionHandler: { (SubDomain) in
print(SubDomain)
arr.append(SubDomain)
return SubDomain
})
return arr
}
func getDATA(XDOMAIN: String, userCompletionHandler: #escaping ((String) -> String)) {
let token = "<TOKEN>"
guard let url = URL(string: "https://www.lalalla.com/subdomains") else {fatalError("Invalid URL")}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("\(token)", forHTTPHeaderField: "x-apikey")
let task = URLSession.shared.dataTask(with: request, completionHandler: { data, response, error in
guard let data = data else { return }
let decoder = JSONDecoder()
let result = try? decoder.decode(VTResponse.self, from: data)
if let result = result {
for SubDo in result.data {
let SubDomain = SubDo.id
userCompletionHandler(SubDomain)
}
}
else {
fatalError("Could not decode")
}
})
task.resume()
}
}
I'm getting no errors whatsoever, and the console output shows the correct results:
support.giphy.com
cookies.giphy.com
media3.giphy.com
i.giphy.com
api.giphy.com
developers.giphy.com
media.giphy.com
x-qa.giphy.com
media2.giphy.com
media0.giphy.com
It is also worth mentioning that when I add print(type(of: SubDomain)) to the code I'm getting a String rather than an array.
The preview:
preview
Any ideas?
try this approach, again, to extract the list of subdomain from your API, and display them in
a List using the asynchronous function getDATA(...):
class VTData {
// `func funky` is completely useless, remove it
func getDATA(XDOMAIN: String, completion: #escaping ([String]) -> Void) { // <-- here
let token = "<TOKEN>"
guard let url = URL(string: "https://www.virustotal.com/api/v3/domains/\(XDOMAIN)/subdomains") else {
print("Invalid URL")
return
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("\(token)", forHTTPHeaderField: "x-apikey")
URLSession.shared.dataTask(with: request) { data, response, error in
guard let data = data else { return } // todo return some error msg
do {
let results = try JSONDecoder().decode(VTResponse.self, from: data)
return completion(results.data.map{ $0.id }) // <-- here
} catch {
print(error) // <-- here important
}
}.resume()
}
}
struct SubdomainsList: View {
#State var subDomains: [String] = [] // <--- here
var body: some View {
VStack {
List{
Text("Subdomains")
ForEach(subDomains, id: \.self) { SuDo in
Text(SuDo)
}
}
}
.onAppear {
// async function
VTData().getDATA(XDOMAIN: "giphy.com") { subs in // <--- here
subDomains = subs
}
}
}
}

How to fetch JSON data in background Swiftui

I have a JSON API I want my app to download in background.
My Quotes app send out notifications with timeintervals and I want the quotes to be in the notification.
struct Quotetype: Codable {
let text: String?
let author: String?
}
class ViewModel: ObservableObject {
#Published var quotes: [Quotetype]?
#Published var isLoading: Bool = true
var quoteText: String = ""
var quoteAuthor: String = ""
init() {
fetchQuotes()
setUpNotificationPermission()
setUpNotificationTriggers()
}
func fetchQuotes() {
guard let url = URL(string: "https://type.fit/api/quotes") else { return }
URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
if error == nil {
if let data = data {
do {
let jsonContent = try JSONDecoder().decode([Quotetype].self, from: data)
DispatchQueue.main.async { [self] in
self?.quotes = jsonContent
if self?.quotes?.count != nil {
let randomNumber = Int.random(in: 0..<self!.quotes!.count )
self?.isLoading = false
self?.quoteText = self!.quotes?[randomNumber].text ?? ""
self?.quoteAuthor = self!.quotes?[randomNumber].author ?? ""
}
}
} catch {
print("\(error)")
}
} else {
print("Data nil")
}
} else {
print("err \(String(describing: error))")
}
}.resume()
}
func setUpNotificationTriggers() {
let content = UNMutableNotificationContent()
content.title = "Quote of the Day"
content.subtitle = quoteText
content.sound = UNNotificationSound.default
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 3, repeats: false)
let request = UNNotificationRequest(identifier: UUID().uuidString, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request)
}
this is my fetch & Notification. How can I get the quoteText in background so notification can go through?
please provide with examples.

Why is my API call not completely reached?

I am currently building a public transportation app in SwiftUI Xcode 12 beta, where you input origin, destination, time of arrival, and date. The idea is to then convert the origin and destination input (private func FetchOriginCoordinates and private func FetchDestCoordinates) into coordinates using an API. This API will then give that data to another API that plans the route.
The problem is that the route planner API doesn't give any output. I have tried printing self.Trips.append(...) but the list is completely empty. I believe this is because the code doesn't reach that part of the code. I have put some print statements in the code in order to understand where the error is occurring. When I call the API my output in the console is this:
Test 2021-04-16 22:33:19.271246+0200 OnTrack2.0[6592:428359] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed Optional(http://api.sl.se/api2/TravelplannerV3_1/trip.json?key=40892db48b394d3a86b2439f9f3800fd&originExtId=300101115&destExtId=300101426&Date=2021-04-18&Time=09:20&searchForArrival=1) [] 10767 bytes Level 1 Level 1.5
This means that the code only reaches the print("Level 1.5") statement. But never the following print statements such as print("Level 2") or print(self.trips). Why is that and how do I solve it?
import Foundation
struct NominationStructure: Decodable {
var lat: String
var lon: String
enum CodingKeys: String, CodingKey {
case lat = "lat"
case lon = "lon"
}
}
struct NearbyStopsStructure: Decodable {
var stopLocationOrCoordLocation: [stopLocationOrCoordLocationStructure]
}
struct stopLocationOrCoordLocationStructure: Decodable {
var StopLocation: StopLocationStructure
}
struct StopLocationStructure: Decodable {
var mainMastExtId: String
}
struct JSONStructure: Decodable {
var Trip: [TripStructure]
}
struct TripStructure: Decodable {
var LegList: LegListStructure
}
struct LegListStructure: Decodable {
var Leg: [LegStructure]
}
struct LegStructure: Decodable {
var Origin: StationStructure
var Destination: StationStructure
var Product: ProductStructure
var name: String
var type: String
var dist: String
}
struct StationStructure: Decodable {
var time: String
var name: String
var date: String
}
struct ProductStructure: Decodable {
var catIn: String
}
struct LocationInfo {
var iD = String()
var input = String()
var lat = String()
var lon = String()
var name = String()
var time = String()
var date = String()
var vehicleType = String()
var transportType = String()
var dist = String()
var legName = String()
}
//One way is to call your functions when you know a step is completed
enum TripFetchStatus: String {
case start
case fetchedOriginCoordinates
case fetchedOriginId
case fetchedDestinationCoordinates
case fetchedDestinationId
case fetchingTrip
case done
case stopped
}
class PlanTripViewModel: ObservableObject {
//Apple frowns upon frezzing a screen so having a way to update the user on what is going on
//When a step is complete have it do something else
#Published var fetchStatus: TripFetchStatus = .stopped{
didSet{
switch fetchStatus {
case .start:
print("Test")
FetchOriginCoordinates { (cors) in
self.origin.lat = cors[0].lat
self.origin.lon = cors[0].lon
self.fetchStatus = .fetchedOriginCoordinates
}
case .fetchedOriginCoordinates:
self.FetchOriginID { (stops) in
self.origin.iD = stops.stopLocationOrCoordLocation[0].StopLocation.mainMastExtId
self.fetchStatus = .fetchedOriginId
}
case .fetchedOriginId:
FetchDestCoordinates { (cors) in
self.dest.lat = cors[0].lat
self.dest.lon = cors[0].lon
self.fetchStatus = .fetchedDestinationCoordinates
}
case .fetchedDestinationCoordinates:
self.FetchDestID { (stops) in
self.dest.iD = stops.stopLocationOrCoordLocation[0].StopLocation.mainMastExtId
self.fetchStatus = .fetchedDestinationId
}
case .fetchedDestinationId:
//Once you have everthing in place then go to the next API
FetchTrip()
case .fetchingTrip:
print("almost done")
case .done:
print("any other code you need to do")
case .stopped:
print("just a filler")
}
}
}
#Published var trip: LocationInfo = LocationInfo()
#Published var dest: LocationInfo = LocationInfo()
#Published var origin: LocationInfo = LocationInfo()
#Published var arrivalTime = String()
#Published var travelDate = String()
#Published var tripIndex = Int()
#Published var Trips: [Dictionary<String, String>] = []
public func FetchTrip() {
Trips.removeAll()
let tripKey = "40892db48b394d3a86b2439f9f3800fd"
let tripUrl = URL(string: "http://api.sl.se/api2/TravelplannerV3_1/trip.json?key=\(tripKey)&originExtId=\(self.origin.iD)&destExtId=\(self.dest.iD)&Date=\(self.travelDate)&Time=\(self.arrivalTime)&searchForArrival=1")
print(tripUrl)
URLSession.shared.dataTask(with: tripUrl!) {data, response, error in
if let data = data {
print(data)
print("Level 1")
do {
print("Level 1.5")
if let decodedJson = try? JSONDecoder().decode(JSONStructure.self, from: data) {
self.tripIndex = decodedJson.Trip.count - 1
print("Level 2")
for i in 0..<decodedJson.Trip[self.tripIndex].LegList.Leg.count {
self.trip.transportType = decodedJson.Trip[self.tripIndex].LegList.Leg[i].type
if self.trip.transportType == "WALK" {
self.origin.name = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.name
self.origin.time = String(decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.time.prefix(5))
self.origin.date = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.date
self.dest.name = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.name
self.dest.time = String(decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.time.prefix(5))
self.dest.date = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.date
self.trip.vehicleType = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Product.catIn
self.trip.dist = decodedJson.Trip[self.tripIndex].LegList.Leg[i].dist
print(self.origin.name)
print(self.dest.name)
self.Trips.append(["Origin": self.origin.name, "Destination": self.dest.name, "OriginTime": self.origin.time, "DestTime": self.dest.time, "OriginDate": self.origin.date, "DestDate": self.dest.date, "TransportType": self.trip.transportType, "VehicleType": self.trip.vehicleType, "Distance": self.trip.dist])
}
else {
self.origin.name = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.name
self.origin.time = String(decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.time.prefix(5))
self.origin.date = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Origin.date
self.dest.name = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.name
self.dest.time = String(decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.time.prefix(5))
self.dest.date = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Destination.date
self.trip.vehicleType = decodedJson.Trip[self.tripIndex].LegList.Leg[i].Product.catIn
self.trip.legName = decodedJson.Trip[self.tripIndex].LegList.Leg[i].name
print(self.origin.name)
print(self.dest.name)
self.Trips.append(["Origin": self.origin.name, "Destination": self.dest.name, "OriginTime": self.origin.time, "DestTime": self.dest.time, "OriginDate": self.origin.date, "DestDate": self.dest.date, "TransportType": self.trip.transportType, "VehicleType": self.trip.vehicleType, "LegName": self.trip.legName])
}
}
}
} catch {
print(error)
}
}
}.resume()
print(self.Trips)
}
//Simple version just to replicate put your code within
private func FetchOriginCoordinates(completion: #escaping ([NominationStructure]) -> ()) {
let scheme = "https"
let host = "nominatim.openstreetmap.org"
let path = "/search"
let queryItemCountry = URLQueryItem(name: "country", value: "Sweden")
let queryItemCity = URLQueryItem(name: "city", value: "Stockholm")
let queryItemStreet = URLQueryItem(name: "street", value: self.origin.input)
let queryItemFormat = URLQueryItem(name: "format", value: "json")
var urlComponents = URLComponents()
urlComponents.scheme = scheme
urlComponents.host = host
urlComponents.path = path
urlComponents.queryItems = [queryItemCountry, queryItemCity, queryItemStreet, queryItemFormat]
URLSession.shared.dataTask(with: urlComponents.url!) {data, response, error in
if let data = data {
do {
if let decodedJson = try? JSONDecoder().decode([NominationStructure].self, from: data) {
DispatchQueue.main.async {
completion (decodedJson)
}
}
} catch {
print(error)
}
}
}.resume()
}
private func FetchDestCoordinates(completion: #escaping ([NominationStructure]) -> ()) {
let scheme = "https"
let host = "nominatim.openstreetmap.org"
let path = "/search"
let queryItemCountry = URLQueryItem(name: "country", value: "Sweden")
let queryItemCity = URLQueryItem(name: "city", value: "Stockholm")
let queryItemStreet = URLQueryItem(name: "street", value: self.dest.input)
let queryItemFormat = URLQueryItem(name: "format", value: "json")
var urlComponents = URLComponents()
urlComponents.scheme = scheme
urlComponents.host = host
urlComponents.path = path
urlComponents.queryItems = [queryItemCountry, queryItemCity, queryItemStreet, queryItemFormat]
URLSession.shared.dataTask(with: urlComponents.url!) {data, response, error in
if let data = data {
do {
if let decodedJson = try? JSONDecoder().decode([NominationStructure].self, from: data) {
DispatchQueue.main.async {
completion (decodedJson)
}
}
} catch {
print(error)
}
}
}.resume()
}
private func FetchOriginID(completion: #escaping (NearbyStopsStructure) -> ()) {
let nearbyStopsKey = "8444f9a2f75f4c27937a7165abd532a0"
let stopIDUrl = URL(string: "http://api.sl.se/api2/nearbystopsv2.json?key=\(nearbyStopsKey)&originCoordLat=\(self.origin.lat)&originCoordLong=\(self.origin.lon)&maxNo=1")
URLSession.shared.dataTask(with: stopIDUrl!) {data, response, error in
if let data = data {
do {
if let decodedJson = try? JSONDecoder().decode(NearbyStopsStructure.self, from: data) {
DispatchQueue.main.async {
completion (decodedJson)
}
}
} catch {
print(error)
}
}
}.resume()
}
private func FetchDestID(completion: #escaping (NearbyStopsStructure) -> ()) {
let nearbyStopsKey = "8444f9a2f75f4c27937a7165abd532a0"
let stopIDUrl = URL(string: "http://api.sl.se/api2/nearbystopsv2.json?key=\(nearbyStopsKey)&originCoordLat=\(self.dest.lat)&originCoordLong=\(self.dest.lon)&maxNo=1")
URLSession.shared.dataTask(with: stopIDUrl!) {data, response, error in
if let data = data {
do {
if let decodedJson = try? JSONDecoder().decode(NearbyStopsStructure.self, from: data) {
DispatchQueue.main.async {
completion (decodedJson)
}
}
} catch {
print(error)
}
}
}.resume()
}
}

Reading single string of json in swift ui

I'm using this API http://shibe.online/api/shibes? although when i load it, all i see is a single link which is what i want to use but there is no variable identifier associated with it.
["https://cdn.shibe.online/shibes/be12be31e2c880b4ac6992b3bb02751211b6ee68.jpg"]
import Foundation
import SwiftUI
import Combine
import SDWebImageSwiftUI
struct ShibeView: View {
#ObservedObject var fetcher = ShibeFetcher()
var body: some View {
AnimatedImage(url: URL(string: self.fetcher.shibe?.url ?? "")).resizable().scaledToFit()
}
}
public class ShibeFetcher: ObservableObject {
#Published var shibe: ShibeRecievers?
init(){
load()
}
func load() {
let url = URL(string: "http://shibe.online/api/shibes?")!
URLSession.shared.dataTask(with: url) {(data,response,error) in
do {
if let d = data {
let webData = try JSONDecoder().decode(ShibeRecievers.self, from: d)
DispatchQueue.main.async {
self.shibe = webData
}
}else {
print("No Data")
}
} catch {
print ("Error here")
}
}.resume()
}
}
struct ShibeRecievers: Codable {
var url: String //Not sure how to store the link from the api
}
The problem i have is storing the link from the api into my app and I can't find any json parsing documentation without variable names.
Your json response is just an array of String. So you can decode it like this:
try! JSONDecoder().decode([String].self, from: d)
Completed version based on your current implementation:
struct ShibeView: View {
#ObservedObject var fetcher = ShibeFetcher()
var body: some View {
Image(uiImage: UIImage(data: try! Data(contentsOf: URL(string: fetcher.shibe?.first ?? "https://i.imgur.com/epMSRQH.png")!)) ?? UIImage())
}
}
public class ShibeFetcher: ObservableObject {
#Published var shibe: [String]?
init(){
load()
}
func load() {
let url = URL(string: "http://shibe.online/api/shibes?")!
URLSession.shared.dataTask(with: url) {(data,response,error) in
do {
if let d = data {
let webData = try JSONDecoder().decode([String].self, from: d)
DispatchQueue.main.async {
self.shibe = webData
}
}else {
print("No Data")
}
} catch {
print ("Error here")
}
}.resume()
}
}

swift+tableview data is not populated when searchbar is active

When I load my table then activate the search, I can get the search data in my tableview. To make my app faster, I create a seperate view only for search. But I couldn't get my tableview populated with search data. I can see I get the data correctly with print(data), but my table is not populated.
class YazarTableViewController: UITableViewController, UISearchResultsUpdating, UISearchBarDelegate {
var searchResults: [JSON]? = []
var searchController = UISearchController()
#IBOutlet weak var searchButton: UIBarButtonItem!
#IBAction func searchButtonTouched(sender: AnyObject) {
showSearchBar()
}
func showSearchBar() {
self.searchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.searchBar.delegate = self
controller.dimsBackgroundDuringPresentation = true
controller.searchBar.alpha = 0
navigationItem.setLeftBarButtonItem(nil, animated: true)
UIView.animateWithDuration(0.5, animations: {
controller.searchBar.alpha = 1
}, completion: { finished in
controller.searchBar.becomeFirstResponder()
})
self.tableView.tableHeaderView = controller.searchBar
return controller
})()
}
}
func updateSearchResultsForSearchController(searchController: UISearchController)
{
self.searchResults?.removeAll(keepCapacity: false)
if (!searchController.searchBar.text!.isEmpty){
let parameters = ["q" : searchController.searchBar.text]
var searchURL = "http://apilink.com/" + String(parameters) + "/1"
searchURL = searchURL.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet())!
Alamofire.request(.GET, searchURL, encoding: .JSON)
.responseJSON{ response in
guard response.result.error == nil else {
// got an error in getting the data, need to handle it
print("error calling GET on /posts/1")
print(response.result.error!)
return
}
if let value: AnyObject = response.result.value {
let post = JSON(value)
if let data = post[].arrayValue as [JSON]?{
self.searchResults = data
self.tableView.reloadData()
print(data)
}
}
}
}
}