SwiftUI List is working but not the picker - list

I have this view. The list works fine by showing the data. The picker is not working. It does not display any data. Both use the same objects and functions. I do not know the reason for the picker not showing data. I want to use the picker. I placed the List just to try to determine the problem but I still don't know.
import SwiftUI
struct GameListPicker: View {
#ObservedObject var gameListViewModel = GameListViewModel()
#State private var selectedGameList = ""
var body: some View {
VStack{
List(gameListViewModel.gameList){gameList in
HStack {
Text(gameList.gameName)
}
}
.onAppear() {self.gameListViewModel.fetchData()}
Picker(selection: $selectedGameList, label: Text("")){
ForEach(gameListViewModel.gameList) { gameList in
Text(gameList.gameName)
}
}
.onAppear() {self.gameListViewModel.fetchData()}}
}
}
This is the GameListViewModel
import Foundation
import Firebase
class GameListViewModel: ObservableObject{
#Published var gameList = [GameListModel]()
let db = Firestore.firestore()
func fetchData() {
db.collection("GameData").addSnapshotListener {(querySnapshot, error) in
guard let documents = querySnapshot?.documents else {
print("No documents")
return
}
self.gameList = documents.map { queryDocumentSnapshot -> GameListModel in
let data = queryDocumentSnapshot.data()
let gameName = data["GameName"] as? String ?? ""
return GameListModel(id: gameName, gameName: gameName)
}
}
}
}
This is the GameListModel
import Foundation
struct GameListModel: Codable, Hashable,Identifiable {
var id: String
//var id: String = UUID().uuidString
var gameName: String
}
Thanks in advance for any help

Picker is not designed to be dynamic, so it is not refreshed when data updated, try to force-rebuild picker when data changed, like below
Picker(selection: $selectedGameList, label: Text("")){
ForEach(gameListViewModel.gameList) { gameList in
Text(gameList.gameName)
}
}.id(gameListViewModel.gameList) // << here !!

Related

Using ForEach inside a Picker

I'm having issues pulling data from an Array into a picker using SwiftUI. I can correctly make a list of the data I'm interested in, but can't seem to make the same logic work to pull the data into a picker. I've coded it a few different ways but the current way I have gives this error:
Referencing initializer 'init(_:content:)' on 'ForEach' requires that 'Text' conform to 'TableRowContent'
The code is below:
import SwiftUI
struct BumpSelector: View {
#ObservedObject var model = ViewModel()
#State var selectedStyle = 0
init(){
model.getData2()}
var body: some View {
VStack{
List (model.list) { item in
Text(item.style)}
Picker("Style", selection: $selectedStyle, content: {
ForEach(0..<model.list.count, content: { index in
Text(index.style)
})
})
}
}
The model is here:
import Foundation
struct Bumps: Identifiable{
var id: String
var style: String
}
and the ViewModel is here:
import Foundation
import Firebase
import FirebaseFirestore
class ViewModel: ObservableObject {
#Published var list = [Bumps]()
#Published var styleArray = [String]()
func getData2() {
let db = Firestore.firestore()
db.collection("bumpStop").getDocuments { bumpSnapshot, error in
//Check for errors first:
if error == nil {
//Below ensures bumpSnapshot isn't nil
if let bumpSnapshot = bumpSnapshot {
DispatchQueue.main.async {
self.list = bumpSnapshot.documents.map{ bump in
return Bumps(id: bump.documentID,
style: bump["style"] as? String ?? "")
}
}
}
}
else {
//Take care of the error
}
}
}
}
index in your ForEach is just an Int, there is no style associated with an Int. You could try this approach to make the Picker work with its ForEach:
struct BumpSelector: View {
#ObservedObject var model = ViewModel()
#State var selectedStyle = 0
init(){
model.getData2()
}
var body: some View {
VStack{
List (model.list) { item in
Text(item.style)}
Picker("Style", selection: $selectedStyle) {
ForEach(model.list.indices, id: \.self) { index in
Text(model.list[index].style).tag(index)
}
}
}
}
}
EDIT-1:
Text(model.list[selectedStyle].style) will give you the required style of the selectedStyle.
However, as always when using index, you need to ensure it is valid at the time of use.
That is, use if selectedStyle < model.list.count { Text(model.list[selectedStyle].style) }.
You could also use this alternative approach that does not use index:
struct Bumps: Identifiable, Hashable { // <-- here
var id: String
var style: String
}
struct BumpSelector: View {
#ObservedObject var model = ViewModel()
#State var selectedBumps = Bumps(id: "", style: "") // <-- here
init(){
model.getData2()
}
var body: some View {
VStack{
List (model.list) { item in
Text(item.style)
}
Picker("Style", selection: $selectedBumps) {
ForEach(model.list) { bumps in
Text(bumps.style).tag(bumps) // <-- here
}
}
}
.onAppear {
if let first = model.list.first {
selectedBumps = first
}
}
}
}
Then use selectedBumps, just like any Bumps, such as selectedBumps.style

Firebase List Constantly Refreshing

I am still trying to figure out swiftui. I am writing a program that utilizes a database for a grocery app. I decided to go with Google Firebase and so far so good. The issue I have though is I am trying to load a list of products and this list is constantly refreshing. When I scroll it refreshes and I am back at the top of the list. I was wondering if I could get some help as to what I am doing wrong here. I will include my code below and try to best explain. Thanks in advance!
struct ContentView: View {
#State var selectedIndex = 0
var body: some View {
VStack {
Button( action: {
selectedIndex = 5
} label: {
Image(systemName: "magnyfyingglass")
}
}
switch selectedIndex {
case 0:
// some code
case 1:
// some code
case 2:
// some code
case 3:
// some code
case 4:
// some code
case 5:
SearchView()
default
// some code
}
}
}
SearchView looks like this:
struct SearchView: View {
#State private var searchText = ""
#ObservedObject var listModel = InvListView()
var body: some View {
NavigationView {
List {
ForEach(self.listModel.invList.filter{(self.searchText.isEmpty ? true : $0.description.localizedCaseInsensitiveContains(self.searchText))}, id: \.id) {products in
NavigationLink(destination: Detail(data: products)) {
Text(products.description)
}
}
}
.searchable(text: self.$searchText)
{
ForEach(listModel.invList, id:\.id) {info in
HStack {
Text(info.description)
.searchCompletion(info.description)
}
}
}
}
}
}
struct Detail: View {
var data: InventoryList
var body: some View {
VStack {
Text(data.description)
Text(data.category)
}.padding()
}
}
InventoryList
import Foundation
import Firebase
class InvListView: ObservableObject {
#Published var invList = [InventoryList]()
init() {
// Access inventory in the database
let database = Firestore.firestore()
database.collection("inventory").getDocuments { snapshot, error in
if error != nil {
// Errors will fix later
return
}
if let snapshot = snapshot {
DispatchQueue.main.async {
self.invList = snapshot.documents.map { d in
return InventoryList(id: d.documentID,
upc: d["upc"] as? Int ?? 0,
description: d["description"] as? String ?? "",
category: d["category"] as? String ?? "",
price: d["price"] as? Double ?? 0.0,
url: d["imageUrl"] as? String ?? "")
}
}
}
}
}
}
struct InventoryList: Identifiable {
var id: String
var upc: Int
var description: String
var category: String
var price: Double
var url: String
}
I hope this is enough to go on. I think it has something to do either with the switch or the init but not sure how to fix it.
I was able to figure it out on my own. The #ObservedObject in SearchView was causing the view to refresh. I changed it to #StateObject and that seemed to fix the problem. There were some small bugs after, but once I removed them everything else worked.

swiftui list doens't appear but array isn't empty

I am working on a Swiftui file that loads data from Firebase.
It did work but when I added things it suddenly stopt working...
I tried to strip it back down but I can't get it working again.
Does anyone know what I do wrong?
import SwiftUI
import Firebase
struct Fav: View {
#StateObject var loader = Loader()
var body: some View {
ScrollView {
if loader.userfav.count != 0 {
List (loader.userfav, id: \.id) { fav in
Text(fav.name.capitalized)
}
}
else
{
Text("You haven't added favorits yet...")
}
}
.onAppear{
loader.loadfav(loadfavorits: "asd")
}
.navigationBarTitle("")
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
}
func deletefav (docid: String) {
print(docid)
}
}
struct Fav_Previews: PreviewProvider {
static var previews: some View {
Fav()
}
}
and the loader file
import Foundation
import Firebase
import FirebaseFirestore
class Loader : ObservableObject {
private var db = Firestore.firestore()
#Published var userfav = [fav]()
func loadfav (loadfavorits: String) {
userfav = [fav]()
db.collection("favo").whereField("user", isEqualTo: loadfavorits).getDocuments() { (querySnapshot, err) in
if let err = err {
print("Error getting favorits: \(err.localizedDescription)")
}
else
{
for fav in querySnapshot!.documents {
let brand = fav.get("brand") as! String
let store = fav.get("store") as! String
let name = fav.get("name") as! String
let type = fav.get("type") as! String
let docid = fav.get("docid") as! String
self.userfav.append(fav(brand: brand, store: store, name: name, type: type, docid: docid))
}
}
}
}
}
It doesn't show the Text("You haven't added favorits yet...")
So that means dat loader.userfav.count is not empty
Having a List embedded in a ScrollView (which also scrolls) can lead to layout problems. Remove the outer ScrollView and the issue will be solved.

How Share Variables Among View and Sub Views in SwiftUI

I have a date picker, when the date selection value changes, I would like to store it in a string array called filterSelections, how do I do that? Thanks in advance.
import SwiftUI
public var filterSelections: [String: Any]?
func setFilterSelections(name: String, selectedValue: Any) {
filterSelections[name] = selectedValue
}
struct myMainSwiftUIView: View{
var body: some View {
ScrollView {
VStack{
mySub1View()
}
}
}
}
struct mySub1View: View {
#State public var fromDate: Date = Calendar.current.date(byAdding: DateComponents(year: -40), to: Date()) ?? Date()
var body: some View {
HStack(spacing:10) {
VStack(alignment:.leading, spacing:20) {
DatePicker(selection: $fromDate, displayedComponents: .date) {
Text("From")
.font(.body)
.fixedSize()
}
}
}
}
}
}
It's hard for me to see the application of what storing all of the changes to the date picker would be, since there wouldn't be any way to cancel them out (and, in pre-iOS 14, I think the wheel would make this a particular crazy looking list when things were changing).
My suspicion is that you probably want the date along with some other filters added together. And, you specified wanting to share that state between views and subviews, which I've tried to accommodate. I also used the date format that you asked for.
I did not include the [String:Any] as your question said "array", not dictionary.
Lots of guess work here, since it's not totally clear what your goal is, but hopefully this gives you some ideas of how to share state.
class FilterViewModel : ObservableObject {
#Published var dateFilter : Date = Calendar.current.date(byAdding: DateComponents(year: -40), to: Date()) ?? Date()
#Published var myOtherFilter = "Filter1"
static var formatter = DateFormatter()
var allFilters : [String] {
Self.formatter.dateFormat = "yyyy/MM/dd"
return [myOtherFilter, Self.formatter.string(from: dateFilter)]
}
}
struct ContentView: View{
#StateObject private var filterModel = FilterViewModel()
var body: some View {
ScrollView {
VStack{
MySub1View(filterModel: filterModel)
}
ForEach(filterModel.allFilters, id: \.self) { filter in
Text(filter)
}
}
}
}
struct MySub1View: View {
#ObservedObject var filterModel : FilterViewModel
var body: some View {
HStack(spacing:10) {
VStack(alignment:.leading, spacing:20) {
DatePicker(selection: $filterModel.dateFilter, displayedComponents: .date) {
Text("From")
.font(.body)
.fixedSize()
}
}
}
}
}
It is so simple, make an array and store all of them, do not make more complex in your code, if you want export your Date array then use StateObject, there is really not a big issue. after all then start working on your stored array, for example where and how you want use it!
import SwiftUI
struct ContentView: View {
var body: some View {
mySub1View()
}
}
struct mySub1View: View {
#State private var selection: Date = Date()
#State private var selectionArray: [Date] = [Date]()
var body: some View {
if #available(iOS 14.0, *) {
DatePicker(selection.description, selection: $selection, displayedComponents: .date)
.onChange(of: selection) { newValue in
selectionArray.append(newValue)
print(selectionArray)
}
}
}
}

How should I get and set a value of UserDefaults?

I'm currently developing an application using SwiftUI.
I want to use a UserDefaults value in this app.
So I made a code below.
But in this case, when I reboot the app(the 4'th process in the process below), I can't get value from UserDefaults...
Build and Run this project.
Pless the home button and the app goes to the background.
Double-tap the home button and remove the app screen.
press the app icon and reboot the app. Then I want to get value from UserDefaults.
to resolve this problem how should I set and get a value in UserDefaults?
Here is the code:
import SwiftUI
struct ContentView: View {
#State var text = "initialText"
var body: some View {
VStack {
Text(text)
TextField( "", text: $text)
}.onAppear(){
if let text = UserDefaults.standard.object(forKey: "text" ){
self.text = text as! String
}
}
.onDisappear(){
UserDefaults.standard.set(self.text, forKey: "text")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
ADD
When I add this class following the first answer, that code has a couple of errors like this, is it usual?
Xcode: Version 11.7
Swift: Swift 5
Set in a class like this your values: Bool, String(see example), Int, etc...
#if os(iOS)
import UIKit
#else
import AppKit
#endif
import Combine
#propertyWrapper struct UserDefault<T> {
let key: String
let defaultValue: T
init(_ key: String, defaultValue: T) {
self.key = key
self.defaultValue = defaultValue
}
var wrappedValue: T {
get {
return UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}
}
final class UserSettings: ObservableObject {
let objectWillChange = PassthroughSubject<Void, Never>()
#UserDefault("myText", defaultValue: "initialText")
var myText: String {
willSet { objectWillChange.send() }
}
}
this to read:
let settings = UserSettings()
let count = settings.countSentence // default countsentence 1
this to update:
let settings = UserSettings()
settings.countSentence = 3 // default countsentence 3
Based on your code:
struct ContentView: View {
let UserDef = UserSettings()
#State var text = ""
var body: some View {
VStack {
Text(UserDef.myText)
TextField("placeholder", text: $text, onCommit: { self.UserDef.myText = self.text})
}.onAppear() {
self.text = self.UserDef.myText
}
}
}