Hy Patrick,
i have reduced everything to minimum to reproduce my problems. With the project included you will see one of the problems:
Switch to Tab “Settings”, Select “League”. The selection doesn’t get stored, whereas country does. The difference is, that in the first case the id is a String, in the second case it is an UUID and im using an AppStorage Property Wrapper.
The second problem, the strange behavior of the Picker View at the home Tab, I just can send you the two screenshots that you see the difference.
The only difference is, that the values get populated by a Firestore database (there the Pickerview doesn’t show up), whereas when the value’s for the picker get populated hardcoded (code enclosed), it works.
It’s also interesting, that in the Homeview with a State property for the “selection” the Picker “leagues” works as designed, whereas in the Settingsview with an AppState property wrapper not.
Because I can’t upload the code files, I will insert them here:
ProblemsWithPicker.swift
import SwiftUI
@main
struct ProblemsWithPickerApp: App {
var body: some Scene {
WindowGroup {
MainTabView()
.environmentObject(ContentModel())
.environmentObject(GameModel())
}
}
}
Models.swift
import SwiftUI
import Foundation
// Enumerations
enum Tab: String {
case home
case settings
}
// structs
struct Country: Decodable, Hashable, Identifiable {
var id: String = "" // ISO 3166 - ALPHA-3
var name: String = ""
var language: String = ""
}
struct League: Decodable, Hashable, Identifiable {
var id: UUID
var name: String = ""
var since: String = ""
var short: String = ""
var division: String = ""
}
struct Game: Identifiable, Hashable {
var id: Int
var league: String = ""
var homeTeam: String = ""
var homeTeamLogo: String = ""
var guestTeam: String = ""
var guestTeamLogo: String = ""
var scheduledDate: String = ""
var scheduledTime: String = ""
var ballpark: String = ""
}
//classes
class TabController: ObservableObject {
@Published var activeTab = Tab.home
func open(_ tab: Tab) {
activeTab = tab
}
}
ContenModel.swift
import Foundation
// MARK: GameModel
class GameModel: ObservableObject {
@Published var schedule = [Game]()
@Published var selectedScheduledGame: Int?
init() {
// Create some dummy games
schedule.append(Game(id: 0, league: "U12", homeTeam: "Little Indians", homeTeamLogo: "Primary Logo Dornbirn Indians", guestTeam:"Bulls U12", guestTeamLogo: "Primary Logo Hard Bulls", scheduledDate: "Gestern", scheduledTime: "10:00", ballpark: "Sportanlage Rohrbach"))
schedule.append(Game(id: 1, league: "U10", homeTeam: "Indians Kids", homeTeamLogo: "Primary Logo Dornbirn Indians", guestTeam: "Cardinals", guestTeamLogo: "Primary Logo Feldkirch Cardinals", scheduledDate: "Heute", scheduledTime: "13:00", ballpark: "Sportanlage Rohrbach"))//DateFormatter().date(from:"08-02-2022 20:00:00 +0100")!))
schedule.append(Game(id: 2, league: "U12", homeTeam: "Bulls U12", homeTeamLogo: "Primary Logo Hard Bulls", guestTeam:"Cardinals", guestTeamLogo: "Primary Logo Feldkirch Cardinals", scheduledDate: "19.02.2022", scheduledTime: "11:00", ballpark: "Ballpark am See"))//DateFormatter().date(from:"10-02-2022 20:00:00 +0100")!))
schedule.append(Game(id: 3, league: "U12", homeTeam: "Cardinals U12", homeTeamLogo: "Primary Logo Feldkirch Cardinals", guestTeam:"Indians", guestTeamLogo: "Primary Logo Dornbirn Indians", scheduledDate: "28.02.2022", scheduledTime: "11:00", ballpark: "GRAWE Ballpark"))
}
}
// MARK: ContentModel
class ContentModel: ObservableObject {
// List of countries
@Published var countries = [Country]()
// List of leagues in a country
@Published var leagues = [League]()
// List of games
@Published var games = [Game]()
init() {
// load
getSupportedCountries()
getLeagesInCountry(country: "AUT", division: "")
}
// MARK: - Authentication methods
func getSupportedCountries() {
// original code below, now just get values polulated
countries.append(Country(id: "AUT", name: "Austria", language: "German"))
countries.append(Country(id: "SUI", name: "Switzerland", language: "German"))
countries.append(Country(id: "ITA", name: "Italy", language: "Italiano"))
// // Get a referene to the countries collection
// let db = Firestore.firestore()
// //let collection = db.collection("countries")
//
// db.collection("countries")
// .getDocuments { snapshot, error in
// // Check there's no errors
// if error == nil {
//
// // Declare temp country list
// var temp = [Country]()
//
// for doc in snapshot!.documents {
// var m = Country(id: doc["id"] as? String ?? "",
// name: doc["name"] as? String ?? "",
// language: doc["language"] as? String ?? "")
//
// temp.append(m)
// }
//
// DispatchQueue.main.async {
// self.countries = temp
// }
// }
// }
//
}
func getLeagesInCountry(country: String, division: String) {
// original code below, now just get values polulated
leagues.append(League(id: UUID(), name: "Test 1", since: "1990", short: "TES", division: "west"))
leagues.append(League(id: UUID(), name: "Test 2", since: "2005", short: "TAS", division: "west"))
leagues.append(League(id: UUID(), name: "Test 3", since: "2020", short: "TOS", division: "east"))
// // Get a referene to the countries collection
// let db = Firestore.firestore()
//
// //var collection = db.collection("countries").document("\(country)").collection("leagues")
//
// if division == "" || division == "none" {
// db.collection("countries").document("\(country)").collection("leagues")
// .getDocuments() { (querySnapshot, error) in
// if let error = error {
// print("Error getting documents: \(error)")
// } else {
// // Declare temp league list
// var leagues = [League]()
//
// for doc in querySnapshot!.documents {
// let m = League(id: doc["id"] as? UUID ?? UUID(),
// name: doc["name"] as? String ?? "",
// since: doc["since"] as? String ?? "",
// short: doc["short"] as? String ?? "",
// division: doc["division"] as? String ?? "")
// leagues.append(m)
//
// }
// self.leagues = leagues
// DispatchQueue.main.async {
// self.leagues = leagues
// }
// }
// }
// } else {
// db.collection("countries").document("\(country)").collection("leagues").whereField("division", isEqualTo: "\(division)")
// .getDocuments() { (querySnapshot, error) in
// if let error = error {
// print("Error getting documents: \(error)")
// } else {
// // Declare temp league list
// var leagues = [League]()
//
// for doc in querySnapshot!.documents {
// //print("\(doc.documentID) => \(doc.data())")
// let m = League(id: doc["id"] as? UUID ?? UUID(),
// name: doc["name"] as? String ?? "",
// since: doc["since"] as? String ?? "",
// short: doc["short"] as? String ?? "",
// division: doc["division"] as? String ?? "")
// leagues.append(m)
//
// }
// self.leagues = leagues
// DispatchQueue.main.async {
// }
// }
// }
// }
}
}
SettingsModel.swift
import Foundation
import SwiftUI
class AppSettings: ObservableObject {
// MARK: properties
@Published var country: String
@Published var language: String
init(country: String = "Austrian", language: String = "English") {
self.country = country
self.language = language
}
}
MainTabView
import SwiftUI
struct MainTabView: View {
@EnvironmentObject var model: ContentModel
@EnvironmentObject var games: GameModel
@StateObject private var tabController = TabController()
var body: some View {
TabView (selection: $tabController.activeTab){
HomeView()
.tabItem {
Label("Home", systemImage: "house")
}
.tag(Tab.home)
SettingsView()
.tabItem {
Label("Settings", systemImage: "gear")
}
.tag(Tab.settings)
}
.environmentObject(tabController)
}
}
struct MainTabView_Previews: PreviewProvider {
static var previews: some View {
MainTabView()
.environmentObject(ContentModel())
.environmentObject(GameModel())
}
}
HomeView.swift
import SwiftUI
import Foundation
struct HomeView: View {
@EnvironmentObject var model: ContentModel
@EnvironmentObject var games: GameModel
// selection filters
@State private var filterLeague: String = ""
@State private var filterCountry: String = ""
@State private var selectedGame: Game?
@State private var scheduleCollapsed: Bool = false
@State private var scoreCollapsed: Bool = false
@State private var tabIndex: Int = 0
init() {
if #available(iOS 15.0, *) {
let appearance = UITabBarAppearance()
UITabBar.appearance().scrollEdgeAppearance = appearance
}
UINavigationBar.appearance().titleTextAttributes = [.font:UIFont.preferredFont(forTextStyle:.subheadline)]
}
var body: some View {
NavigationView{
VStack {
//MARK - Schedule
HStack {
Text("Schedule")
.padding(.leading)
.font(.largeTitle)
Spacer()
Button(
action: { self.scheduleCollapsed.toggle()
},
label: {
Image(systemName: self.scheduleCollapsed ? "chevron.down" : "chevron.up")
.resizable()
.scaledToFit()
}
)
.frame(width: 32)
.padding(.trailing)
}
HStack {
Picker ("League", selection: $filterLeague) {
ForEach(model.leagues) { arg in
Text(arg.name).tag(arg)
}
}
Picker ("Country", selection: $filterCountry) {
ForEach(model.countries) { arg in
Text(arg.name).tag(arg)
}
}.pickerStyle(WheelPickerStyle())
}
ScrollView{
LazyVStack {
VStack(spacing: 10) {
ForEach(games.schedule) {game in
NavigationLink(
tag: game.id,
selection: $games.selectedScheduledGame,
destination: {
//print(value)
}, label: {
ScheduleCardView(scheduledGame: game)
}
)
}
}
}
}
.background(Color(UIColor.systemBackground))
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: scheduleCollapsed ? 0 : .none)
.padding(.horizontal, 10)
// Divider()
}
.navigationBarItems(leading: HStack {
Button(action: {
//SettingsView()
}, label: {NavigationLink(destination: SettingsView()) {
Image(systemName: "person")
}})
}, trailing: HStack {
Button(action: {
}, label: {Image(systemName: "xmark")})
})
.navigationBarTitle(Text("Hello"),displayMode: .inline)
}
}
}
struct HomeView_Previews: PreviewProvider {
static var previews: some View {
// Create a dummy GameModel and pass it to the detail view so that we can see a preview
let game = GameModel()
Group {
HomeView()
.environmentObject(ContentModel())
.environmentObject(GameModel())
}
}
}
struct Collapsible<Content: View>: View {
@State var label: () -> Text
@State var content: () -> Content
@State private var collapsed: Bool = true
var body: some View {
VStack {
Button(
action: { self.collapsed.toggle() },
label: {
HStack {
self.label()
Spacer()
Image(systemName: self.collapsed ? "chevron.down" : "chevron.up")
}
.padding(.bottom, 1)
.background(Color.white.opacity(0.01))
}
)
.buttonStyle(PlainButtonStyle())
VStack {
self.content()
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: collapsed ? 0 : .none)
.clipped()
.animation(.easeOut)
.transition(.slide)
}
}
}
struct ScheduleCardView: View {
var scheduledGame: Game
var body: some View {
ZStack {
Rectangle()
.foregroundColor(.white)
.cornerRadius(10)
.shadow(radius: 3)
.aspectRatio(3, contentMode: .fit)
VStack(alignment: .leading) {
HStack{
Text("\(scheduledGame.league)")
.font(.callout).foregroundColor(.gray)
.padding(.leading)
Spacer()
Text("\(scheduledGame.ballpark)")
.font(.callout).foregroundColor(.gray)
.padding(.trailing)
}
Group{
HStack {
VStack {
HStack {
Image("\(scheduledGame.guestTeamLogo)")
.resizable()
.scaledToFit()
.frame(width: 30, height: 30)
.padding(.horizontal)
Text("\(scheduledGame.guestTeam)")
Spacer()
}
HStack {
Image("\(scheduledGame.homeTeamLogo)")
.resizable()
.scaledToFit()
.frame(width: 30, height: 30)
.padding(.horizontal)
Text("\(scheduledGame.homeTeam)")
Spacer()
}
}
Divider()
VStack {
Text("\(scheduledGame.scheduledDate)")
Text("\(scheduledGame.scheduledTime)")
}
.font(.subheadline)
.frame(width: 80)
.padding(.trailing)
}
}
.foregroundColor(.black)
}
.padding(.vertical, 8)
}
.padding(5)
}
}
SettingsView
import SwiftUI
import Foundation
struct SettingsView: View {
// Store Settings by using class UserdDefaults
@AppStorage("country") var country: String = ""
@AppStorage("league") var league: String = ""
@EnvironmentObject var settings: AppSettings
@EnvironmentObject var model: ContentModel
@EnvironmentObject var tabController: TabController
@State private var navigateBackHome: Bool = false
var body: some View {
NavigationView {
Form {
Section {
Picker ("Country", selection: $country) {
ForEach(model.countries) { arg in
Text(arg.name).tag(arg)
}
}
}
Section {
Picker ("League", selection: $league) {
ForEach(model.leagues) { arg in
Text(arg.name).tag(arg)
}
}
}
}
.navigationBarItems(leading: HStack {
Button(action: {
tabController.open(.home)
//appState.rootViewID = UUID()
}, label: {Image(systemName: "arrow.backward")})
}, trailing: HStack {
Button(action: {}, label: {Text("Save")})
})
.navigationBarTitle(Text("Settings"), displayMode: .inline)
//}
}
}
}
struct SettingsView_Previews: PreviewProvider {
static var previews: some View {
SettingsView()
.environmentObject(AppSettings())
}
}
Thank you so much for your help and efforts!
Peter