How to pass value form view to view model in SwiftUI? - ios

I am having one email filed in view, that value I want to pass to viewModel. But I am not sure how.
Email View
import SwiftUI
struct EmailView: View {
#State private var email: String = ""
init(viewModel:EmailViewModel) {
self.viewModel = viewModel
}
TextField("", text: $email)
func sendButtonAction() {
viewModel.updateDataToServer()
}
}
Email Viewmodel
import Foundation
import SwiftUI
class EmailViewModel: NSObject, ObservableObject {
var emailText = ""
convenience init(emailText: String) {
self.init()
self.emailText = emailText
}
func updateDataToServer() {
print("Show email text" + emailText). // not getting email value here???
}
}
I am coming to email screen from other screen. How should I pass email value form here?
NavigationLink(destination: EmailView(viewModel: EmailViewModel(emailText: "")), isActive: $pushToMail) {
EmptyView()
}.hidden()

Try this type of approach as shown in the example code:
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
#StateObject var emailModel = EmailViewModel()
var body: some View {
NavigationView {
VStack (spacing: 55) {
NavigationLink(destination: EmailView(viewModel: emailModel)) {
Text("go to EmailView")
}
Text("email was: \(emailModel.emailText)")
}
}.navigationViewStyle(.stack)
}
}
class EmailViewModel: ObservableObject {
#Published var emailText = ""
func updateDataToServer() {
print("----> Show email text " + emailText)
}
}
struct EmailView: View {
#ObservedObject var viewModel: EmailViewModel
var body: some View {
TextField("Enter your email", text: $viewModel.emailText).border(.green)
.onSubmit {
viewModel.updateDataToServer()
}
}
}

Related

Supply any string value to swiftUI form in different for textfield

I have two view file. I have textfield. I want to supply string value to the textfield from another view
File 1 :- Place where form is created
struct ContentView: View {
#State var subjectLine: String = ""
var body: some View {
form {
Section(header: Text(NSLocalizedString("subjectLine", comment: ""))) {
TextField("SubjectLine", text: $subjectLine
}
}
}
}
File 2 :- Place where I want to provide value to the string and that will show in the textfield UI
struct CalenderView: View {
#Binding var subjectLine: String
var body : some View {
Button(action: {
subjectLine = "Assigned default value"
}, label: {
Text("Fill textfield")
}
}
})
}
}
This is not working. Any other way we can supply value to the textfield in other view file.
As i can understand you have binding in CalenderView
that means you want to navigate there when you navigate update there.
struct ContentView: View {
#State private var subjectLine: String = ""
#State private var showingSheet: Bool = false
var body: some View {
NavigationView {
Form {
Section(header: Text(NSLocalizedString("subjectLine", comment: ""))) {
TextField("SubjectLine", text: $subjectLine)
}
}
.navigationBarItems(trailing: nextButton)
.sheet(isPresented: $showingSheet) {
CalenderView(subjectLine: $subjectLine)
}
}
}
var nextButton: some View {
Button("Next") {
showingSheet.toggle()
}
}
}
CalendarView
struct CalenderView: View {
#Binding var subjectLine: String
#Environment(\.dismiss) private var dismiss
var body: some View {
Button {
subjectLine = "Assigned default value"
dismiss()
} label: {
Text("Fill textfield")
}
}
}

SwiftUI view parameter does not update as expected

I am curious why this .fullScreenCover display of a view does not update properly with a passed-in parameter unless the parameter is using the #Binding property wrapper. Is this a bug or intended behavior? Is this the fact that the view shown by the fullScreenCover is not lazily generated?
import SwiftUI
struct ContentView: View {
#State private var showFullScreen = false
#State private var message = "Initial Message"
var body: some View {
VStack {
Button {
self.message = "new message"
showFullScreen = true
} label: {
Text("Show Full Screen")
}
}.fullScreenCover(isPresented: $showFullScreen) {
TestView(text: message)
}
}
}
struct TestView: View {
var text: String
var body: some View {
Text(text)
}
}
There is a different fullScreenCover for passing in dynamic data, e.g.
import SwiftUI
struct CoverData: Identifiable {
var id: String {
return message
}
let message: String
}
struct FullScreenCoverTestView: View {
#State private var coverData: CoverData?
var body: some View {
VStack {
Button {
coverData = CoverData(message: "new message")
} label: {
Text("Show Full Screen")
}
}
.fullScreenCover(item: $coverData, onDismiss: didDismiss) { item in
TestView(text: item.message)
.onTapGesture {
coverData = nil
}
}
}
func didDismiss() {
// Handle the dismissing action.
}
}
struct TestView: View {
let text: String
var body: some View {
Text(text)
}
}
More info and an example in the docs:
https://developer.apple.com/documentation/SwiftUI/AnyView/fullScreenCover(item:onDismiss:content:)

How to pass data between ViewModels in SwiftUI

I have this use case where I have a parent view and a child view. Both of the views have their own corresponding ViewModels.
ParentView:
struct ParentView: View {
#StateObject var parentViewModel = ParentViewModel()
var body: some View {
NavigationView {
List {
TextField("Add Name", text: $parentViewModel.newListName)
NavigationLink(destination: ChildView()) {
Label("Select Products", systemImage: K.ListIcons.productsNr)
}
}
}
}
ParentViewModel:
class ParentViewModel: ObservableObject {
#Published var newListName: String = ""
func saveList() {
// some logic to save to CoreData, method would be called via a button
// how do I reference "someString" from ChildViewModel in this ViewModel?
}
}
ChildView:
struct ChildView: View {
#StateObject var childViewModel = ChildViewModel()
var body: some View {
NavigationView {
List{
Text("Some element")
.onTapGesture {
childViewModel.alterData()
}
}
}
}
}
ChildViewModel:
class ChildViewModel: ObservableObject {
#Published var someString: String = ""
func alterData() {
someString = "Toast"
}
}
My question now is, how do I pass the new value of "someString" from ChildViewModel into the ParentViewModel, in order to do some further stuff with it?
I've tried to create a #StateObject var childViewModel = ChildViewModel() reference in the ParentViewModel, but that does obviously not work, as this will create a new instance of the ChildViewModel and therefore not know of the changes made to "someString"
Solution:
As proposed by Josh, I went with the approach to use a single ViewModel instead of two. To achieve this, the ParentView needs a .environmentObject(T) modifier.
ParentView:
struct ParentView: View {
#StateObject var parentViewModel = ParentViewModel()
var body: some View {
NavigationView {
List {
TextField("Add Name", text: $parentViewModel.newListName)
NavigationLink(destination: ChildView()) {
Label("Select Products", systemImage: K.ListIcons.productsNr)
}
}
}.environmentObject(parentViewModel)
}
The ChildView then references that environment Object via #EnvironmentObject without an initializer:
struct ChildView: View {
#EnvironmentObject var parentViewModel: ParentViewModel
var body: some View {
NavigationView {
List{
Text("Some element")
.onTapGesture {
parentViewModel.alterData()
}
}
}
}
}
Most likely you would use a binding for this situation:
struct ChildView: View {
#Binding var name: String
var body: some View {
NavigationView {
List{
Text("Some element")
.onTapGesture {
name = "Altered!"
}
}
}
}
}
And in the parent:
struct ParentView: View {
#StateObject var parentViewModel = ParentViewModel()
var body: some View {
NavigationView {
List {
TextField("Add Name", text: $parentViewModel.newListName)
NavigationLink(destination: ChildView(name: $parentViewModel.newListName)) {
Label("Select Products", systemImage: K.ListIcons.productsNr)
}
}
}
}
Also, I think you can remove the NavigationView view from ChildView. Having it ParentView is enough.

How to pass data from one model to another model?

I have two different views each with a model to hold their data. I'm trying to pass the value of one variable in the model to be used in the other model but the value from the first model isn't being passed.
First file
struct Number: View {
#StateObject var model = NumberModel()
var body: some View {
NavigationView {
Form {
Section {
TextField("Enter your first number", text: $model.firstNum)
.keyboardType(.decimalPad)
}
Section {
Text("\(model.firstNum)")
}
}
}
}
}
Model for first file
class NumberModel: ObservableObject {
#Published var firstNum: String
init() {
self.firstNum = ""
}
}
Second file
struct SecondNumber: View {
#StateObject var model = SecondNumberModel()
var body: some View {
NavigationView {
Form {
Section {
TextField("Enter your second number", text: $model.secondNum)
.keyboardType(.decimalPad)
}
Section {
Button {
model.add()
} label: {
Text("Press Me!!!")
}
}
Section {
Text("\(model.total)")
}
}
}
}
}
Model for second file
class SecondNumberModel: ObservableObject {
#ObservedObject var model = NumberModel()
#Published var secondNum: String
#Published var total: Int
init() {
self.secondNum = ""
self.total = 0
}
func add() {
self.total = Int(self.secondNum + self.model.firstNum) ?? 0
}
}
This is the content view
struct ContentView: View {
var body: some View {
TabView {
Number()
.tabItem {
Image(systemName: "circle.fill")
Text("First")
}
SecondNumber()
.tabItem {
Image(systemName: "circle.fill")
Text("Second")
}
}
}
}
I'm trying to get user input from the first file and then send that number to the second file to be added with the second number gathered. But the value of the first number doesn't get passed into the second file's model. Appreciate any help. Thanks.
If you have sibling views that need to share state, that state should be controlled by the parent view. For example, this would work in your case:
class NumberModel: ObservableObject {
#Published var firstNum: String = ""
#Published var secondNum: String = ""
#Published var total: Int = 0
func add() {
self.total = Int(self.secondNum + self.firstNum) ?? 0
}
}
struct Number: View {
#ObservedObject var model : NumberModel
var body: some View {
NavigationView {
Form {
Section {
TextField("Enter your first number", text: $model.firstNum)
.keyboardType(.decimalPad)
}
Section {
Text("\(model.firstNum)")
}
}
}
}
}
struct SecondNumber: View {
#ObservedObject var model : NumberModel
var body: some View {
NavigationView {
Form {
Section {
TextField("Enter your second number", text: $model.secondNum)
.keyboardType(.decimalPad)
}
Section {
Button {
model.add()
} label: {
Text("Press Me!!!")
}
}
Section {
Text("\(model.total)")
}
}
}
}
}
struct ContentView: View {
#StateObject private var appState = NumberModel()
var body: some View {
TabView {
Number(model: appState)
.tabItem {
Image(systemName: "circle.fill")
Text("First")
}
SecondNumber(model: appState)
.tabItem {
Image(systemName: "circle.fill")
Text("Second")
}
}
}
}

When a property value is changed in a class via #Published, some of the observedObjects wont get updated?

These are all the codes.
the SurveyQuestion class
class SurveyQuestion: ObservableObject {
#Published var text: String
init() {
print("Initialising now ...")
self.text = "HELLO"
changeText()
}
func changeText() {
print("Changing Text Now from \(text).. ")
if self.text == "HELLO"{
self.text = "BYE"
}
else{
self.text = "HELLO"
}
print("to \(self.text) \n")
}
}
SubView.swift
struct SubView: View {
#ObservedObject var someOtherClass = SurveyQuestion()
var body: some View {
Text("Text now is \(someOtherClass.text)")
}
}
ContentView.swift
struct ContentView: View {
#ObservedObject var someClass = SurveyQuestion()
var body: some View {
VStack{
Button(action: {
print("Changing Text Now !")
self.someClass.changeText()
}) {
Text("Change Text ")
}
Text("Text now is \(someClass.text)")
SubView()
}
}
}
Whenever I click 'changeText' button, it changes the text in Text("Text now is (someClass.text)") but not Subview(). They should all be updated with the same text change.
Any idea what went wrong here?
To make it work you should use same instance of ObservableObject, like below
struct SubView: View {
#ObservedObject var someOtherClass: SurveyQuestion // to be injected
var body: some View {
Text("Text now is \(someOtherClass.text)")
}
}
struct ContentView: View {
#ObservedObject var someClass = SurveyQuestion() // created
var body: some View {
VStack{
Button(action: {
print("Changing Text Now !")
self.someClass.changeText()
}) {
Text("Change Text ")
}
Text("Text now is \(someClass.text)")
SubView(someOtherClass: someClass) // << injected
}
}
}
Your someClass and someOtherClass are two completely different objects. Changing one has no effect on the other. If this was supposed to be a singleton observable that could affect different views simultaneously, you wanted an environment object.

Resources