Textfield toggling Bool when clicked - swift

I have a Bool value titled editMode which determines if the user can edit information on a current page. When this is toggled true and I enter a textfield, the Bool is flipped back to false. This only occurs on the first click into the textfield. On subsequent clicks into the textfield, it does not toggle (working fine)
How can I fix this issue?
struct RecipeControllerModal: View {
#Environment(\.dismiss) var dismiss // << dismiss view
#ObservedObject var rm = RecipeLogic()
#ObservedObject var ema = EditModeActive() // calling in next view
var body: some View {
NavigationView{
VStack{
VStack{
RecipeDashHeader(ema: ema) // <<passing editMode
.padding()
}
}
}
RecipeDashHeader // <<textfield view
struct RecipeDashHeader: View {
#State var recipeName = ""
#ObservedObject var ema: EditModeActive
#ViewBuilder
var body: some View {
if ema.editMode {
VStack{
TextField(recipeName, text: $recipeName)
.foregroundColor(!ema.editMode ? Color.black : Color.red)
.font(.title2)
.multilineTextAlignment(.center)
.padding()
.onChange(of: recipeName, perform: { _ in
ema.recipeTitle = recipeName
})
}
Published EditMode Variable
class EditModeActive: ObservableObject {
#Published var editMode: Bool = false
#Published var recipeTitle = ""
}

Try this approach using the ema.recipeTitle directly in your TextField and
using #StateObject var ema as mentioned in the comments.
Here is the example code I used in my tests:
struct ContentView: View {
var body: some View {
RecipeControllerModal()
}
}
struct RecipeControllerModal: View {
#Environment(\.dismiss) var dismiss // << dismiss view
// #ObservedObject var rm = RecipeLogic() // for testing
#StateObject var ema = EditModeActive() // <-- here
var body: some View {
NavigationView{ // <-- here should use NavigationStack
VStack{
VStack{
RecipeDashHeader(ema: ema) // <<passing editMode
.padding()
}
// for testing
Button(ema.editMode ? "save" : "edit") {
ema.editMode.toggle()
}
}
}
}
}
struct RecipeDashHeader: View {
// #State var recipeName = "" // <-- here not needed
#ObservedObject var ema: EditModeActive
var body: some View {
if ema.editMode {
VStack{
TextField(ema.recipeTitle, text: $ema.recipeTitle) // <-- here
.foregroundColor(!ema.editMode ? Color.black : Color.red) // <--- usesless here ema.editMode == true always
.font(.title2)
.multilineTextAlignment(.center)
.padding()
.border(.red)
// not needed
// .onChange(of: ema.recipeTitle, perform: { name in
// ema.recipeTitle = name
// })
}
} else {
Text(ema.recipeTitle)
}
}
}
class EditModeActive: ObservableObject {
#Published var editMode: Bool = false
#Published var recipeTitle: String = "recipe title" // <-- here
}

Related

How to clear variable when triggered in another view

I am attempting to clear variable values when a button is pressed in another view. Code below:
Root view
struct RecipeEditorHomeMenu: View {
#Environment(\.dismiss) var dismiss
#State var recipeAddedSuccess = false
#State private var showSaveButton = false
#State var showSuccessMessage = false
#State private var sheetMode: SheetMode = .none
#ObservedObject var recipeClass = Recipe()
var onDismiss: (() -> Void)?
var body: some View {
GeometryReader{ geo in
VStack{
if showSuccessMessage {
RecipeSuccessPopUp(shown: $showSuccessMessage, onDismiss: onDismiss)
}
RecipeEditorView(showSuccessMessage: $showSuccessMessage)
.blur(radius: showSuccessMessage ? 15 : 0)
.padding(.top, 80)
// Spacer()
//display save button
FlexibleSheet(sheetMode: $sheetMode) {
SaveRecipeButton(showSuccessMessage: $showSuccessMessage)
//sets coordinates of view on dash
.offset(y:-200)
}
}
.onAppear{
recipeAddedSuccess = false
}
//center view
.alignmentGuide(VerticalAlignment.center, computeValue: { $0[.bottom] })
.position(x: geo.size.width / 2, y: geo.size.height / 2)
.environmentObject(recipeClass)
}
}
}
EditorView (where I would like to clear variables and refresh the view so now it appears new and no variables have values anymore:
struct RecipeEditorView: View {
#EnvironmentObject var recipeClass: Recipe
#State var recipeTitle = ""
#State private var recipeTime = "Cook Time"
#State private var pickerTime: String = ""
//calls macro pickers#State var proteinPicker: Int = 0
#Binding var showSuccessMessage: Bool
var cookingTime = ["5 Mins", "10 Mins","15 Mins","20 Mins","25 Mins","30 Mins ","45 Mins ","1 Hour","2 Hours", "A Long Time", "A Very Long Time"]
var resetPickerTime: (() -> Void)?
var body: some View {
VStack{
TextField("Recipe Title", text: $recipeTitle)
.onChange(of: recipeTitle, perform: { _ in
recipeClass.recipeTitle = recipeTitle
})
.foregroundColor(Color.black)
.font(.title3)
.multilineTextAlignment(.center)
//macro selectors
HStack{
Picker(selection: $carbPicker, label: Text("")) {
ForEach(pickerGramCounter(), id: \.self) {
Text(String($0) + "g Carbs")
}
.onChange(of: carbPicker, perform: { _ in
recipeClass.recipeCarbMacro = carbPicker
})
}
.accentColor(.gray)
}
.accentColor(.gray)
}
HStack(spacing: 0){
ZStack{
Image(systemName:("clock"))
.padding(.leading, 150)
.foregroundColor(Color("completeGreen"))
Picker(selection: $pickerTime, label: Text("Gender")) {
ForEach(cookingTime, id: \.self) {
Text($0)
}
}
.onChange(of: pickerTime, perform: { _ in
recipeClass.recipePrepTime = pickerTime
})
.accentColor(.gray)
}
.multilineTextAlignment(.center)
}
}
}
}
Button where I save the recipes (and would like to clear the variables here:
struct SaveRecipeButton: View {
#Binding var showSuccessMessage: Bool
//stores recipe
#EnvironmentObject var recipeClass: Recipe
#State var isNewRecipeValid = false
static var newRecipeCreated = false
var body: some View {
Button(action: {
//action
}){
HStack{
Image(systemName: "pencil").resizable()
.frame(width:40, height:40)
.foregroundColor(.white)
Button(action: {
if (recipeClass.recipeTitle != "" &&
recipeClass.recipePrepTime != "" &&
){
isNewRecipeValid = true
showSuccessMessage = true
saveRecipe()
}
else{
showSuccessMessage = false
isNewRecipeValid = false
}
}){
Text("Save Recipe")
.font(.title)
.frame(width:200)
.foregroundColor(.white)
}
}
.padding(EdgeInsets(top: 12, leading: 100, bottom: 12, trailing: 100))
.background(
RoundedRectangle(cornerRadius: 10)
.fill(
Color("completeGreen")))
}
.transition(.sideSlide)
.animation(.easeInOut(duration: 0.25))
}
}
My recipe class holds each variable as #Published variables. I tried clearing the variables in this class as well, but the values don't refresh in the view (example: If I have a recipeTitle as "Eggs" and save that recipe down, recipeTitle does not update in the view)
**update recipe class:
class Recipe: ObservableObject, Identifiable {
let id = UUID().uuidString
#Published var recipeTitle: String = ""
#Published var isCompleted = false
#Published var recipeCaloriesMacro: Int = 0
#Published var recipeFatMacro: Int = 0
#Published var recipeProteinMacro: Int = 0
}
I asume you want to reset the values in Recipe. The reason because this doesn´t reflect into RecipeEditorView are the #State values in your RecipeEditorView.
You are neglecting a basic principle of SwiftUI: "Single source of truth".
RecipeEditorView has several #State values that you then bind to your Pickers and TextFields. Then you observe their changes and synchronize with your ViewModel Recipe.
Don´t do that, use the ViewModel itself as the (I know I´m repeating myself here) "Single source of truth".
Another issue. If a view manages the lifecycle of an ObservableObject like instantiating it and holding on to it, declare it as #StateObject. #ObservedObject is only needed it the class is injected and managed up the hierarchy.
In RecipeEditorHomeMenu:
#StateObject var recipeClass = Recipe()
in RecipeEditorView remove all that #State vars that are used to define the recipe e.g.: recipeTitle, carbPicker and pickerTime and bind to the values in the ViewModel.
Possible implementation for recipeTitle:
// this needs to be changed
TextField("Recipe Title", text: $recipeClass.recipeTitle)
Do this for the others too.
Now in SaveRecipeButton you can change these vars to whatever value you want. E.g.: resetting the title would look like:
recipeClass.recipeTitle = ""

SwiftUI - Save status when language is selected

I have successfully displayed the language in the UI, but I have a problem: when I click the "Save" button it still doesn't save the language I selected.
I want when I have selected the language and clicked the Save Button , it will return to the previous page and when I click it again it will show the language I selected before.
Above data is simulation only, I mainly focus on its features
All my current code
struct ContentView: View {
var language: String? = ""
var body: some View {
NavigationView {
HStack {
NavigationLink(destination:LanguageView(language: language)) {
Text("Language")
.padding()
Spacer()
Text(language!)
.padding()
}
}
}
}
}
struct LanguageView: View {
#Environment(\.presentationMode) var pres
#State var language: String?
#State var selectedLanguage: String? = ""
var body: some View {
VStack {
CustomLanguageView(selectedLanguage: $language)
Button(action: {
language = selectedLanguage
pres.wrappedValue.dismiss()
})
{
Text("Save")
.foregroundColor(.black)
}
.padding()
Spacer()
}
}
}
struct CustomLanguageView: View {
var language = ["US", "English", "Mexico", "Canada"]
#Binding var selectedLanguage: String?
var body: some View {
LazyVStack {
ForEach(language, id: \.self) { item in
SelectionCell(language: item, selectedLanguage: self.$selectedLanguage)
.padding(.trailing,40)
Rectangle().fill(Color.gray)
.frame( height: 1,alignment: .bottom)
}
.frame(height:15)
}
}
}
struct SelectionCell: View {
let language: String
#Binding var selectedLanguage: String?
var body: some View {
HStack {
Text(language)
Spacer()
if language == selectedLanguage {
Image(systemName: "checkmark")
.resizable()
.frame(width:20, height: 15)
}
}
.onTapGesture {
self.selectedLanguage = self.language
}
}
}
Edit to my previous answer, since something is blocking my edit to my previous answer, this one shows all the code I used to make it works well for me:
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
#State var language: String? = ""
var body: some View {
NavigationView {
HStack {
NavigationLink(destination: LanguageView(language: $language)) {
Text("Language").padding()
Spacer()
Text(language!).padding()
}
}
}
}
}
struct LanguageView: View {
#Environment(\.presentationMode) var pres
#Binding var language: String?
var body: some View {
VStack {
CustomLanguageView(selectedLanguage: $language) // <--- here
Button(action: { pres.wrappedValue.dismiss() }) { // <--- here
Text("Save").foregroundColor(.black)
}.padding()
Spacer()
}
}
}
struct CustomLanguageView: View {
var language = ["US", "English", "Mexico", "Canada"]
#Binding var selectedLanguage: String?
var body: some View {
LazyVStack {
ForEach(language, id: \.self) { item in
SelectionCell(language: item, selectedLanguage: self.$selectedLanguage).padding(.trailing,40)
Rectangle().fill(Color.gray).frame( height: 1,alignment: .bottom)
}.frame(height:15)
}
}
}
struct SelectionCell: View {
let language: String
#Binding var selectedLanguage: String?
var body: some View {
HStack {
Text(language)
Spacer()
if language == selectedLanguage {
Image(systemName: "checkmark").resizable().frame(width:20, height: 15)
}
}
.onTapGesture {
self.selectedLanguage = self.language
}
}
}
you could use #State var language throughout and use dismiss, such as this,
to achieve what you want:
struct LanguageView: View {
#Environment(\.dismiss) var dismiss // <--- here
#Binding var language: String?
var body: some View {
VStack {
CustomLanguageView(selectedLanguage: $language) // <--- here
Button(action: {
dismiss() // <--- here
})
{
Text("Save").foregroundColor(.black)
}
.padding()
Spacer()
}
}
}

SwiftUI: How to pass a data from one view and use it in the viewModel of another view

Just playing around with passing data between views and using it in view models. Below is the code for the FirstView from which to pass a phone number:
struct FirstView: View {
#State private var phone: String = "9876543210"
#State private var isPresented: Bool = false
var body: some View {
ZStack {
Color.background
VStack {
Spacer()
TextField("type here...", text: $phone)
.onTapGesture {
isPresented.toggle()
}
.fullScreenCover(isPresented: $isPresented, content: {
SecondView(phone: phone)
})
Spacer()
}
}
}
}
How do I use the phone data passed from main view in sub view model as the initial value for the textfield? Below is the unfinished code for SecondView:
struct SecondView: View {
#StateObject private var viewModel = SecondViewModel()
let phone: String // how to assign this obtained data in viewModel.phone?
var body: some View {
ZStack {
Color.background
VStack {
Spacer()
// textfield default text should be the phone number passed from FirstView
TextField("type here...", text: $viewModel.phone)
Spacer()
}
}
}
}
Here's the second view model:
final class SecondViewModel: ObservableObject {
#Published var phone = ""
}
Use init and StateObject(wrappedValue:). Here is the possible solution.
Your view model
final class SubViewModel: ObservableObject {
#Published var phone = ""
init(phone: String) {
self.phone = phone
}
}
Your subview
struct SubView: View {
#StateObject private var viewModel: SubViewModel
private let phone: String
init(phone: String) {
self.phone = phone
_viewModel = StateObject(wrappedValue: SubViewModel(phone: phone))
}
var body: some View {
ZStack {
Color.background
VStack {
Spacer()
// textfield default text should be the phone number passed from main view
TextField("type here...", text: $viewModel.phone)
Spacer()
}
}
}
}

How to make Sheet toggle and the navigate to a View

I have 3 Views A, BSheet, C.
When I click on a button inside A View, B Sheet becomes visible. I have a button in B Sheet. I want to press a button in BSheet View to close/toggle BSheet and then navigate to C View. Any ideas how I can accomplish that?
A.swift
struct A: View{
#State var displayBSheet = false
var body: some View{
NavigationView{
VStack{
Button(action: {self.displayBSheet.toggle()}){
Text("Navigate to BSheet")
}.sheet(isPresented: $displayBSheet){
BSheet()
}
NavgiationLink(destination: C()){
Text("Navigate To C")
}
}
}
}
}
BSheet.swift
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
struct MainView: View {
var body: some View {
Button(action:{
self.presentationMode.wrappedValue.dismiss()//This only sheet but I want to navigate to C View also
}){
Text("Close sheet and navigate to struct C")
}
}
}
C.swift
struct C: View {
var body: some View {
Text("C")
}
}
I cleaned up your codes for a good working one:
import SwiftUI
struct ContentView: View {
var body: some View {
HomeView()
}
}
struct HomeView: View {
#State var homeView: Bool = false
#State var displayViewB: Bool = false
#State var displayViewC: Bool = false
var body: some View{
ZStack {
VStack {
HStack {
Text("Home").font(Font.largeTitle.bold()).padding()
Spacer()
}
Spacer()
}
VStack {
Button(action: { homeView = true; displayViewB = true }) {
Text("open ViewB")
}
.sheet(isPresented: $homeView) {
if displayViewB
{
ViewB(homeView: $homeView, displayViewB: $displayViewB, displayViewC: $displayViewC)
}
if displayViewC
{
ViewC(homeView: $homeView, displayViewC: $displayViewC)
}
}
}
}
.onChange(of: homeView) { _ in
if displayViewB == false && displayViewC == true {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {homeView = true}
}
else if displayViewB == false && displayViewC == false {
homeView = false
}
}
}
}
struct ViewB: View {
#Binding var homeView: Bool
#Binding var displayViewB: Bool
#Binding var displayViewC: Bool
var body: some View {
Button(action:{
displayViewB = false
displayViewC = true
homeView = false
}){
Text("close ViewB and open Viewc")
}
}
}
struct ViewC: View {
#Binding var homeView: Bool
#Binding var displayViewC: Bool
var body: some View {
Button(action:{
displayViewC = false
homeView = false
}){
Text("close ViewC go to HomeView")
}
}
}
You can do this simple - in onDismiss of sheet
struct A: View{
#State var displayBSheet = false
#State var displayCSheet = false // << this !!
var body: some View{
NavigationView{
VStack{
Button(action: {self.displayBSheet.toggle()}){
Text("Navigate to BSheet")
}.sheet(isPresented: $displayBSheet, onDismiss: {
self.displayCSheet = true // << here !!
}){
BSheet()
}
NavgiationLink(destination: C(), isActive: $displayCSheet){ // << here !!
Text("Navigate To C")
}
}
}
}
}

SwiftUI: assign binding variable from json parsed object

I am trying to assign a value I fetch and parse from JSON to another view.
struct ContentView: View {
#State private var showAlert = false
#State private var showAbout = false
#State private var showModal = false
#State private var title = "hi"
#State private var isCodeSelectorPresented = false
#ObservedObject var fetch = FetchNovitads()
var body: some View {
VStack {
NavigationView {
List(fetch.Novitadss) { Novitads in
VStack(alignment: .leading) {
// 3.
Text(Novitads.name!.de)
.platformFont()
.fontWeight(.black)
Text(Novitads.textTeaser.de)
.platformFont()
.fontWeight(.medium)
.onTapGesture {
self.showModal.toggle()
// 3.
}.sheet(isPresented: self.$showModal) {
ModalView(showModal: self.$showModal,
title: self.$title)
}
In this sample code the title (defined as "hi") is passed correctly.
What I want to do however is to assign the value of Novitads.name!.de to the title variable so that I can use it in the modal view.
I just display self.$title in the ModalView Text("(String(title))")
Then you don't need binding here and pass value directly, like
ModalView(showModal: self.$showModal, title: Novitads.name!.de)
and your ModalView declaration be as
struct ModalView: View {
#Binding showModal: Bool
let title: String
/// .. all other your code
}
Note: #State private var title = "hi" can be removed at all
try assigning the title like this:
struct ContentView: View {
struct ContentView: View {
#State private var showAlert = false
#State private var showAbout = false
#State private var showModal = false
#State private var title = "hi"
#State private var isCodeSelectorPresented = false
#ObservedObject var fetch = FetchNovitads()
var body: some View {
VStack {
NavigationView {
List(fetch.Novitadss) { Novitads in
VStack(alignment: .leading) {
// 3.
Text(Novitads.name!.de)
.platformFont()
.fontWeight(.black)
Text(Novitads.textTeaser.de)
.platformFont()
.fontWeight(.medium)
.onTapGesture {
self.showModal.toggle()
// 3.
}.sheet(isPresented: self.$showModal) {
ModalView(showModal: self.$showModal, title: self.$title)
}
}
}
}
}.onAppear(perform:{ self.title = self.fetch.Novitads.name!.de })
}