SwiftUI EnvironmentObject ViewModel data not reflected in View - swift

ViewModel
Api is called here and response is stored in watchLaterDataArr the variable
class HomeViewModel: ObservableObject {
#Published var pageCount = ""
#Published var hasNoMoreRows = false
#Published var watchLaterDataArr = [WatchLaterModelResponseData]()
init(apiService: IStudioRepository) {
self.apiService = apiService
}
func getWatchLaterListAPI() {
apiService.getWatchLaterShoSeriesPage(page: self.pageCount)
.receive(on: RunLoop.main)
.sink(receiveCompletion: { [weak self] (completion) in
switch completion {
case .finished:
break
case .failure(let error):
}
}, receiveValue: { response in
self.getStudioLatestReleasesSuccess(response)
})
.store(in: &self.cancellableBag)
}
func getStudioLatestReleasesSuccess(_ result: WatchLaterModel) {
if(result.statusCode == 200) {
for i in 0..<(result.response?.data.count)!{
if !self.hasNoMoreRows {
self.watchLaterDataArr.append((result.response?.data[i])!)
}
}
if result.response?.nextPage != nil && (result.response?.nextPage ?? "") != ""{
self.pageCount = result.response?.nextPage ?? ""
getWatchLaterListAPI()
}
else {
print(self.watchLaterDataArr.count)
self.hasNoMoreRows = true
}
}
}
}
Views In Views the data is not reflected the if condition is not satisfied or not called even when the view model is observableobject and publisher variable is used.
struct WatchLaterView: View {
#Environment(\.presentationMode) var presentation
#EnvironmentObject var watchLaterViewModel:HomeViewModel
var body: some View {
VStack {
VStack{
if (self.watchLaterViewModel.watchLaterDataArr.count > 0 && self.watchLaterViewModel.hasNoMoreRows) {
QGrid(self.watchLaterViewModel.watchLaterDataArr, columns: 3) {
StudioWatchLaterCellView(imageUrl: $0.thumbnailImage ?? "",videoItem: $0)
}
}
Spacer()
}
}.onAppear {
self.watchLaterViewModel.getWatchLaterListAPI()
}
.navigationBarTitle("")
.navigationBarHidden(true)
}
}
HomeView is called is tabBar like
HomeView().environmentObject(HomeViewModel(apiService: Constants.studioApiService))
.tabItem {
Text("Home")
self.isSelected == 0 ? Image("Home_selected") : Image("Home")
} .tag(0)
Help please the code is too big i can't shorten it anymore.

Related

SwiftUI Modifying state during view update, this will cause undefined behavior - Proper use explanation

Im learning swift and this error/warning is driving me crazy because I cant see what call Im making that causing it... The Xcode warning only shows up in my #main struct
Modifying state during view update, this will cause undefined behavior.
I thought it might be in the ListView, but I realized the warning only shows after the "Submit Post" button is it.
Im looking for a fix, but more importantly and explanation as to why this is happening and the proper usage moving forward.
import SwiftUI
import Firebase
#main
struct SocialcademyApp: App {
init() {
FirebaseApp.configure()
}
var body: some Scene {
WindowGroup {
PostsList()
}
}
}
struct PostsList: View {
#StateObject var viewModel = PostsViewModel()
#State private var searchText = ""
#State private var showNewPostForm = false
var body: some View {
NavigationView {
List(viewModel.posts) { post in
if searchText.isEmpty || post.contains(searchText) {
PostRow(post: post)
}
}
.searchable(text: $searchText)
.navigationTitle("Posts")
.toolbar {
Button {
showNewPostForm = true
} label: {
Label("New Post", systemImage: "square.and.pencil")
}
}
.sheet(isPresented: $showNewPostForm) {
NewPostView(creationAction: viewModel.makeCreationAction())
}
}
}
}
struct NewPostView: View {
typealias CreationAction = (Post) async throws -> Void
let creationAction: CreationAction
#State private var post = Post(title: "", content: "", authorName: "")
#State private var state = FormState.idle
#Environment(\.dismiss) private var dismiss
var body: some View {
NavigationView {
Form {
Section {
TextField("Title", text: $post.title)
TextField("Author Name", text: $post.authorName)
}
Section {
TextField("Content", text: $post.content)
.multilineTextAlignment(.leading)
}
Button(action: createPost, label: {
if state == .working {
ProgressView() } else {
Text("Submit Post")
}
})
.font(.headline)
.frame(maxWidth: .infinity)
.foregroundColor(.white)
.padding()
.listRowBackground(Color.accentColor)
}
}
.navigationTitle("New Post")
.disabled(state == .working)
.alert("Cannot Create Post", isPresented: $state.isError, actions: {}) {
Text("Sorry, something went wrong")
}
.onSubmit {
createPost()
}
}
private func createPost() {
print("[NewPostForm] creating a new post")
Task {
state = .working
do {
try await creationAction(post)
dismiss()
} catch {
state = .error
print("[NewPostForm] Cannot create post: \(error)")
}
}
}
}
private extension NewPostView {
enum FormState {
case idle, working, error
var isError: Bool {
get {
self == .error
}
set {
guard !newValue else { return }
self = .idle
}
}
}
}
#MainActor
class PostsViewModel: ObservableObject {
#Published var posts = [Post.testPost]
func makeCreationAction() -> NewPostView.CreationAction {
return { [weak self] post in
try await PostsRepository.create(post)
self?.posts.insert(post, at: 0)
}
}
}

How to pass user id to view with firebase

I am working on a social media app and I am having trouble displaying a Profile of a specific user. I am able to do it with currentUser?.uid but I don't know how to pass a different id/user to a profile view. Sorry if this explanation is confusing, I'm also having a hard time putting it into words.
This is my view that fetches the currently logged in user and displays their username:
import SwiftUI
class TestProfileViewModel: ObservableObject {
#Published var qUser: User?
init() {
fetchCurrentUser()
}
func fetchCurrentUser() {
guard let uid = FirebaseManager.shared.auth.currentUser?.uid else { return }
FirebaseManager.shared.firestore.collection("users").document(uid).getDocument { snapshot, err in
if let err = err {
print("\(err)")
return
}
guard let data = snapshot?.data() else { return }
print(data)
self.qUser = .init(data: data)
}
}
}
struct TestProfileView: View {
#ObservedObject var vm = TestProfileViewModel()
var body: some View {
Text(vm.qUser?.username ?? "No User")
}
}
struct TestProfileView_Previews: PreviewProvider {
static var previews: some View {
TestProfileView()
}
}
This is the view, where I want to use the ID to fetch a user from my database and use it for a Profile View, ike how I did with the currentUser:
import SwiftUI
struct TestSongVIew: View {
let testUsername = "John"
let testUserID = "123123"
#State var showingUserProfile = false
var body: some View {
VStack {
Button("Open \(testUsername)'s Profile") {
}
}
.fullScreenCover(isPresented: $showingUserProfile, onDismiss: nil) {
TestProfileView()
}
}
}
struct TestSongVIew_Previews: PreviewProvider {
static var previews: some View {
TestSongVIew()
}
}
Here is my FirebaseManager code
import Foundation
import Firebase
import FirebaseFirestore
class FirebaseManager: NSObject {
let auth: Auth
let storage: Storage
let firestore: Firestore
static let shared = FirebaseManager()
override init() {
FirebaseApp.configure()
self.auth = Auth.auth()
self.storage = Storage.storage()
self.firestore = Firestore.firestore()
super .init()
}
}
There are a couple of ways to achieve this, and they all depend on how you set up the navigation for your app.
I'm currently working on a blog post / video to demonstrate how to monitor authentication state in a SwiftUI app. To demonstrate how to implement your use case, I added a profile screen that you can use in two ways:
You can navigate to the profile screen from the app's settings screen. This will show the user profile of the currently signed in user.
You can navigate to the profile screen from a List view showing all user profiles in your user profile collection in Firestore. This might be useful if you want to implement a high score screen that allows the user to navigate to the profile screen for each of the top 10 players in a game.
Ok, here goes:
Profile
The profile model
import Foundation
import FirebaseFirestore
import FirebaseFirestoreSwift
struct Profile: Identifiable, Codable {
#DocumentID var id: String? = ""
var nickname: String
}
extension Profile {
static let empty = Profile(nickname: "")
}
The profile view model
import Foundation
import Combine
import FirebaseFirestore
import FirebaseFirestoreSwift
class ProfileViewModel: ObservableObject {
// MARK: - Output
#Published var profile: Profile
init(profile: Profile) {
self.profile = profile
}
init(uid: String) {
self.profile = Profile.empty
fetchProfile(uid)
}
// MARK: - Private attributes
private var db = Firestore.firestore()
func fetchProfile(_ uid: String) {
db.collection("profiles")
.whereField("uid", isEqualTo: uid)
.getDocuments { querySnapshot, error in
if let error = error {
print("Error getting documents: \(error)")
}
else {
if let querySnapshot = querySnapshot {
if let document = querySnapshot.documents.first {
do {
self.profile = try document.data(as: Profile.self)
}
catch {
}
}
}
}
}
}
}
The profile view
struct ProfileView: View {
#ObservedObject var viewModel: ProfileViewModel
init(profile: Profile) {
self.viewModel = ProfileViewModel(profile: profile)
}
init(uid: String) {
self.viewModel = ProfileViewModel(uid: uid)
}
var body: some View {
Form {
Text(viewModel.profile.nickname)
}
.navigationTitle("Details")
}
}
struct ProfileView_Previews: PreviewProvider {
static var previews: some View {
ProfileView(profile: Profile(nickname: "freter"))
}
}
Settings
The settings view model
import Foundation
import Combine
import FirebaseAuth
class SettingsViewModel: ObservableObject {
// MARK: - Output
#Published var email: String = ""
#Published var idToken: String = ""
#Published var user: User?
#Published var authenticationState: AuthenticationState = .unauthenticated
// MARK: - Dependencies
private var authenticationService: AuthenticationService?
func connect(authenticationService: AuthenticationService) {
if self.authenticationService == nil {
self.authenticationService = authenticationService
self.authenticationService?
.$authenticationState
.assign(to: &$authenticationState)
self.authenticationService?
.$user
.assign(to: &$user)
$user
.map { $0?.email }
.replaceNil(with: "(no email address)")
.assign(to: &$email)
}
}
#MainActor
func refreshIDToken() {
Task {
do {
idToken = try await user?.idTokenForcingRefresh(true) ?? ""
}
catch {
idToken = error.localizedDescription
print(error)
}
}
}
}
The settings view
import SwiftUI
struct SettingsView: View {
#StateObject var viewModel = SettingsViewModel()
#Environment(\.dismiss) var dismiss
#EnvironmentObject var authenticationService: AuthenticationService
#State private var presentingLoginScreen = false
var loginButton: some View {
Button(authenticationService.authenticationState == .unauthenticated ? "Login" : "Logout") {
if authenticationService.authenticationState == .unauthenticated {
presentingLoginScreen.toggle()
}
else {
authenticationService.signOut()
}
}
.frame(maxWidth: .infinity)
}
var body: some View {
Form {
Section {
Label("Help & Feedback", systemImage: "questionmark.circle")
Label("About", systemImage: "info.circle")
}
Section {
Label(viewModel.email, systemImage: "at")
Label(viewModel.idToken, systemImage: "person")
Button(action: viewModel.refreshIDToken) {
Text("Refresh ID token")
}
NavigationLink(destination: ProfileView(uid: viewModel.user?.uid ?? "unknown")) {
Label("Show user profile", systemImage: "person")
}
} header: {
Text("User Details")
}
Section {
loginButton
}
}
.sheet(isPresented: $presentingLoginScreen) {
LoginView()
}
.navigationTitle("Settings")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .confirmationAction) {
Button("Done") {
dismiss()
}
}
}
.onAppear {
viewModel.connect(authenticationService: authenticationService)
}
}
}
struct SettingsView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
SettingsView()
.environmentObject(AuthenticationService())
}
}
}
Authentication
The authentication service
import Foundation
import FirebaseAuth
enum AuthenticationState {
case unauthenticated
case authenticating
case authenticated
}
class AuthenticationService: ObservableObject {
// MARK: - Output
#Published var authenticationState: AuthenticationState = .unauthenticated
#Published var errorMessage: String = ""
#Published var user: User?
init() {
registerAuthStateListener()
}
#MainActor
func signIn(withEmail email: String, password: String) async -> Bool {
authenticationState = .authenticating
do {
try await Auth.auth().signIn(withEmail: email, password: password)
return true
}
catch {
await MainActor.run {
errorMessage = error.localizedDescription
authenticationState = .unauthenticated
}
print(error)
return false
}
}
func signOut() {
do {
try Auth.auth().signOut()
}
catch {
print(error)
}
}
private var handle: AuthStateDidChangeListenerHandle?
private func registerAuthStateListener() {
if handle == nil {
handle = Auth.auth().addStateDidChangeListener { auth, user in
Task {
await MainActor.run {
self.user = user
if let user = user {
self.authenticationState = .authenticated
print("User \(user.uid) signed in. Email: \(user.email ?? "(no email address set)"), anonymous: \(user.isAnonymous)")
}
else {
self.authenticationState = .unauthenticated
print("User signed out.")
}
}
}
}
}
}
}
The login view model
import Foundation
import Combine
import FirebaseAuth
class LoginViewModel: ObservableObject {
// MARK: - Input
#Published var email: String = ""
#Published var password: String = ""
// MARK: - Output
#Published var isValid: Bool = false
#Published var authenticationState: AuthenticationState = .unauthenticated
#Published var errorMessage: String = ""
#Published var user: User?
// MARK: - Dependencies
private var authenticationService: AuthenticationService?
func connect(authenticationService: AuthenticationService) {
if self.authenticationService == nil {
self.authenticationService = authenticationService
self.authenticationService?
.$authenticationState
.assign(to: &$authenticationState)
self.authenticationService?
.$errorMessage
.assign(to: &$errorMessage)
self.authenticationService?
.$user
.assign(to: &$user)
Publishers.CombineLatest($email, $password)
.map { !($0.isEmpty && $1.isEmpty) }
.print()
.assign(to: &$isValid)
}
}
func signInWithEmailPassword() async -> Bool {
if let authenticationService = authenticationService {
return await authenticationService.signIn(withEmail: email, password: password)
}
else {
return false
}
}
}
The login view
import SwiftUI
enum FocusableField: Hashable {
case email
case password
}
struct LoginView: View {
#StateObject var viewModel = LoginViewModel()
#EnvironmentObject var authenticationService: AuthenticationService
#Environment(\.dismiss) var dismiss
#FocusState private var focus: FocusableField?
private func signInWithEmailPassword() {
Task {
if await viewModel.signInWithEmailPassword() == true {
dismiss()
}
}
}
var body: some View {
VStack {
Image("Login")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(minHeight: 0)
Text("Login")
.font(.largeTitle)
.fontWeight(.bold)
.frame(maxWidth: .infinity, alignment: .leading)
HStack {
Image(systemName: "at")
TextField("Email", text: $viewModel.email)
.textInputAutocapitalization(.never)
.disableAutocorrection(true)
.focused($focus, equals: .email)
.submitLabel(.next)
.onSubmit {
self.focus = .password
}
}
.padding(.vertical, 6)
.background(Divider(), alignment: .bottom)
.padding(.bottom, 4)
HStack {
Image(systemName: "lock")
SecureField("Password", text: $viewModel.password)
.focused($focus, equals: .password)
.submitLabel(.go)
.onSubmit {
signInWithEmailPassword()
}
}
.padding(.vertical, 6)
.background(Divider(), alignment: .bottom)
.padding(.bottom, 8)
if !viewModel.errorMessage.isEmpty {
VStack {
Text(viewModel.errorMessage)
.foregroundColor(Color(UIColor.systemRed))
}
}
Button(action: signInWithEmailPassword) {
if viewModel.authenticationState != .authenticating {
Text("Login")
.frame(maxWidth: .infinity)
}
else {
ProgressView()
.progressViewStyle(CircularProgressViewStyle(tint: .white))
.frame(maxWidth: .infinity)
}
}
.disabled(!viewModel.isValid)
.frame(maxWidth: .infinity)
.buttonStyle(.borderedProminent)
.controlSize(.large)
HStack {
VStack { Divider() }
Text("or")
VStack { Divider() }
}
Button(action: { }) {
Image(systemName: "applelogo")
.frame(maxWidth: .infinity)
}
.foregroundColor(.black)
.buttonStyle(.bordered)
.controlSize(.large)
HStack {
Text("Don't have an account yet?")
Button(action: {}) {
Text("Sign up")
.fontWeight(.semibold)
.foregroundColor(.blue)
}
}
.padding([.top, .bottom], 50)
}
.onAppear {
viewModel.connect(authenticationService: authenticationService)
}
.listStyle(.plain)
.padding()
}
}
struct LoginView_Previews: PreviewProvider {
static var previews: some View {
Group {
LoginView()
.environmentObject(AuthenticationService())
LoginView()
.preferredColorScheme(.dark)
.environmentObject(AuthenticationService())
}
}
}

How to listen to a computed property in SwiftUI?

I am trying to disable a button based on a computed property from the View Model, but is only disabled after the view is reloaded.
This is the View Model :
class VerifyFieldViewModel : ObservableObject {
#ObservedObject var coreDataViewModel = CoreDataViewModel()
func isValidFirstName() -> Bool {
guard coreDataViewModel.savedDetails.first?.firstName?.count ?? 0 > 0 else {
return false
}
return true
}
func isValidLastName() -> Bool {
guard coreDataViewModel.savedDetails.first?.lastName?.count ?? 0 > 0 else {
return false
}
return true
}
var isFirstNameValid : String {
if isValidFirstName() {
return ""
} else {
return "Name is empty"
}
}
var isLastNameValid : String {
if isValidLastName() {
return ""
} else {
return "Surname is empty"
}
}
var isSignUpComplete: Bool {
if !isValidFirstName() || !isValidLastName() {
return false
}
return true
}
}
This is how I am disabling the button .
struct CartsView: View {
#State var onboardingState: Int = 0
#StateObject var coreDataViewModel = CoreDataViewModel()
#ObservedObject var verifyFieldViewModel = VerifyFieldViewModel()
var body: some View {
ZStack {
switch onboardingState {
case 0 :
VStack {
detailOrder
.transition(transition)
Spacer()
bottomButton
.padding(30)
}
case 2 :
VStack {
detailOrder2
.transition(transition)
Spacer()
bottomButton
.padding(30)
.opacity(verifyFieldViewModel.isSignUpComplete ? 1 : 0.6)
.disabled(!verifyFieldViewModel.isSignUpComplete)
}
default:
EmptyView()
}
}
}
}
This is the Core Data View Model :
class CoreDataViewModel : ObservableObject {
let manager = CoreDataManager.instance
#Published var savedDetails : [Details] = []
init() {
fetchSavedDetails()
}
func fetchSavedDetails() {
let request = NSFetchRequest<Details>(entityName: "Details")
do {
savedDetails = try manager.context.fetch(request)
} catch let error {
print("Error fetching \(error)")
}
}
func saveContext() {
DispatchQueue.main.async {
self.manager.save()
self.fetchSavedDetails()
}
}
}
NOTE : It works, but only when the view is reloaded.
EDITED : I updated the question to make it easier to understand. Hope that you can help me now.
EDITED2: Added Core Data View Model .
As mentioned above you don't need a computed property in this case. I made a small example of Login procedure which demonstrates the same behavior.
class LoginViewModel: ObservableObject {
#Published var username: String = ""
#Published var password: String = ""
var isValid: Bool {
(username.isNotEmpty && password.isNotEmpty)
}
func login() {
// perform login
}
}
struct ContentView: View {
#StateObject private var vm: LoginViewModel = LoginViewModel()
var body: some View {
Form {
TextField("User name", text: $vm.username)
TextField("Password", text: $vm.password)
Button("Login") {
vm.login()
}.disabled(!vm.isValid)
}
}
}

SwiftUI conditional doesn't work at top level in view

I have a view model that looks like this:
class SegmentViewModel: ObservableObject {
#Published private(set) var itemIds = [Int]()
private var cancellables: Set<AnyCancellable> = []
init() {
fetchIds()
}
private func fetchIds() {
let request = URLRequest(path: "/ids")
network
.send(request)
.receive(on: DispatchQueue.main)
.sink { completion in
switch completion {
case .failure(let error): print(error.localizedDescription)
case .finished: break
}
} receiveValue: { response in
self.itemIds = response
}
.store(in: &cancellables)
}
}
That all works fine and then my View is as follows:
struct SegmentView: View {
#ObservedObject var viewModel: SegmentViewModel
var body: some View {
if viewModel.itemIds.isEmpty {
ProgressView()
} else {
ScrollView {
LazyVStack {
ForEach(viewModel.itemIds, id: \.self) { id in
ItemView(viewModel: ItemViewModel(itemId: id))
.cornerRadius(15)
.padding()
.shadow(radius: 3)
}
}
}
}
}
}
But this doesn't work. The ProgressView is never hidden and replaced with the ScrollView. However, if I change my view to:
struct SegmentView: View {
#ObservedObject var viewModel: SegmentViewModel
var body: some View {
ScrollView {
if viewModel.itemIds.isEmpty {
ProgressView()
} else {
LazyVStack {
ForEach(viewModel.itemIds, id: \.self) { id in
ItemView(viewModel: ItemViewModel(itemId: id))
.cornerRadius(15)
.padding()
.shadow(radius: 3)
}
}
}
}
}
}
Then it works as expected and hides the ProgressView once the itemIds aren't empty.
Why is this?
It looks like ViewBuidler consumed the condition and so it is not observed. I would wrap top at Group
struct SegmentView: View {
#ObservedObject var viewModel: SegmentViewModel
var body: some View {
Group { // << here !!
if viewModel.itemIds.isEmpty {
ProgressView()
} else {
ScrollView {
LazyVStack {
ForEach(viewModel.itemIds, id: \.self) { id in
ItemView(viewModel: ItemViewModel(itemId: id))
.cornerRadius(15)
.padding()
.shadow(radius: 3)
}
}
}
}
}
}
}

SwiftUI - How to toogle all the booleans in a CoreData

I have a database with several objects with booleans as attribute. I'm looking for a function to invert all boolean objects when I press a button. I tried this function but several errors are displayed like (Value of type 'Bool' has no member 'indices') :
struct ViewList: View {
#Environment(\.managedObjectContext) var context
#State var newName: String = ""
#FetchRequest(
entity: Product.entity(),
sortDescriptors: [NSSortDescriptor(keyPath: \Product.name, ascending: true)]
) var list: FetchedResults<Product>
var body: some View {
VStack {
HStack {
TextField("I insert the name of the product", text: $newName)
Button(action: { self.add()
self.newName = ""
})
{ Image(systemName: "plus") }
}
List {
ForEach(list, id: \.self) {
product in ViewItem(product: product)
}
}
}
}
public func add() {
let newProduct = Product(context: context)
newProduct.name = newName
do {
try context.save()
} catch {
print(error)
}
}
}
struct ViewItem: View {
#State var product: Product
#State var refresh: Bool = false
var body: some View {
NavigationLink(destination: ViewDetail(product: product, refresh: $refresh)) {
HStack(alignment: .top) {
Button( action: {
self.clean()
self.product.isSelected.toggle()
}) {
if self.product.isSelected == true {
Image(systemName: "checkmark")
} else {
Image(systemName: "checkmark").colorInvert()
}
}
VStack() {
Text(product.name)
if product.password != "" {
Text("Password : " + product.password)
}
Text(String(refresh)).hidden()
}
}
}
.onAppear {
self.refresh = false
}
}
}
I've been thinking about it, but I don't know how to go about it...
func clean() {
for( index ) in self.product.isSelected.indices {
self.product[index]isSelected = false
}
}
You need to create a query to flip the state of the isSelected flag. This logic is best kept out of the view system so you can use it anywhere.
You create a SelectionHandler
import CoreData
class SelectionHandler {
func clearSelection(in context: NSManagedObjectContext) {
for item in currentSelected(in: context) {
item.isSelected = false
}
}
func selectProduct(_ product: Product) {
guard let context = product.managedObjectContext else {
assertionFailure("broken !")
return
}
clearSelection(in: context)
product.isSelected = true
}
func currentSelected(in context: NSManagedObjectContext) -> [Product] {
let request = NSFetchRequest<Product>(entityName: Product.entity().name!)
let predicate = NSPredicate(format: "isSelected == YES")
request.predicate = predicate
do {
let result = try context.fetch(request)
return result
} catch {
print("fetch error =",error)
return []
}
}
}
which you can then use to select your desired product.
SelectionHandler().selectProduct(product)
As it stands your NavigationLink will do nothing because the parent list is not held in a NavigationView so you'll need to change the body of ViewList to look like this.
var body: some View {
NavigationView {
VStack {
HStack {
TextField("Create product with name", text: $newName)
Button(action: {
self.add()
self.newName = ""
})
{ Image(systemName: "plus") }
}
.padding()
List {
ForEach(list, id: \.self) { product in
ViewItem(product: product)
}
}
}
}
}
and in ViewItem , Product should be an ObservedObject so that changes are detected in the managedObject.
struct ViewItem: View {
#ObservedObject var product: Product
#State var refresh: Bool = false
var checkmarkImage: some View {
return Group {
if self.product.isSelected {
Image(systemName: "checkmark")
} else {
Image(systemName: "checkmark").colorInvert()
}
}
}
var body: some View {
NavigationLink(destination: ViewDetail(product: product, refresh: $refresh)) {
HStack {
checkmarkImage
Text(product.name ?? "wat")
}
}
}
}
The original Button won't play with the NavigationLink but you can simply apply the selection to onAppear in ViewDetail
struct ViewDetail: View {
#ObservedObject var product: Product
#Binding var refresh: Bool
var body: some View {
VStack {
Text("Hello, World!")
Text("Product is \(product.name ?? "wat")")
}
.onAppear {
SelectionHandler().selectProduct(self.product)
}
}
}