This works - all student.name entries are correctly displayed
import SwiftUI
import CoreData
let students = loadData()
struct ContentView: View {
var body: some View {
NavigationView {
List(students) { student in
HStack {
Text(student.name ?? "No data")
}
}
}
}
}
But this - not. After correctly skipping "if", "students" become nil.
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[UserEntity name]: unrecognized selector sent to instance 0x60000343f840' V
XCode Version 13.0 (13A233)
import SwiftUI
import CoreData
let students: [UserEntity] = loadData()
struct ContentView: View {
var body: some View {
if students.isEmpty{
NoDataView()
}
else{
NavigationView {
List(students) { student in
HStack {
Text(student.name ?? "dsada")
}
}
}
}
}
}
Related
I'm trying to use CoreData in my Xcode-project (SwiftUI). I've created a Player entity and wanna use it in my View called "YouView". But when I'm trying to fetch the data, I get the error from Title. My app is called Dart Tools. My Application Language is German, so don't worry if you don't understand everything of the ui :).
Thanks for helping!
I already tried the .shared variant - same error
This is my .xcdatamodeld file (called DataModel.xcdatamodeld)
This is the Code of my DataController file:
import Foundation
import CoreData
class DataController: ObservableObject {
let container = NSPersistentContainer(name: "DataModel")
init() {
container.loadPersistentStores { desc, error in
if let error = error {
print("Daten wurden nicht geladen: \(error.localizedDescription)")
}
}
}
func save(context: NSManagedObjectContext) {
do {
try context.save()
print("Daten wurden gespiechert!")
} catch {
print("Daten konnten nicht gespeichert werden.")
}
}
func addPlayer(name: String, isUser: Bool, context: NSManagedObjectContext) {
let player = Player(context: context)
player.id = UUID()
player.name = name
player.isUser = isUser
save(context: context)
}
func editPlayerName(player: Player, name: String, context: NSManagedObjectContext) {
player.name = name
save(context: context)
}
}
Here is my DartToolsApp.swift file:
import SwiftUI
//The error is here:
#main
struct DartToolsApp: App {
#StateObject private var userDefaults = UserDefaults()
#StateObject private var dataController = DataController()
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, DataController().container.viewContext)
.environmentObject(UserDefaults())
}
}
}
This is the file where I want to use the Data:
Btw, this view is a piece of a TabView.
import SwiftUI
import CoreData
struct YouView: View {
#EnvironmentObject var userDefaults: UserDefaults
// I guess the error is because of this line:
#FetchRequest(sortDescriptors: []) var players: FetchedResults<Player>
#Environment(\.managedObjectContext) var managedObjectContext
var body: some View {
NavigationStack {
Form {
}
.navigationTitle("\(findUser()) (Du)")
}
}
func findUser() -> String {
if let index = players.firstIndex(where: { $0.isUser }) {
let output = players[index].name!
return output
}
else {
return ""
}
}
}
This is the code to create the user:
DataController().addPlayer(name: nameText, isUser: true, context: managedObjectContext)
Wow, that was a lot of code. I hope you can help me!
You are creating new instances of DataController everywhere which is a problem in itself but what think causes your particular error is when you assign the \.managedObjectContext environment variable.
So instead of creating a new instance you should use your #StateObject instance instead.
So change this in the App code
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, dataController.container.viewContext)
}
}
I'm trying to update the list from view model when the app is starting. But it only updates when items are added or removed. How to fix it??
import SwiftUI
import UserNotifications
var center = UNUserNotificationCenter.current()
struct ContentView: View {
#ObservedObject var vm = ViewModel()
var body: some View {
VStack (alignment: .center, spacing: 0.3) {
List {
ForEach(0..<vm.list.count, id: \.self) { index in
NotificationCellView(
index: index,
vm: vm)
}
.onDelete { vm.del($0) }
}
.onAppear{
vm.getNotifications()
requestNotification()
}}}
}
View model class. I'am appending the list with UNNotificationRequest elements :
class ViewModel: ObservableObject {
#Published var list: [UNNotificationRequest] = []
func getNotifications() {
center.getPendingNotificationRequests { requests in
for request in requests {
DispatchQueue.main.async {
self.list.append(request)
}
}
}
}
}
I am working on a SwiftUI Project using MVVM. I have the following files for a marketplace that has listings.
ListingRepository.swift - Connecting to Firebase Firestore
Listing.swift - Listing Model File
MarketplaceViewModel - Marketplace View Model
MarketplaceView - List view of listings for the marketplace
Originally, I was making my repository file the EnvironmentObject which worked. While researching I am realizing it makes more sense to make the ViewModel the EnvironmentObject. However, I am having trouble making an EnvironmentObject. Xcode is giving me the following error in my MarketplaceView.swift file when I try and access marketplaceViewModel and I can't understand why?
SwiftUI:0: Fatal error: No ObservableObject of type
MarketplaceViewModel found. A View.environmentObject(_:) for
MarketplaceViewModel may be missing as an ancestor of this view.
Here are the files in a simplified form.
App File
#main
struct Global_Seafood_ExchangeApp: App {
#StateObject private var authSession = AuthSession()
#StateObject private var marketplaceViewModel = MarketplaceViewModel(listingRepository: ListingRepository())
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(marketplaceViewModel)
.environmentObject(authSession)
}
}
}
ListingRepository.swift
class ListingRepository: ObservableObject {
let db = Firestore.firestore()
#Published var listings = [Listing]()
init() {
startSnapshotListener()
}
func startSnapshotListener() {
db.collection(FirestoreCollection.listings).addSnapshotListener { (querySnapshot, error) in
if let error = error {
print("Error getting documents: \(error)")
} else {
guard let documents = querySnapshot?.documents else {
print("No Listings.")
return
}
self.listings = documents.compactMap { listing in
do {
return try listing.data(as: Listing.self)
} catch {
print(error)
}
return nil
}
}
}
}
}
Listing.swift
struct Listing: Codable, Identifiable {
#DocumentID var id: String?
var title: String?
}
MarketplaceModelView.swift
class MarketplaceViewModel: ObservableObject {
var listingRepository: ListingRepository
#Published var listingRowViewModels = [ListingRowViewModel]()
private var cancellables = Set<AnyCancellable>()
init(listingRepository: ListingRepository) {
self.listingRepository = listingRepository
self.startCombine()
}
func startCombine() {
listingRepository
.$listings
.receive(on: RunLoop.main)
.map { listings in
listings.map { listing in
ListingRowViewModel(listing: listing)
}
}
.assign(to: \.listingRowViewModels, on: self)
.store(in: &cancellables)
}
}
MarketplaceView.swift
struct MarketplaceView: View {
#EnvironmentObject var marketplaceViewModel: MarketplaceViewModel
var body: some View {
// ERROR IS HERE
Text(self.marketplaceViewModel.listingRowViewModels[1].listing.title)
}
}
ListingRowViewModel.swift
class ListingRowViewModel: ObservableObject {
var id: String = ""
#Published var listing: Listing
private var cancellables = Set<AnyCancellable>()
init(listing: Listing) {
self.listing = listing
$listing
.receive(on: RunLoop.main)
.compactMap { listing in
listing.id
}
.assign(to: \.id, on: self)
.store(in: &cancellables)
}
}
ContentView.swift
struct ContentView: View {
#EnvironmentObject var authSession: AuthSession
#EnvironmentObject var marketplaceViewModel: MarketplaceViewModel
var body: some View {
Group{
if (authSession.currentUser != nil) {
TabView {
MarketplaceView()
.tabItem {
Image(systemName: "shippingbox")
Text("Marketplace")
}.tag(0) // MarketplaceView
AccountView(user: testUser1)
.tabItem {
Image(systemName: "person")
Text("Account")
}.tag(2) // AccountView
} // TabView
.accentColor(.white)
} else if (authSession.currentUser == nil) {
AuthView()
}
}// Group
.onAppear(perform: authenticationListener)
}
// MARK: ++++++++++++++++++++++++++++++++++++++ Methods ++++++++++++++++++++++++++++++++++++++
func authenticationListener() {
// Setup Authentication Listener
authSession.listen()
}
}
Any help would be greatly appreciated.
in your app you have:
ContentView().environmentObject(marketplaceViewModel)
so in "ContentView" you should have as the first line:
#EnvironmentObject var marketplaceViewModel: MarketplaceViewModel
Note in "ContentView" you have, "#EnvironmentObject var authSession: AuthSession"
but this is not passed in from your App.
Edit: test passing "marketplaceViewModel", using this limited setup.
class MarketplaceViewModel: ObservableObject {
...
let showMiki = "here is Miki Mouse"
...
}
and
struct MarketplaceView: View {
#EnvironmentObject var marketplaceViewModel: MarketplaceViewModel
var body: some View {
// ERROR NOT HERE
Text(marketplaceViewModel.showMiki)
// Text(self.marketplaceViewModel.listingRowViewModels[1].listing.title)
}
}
Anyone looking for a way to use MVVM with Firebase Firestore and make your View Model the EnvironmentObject I've added my code below. This project has a list view and a detail view. Each view has a corresponding view model. The project also uses a repository and uses Combine.
App.swift
import SwiftUI
import Firebase
#main
struct MVVMTestApp: App {
#StateObject private var marketplaceViewModel = MarketplaceViewModel(listingRepository: ListingRepository())
// Firebase
init() {
FirebaseApp.configure()
}
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(marketplaceViewModel)
}
}
}
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
Group {
MarketplaceView()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
MarketplaceView.swift
import SwiftUI
struct MarketplaceView: View {
#EnvironmentObject var marketplaceViewModel: MarketplaceViewModel
var body: some View {
NavigationView {
List {
ForEach(self.marketplaceViewModel.listingRowViewModels, id: \.id) { listingRowViewModel in
NavigationLink(destination: ListingDetailView(listingDetailViewModel: ListingDetailViewModel(listing: listingRowViewModel.listing))) {
ListingRowView(listingRowViewModel: listingRowViewModel)
}
} // ForEach
} // List
.navigationTitle("Marketplace")
} // NavigationView
}
}
struct MarketplaceView_Previews: PreviewProvider {
static var previews: some View {
MarketplaceView()
}
}
ListingRowView.swift
import SwiftUI
struct ListingRowView: View {
#ObservedObject var listingRowViewModel: ListingRowViewModel
var body: some View {
VStack(alignment: .leading, spacing: 5) {
Text(listingRowViewModel.listing.name)
.font(.headline)
Text(String(listingRowViewModel.listing.description))
.font(.footnote)
}
}
}
struct ListingRowView_Previews: PreviewProvider {
static let listingRowViewModel = ListingRowViewModel(listing: testListing1)
static var previews: some View {
ListingRowView(listingRowViewModel: listingRowViewModel)
}
}
ListingDetailView.swift
import SwiftUI
struct ListingDetailView: View {
var listingDetailViewModel: ListingDetailViewModel
var body: some View {
VStack(spacing: 5) {
Text(listingDetailViewModel.listing.name)
.font(.headline)
Text(String(listingDetailViewModel.listing.description))
.font(.footnote)
}
}
}
struct ListingDetailView_Previews: PreviewProvider {
static let listingDetailViewModel = ListingDetailViewModel(listing: testListing1)
static var previews: some View {
ListingDetailView(listingDetailViewModel: listingDetailViewModel)
}
}
MarketplaceViewModel.swift
import Foundation
import SwiftUI
import Combine
class MarketplaceViewModel: ObservableObject {
var listingRepository: ListingRepository
#Published var listingRowViewModels = [ListingRowViewModel]()
private var cancellables = Set<AnyCancellable>()
init(listingRepository: ListingRepository) {
self.listingRepository = listingRepository
self.startCombine()
}
func startCombine() {
listingRepository
.$listings
.receive(on: RunLoop.main)
.map { listings in
listings.map { listing in
ListingRowViewModel(listing: listing)
}
}
.assign(to: \.listingRowViewModels, on: self)
.store(in: &cancellables)
}
}
ListingRowViewModel.swift
import Foundation
import SwiftUI
import Combine
class ListingRowViewModel: ObservableObject {
var id: String = ""
#Published var listing: Listing
private var cancellables = Set<AnyCancellable>()
init(listing: Listing) {
self.listing = listing
$listing
.receive(on: RunLoop.main)
.compactMap { listing in
listing.id
}
.assign(to: \.id, on: self)
.store(in: &cancellables)
}
}
ListingDetailViewModel.swift
import Foundation
import SwiftUI
import Combine
class ListingDetailViewModel: ObservableObject, Identifiable {
var listing: Listing
init(listing: Listing) {
self.listing = listing
}
}
Listing.swift
import Foundation
import SwiftUI
import FirebaseFirestore
import FirebaseFirestoreSwift
struct Listing: Codable, Identifiable {
#DocumentID var id: String?
var name: String
var description: String
}
ListingRepository.swift
import Foundation
import Firebase
import FirebaseFirestore
import FirebaseFirestoreSwift
class ListingRepository: ObservableObject {
// MARK: ++++++++++++++++++++++++++++++++++++++ Properties ++++++++++++++++++++++++++++++++++++++
// Access to Firestore Database
let db = Firestore.firestore()
#Published var listings = [Listing]()
init() {
startSnapshotListener()
}
// MARK: ++++++++++++++++++++++++++++++++++++++ Methods ++++++++++++++++++++++++++++++++++++++
func startSnapshotListener() {
db.collection("listings").addSnapshotListener { (querySnapshot, error) in
if let error = error {
print("Error getting documents: \(error)")
} else {
guard let documents = querySnapshot?.documents else {
print("No Listings.")
return
}
self.listings = documents.compactMap { listing in
do {
return try listing.data(as: Listing.self)
} catch {
print(error)
}
return nil
}
}
}
}
}
I am trying to rewrite my app using SwiftUI only and I am having difficulty with the EnvironmentObject, trying to understand how it works…
I want to redirect my app users to the appropriate page at launch, depending on:
if this is their first time
if they have a login,
if they want to start using without login
If it is the first time the app is launched, LocalStorage has no data so I present the app on a welcome page
I offer the choice of 2 buttons to click on:
“New User” which redirect to the main page of the app and create a new user
“Login” which present the login page to retrieve the last backup
If the app has previously been launched, I present the main page straight away.
Now said, if I initiate my “currentPage” as “MainView” or “LoginView”, it works - but NOT if it is set as “WelcomeView”.
I presume the problem comes when the variable gets changed from a subview? I thought the use of #EnvironmentObject was the way to get around this…
Can someone explain to me how it works?
My various files are:
import SwiftUI
import Combine
class ViewRouter: ObservableObject {
let objectWillChange = PassthroughSubject<ViewRouter,Never>()
var currentPage: String = "WelcomeView" {
didSet {
objectWillChange.send(self)
}
}
}
import SwiftUI
struct ParentView : View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
VStack {
if viewRouter.currentPage == "WelcomeView" {
WelcomeView()
}
else if viewRouter.currentPage == "MainView" {
MainView()
}
else if viewRouter.currentPage == "LoginView" {
LoginView()
}
}
}
}
import SwiftUI
struct WelcomeView: View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
ZStack{
// VStack { [some irrelevant extra code here] }
VStack {
LoginButtons().environmentObject(ViewRouter())
}
// VStack { [some irrelevant extra code here] }
}
}
}
import SwiftUI
struct LoginButtons: View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
VStack {
Button(action: {
self.viewRouter.currentPage = "MainView"
}) {
Text("NEW USER")
}
Button(action: {
self.viewRouter.currentPage = "LoginView"
}) {
Text("I ALREADY HAVE AN ACCOUNT")
}
}
}
}
import SwiftUI
struct MainView : View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
VStack {
// Just want to check if it is working for now before implementing the appropriate Views...
Button(action: {
self.viewRouter.currentPage = "WelcomeView"
}) {
Text("BACK")
}
}
}
}
import SwiftUI
struct LoginView : View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
VStack {
// Just want to check if it is working for now before implementing the appropriate Views...
Button(action: {
self.viewRouter.currentPage = "WelcomeView"
}) {
Text("BACK")
}
}
}
}
Many Thanks in advance! :wink:
Ok so in your main view, the one that you are going to decide where to send your user, you could check for the app if it was lunched before or not, depending on that do whatever you want. Once you know how to do this, you can adapt to the other things. This is how you can check for it, again, in your main view router:
init() {
// Create initial Data if not data has been setup
if (InitialAppSetup().initialDataLoaded == false) {
InitialAppSetup().createInitialData()
}
// Onboarding screen
if !UserDefaults.standard.bool(forKey: "didLaunchBefore") {
UserDefaults.standard.set(true, forKey: "didLaunchBefore")
currentPage = "onboardingView"
} else {
currentPage = "homeView"
}
}
The InitialAppSetup() class has a UserDefault which goes like this:
#Published var initialDataLoaded: Bool = UserDefaults.standard.bool(forKey: "InitialData") {
didSet {
UserDefaults.standard.set(self.initialDataLoaded, forKey: "InitialData")
}
}
Ok... My 'mistake' was to add an extra ".environmentObject(ViewRouter())" when calling my subview "LoginButtons".
If I remove it, it works!.. But why?!?
struct WelcomeView: View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
ZStack{
// VStack { [some irrelevant extra code here] }
VStack {
LoginButtons()
// --> .environmentObject(ViewRouter())
}
// VStack { [some irrelevant extra code here] }
}
}
}
I have a master detail view that navigates a series of sequential lists using totally vanilla SwiftUI and I'm noticing two things.
The list rows width jitters when you access one via navigation link?
The .navigationBarTitle isn't visible until the new view is completely on screen?
Seems like neither of these things are suppose to happen? Wonder if anyone has any ideas (video and code attached).
Video of the issues: https://www.dropbox.com/s/5jq3e8chay6hsy5/jitter.mov?dl=0
UserList.swift:
import SwiftUI
struct UserList: View {
var body: some View {
NavigationView {
List(userData) { this in
NavigationLink(destination: CityList(user: this, cities: this.cities)) {
UserRow(user: this)
}
}
.navigationBarTitle(Text("Users"))
}
}
}
UserRow.swift:
import SwiftUI
struct UserRow: View {
var user: UserModel
var body: some View {
VStack(alignment: .leading) {
Text(user.firstName + " " + user.lastName)
.font(.headline)
}
}
}
CityList.swift:
import SwiftUI
struct CityList: View {
var user: UserModel
var cities: [CityModel]
var body: some View {
List (cities) { this in
NavigationLink(destination: TownList(city: this, towns: this.towns)) {
CityRow(city: this)
}.navigationBarTitle(Text(self.user.firstName + self.user.lastName))
}
}
}
CityRow.swift:
import SwiftUI
struct CityRow: View {
var city: CityModel
var body: some View {
VStack(alignment: .leading) {
Text(city.name)
.font(.headline)
}
}
}