I have a function in one view, but a button that should call it in another

This is an expansion of an earlier thread, How to pass data from a TextField to a List (SwiftUI), but I strayed away from the topic and was advised to start a new thread.

To start, there are 2 views I need to work on; CardsView and AddView.

CardsView:
Here's the raw code:
Code Block
//
//  CardsView.swift
//  Lunch Card (iOS)
//
//  Created by Joshua Srery on 12/17/20.
//  Additional code by OOPer on Apple Developer Forums
//
import SwiftUI
struct Card: Identifiable {
    let id = UUID()
    let title: String
}
struct CardsView: View {
    @StateObject var cardsInfo = CardsInfo()
    @State var showSheetView = false
    @State private var editMode = EditMode.inactive
    @State private var cards: [Card] = []
        private static var count = 0
    var body: some View {
        NavigationView {
            List {
                ForEach(cards) { cards in
                    NavigationLink(destination: CardFullView(cname: cardsInfo.newCard.cname, name: cardsInfo.newCard.name, id: cardsInfo.newCard.id)) {
                        CardRow(cname: cardsInfo.newCard.cname, name: cardsInfo.newCard.name, id: cardsInfo.newCard.id)
                    }
                }
                .onDelete(perform: onDelete)
                .onMove(perform: onMove)
            }
                .navigationTitle("Cards")
                .toolbar {
                    ToolbarItem(placement: .navigationBarLeading) {
                        EditButton()
                    }
                    ToolbarItem(placement: .navigationBarTrailing) {
                        Button(action: {
                            self.showSheetView.toggle()
                        }) {
                            Image(systemName: "plus")
                        }.sheet(isPresented: $showSheetView) {
                            AddView(cardInfo: $cardsInfo.newCard)
                    }
                }
            }
            .environment(\.editMode, $editMode)
        }
    }
    private var addButton: some View {
        switch editMode {
        case .inactive:
            return AnyView(Button(action: onAdd) { Image(systemName: "plus") })
        default:
            return AnyView(EmptyView())
        }
    }
        
    func onAdd() {
        withAnimation {
            cards.append(Card(title: "Card #\(Self.count)"))
                Self.count += 1
        }
    }
  
    private func onDelete(offsets: IndexSet) {
        cards.remove(atOffsets: offsets)
    }
    
    private func onMove(source: IndexSet, destination: Int) {
        cards.move(fromOffsets: source, toOffset: destination)
    }
}

As you can see, there is a List being used to show the Cards you have made. Obviously, we start with none. The identifiable Card is being used, along with a declaration of cardsInfo, which is calling CardsInfo, an ObservableObject class.

AddView:
Here's the raw code:
Code Block
//
//  AddView.swift
//  Lunch Card (iOS)
//
//  Created by Joshua Srery on 12/18/20.
//
import SwiftUI
struct AddView: View {
    @Binding var cardInfo: CardInfo
    var body: some View {
        NavigationView {
            VStack {
                CardView(name: cardInfo.name, id: cardInfo.id)
                TextField("Name", text: $cardInfo.name)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .shadow(radius: 10)
                TextField("ID", text: $cardInfo.id)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .shadow(radius: 10)
                TextField("Card Name", text: $cardInfo.cname)
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                    .shadow(radius: 10)
                Button(action: {}) {
                    Text("Create")
                        .bold()
                }
                .disabled(self.cardInfo.name.isEmpty self.cardInfo.id.isEmpty self.cardInfo.cname.isEmpty)
                .foregroundColor(.white)
                .padding()
                .padding(.horizontal, 100)
                .background(Color.accentColor)
                .cornerRadius(10)
            }.padding()
            .navigationTitle(cardInfo.cname)
            .navigationBarTitleDisplayMode(.inline)
      }
    }
}

This view contains a @Binding calling on CardInfo, which is with the CardsInfo class in CardInfo.swift:
Code Block
//
//  CardsInfo.swift
//  Lunch Card (iOS)
//
//  Created by Joshua Srery on 12/21/20.
//  Code provided by OOPer on Apple Developer Forums
//
import Foundation
struct CardInfo {
    var name: String = ""
    var id: String = ""
    var cname: String = ""
}
class CardsInfo: ObservableObject {
    @Published var newCard: CardInfo = CardInfo()
}

By the way, cname means Card Name.

AddView contains three TextFields and a Button. The TextFields are for the user's name, card ID, and card name respectively. The button says Create, and has no action at the moment.

The onAdd function in CardsView adds the information from the filled out cardsInfo to the List.

I need the Create button in AddView to accomplish the same thing, but still adding it to CardView's list.
Answered by OOPer in 654859022
One way to solve your issue is creating a sharable object and move onAdd into the object.
You can extend CardsInfo.

Something like this:
Code Block
class CardsInfo: ObservableObject {
@Published var newCard: CardInfo = CardInfo()
@Published var cards: [Card] = [] //<-
//↓ Move `onAdd` here (and renamed)
func add() {
cards.append(Card(title: "Card #\(cards.count)"))
}
}


Your AddView would look like this sharing CardsInfo:
Code Block
struct AddView: View {
@ObservedObject var cardsInfo: CardsInfo //<- Hold shared object here
var body: some View {
NavigationView {
VStack {
CardView(name: cardsInfo.newCard.name, id: cardsInfo.newCard.id) //<-
TextField("Name", text: $cardsInfo.newCard.name) //<-
.textFieldStyle(RoundedBorderTextFieldStyle())
.shadow(radius: 10)
TextField("ID", text: $cardsInfo.newCard.id) //<-
.textFieldStyle(RoundedBorderTextFieldStyle())
.shadow(radius: 10)
TextField("Card Name", text: $cardsInfo.newCard.cname) //<-
.textFieldStyle(RoundedBorderTextFieldStyle())
.shadow(radius: 10)
Button(action: {
cardsInfo.add() //<- You can call methods in a shared object
}) {
Text("Create")
.bold()
}
.disabled(self.cardsInfo.newCard.name.isEmpty self.cardsInfo.newCard.id.isEmpty self.cardsInfo.newCard.cname.isEmpty) //<-
.foregroundColor(.white)
.padding()
.padding(.horizontal, 100)
.background(Color.accentColor)
.cornerRadius(10)
}.padding()
.navigationTitle(cardsInfo.newCard.cname) //<-
.navigationBarTitleDisplayMode(.inline)
}
}
}

And your CardsView as:
Code Block
struct CardsView: View {
@StateObject var cardsInfo = CardsInfo() //<- Share this object
@State var showSheetView = false
@State private var editMode = EditMode.inactive
//↓ Move `cards` into `CardsInfo`
//@State private var cards: [Card] = []
//↓ You have no need to have `count` where you can easily access `cards.count`
//private static var count = 0
var body: some View {
NavigationView {
List {
ForEach(cardsInfo.cards) { card in
NavigationLink(destination: CardFullView(cname: cardsInfo.newCard.cname, name: cardsInfo.newCard.name, id: cardsInfo.newCard.id)) {
CardRow(cname: cardsInfo.newCard.cname, name: cardsInfo.newCard.name, id: cardsInfo.newCard.id)
}
}
.onDelete(perform: onDelete)
.onMove(perform: onMove)
}
.navigationTitle("Cards")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
EditButton()
}
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
self.showSheetView.toggle()
}) {
Image(systemName: "plus")
}.sheet(isPresented: $showSheetView) {
AddView(cardsInfo: cardsInfo) //<- Pass the shared object
}
}
}
.environment(\.editMode, $editMode)
}
}
//↓ Remove this method
// private var addButton: some View {
// switch editMode {
// case .inactive:
// return AnyView(Button(action: onAdd) { Image(systemName: "plus") })
// default:
// return AnyView(EmptyView())
// }
// }
//↓ Move this into the shared object
// func onAdd() {
// withAnimation {
// cards.append(Card(title: "Card #\(Self.count)"))
// Self.count += 1
// }
// }
private func onDelete(offsets: IndexSet) {
cardsInfo.cards.remove(atOffsets: offsets) //<-
}
private func onMove(source: IndexSet, destination: Int) {
cardsInfo.cards.move(fromOffsets: source, toOffset: destination) //<-
}
}


There may be many parts to fix to make this work, but please try.
Accepted Answer
One way to solve your issue is creating a sharable object and move onAdd into the object.
You can extend CardsInfo.

Something like this:
Code Block
class CardsInfo: ObservableObject {
@Published var newCard: CardInfo = CardInfo()
@Published var cards: [Card] = [] //<-
//↓ Move `onAdd` here (and renamed)
func add() {
cards.append(Card(title: "Card #\(cards.count)"))
}
}


Your AddView would look like this sharing CardsInfo:
Code Block
struct AddView: View {
@ObservedObject var cardsInfo: CardsInfo //<- Hold shared object here
var body: some View {
NavigationView {
VStack {
CardView(name: cardsInfo.newCard.name, id: cardsInfo.newCard.id) //<-
TextField("Name", text: $cardsInfo.newCard.name) //<-
.textFieldStyle(RoundedBorderTextFieldStyle())
.shadow(radius: 10)
TextField("ID", text: $cardsInfo.newCard.id) //<-
.textFieldStyle(RoundedBorderTextFieldStyle())
.shadow(radius: 10)
TextField("Card Name", text: $cardsInfo.newCard.cname) //<-
.textFieldStyle(RoundedBorderTextFieldStyle())
.shadow(radius: 10)
Button(action: {
cardsInfo.add() //<- You can call methods in a shared object
}) {
Text("Create")
.bold()
}
.disabled(self.cardsInfo.newCard.name.isEmpty self.cardsInfo.newCard.id.isEmpty self.cardsInfo.newCard.cname.isEmpty) //<-
.foregroundColor(.white)
.padding()
.padding(.horizontal, 100)
.background(Color.accentColor)
.cornerRadius(10)
}.padding()
.navigationTitle(cardsInfo.newCard.cname) //<-
.navigationBarTitleDisplayMode(.inline)
}
}
}

And your CardsView as:
Code Block
struct CardsView: View {
@StateObject var cardsInfo = CardsInfo() //<- Share this object
@State var showSheetView = false
@State private var editMode = EditMode.inactive
//↓ Move `cards` into `CardsInfo`
//@State private var cards: [Card] = []
//↓ You have no need to have `count` where you can easily access `cards.count`
//private static var count = 0
var body: some View {
NavigationView {
List {
ForEach(cardsInfo.cards) { card in
NavigationLink(destination: CardFullView(cname: cardsInfo.newCard.cname, name: cardsInfo.newCard.name, id: cardsInfo.newCard.id)) {
CardRow(cname: cardsInfo.newCard.cname, name: cardsInfo.newCard.name, id: cardsInfo.newCard.id)
}
}
.onDelete(perform: onDelete)
.onMove(perform: onMove)
}
.navigationTitle("Cards")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
EditButton()
}
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
self.showSheetView.toggle()
}) {
Image(systemName: "plus")
}.sheet(isPresented: $showSheetView) {
AddView(cardsInfo: cardsInfo) //<- Pass the shared object
}
}
}
.environment(\.editMode, $editMode)
}
}
//↓ Remove this method
// private var addButton: some View {
// switch editMode {
// case .inactive:
// return AnyView(Button(action: onAdd) { Image(systemName: "plus") })
// default:
// return AnyView(EmptyView())
// }
// }
//↓ Move this into the shared object
// func onAdd() {
// withAnimation {
// cards.append(Card(title: "Card #\(Self.count)"))
// Self.count += 1
// }
// }
private func onDelete(offsets: IndexSet) {
cardsInfo.cards.remove(atOffsets: offsets) //<-
}
private func onMove(source: IndexSet, destination: Int) {
cardsInfo.cards.move(fromOffsets: source, toOffset: destination) //<-
}
}


There may be many parts to fix to make this work, but please try.
I will try this out! Thank you!
This works! But...I need the Create button to dismiss the AddView sheet. I tried adding showSheetView to a shared object but @State is not allowed there. I tried changing it to @Published but it changed nothing.

I tried adding showSheetView to a shared object but @State is not allowed there. I tried changing it to @Published but it changed nothing. 

Moving showSheetView into a shared object (and making it @Published) should work.
But you can pass a Binding to showSheetView in CardsView to AddView:
Code Block
struct AddView: View {
@ObservedObject var cardsInfo: CardsInfo // Hold shared object here
@Binding var isShowing: Bool //<-
var body: some View {
//...
Button(action: {
cardsInfo.add() // You can call methods in a shared object
isShowing = false //<-
}) {
Text("Create")
.bold()
}
//...
}
}

And in CardsView:
Code Block
var body: some View {
//...
}.sheet(isPresented: $showSheetView) {
AddView(cardsInfo: cardsInfo, isShowing: $showSheetView) //<-
}
//...
}


Please try and see what happens.
This didn't work.

I put showSheetView into a shared object, SheetInfo, and called it in both CardsView and AddView as sheetInfo. I changed every declaration of $showSheetView to $sheetInfo.showSheetView. Still didn't work. Is there something I missed? Here's the code:

SheetInfo:
Code Block
class SheetInfo: ObservableObject {
@Published var showSheetView = false
}


CardsView:
Code Block
struct CardsView: View {
    @StateObject var cardsInfo = CardsInfo()
    @StateObject var sheetInfo = SheetInfo() // <-
    @State private var editMode = EditMode.inactive
    
    var body: some View {
(...)
ToolbarItem(placement: .navigationBarTrailing) {
        Button(action: {
            self.sheetInfo.showSheetView.toggle()
                }) {
                    Image(systemName: "plus")
                    }.sheet(isPresented: $sheetInfo.showSheetView) {
                    AddView(cardsInfo: cardsInfo, sheetInfo: sheetInfo, isShowing: $sheetInfo.showSheetView)
                    }
                }
(...)
}


AddView:
Code Block
struct AddView: View {
    @ObservedObject var cardsInfo: CardsInfo
    @ObservedObject var sheetInfo: SheetInfo
    @Binding var isShowing: Bool
  var body: some View {
(...)
Button(action: {
cardsInfo.add()
isShowing = false
}) {
Text("Create")
.bold()
}
(...)
}

This didn't work.

Thanks for reporting.
And you are right. I made my testing code updated and found that this pattern would not work, when .sheet was added on the Button in a TabBarItem.

Please try this:

CardsView:

Code Block
struct CardsView: View {
@StateObject var cardsInfo = CardsInfo()
@StateObject var sheetInfo = SheetInfo() // <-
@State private var editMode = EditMode.inactive
var body: some View {
NavigationView {
List {
//...
}
.navigationTitle("Cards")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
EditButton()
}
ToolbarItem(placement: .navigationBarTrailing) {
Button(action: {
self.sheetInfo.showSheetView.toggle()
}) {
Image(systemName: "plus")
}//<- No `.sheet` here
}
}
.environment(\.editMode, $editMode)
//↓ Add `.sheet` on `List`
.sheet(isPresented: $sheetInfo.showSheetView) {
AddView(cardsInfo: cardsInfo, sheetInfo: sheetInfo) //<-
}
}
}
//...
}


AddView:

Code Block
struct AddView: View {
@ObservedObject var cardsInfo: CardsInfo
@ObservedObject var sheetInfo: SheetInfo
//@Binding var isShowing: Bool
var body: some View {
//...
Button(action: {
print("Create action")
cardsInfo.add()
sheetInfo.showSheetView = false //<-
}) {
Text("Create")
.bold()
}
//...
}
}


Please try.

Please try.

This worked! Thank you so much! 😊
I have a function in one view, but a button that should call it in another
 
 
Q