I'm working on my first app, which loads a JSON with the elements that need to be displayed.
At the moment everything seems to work, except a few details which I hope you can help me solve:
My interface is composed of a TabView contained within a NavigationView (which contains a button that brings up a SheetView) ... if I go to the Tab that brings up the list with the contents downloaded from the JSON everything works, but if I move to another Tab and then I go back to the one that displays the JSON, the data is no longer visible.
I obviously inserted in the code (where necessary) ObservableObject, ObservedObject and onAppear that should reload the JSON, but without success ... it is clear that something is missing ...
How could I go about displaying a list of categories starting from a JSON field? Let me explain, I used the "autore" field of the JSON to define categories, so I would need to first display a list of these categories, then, entering the respective categories, I have to display a list with the elements relating to the selected category and then obviously the DetailView for the single elements.
Of course, since is my first time writing here, ask me everything you need to help me solving the problem...
This is the relevant code:
Code: BussoModel.swift
import SwiftUI
class BussoModel: Identifiable {
var id: UUID = UUID()
var titolo: String = ""
var autore: String = ""
var testo: String = ""
var data: String = ""
var extra1: String = ""
var extra2: String = ""
var fotoUrl: String = ""
}
Code: DownloadManager.swift
import SwiftUI
import Combine
import Alamofire
class DownloadManager: ObservableObject {
let objectWillChange = PassthroughSubject<Void, Never>()
var storage : [BussoModel] = []
func scaricaJSON() {
AF.request("http://geniuspointfrezza.altervista.org/index.php?json=1").responseJSON { response in
if let errore = response.error {
debugPrint(errore.localizedDescription)
return
}
guard let ilJson = response.value else { return }
guard let json = JSON(ilJson).array else { return }
self.storage = []
let totale = json.count
for i in 0..<totale {
let busso = BussoModel()
if let titolo = json[i]["titolo"].string {
busso.titolo = titolo
}
if let autore = json[i]["autore"].string {
busso.autore = autore
}
if let testo = json[i]["testo"].string {
busso.testo = testo
}
if let data = json[i]["data"].string {
busso.data = data
}
if let extra1 = json[i]["extra1"].string {
busso.extra1 = extra1
}
if let extra2 = json[i]["extra2"].string {
busso.extra2 = extra2
}
if let foto = json[i]["fotoUrl"].string {
busso.fotoUrl = foto
}
self.storage.append(busso)
}
self.objectWillChange.send()
}
}
}
Code: CategoryView.swift
import SwiftUI
import URLImage
struct CategoryView: View {
#ObservedObject var dm: DownloadManager
var body: some View {
List {
ForEach(dm.storage) { busso in
NavigationLink(
destination: DetailView(busso: busso)) {
HStack {
URLImage(URL(string: busso.fotoUrl) ?? furl)
.resizable()
.aspectRatio(contentMode: .fit)
.clipped()
.padding()
VStack(alignment: .leading) {
Text(busso.titolo)
.font(.headline)
Text(busso.autore)
.font(.subheadline)
}
Spacer().layoutPriority(-0.1)
}
.frame(minWidth: 0, maxWidth: .infinity)
.frame(height: 50)
}
}
}
.onAppear {
self.dm.scaricaJSON()
}
}
}
struct CategoryView_Previews: PreviewProvider {
static var previews: some View {
CategoryView(dm: DownloadManager())
}
}
Code: MainTabView.swift
import SwiftUI
struct MainTabView: View {
#State private var selected = 0
var body: some View {
TabView(selection: $selected) {
HomeView()
.tabItem {
Image(systemName: (selected == 0 ? "house.fill" : "house"))
Text("Home")
}.tag(0)
CategoryView(dm: DownloadManager())
.tabItem {
Image(systemName: (selected == 1 ? "text.justify" : "text.justify"))
Text("Categorie")
}.tag(1)
Text("Galleria?")
.tabItem {
Image(systemName: (selected == 2 ? "photo.fill" : "photo"))
Text("Galleria")
}.tag(2)
Text("Preferiti?")
.tabItem {
Image(systemName: (selected == 3 ? "star.fill" : "star"))
Text("Preferiti")
}.tag(3)
}
.accentColor(.white)
.onAppear() {
UINavigationBar.appearance().barTintColor = UIColor(red: 115.0/255.0, green: 90.0/255.0, blue: 143.0/255.0, alpha: 1.0)
UINavigationBar.appearance().titleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
UITabBar.appearance().barTintColor = UIColor(red: 115.0/255.0, green: 90.0/255.0, blue: 143.0/255.0, alpha: 1.0)
}
}
}
struct MainTabView_Previews: PreviewProvider {
static var previews: some View {
MainTabView()
}
}
Code: ContentView.swift
import SwiftUI
import URLImage
let furl = URL(fileURLWithPath: "path")
struct ContentView: View {
#State var showInfoView = false
var body: some View {
NavigationView {
MainTabView()
.sheet(isPresented: $showInfoView) {
InfoView(showInfoView: $showInfoView)
}
.navigationBarTitle((Text("ViviBusso")), displayMode: .inline)
.navigationBarItems(leading:
Button(action: {
debugPrint("QR Code")
}) {
Image(systemName: "qrcode")
}
.foregroundColor(.white), trailing: Button(action: {
self.showInfoView.toggle()
}) {
Image(systemName: "info.circle")
}
.foregroundColor(.white)
)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Waiting for suggestions, thanks!
EDIT: New code tested:
import SwiftUI
import URLImage
struct Busso: Codable, Identifiable {
public var id: UUID
public var titolo: String
public var autore: String
public var testo: String
public var data: String
public var extra1: String
public var extra2: String
public var foto: String
public var fotoUrl: String
}
class FetchBusso: ObservableObject {
#Published var Bussos = [Busso]()
init() {
let url = URL(string: "https://geniuspointfrezza.altervista.org/index.php?json=1")!
URLSession.shared.dataTask(with: url) {(data, response, error) in
do {
if let bussoData = data {
let decodedData = try JSONDecoder().decode([Busso].self, from: bussoData)
DispatchQueue.main.async {
self.Bussos = decodedData
}
} else {
print("No data")
}
} catch {
print("Error")
}
}.resume()
}
}
struct CategoryView: View {
#ObservedObject var fetch = FetchBusso()
var body: some View {
VStack {
List(fetch.Bussos) { todo in
VStack(alignment: .leading) {
Text(todo.titolo)
}
}
}
}
}
struct CategoryView_Previews: PreviewProvider {
static var previews: some View {
CategoryView()
}
}
Related
I'm a beginner with SwiftUI and I wanted to create a dynamic list with the birthdays of people. I also wanted to integrate a filter that facilitates to find birthdays. But in the lines:
if enableFilter == true {
return json.filter {$0.BirthdayString.contains(filter(date: filterDate))}
} else {
return json
}
I always get these errors:
Instance member 'enableFilter' cannot be used on type 'ContentView';
did you mean to use a value of this type instead?
and
Instance member 'filterDate' cannot be used on type 'ContentView'; did
you mean to use a value of this type instead?
I think I understand why the errors are present but I don't no how to fix it. I tried:
#State static var
but then I cannot change the values with my
filterView
Thank you for your help, here is the full source code:
import SwiftUI
struct person: Codable, Hashable, Identifiable {
var id: Int
var Birthday: Date
var BirthdayString: String
}
func filter(date: Date) -> String {
let DateComponents = Calendar.current.dateComponents([.year, .month, .day], from: date)
let DateComponentsString: String = "\(DateComponents.day)/\(DateComponents.month)/\(DateComponents.year)"
return DateComponentsString
}
struct ContentView: View {
#State var people: [person] = {
guard let data = UserDefaults.standard.data(forKey: "people") else { return [] }
if let json = try? JSONDecoder().decode([person].self, from: data) {
if enableFilter == true {
return json.filter {$0.BirthdayString.contains(filter(date: filterDate))}
} else {
return json
}
}
return []
}()
#State var filterDate: Date = Date()
#State var enableFilter: Bool = false
#State var showFilter: Bool = false
#State var newPersonDate: Date = Date()
var body: some View {
NavigationView {
VStack {
HStack {
DatePicker(selection: $newPersonDate, label: {Text("Birthday")}).padding()
Button(action: {didTapAddTask()}, label: {Text("Add")}).padding()
}
List {
ForEach($people) { $person in
Text("\(person.Birthday)")
}
}
}
.navigationTitle(Text("People's birthday"))
}
}
var filterView: some View {
VStack {
DatePicker(selection: $filterDate, label: {Text("Date")}).padding()
Toggle(isOn: $enableFilter, label: {Text("enable filter")}).padding()
}
}
func didTapAddTask() {
let id = people.reduce(0) { max($0, $1.id) } + 1
people.insert(person(id: id, Birthday: newPersonDate, BirthdayString: filter(date: newPersonDate)), at: 0)
newPersonDate = Date()
save()
}
func save() {
guard let data = try? JSONEncoder().encode(people) else { return }
UserDefaults.standard.set(data, forKey: "people")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
You can use another Computed property which will get the data based on some another property's value. i.e enableFilter.
#State var people: [person] = {
guard let data = UserDefaults.standard.data(forKey: "people") else { return [] }
if let json = try? JSONDecoder().decode([person].self, from: data) {
return json
}
return []
}()
var data : [person] {
if enableFilter {
return people.filter {$0.BirthdayString.contains(filter(date: filterDate))}
} else {
return people
}
}
And use this property to get the relevant data:
var body: some View {
NavigationView {
VStack {
HStack {
DatePicker(selection: $newPersonDate, label: {Text("Birthday")}).padding()
Button(action: {didTapAddTask()}, label: {Text("Add")}).padding()
}
List {
ForEach(data) { person in // <<--- Here `data`
Text("\(person.Birthday)")
}
}
}
.navigationTitle(Text("People's birthday"))
}
}
i am making this app that uses jikan API to show a list of animes, so in the url there is an option to change stuff like the type - anime, manga, etc and the subtype - upcoming, tv, movie, etc, the API is working working fine and is fetching details but now I want to show two pickers in a form view preferably to allow the user to select type and subtype thus I have used #State properties for the picker but it's not updating the list when I run the app and select a different value from the picker
here is the code -
import SwiftUI
struct Response: Codable{
var top: [Result]
}
struct Result: Codable {
var mal_id: Int
var rank: Int
var title: String
var type: String
var start_date: String?
var image_url: String
}
struct ContentView: View {
#State private var str2 = ""
#State private var str3 = ""
func loadData() {
str3 = String("/\(subtype[subTypeSelection])")
str2 = String("\(type[typeSelection])/")
let str1 = "https://api.jikan.moe/v3/top/"
guard let url = URL(string: str1 + str2 + "\(1)" + str3) 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) {
// we have good data – go back to the main thread
DispatchQueue.main.async {
// update our UI
self.top = decodedResponse.top
}
// everything is good, so we can exit
return
}
}
// if we're still here it means there was a problem
print("Fetch failed: \(error?.localizedDescription ?? "Unknown error")")
}.resume()
}
var type = ["anime", "manga", "people", "characters"]
var subtype = ["airing", "upcoming", "tv", "movie", "ova", "special"]
#State private var page = 1
#State private var typeSelection = 0
#State private var subTypeSelection = 2
#State private var top = [Result]()
var body: some View {
// ScrollView {
VStack {
Picker("Your Selection", selection: $subTypeSelection) {
ForEach(0 ..< 6) {
somehting in
Text("\(subtype[somehting])")
}
}
Picker("Your Selection", selection: $typeSelection) {
ForEach(0 ..< 4) {
somehting in
Text("\(type[somehting])")
}
}
}
// .onAppear(perform: {
// loadData()
// })
List(top, id: \.mal_id) { item in
HStack {
AsyncImage(url: URL(string: item.image_url)!,
placeholder: { Text("Loading ...") },
image: { Image(uiImage: $0).resizable() })
.aspectRatio(contentMode: .fit)
.frame(width: 100, height: 100)
// .frame(idealHeight: UIScreen.main.bounds.width / 10 * 10 )
.clipShape(Capsule())
VStack(alignment: .leading) {
Text(item.title)
.font(.headline)
Text(String("\(item.rank)"))
.font(.headline)
Text(item.type)
.font(.headline)
Text(item.start_date ?? "")
.font(.headline)
}
}
}
// }
.onAppear(perform: loadData)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
you could try to use "onChange" on each picker:
VStack {
Picker("Your subtype Selection", selection: $subTypeSelection) {
ForEach(0 ..< 6) { somehting in
Text("\(subtype[somehting])").tag(somehting)
}
}.onChange(of: subTypeSelection) { value in
loadData()
}
Picker("Your type Selection", selection: $typeSelection) {
ForEach(0 ..< 4) { somehting in
Text("\(type[somehting])").tag(somehting)
}
}.onChange(of: typeSelection) { value in
loadData()
}
}
It's not very efficient, but I'm sure you will find a better way to re-load the data.
I have not gotten to 50 reputation so i could not comment on this question SwiftUI Issue displaying specific number of Json Data Items to ask how it was done. The idea is to have the first 10 items in a json array show when the view is loaded and then a load more button to show more items.
This is how my code looks like.
Group {
HStack {
Text("Recommended Events")
.font(.title3)
.foregroundColor(.white)
.fontWeight(.bold)
Spacer()
Button(action: {
}) {
Text("Show all")
.font(.title3)
.foregroundColor(Color.white)
.fontWeight(.bold)
}
}
.padding(15)
ForEach(recommendeds) { recommended in
NavigationLink(destination: RecommendedEventsDetailView(recommended: recommended)) {
RecommendedEventsView(recommended: recommended)
}
}
}
Edited
After https://stackoverflow.com/users/14733292/raja-kishan response i tried it and got this error Failed to produce diagnostic for expression; please file a bug report This is the stage of my code now.
struct RecommendedModel: Identifiable {
var id = UUID()
var number: Int
init(_ number: Int) {
self.number = number
}
}
struct PlacesView: View {
private var arrData: [RecommendedModel] = (0...10).map({RecommendedModel($0)})
#State private var isMore: Bool = false
//E-MARK: - Body
var body: some View {
Group {
HStack {
Text("Recommended Events")
.font(.title3)
.foregroundColor(.white)
.fontWeight(.bold)
Spacer()
Button(action: {
withAnimation {
isMore.toggle()
}
}) {
Text("Show all")
.font(.title3)
.foregroundColor(Color.white)
.fontWeight(.bold)
}
}
.padding(15)
ForEach( (isMore ? arrData : recommendeds(arrData.prefix(5)))) { recommended in
NavigationLink(destination: RecommendedEventsDetailView(recommended: \(recommended.number)) {
RecommendedEventsView(recommended: \(recommended.number))
}
}
}
}
}
Below is the Data model i had before that loads the json data saved as RecommendedModel.swift
struct Recommended: Codable, Identifiable {
let id: String
let image: String
let date: String
let month: String
let like: String
let rating: String
let heading: String
let place: String
let article: String
let more: String
}
You can do this.
You can load the first 10 or 50 by .prefix() from the array.
Demo code
struct DataModel: Identifiable {
var id = UUID()
var number: Int
init(_ number: Int) {
self.number = number
}
}
struct LoadMoreDemo: View {
private var arrData: [DataModel] = (0...100).map({DataModel($0)})
#State private var isMore: Bool = false
var body: some View {
VStack {
ScrollView {
ForEach( (isMore ? arrData : Array(arrData.prefix(15)))) { item in
Text("\(item.number)")
}
}
Button("Load More") {
withAnimation {
isMore.toggle()
}
}
}
}
}
I'm stuck on this.
I have a json that I'm parsing and that Json has an entry value and I want to do a swift list based in user input that will trigger from the external json different responses.
I have the following code so far
import SwiftUI
import Combine
import Foundation
var lokas : String = "ABCY"
struct ContentView: View {
#State var name: String = ""
#ObservedObject var fetcher = Fetcher()
var body: some View {
VStack {
TextField("Enter Loka id", text: $name)
List(fetcher.allLokas) { vb in
VStack (alignment: .leading) {
Text(movie.device)
Text(String(vb.seqNumber))
.font(.system(size: 11))
.foregroundColor(Color.gray)
}
}
}
}
}
public class Fetcher: ObservableObject {
#Published var allLokas = [AllLoka_Data]()
init(){
load(devices:lokas)
}
func load(devices:String) {
let url = URL(string: "https://xxx.php?device=\(devices)&hours=6")!
URLSession.shared.dataTask(with: url) {(data,response,error) in
do {
if let d = data {
let decodedLists = try JSONDecoder().decode([AllLoka_Data].self, from: d)
DispatchQueue.main.async {
self.allLokas = decodedLists
}
}else {
print("No Data")
}
} catch {
print ("Error")
}
}.resume()
}
}
struct AllLoka_Data: Codable {
var date: String
var time: String
var unix_time: Int
var seqNumber : Int
}
// Now conform to Identifiable
extension AllLoka_Data: Identifiable {
var id: Int { return unix_time }
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
But I just added a Global variable to test it, but how do I pass the variable name to make the function work ?
Thank you
I make a json call to get some data from as seen from the image, but I have to sort these items by stargazers_count, ie the items with the largest stargazers_count put before.
Can you give me a hand?
Code:
import SwiftUI
import AppKit
struct Obj: Codable, Identifiable {
public var id: Int
public var name: String
public var language: String?
public var description: String?
public var stargazers_count: Int
public var forks_count: Int
}
class Fetch: ObservableObject {
#Published var results = [Obj]()
init(name: String) {
let url = URL(string: "https://api.github.com/users/"+name+"/repos?per_page=1000")!
URLSession.shared.dataTask(with: url) { data, response, error in
do {
if let data = data {
let results = try JSONDecoder().decode([Obj].self, from: data)
DispatchQueue.main.async {
self.results = results
}
print("Ok.")
} else {
print("No data.")
}
} catch {
print("Error:", error)
}
}.resume()
}
}
struct ContentViewBar: View {
#ObservedObject var fetch = Fetch(name: "github")
var body: some View {
VStack(alignment: .leading, spacing: 0) {
List(fetch.results) { el in
VStack(alignment: .leading, spacing: 0) {
Text("\(el.name) (\(el.stargazers_count)/\(el.forks_count)) \(el.language ?? "")").padding(EdgeInsets(top: 5, leading: 0, bottom: 0, trailing: 0))
if el.description != nil {
Text("\(el.description ?? "")")
.font(.system(size: 11))
.foregroundColor(Color.gray)
}
}
}.listStyle(SidebarListStyle())
// Button(action: { NSApplication.shared.terminate(self) }){
// Text("X")
// .font(.caption)
// .fontWeight(.semibold)
// }
// .padding(EdgeInsets(top: 2, leading: 0, bottom: 2, trailing: 2))
// .frame(width: 360.0, alignment: .trailing)
}
.padding(0)
.frame(width: 360.0, height: 360.0, alignment: .top)
}
}
struct ContentViewBar_Previews: PreviewProvider {
static var previews: some View {
ContentViewBar()
}
}
In the end, he seems to have succeeded.
Code:
List(fetch.results.sorted { $0.stargazers_count > $1.stargazers_count }) { el in
Sorting in UI drawing cycle might result in performance issue (especially in case of huge container), so it is better to perform sort either in-advance, or out-of-UI flow.
So I'd recommend to do it here
let results = try JSONDecoder().decode([Obj].self, from: data)
let sorted = results.sorted { $0.stargazers_count > $1.stargazers_count }
DispatchQueue.main.async {
self.results = sorted
}