SwiftUI: assign binding variable from json parsed object - swift

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 })
}

Related

Textfield toggling Bool when clicked

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
}

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 = ""

NavigationLink causing ChildView to reinitialize whenever ParentView is visible again (SwiftUI)

I currently have an app where the user goes through pages of lists to make multiple selections from. (using NavigationLinks)
PROBLEM: The functionality is fine if the user simply makes their selection then moves on, however the issue is when the user goes back THEN forward to a page. I.e. ViewA -> ViewB -> View->A -> ViewB.
Doing this causes ViewB to reinitialize and delete all previous selections on that page, even if ViewA didn't update.
Note that using the back button preserves selections as expected.
EXPECTED BEHAVIOR:
I want to preserve states through navigation of these pages.
ViewA:
struct YouthEventCheckInView: View {
#StateObject var trackable = TrackableMetricsManager(metricType: TrackableMetricType.Event, isCheckin: true)
#StateObject var event = CustomMetricManager()
#StateObject var checkInViewModel = CheckInViewModel()
#State private var moveToDailyStressorsView = false
#State private var newEvent = false
var body: some View {
NavigationView {
ZStack {
ScrollView {
VStack(alignment: .leading) {
NavigationLink(destination: YouthStressorCheckInView(checkInViewModel: checkInViewModel), isActive: $moveToDailyStressorsView) {
EmptyView()
}
Button {
moveToDailyStressorsView = true
} label: {
HStack {
Text("Next")
}
.navigationTitle("Major Life Events")
.onAppear {
trackable.observeEvents()
}
}
}
ViewB (ViewC is same setup as this one):
struct YouthStressorCheckInView: View {
#StateObject var trackable = TrackableMetricsManager(metricType: TrackableMetricType.Stressor, isCheckin: true)
#StateObject var stressor = CustomMetricManager()
#ObservedObject var checkInViewModel: CheckInViewModel
#State private var moveToCopingStrategiesView = false
#State private var newStressor = false
var body: some View {
ZStack {
ScrollView {
VStack(alignment: .leading) {
NavigationLink(destination: YouthStrategyCheckInView(checkInViewModel: checkInViewModel), isActive: $moveToCopingStrategiesView) {
EmptyView()
}
Button( action: {
moveToCopingStrategiesView = true
}, label: {
HStack {
Text("Next")
})
}
}
.navigationTitle("Daily Stressors")
.onAppear {
trackable.observeStressors()
}
}
ViewModel for these views:
class ViewCheckInViewModel: ObservableObject {
struct Item: Hashable {
let name: String
let color: String
let image: String
}
#Published var loading = false
#Published var majorLifeEvents: [Item] = []
#Published var dailyStressors: [Item] = []
#Published var copingStrategies: [Item] = []
#Published var date: String = ""
func loadData(withDataStore dataStore: AWSAppSyncDataStore, checkInId: String) {
self.checkInId = checkInId
loadDate(withDataStore: dataStore)
loadMajorLifeEvents(withDataStore: dataStore)
loadDailyStressors(withDataStore: dataStore)
loadCopingStrategies(withDataStore: dataStore)
}
private func loadMajorLifeEvents(withDataStore dataStore: AWSAppSyncDataStore) {
...
}
private func loadDailyStressors(withDataStore dataStore: AWSAppSyncDataStore) {
...
}
private func loadCopingStrategies(withDataStore dataStore: AWSAppSyncDataStore) {
...
}
NOTE: Obviously some code is taken out, I left the things that I thought were necessary for this issue

#State with a #Appstorage property does not update a SwiftUI View

I organized some settings to be stored in UserDefauls in a struct like this, because I want to have them in one place and to have getters and Setters.
enum PrefKeys : String {
case KEY1
case KEY2
var key: String { return self.rawValue.lowercased()}
}
struct Preferences {
#AppStorage(PrefKeys.KEY1.key) private var _pref_string_1 = ""
#AppStorage(PrefKeys.KEY1.key) var pref_string_2 = ""
var pref_string_1: String {
set { _pref_string_1 = newValue.lowercased() }
get { return _pref_string_1.lowercased() }
}
}
using it like this works fine:
struct ContentView: View {
var p = Preferences()
var body: some View {
NavigationView{
VStack(alignment: .leading){
Text("pref_string_1: \(p.pref_string_1)")
Text("pref_string_2: \(p.pref_string_2)")
NavigationLink("Sub",destination: SubView())
}
}
.padding()
}
}
If I use p as a #State var, it does not update the view, when the #State var is changed:
struct SubView: View {
#State var psub = Preferences()
#AppStorage("standalone pref") private var standalonePref = ""
var body: some View {
VStack(alignment: .leading){
Text("Preference1 in struct: \(psub.pref_string_1)")
TextField("Preference1 in struct:", text: $psub.pref_string_1)
Text("standalonePref \(standalonePref)")
TextField("standalonePref:", text: $standalonePref)
}
}
}
How can I fix this?

Passing a state variable to parent view

I have the following code:
struct BookView: View {
#State var title = ""
#State var author = ""
var body: some View {
TextField("Title", text: $title)
TextField("Author", text: $author)
}
}
struct MainView: View {
#State private var presentNewBook: Bool = false
var body: some View {
NavigationView {
// ... some button that toggles presentNewBook
}.sheet(isPresented: $presentNewBook) {
let view = BookView()
view.toolbar {
ToolbarItem(placement: principal) {
TextField("Title", text: view.$title)
}
}
}
}
}
This compiles but is giving me the following error on runtime:
Accessing State's value outside of being installed on a View. This will result in a constant Binding of the initial value and will not update.
How do I pass a state variable to some other outside view? I can't use ObservableObject on BookView since that would require me to change it from struct to class
In general, your state should always be owned higher up the view hierarchy. Trying to access the child state from a parent is an anti-pattern.
One option is to use #Bindings to pass the values down to child views:
struct BookView: View {
#Binding var title : String
#Binding var author : String
var body: some View {
TextField("Title", text: $title)
TextField("Author", text: $author)
}
}
struct ContentView: View {
#State private var presentNewBook: Bool = false
#State private var title = ""
#State private var author = ""
var body: some View {
NavigationView {
VStack {
Text("Title: \(title)")
Text("Author: \(author)")
Button("Open") {
presentNewBook = true
}
}
}.sheet(isPresented: $presentNewBook) {
BookView(title: $title, author: $author)
}
}
}
Another possibility is using an ObservableObject:
class BookState : ObservableObject {
#Published var title = ""
#Published var author = ""
}
struct BookView: View {
#ObservedObject var bookState : BookState
var body: some View {
TextField("Title", text: $bookState.title)
TextField("Author", text: $bookState.author)
}
}
struct ContentView: View {
#State private var presentNewBook: Bool = false
#StateObject private var bookState = BookState()
var body: some View {
NavigationView {
VStack {
Text("Title: \(bookState.title)")
Text("Author: \(bookState.author)")
Button("Open") {
presentNewBook = true
}
}
}.sheet(isPresented: $presentNewBook) {
BookView(bookState: bookState)
}
}
}
I've altered your example views a bit because to me the structure was unclear, but the concept of owning the state at the parent level is the important element.
You can also pass a state variable among views as such:
let view = BookView(title: "foobar")
view.toolbar {
ToolbarItem(placement: principal) {
TextField("Title", text: view.$title)
}
}
Then, inside of BookView:
#State var title: String
init(title: String) {
_title = State(initialValue: title)
}
Source: How could I initialize the #State variable in the init function in SwiftUI?