ObservedObject, derived from Json, not loading into Picker, but loads in List. In SwiftUI, Xcode 11 - json

I'm new to mobile development, and in the process of learning SwiftUI.
I've been struggling to figure out what's wrong with my picker. I am successfully returning the data from my URLSession, adding it to my model. I can confirm this by adding my #ObservedObject to a List, which returns all of the items. Putting the same #ObservedObject into a picker returns an empty picker for some reason. Any help would be appreciated. Thanks.
Here's my view with the Picker(). When run, the Picker is empty. I can comment out the Picker(), leaving just the ForEach() with Text(), and the text appears.
import Foundation
import Combine
import SwiftUI
struct ContentView: View {
#ObservedObject var countries = CulturesViewModel()
#State private var selectedCountries = 0
var body: some View {
VStack {
//loop through country array and add them to picker
Picker(selection: $selectedCountries, label: Text("Select Your Country")) {
ForEach(0 ..< countries.cultures.count, id: \.self) { post in
Text(self.countries.cultures[post].Culture).tag(post)
}
}.labelsHidden()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Here's my ViewModel. It set's the #Published variable to the results of the JSON request in the WebService(). If I hard-code the #Published variable to the value that's begin returned, the Picker works.
import Foundation
import Combine
import SwiftUI
class CulturesViewModel: ObservableObject {
init() {
fetchCultures()
}
#Published var cultures = [Culture](){
didSet {
didChange.send(self)
}
}
private func fetchCultures(){
WebService().GetCultures {
self.cultures = $0
}
}
let didChange = PassthroughSubject<CulturesViewModel, Never>()
}
Here's my WebService(). Unfortunately, I'm unable to share the JSON url, I've added in the json that's returned.
import Foundation
import SwiftUI
import Combine
class WebService {
func GetCultures(completion: #escaping([Culture]) ->()) {
guard let url = URL("")
[
{
"CultureId": 0,
"Culture": "Select Your Country"
},
{
"CultureId": 1078,
"Culture": "English (United States)"
},
{
"CultureId": 6071,
"Culture": "English (Canada)"
}
]
URLSession.shared.dataTask(with: url) { (data,_,_) in
do {
if let data = data {
let culturesList = try JSONDecoder().decode([Culture].self, from: data)
DispatchQueue.main.async {
completion(culturesList)
}
} else {
DispatchQueue.main.async {
completion([])
}
}
} catch {
print(error)
DispatchQueue.main.async {
completion([])
}
}
}.resume()
}
}
Lastly, here's my Model.
import Foundation
struct Culture: Codable, Identifiable {
var id = UUID()
var CultureId: Int
var Culture: String
}

The work around to make the picker refresh is to add a unique id. Refreshing (or) reloading the countries, will create a new UUID for the picker items. This will force the picker to refresh. I've modified your code to include an id.
//loop through country array and add them to picker
Picker(selection: $selectedCountries, label: Text("Select Your Country")) {
ForEach(0 ..< countries.cultures.count, id: \.self) { post in
Text(self.countries.cultures[post].Culture).tag(post)
}
}.labelsHidden()
.id(UUID())
This seems to be a known issue with the picker.

Related

How to link "text to speech" in a local Json words list when click each of the icon in swiftUI?

I want to create a vocabulary app with pronounce. Because I am a fresh to swiftUI and try to self-learning, so I am try to watch online coding and modify. I have create a local Json file with a "LearnMode" view, and also I take a reference of text to speech coding example online to made a "soundicon" view, but I have no idea that how I can make them work together. Currently I make each " trumpet“ icon with an "apple" pronounce, I don't know how I can attached each of the Json word to pronounce, I think the way are pretty easy, but I cannot figure out. Here are 2 swiftUI file coding, many thanks for your help:
//LearnMode.swift
import SwiftUI
struct Model1: Codable,Identifiable{
enum CodingKeys: CodingKey{
case word
case interpretation
}
var id = UUID()
var word,interpretation:String
}
class Json1: ObservableObject{
#published var json = Model1
init(){
load()
}
func load(){
let path = Bundle.main.path(forResource:"data",ofType:"json")
let url = URL(fileURLWithPath: path!)
URLSession.shared.dataTask(with: url){(data,response,error) in
do{
if let data = data {
let json = try JSONDecoder().decode([Model1].self, from:data)
DispatchQueue.main.sync{
self.json = json
}
}else{
print("No data")
}
} catch {
print(error)
}
}.resume()
}
}
struct LearnMode: View {
#ObservedObject var datas = Json1()
var body: some View {
NavigationView{ZStack{
List(datas.json){item in
HStack{
Text(item.word)
.font(.system(size: 16))
Spacer()
Text(item.interpretation)
.multilineTextAlignment(.leading)
.frame(width: 50, height: 60)
.font(.system(size: 12))
.foregroundColor(Color.gray)
SoundIcon()
}
}
}
.navigationTitle("IELTS")
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct LearnMode_Previews: PreviewProvider {
static var previews: some View {
LearnMode()
}
}
// SoundIcon.swift
import SwiftUI
import AVFoundation
struct SoundIcon: View {
var body: some View {
Button(){
let utterance = AVSpeechUtterance(string: "apple")
utterance.voice = AVSpeechSynthesisVoice(language:"en-US")
utterance.rate = 0.5
let synthesizer = AVSpeechSynthesizer()
synthesizer.speak(utterance)
}label:{
Image(systemName: "speaker.wave.1")
}
}
}
struct SoundIcon_Previews: PreviewProvider {
static var previews: some View {
SoundIcon()
}
}

SwiftUI - Get data in remote JSON data to refresh on App startup

I am a noob (and my code/model currently will show it so please go easy on me!).
I’m taking a first whack at creating a recipes style (cenotes) app.
So that I can update the data I have a remote json file, so users won't have to reinstall to get the updated data.
The app successfully retrieves and parses the data (that’s a win!), but only when first installed.
It is not subsequently refreshing the data when it is changed online. I have tried using .onAppear and also .refreshable unsuccessfully. I need to delete the App and re-install for it to go and fetch the updated data.
Below are some the redacted versions of my files: Can anybody help? I think that I just have a #StateObject/#EnvironmentObject or something similar incorrect, but can’t spot the error.
Locations.swift
import Foundation
class Locations: ObservableObject {
#Published var locations = [Location]()
init() {
load()
}
func load() {
let url = URL(string: "https://www.i-love-tulum.com/cenotes/locations.json")!
URLSession.shared.dataTask(with: url) {(data,response,error) in
do {
if let d = data {
let data = try JSONDecoder().decode([Location].self, from: d)
DispatchQueue.main.async {
self.locations = data
}
} else {
print("No Data")
}
} catch {
print ("Error")
}
}.resume()
} }
Location.swift - VM
import Foundation
struct Location: Codable, Identifiable {
var id: String
let heroPicture: String
}
App.swift - The App opens to this tab view
import SwiftUI
#main struct App: App {
#StateObject var locations = Locations()
#State private var selection = 1
var body: some Scene {
WindowGroup {
TabView (selection:$selection) {
NavigationView {
ListView()
}
.tabItem {
Image(systemName: "drop.circle")
Text("Cenotes")
} .tag(1)
}
.environmentObject(locations)
}
} }
ListView.swift - The first selected tab is this view - these locations are not updating after the initial install:
import SwiftUI import MapKit
struct ListView: View {
#EnvironmentObject var locations: Locations
var body: some View {
VStack {
GeometryReader { geo in
ScrollView {
LazyVStack (alignment: .leading) {
ForEach (locations: locations.locations) { location in
NavigationLink (
destination: ContentView(location: location),
label: {
Image(location.heroPicture)
}
.navigationTitle("Cenotes")
.navigationBarTitleDisplayMode(.inline)
} }
Try this to reload every time and skip all caches:
UPDATE, now working :)
func load() {
let url = URL(string: "https://www.i-love-tulum.com/cenotes/locations.json")!
let config = URLSessionConfiguration.default
config.requestCachePolicy = .reloadIgnoringLocalAndRemoteCacheData
let session = URLSession.init(configuration: config)
session.dataTask(with: url) {(data,response,error) in
do {
if let d = data {
let data = try JSONDecoder().decode([Location].self, from: d)
DispatchQueue.main.async {
self.locations = data
}
} else {
print("No Data")
}
} catch {
print ("Error")
}
}.resume()
All is well set up and works fine. This here adds two Buttons to the list, to delete all entries and reload again. Maybe that helps.
struct ListView: View {
#EnvironmentObject var locations: Locations
var body: some View {
List {
ForEach (locations.locations) { location in
NavigationLink {
Text("Content View here ...")
} label: {
VStack(alignment: .leading) {
Text(location.name).font(.title)
Text(location.aka).foregroundColor(.secondary)
}
}
.navigationTitle("Cenotes")
.navigationBarTitleDisplayMode(.inline)
}
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("Delete all") {
locations.locations = []
}
}
ToolbarItem(placement: .navigationBarTrailing) {
Button("Reload") {
locations.load()
}
}
}
}
}

SwiftUi Load Local Json Data

I actually learning SwiftUi, and at the moment i don't get into my problem.
I try to load Json Data from my local Json File !
I have searched many many many Post but nothing really helped me, that is why i have chosen to make this post.
I show you how i possible tried it.
Path: Data/test.json
[
{
"id": 1,
"name": "",
"trade_name": "",
"short_name": "",
"test": "",
"test1": "",
"test2": "",
"test3": "",
"test4": "",
"test5": "",
"test6": "",
"test7": "",
"test8": [],
"test9": "",
"test10": "",
"test11": "",
"test12": ""
},
]
Path: App/DataLoader.swift
import Foundation
public class DataLoader {
#Published var contentData = [JSONData]()
init(){
load()
sort()
}
func load(){
if let fileLocation = Bundle.main.url(forResource: "test", withExtension: "json"){
do {
let data = try Data(contentsOf: fileLocation)
let jsonDecoder = JSONDecoder()
let dataFromJson = try jsonDecoder.decode([JSONData].self, from: data)
self.contentData = dataFromJson
} catch {
print(error)
}
}
}
func sort(){
self.contentData = self.contentData.sorted(by: { $0.id < $1.id})
}
}
Here in this File i add the variables.
Path: App/JsonData.swift
import Foundation
struct JSONData: Codable {
var id: Int
var name: String
var trade_name: String
var short_name: String
var test: String
var test1: String
var test2: [String:String]
var test3: [String:String]
var test4: String
var test5: String
var test6: [String:String]
var test7: String
var test8: [String:String]
var test9: String
var test10: String
var test11: String
var test12: String
}
I didn't set the Display yet but i want it in a List.
In my ContentView.swift File i would load the Data from that Json File !
Path: App/ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
Home()
.navigationTitle("Test Interface")
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct Home : View {
let data = DataLoader().contentData
var body: some View {
data[IndexPath.row].id
}
}
When i did it like that, it shows some Errors.
Return type of property 'body' requires that 'Int' conform to 'View'
Instance member 'row' cannot be used on type 'IndexPath'; did you mean to use a value of this type instead?
Regards
CreatingBytes
SwiftUI is not UIKit, there are no index paths.
The body property of a View must return some View, nothing else.
First of all adopt Identifiable
struct JSONData: Codable, Identifiable { ...
Then adopt ObservableObject in DataLoader
public class DataLoader : ObservableObject {
You have to observe the data in the root view, replace ContentView with
struct ContentView: View {
#StateObject var data = DataLoader()
var body: some View {
NavigationView {
Home(data: data)
.navigationTitle("Test Interface")
.navigationBarTitleDisplayMode(.inline)
}
}
}
In Home hand over data and use ForEach
struct Home : View {
#ObservedObject var data : DataLoader
var body: some View {
VStack() {
ForEach(data.contentData) { item in
HStack {
Text(item.name)
Spacer()
Text("\(item.id)")
}
}
}
}
}
Alternatively replace ForEach with List to get a table view
I highly recommend to watch the videos of WWDC 2019 and 2020 about SwiftUI

Parse a Json from a url with entry values and list them in SwiftUI

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

Type '_' has no member 'id' SwiftUI

So over the last couple hours, I've been trying to create a horizontal card stack that will show the most recent headlines for a project I am working on. Where I am at now, is holding up my entire flow, because my list cannot find my id variable in my results struct. Below I've attached the code, and any help would be appreciated.
HeadlinesUI.swift - Screenshot
import SwiftUI
struct Response: Codable {
var results: [Result]
}
struct Result: Codable {
var id: Int
var title: String
var header_image: String
var summary: String
}
struct HeadlineUI: View {
#State var results = [Result]()
var body: some View {
ScrollView(.horizontal) {
HStack(spacing: 20) {
List(results, id: \.id) { items in
}
.onAppear(perform: loadData)
}
}
}
func loadData() {
}
}
struct HeadlineUI_Previews: PreviewProvider {
static var previews: some View {
HeadlineUI()
}
}
As mentioned in the comments Result is a type in Swift, so you have to use a different name.
I went for:
struct MyResult: Codable {
var id: Int
var title: String
var header_image: String
var summary: String
}
since it has got an id you can make it Identifiable:
extension MyResult: Identifiable {}
as for the body:
var body: some View {
ScrollView(.horizontal) {
HStack(spacing: 20) {
ForEach(results) { result in
Text(result.title)
}
.onAppear(perform: { self.loadData() })
}
}
}
You rather need a ForEach then a List and the builder cannot be empty. Also onAppear takes a closure as its argument. Once you make all the changes the View will behave as expected.