Updating SwiftUI view from DB - swift

I am trying to update a view fro a database. As the user types into a field, on each keypress an SQL query is fired that returns a list of items to display.
The problem I have is that the key pres causes the view to redraw and then the DB query changes the data in the view.
I have looked at future and promise but dont understand the swift versions (OK with Java and Scala concurrency).
I have tried using an Observalble instead of a state but get all kinds of problems with that also.
I think I am trying to do this the wrong way but can t think of a better way to try.
It would be nice if I could call the DB method asynchronously and get it to update the data array sometime in the future but just cant work out how to do it or an alternative method.
It might be that I am triggering the DB query from the wrong place, triggers at the start of the view body. The keypress updates an #State variable which triggers the redraw, a List is used to display each field of the DB records held in an array in Text objects. it all works perfectly with static data.
As you can see below not exactly the most complex thing.
struct CompanySearchView: View {
#ObservedObject var viewRouter: ViewRouter
#State private var name: String = ""
#ObservedObject var companys: Companys = nil
var body: some View {
incSearch()
return VStack(spacing: 12){
Text("Company Search")
.font(.headline)
HStack(spacing: 12){
Text("Company name")
.font(.headline)
TextField("Company name", text: $name)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
List(companys.data){item in
Text(item.name)
}
}
.frame(minWidth: 480, minHeight: 300)
}
func incSearch(){
let connection = Connection(logger: self.viewRouter.logger!)
viewRouter.logger!.info("Search string \(name)");
companys.data = connection.search(n: name)
}
}
Many Thanks

struct CompanySearchView: View {
#ObservedObject var viewRouter: ViewRouter
#State private var name: String = ""
#ObservedObject private var companys: Companys
init(viewRouter: ViewRouter){
self.viewRouter = viewRouter
companys = Companys(router: viewRouter)
}
var body: some View {
companys.changeName(newName: name)
return VStack(spacing: 12){
Text("Company Search")
.font(.headline)
HStack(spacing: 12){
Text("Company name")
.font(.headline)
TextField("Company name", text: $name, onEditingChanged: { (changed) in
self.companys.changing = changed
})
.textFieldStyle(RoundedBorderTextFieldStyle())
}
List(companys.data){item in
Text(item.name)
}
}
.frame(minWidth: 480, minHeight: 300)
}
}
struct CompanySearchView_Previews: PreviewProvider {
static var previews: some View {
CompanySearchView(viewRouter: ViewRouter())
}
}
class Companys: ObservableObject {
private var name: String = ""
private let router: ViewRouter
var changing: Bool = false
#Published var data: [Company] = []
init(router: ViewRouter){
self.router = router
}
func changeName(newName: String){
if changing && name != newName {
name = newName;
let connection = Connection(logger: self.router.logger!)
router.logger!.info("Search string \(name)");
connection.search(n: name).then {
result in
self.data = result
print(result)
}
}
}
}
The main problem this solves (there may be better ways) is that when the original code run the first thing it did was run the DB request then render the view, no real problem there except it did a query that returned all companies and but the display remained blank. This is soled using the flag in the companys class set when the editing starts.
Now unless the field is being edited no search is done.
Using a Future the search is now performed asynchronously and updates the Companys class data which is published so that causes the view to be rendered.
Its not perfect as if the query executes very fast it is possible for the observed object to be updated before the current render g=has completed and back to square one.

Related

Save / restore array of booleans to core date

Building my first SwiftUI app, and have some basic knowledge of Swift. So a bit much to chew but I am enjoying learning.
I have a Form with many toggles saving/restoring from core data in my swift app. Works well but the interface is cumbersome with all the toggles.
Instead I want to make an HStack of tappable labels that will be selected / unselected instead. Then when you submit it will map the selected Text objects to the existing State variables I have OR? save an array of selected strings to core data (for restoring later?).
In either case my code for this has been cobbled from a todo list tutorial plus some nice HStack examples I have put in my form. They select/deselect nicely but I do not know how to save their state like I did the toggle switches.
I will paste what I think is relevant code and remove the rest.
#State var selectedItems: [String] = []
#State private var hadSugar = false
#State private var hadGluten = false
#State private var hadDairy = false
let dayvariablesText = [
"Sugar",
"Gluten",
"Dairy"
]
// section 1 works fine
Section {
VStack {
Section(header: Text("Actions")) {
Toggle("Sugar", isOn: $hadSugar)
Toggle("Gluten", isOn: $hadGluten)
Toggle("Dairy", isOn: $hadDairy)
}
}
}
// section 2 trying this
ScrollView(.horizontal) {
LazyHGrid(rows: rows) {
ForEach(0..<dayvariablesText.count, id: \.self) { item in
GridColumn(item: dayvariablesText[item], items: $selectedItems)
}
}
}.frame(width: 400, height: 100, alignment: .topLeading)
// save
Button("Submit") {
DataController().addMood(sugar: hadSugar, gluten: hadGluten, dairy: hadDairy, context: managedObjContext)
dismiss()
}
This works fine with the toggles shown above - how to do this when selecting gridItems in the next section for example?
I think you need to remodel your code. Having multiple sources of truth like in your example (with the vars and the array for the naming) is a bad practice and will hurt you in the long run.
Consider this solution. As there is a lot missing in your question it´s more general. So you need to implement it to fit your needs. But it should get you in the right direction.
//Create an enum to define your items
// naming needs some improvement :)
enum MakroType: String, CaseIterable{
case sugar = "Sugar", gluten = "Gluten", dairy = "Dairy"
}
//This struct will hold types you defined earlier
// including the bool indicating if hasEaten
struct Makro: Identifiable{
var id: MakroType {
makro
}
var makro: MakroType
var hasEaten: Bool
}
// The viewmodel will help you store and load the data
class Viewmodel: ObservableObject{
//define and create the array to hold the Makro structs
#Published var makros: [Makro] = []
init(){
// load the data either here or in the view
// when it appears
loadCoreData()
}
func loadCoreData(){
//load items
// ..... code here
// if no items assign default ones
if makros.isEmpty {
makros = MakroType.allCases.map{
Makro(makro: $0, hasEaten: false)
}
}
}
// needs to be implemented
func saveCoreData(){
print(makros)
}
}
struct ContentView: View {
// Create an instance of the Viewmodel here
#StateObject private var viewmodel: Viewmodel = Viewmodel()
var body: some View {
VStack{
ScrollView(.horizontal) {
LazyHStack {
// Iterate over the items themselves and not over the indices
// with the $ in front you can pass a binding on to the ChildView
ForEach($viewmodel.makros) { $makro in
SubView(makro: $makro)
}
}
}.frame(width: 400, height: 100, alignment: .topLeading)
Spacer()
Button("Save"){
viewmodel.saveCoreData()
}.padding()
}
.padding()
}
}
struct SubView: View{
// Hold the binding to the Makro here
#Binding var makro: Makro
var body: some View{
//Toggle to change the hasEaten Bool
//this will reflect through the Binding into the Viewmodel
Toggle(makro.makro.rawValue, isOn: $makro.hasEaten)
}
}

#EnvironmentObject not updating view when updating from Realm

Edited to remove unneccesary code...
I'm trying to build a view in my app which will display a schedule (for practicing musical instruments). The schedule can have multiple sessions, and each session multiple slots. I am retrieving a 2d array of these slots from Realm and then using a foreach inside another foreach to display each session with its contents. I'm using #EnvironmentObject to access Realm from each view and realm assembles a new [[Slot]] each time any information is changed (which it seems to be doing correctly).
The issue I'm having is that, although it is refreshing the sessions when I add/remove them, it is not updating the contents of each session. Realm is correctly working correctly, but the sub view is not being updated. If I exit and re-enter the ScheduleView it will show correctly again. I have tried various things to get it updating correctly, including changing an #State variable in each view after a delay, but nothing so far has worked and I'm starting to pull my hair out!
struct SessionRow: View {
#State var sessionNumber: Int
#State var session: [Slot]
var delete: () -> Void
var addSlot: () -> Void
#State var refreshToggle = false
var body: some View {
VStack{
HStack{
Text("Session \(sessionNumber + 1)")
Button {
self.delete()
} label: {
Label("", systemImage: "trash")
}
.buttonStyle(PlainButtonStyle())
Button {
self.addSlot()
} label: {
Label("", systemImage: "plus")
}
.buttonStyle(PlainButtonStyle())
}
ForEach(0..<self.session.count, id: \.self) { slotNumber in
SlotRow(slot: self.session[slotNumber], ownPosition: [self.sessionNumber, slotNumber])
}
.onDelete { indexSet in
//
}
}
.onAppear{
print("Session Number: \(sessionNumber) loaded.")
}
}
}
func addSlot(sessionNumber: Int){
DispatchQueue.main.async {
self.realmManager.addSlot(sessionNumber: sessionNumber)
self.refreshToggle.toggle()
schedule = realmManager.schedule
}
}
func deleteSession(at offsets: IndexSet){
DispatchQueue.main.async {
self.realmManager.deleteSession(sessionNumber: Int(offsets.first ?? 0))
schedule = realmManager.schedule
}
}
}
struct SlotRow: View {
//#EnvironmentObject var realmManager: RealmManager
#State var slot: Slot
//#Binding var isPresented: Bool
//#Binding var slotPosition: [Int]
#State var ownPosition: [Int]
// Add position/session to allow editing?
var body: some View {
VStack{
HStack{
Text("Own Position: [\(ownPosition[0]),\(ownPosition[1])]")
}
}
.padding()
.frame(height: 80.0)
.onAppear{
print("Slot \(ownPosition) loaded.")
}
}
func deleteIntervalFromSlot() {
//realmManager.updateSlot(session: ownPosition[0], position: ownPosition[1], interval: nil)
}
}
As you can see I've tried all sorts of hacks to get it to update, and loads of this code is unnecessary and will be removed once I have a solution. I thought I would leave it here to show the sort of things which have been tried.
I've found the solution, and apologies for the outrageous amount of detail above. I will go back and tidy the above when I get some time.
The issue is that I defined when passing the [Slot] to the session row the variable receiving it was #State and it should just be a var.
struct SessionRow: View {
#State var sessionNumber: Int
#State var session: [Slot]
Change it to
struct SessionRow: View {
var sessionNumber: Int
var session: [Slot]
and it works just fine.
A silly error from someone who is new to SwiftUI!

SwiftUI passing an observed object into a new view and getting updates

I am very new to swift working on my first app and having trouble having a view update. I am passing an object into a new view, however the new view does not update when there is change in the Firebase Database. Is there a way to get updates on the Gridview? I though by passing the observed object from the StyleboardView it would update the GridView however Gridview does not update. I am having trouble finding a way for the new Gridview to update and reload the images.
struct StyleBoardView: View {
#State private var showingSheet = false
#ObservedObject var model = ApiModel()
#State var styleboardname = ""
let userEmail = Auth.auth().currentUser?.email
var body: some View {
NavigationView {
VStack {
Text("Select Style Board")
List (model.list) {item in
Button(item.styleboardname) {
showingSheet.toggle()
}
.sheet(isPresented: $showingSheet) {
GridView(item: item)
}
}
struct GridView: View {
var item: Todo
#ObservedObject var model = ApiModel()
#State var newImage = ""
#State var loc = ""
#State var shouldShowImagePicker = false
#State var image: UIImage?
var body: some View {
NavigationView {
var posts = item.styleboardimages
VStack(alignment: .leading){
Text(item.styleboardname)
GeometryReader{ geo in
LazyVGrid(columns: [
GridItem(.flexible()),
GridItem(.flexible()),
GridItem(.flexible())
], spacing: 3 ){
ForEach(posts.sorted(by: <), id: \.key) { key, value in
if #available(iOS 15.0, *) {
AsyncImage(url: URL(string: value), transaction: Transaction(animation: .spring())) { phase in
switch phase {
case .empty:
Color.purple.opacity(0.1)
case .success(let image):
image
.resizable()
.scaledToFill()
case .failure(_):
Image(systemName: "exclamationmark.icloud")
.resizable()
.scaledToFit()
#unknown default:
Image(systemName: "exclamationmark.icloud")
}
}
.frame(width: 100, height: 100)
.cornerRadius(20)
You have a few problems with the code. First of all, the original view that creates the view model, or has created for it originally, should own the object. Therefore you declare it as a #StateObject.
struct StyleBoardView: View {
#State private var showingSheet = false
#StateObject var model = ApiModel() // #StateObject here
#State var styleboardname = ""
let userEmail = Auth.auth().currentUser?.email
var body: some View {
NavigationView {
VStack {
Text("Select Style Board")
List ($model.list) { $item in // Change this to pass a Binding
Button(item.styleboardname) {
showingSheet.toggle()
}
.sheet(isPresented: $showingSheet) {
GridView(item: $item, model: model)
}
}
}
}
}
}
Since you are passing to a .sheet, that will not automatically be re-rendered when StyleBoardView's model changes, so you have to use a #Binding to cause GridView to re-render. Lastly, once you have your #StateObject, you pass that to your next view. Otherwise, you continually make new models, so updates to one will not update the other.
struct GridView: View {
#Binding var item: Todo // Make this a #Binding so it reacts to the changes.
#ObservedObject var model: ApiModel // Pass the originally created view model in.
...
var body: some View {
NavigationView {
...
}
}
}
Lastly, you did not post a Minimal, Reproducible Example (MRE). You also did not post the complete GridView struct. You may not even need your view model in that view as you do not use it in what you have posted.
The problem is that you're initializing the model in an ObservedObject, and passing it down to another initialized Observed Object.
What you actually wanna do is use an #StateObject for where you initialize the model. And then use #ObservedObject with the type of the model you're passing down so that:
struct StyleBoardView: View {
#StateObject var model = ApiModel()
/** Code **/
struct GridView: View {
#ObservedObject var model: ApiModel
Notice the difference, an #ObservedObject should never initialize the model, it should only "inherit" (#ObservedObject var model: ApiModel) a model from a parent View, in this case, ApiModel.

Reload aync call on view state update

I have the following view:
struct SpriteView: View {
#Binding var name: String
#State var sprite: Image = Image(systemName: "exclamationmark")
var body: some View {
VStack{
sprite
}
.onAppear(perform: loadSprite)
}
func loadSprite() {
// async function
getSpriteFromNetwork(self.name){ result in
switch result {
// async callback
case .success(newSprite):
self.sprite = newSprite
}
}
}
What I want to happen is pretty simple: a user modifies name in text field (from parent view), which reloads SpriteView with the new sprite. But the above view doesn't work since when the view is reloaded with the new name, loadSprite isn't called again (onAppear only fires when the view is first loaded). I also can't put loadSprite in the view itself (and have it return an image) since it'll lead to an infinite loop.
There is a beta function onChange that is exactly what I'm looking for, but it's only in the beta version of Xcode. Since Combine is all about async callbacks and SwiftUI and Combine are supposed to play well together, I thought this sort of behavior would be trivial to implement but I've been having a lot of trouble with it.
I don't particular like this solution since it requires creating a new ObservableObject but this how I ended up doing it:
class SpriteLoader: ObservableObject {
#Published var sprite: Image = Image(systemName: "exclamationmark")
func loadSprite(name: String) {
// async function
self.sprite = Image(systemName: "arrow.right")
}
}
struct ParentView: View {
#State var name: String
#State var spriteLoader = SpriteLoader()
var body: some View {
SpriteView(spriteLoader: spriteLoader)
TextField(name, text: $name, onCommit: {
spriteLoader.loadSprite(name: name)
})
}
}
struct SpriteView: View {
#ObservedObject var spriteLoader: SpriteLoader
var body: some View {
VStack{
spriteLoader.sprite
}
}
}
Old answer:
I think the best way to do this is as follows:
Parent view:
struct ParentView: View {
#State var name: String
#State spriteView = SpriteView()
var body: some View {
spriteView
TextField(value: $name, onCommit: {
spriteView.loadSprite(name)
})
}
And then the sprite view won't even need the #Binding name member.

SwiftUI: ObservableObject does not persist its State over being redrawn

Problem
In Order to achieve a clean look and feel of the App's code, I create ViewModels for every View that contains logic.
A normal ViewModel looks a bit like this:
class SomeViewModel: ObservableObject {
#Published var state = 1
// Logic and calls of Business Logic goes here
}
and is used like so:
struct SomeView: View {
#ObservedObject var viewModel = SomeViewModel()
var body: some View {
// Code to read and write the State goes here
}
}
This workes fine when the Views Parent is not being updated. If the parent's state changes, this View gets redrawn (pretty normal in a declarative Framework). But also the ViewModel gets recreated and does not hold the State afterward. This is unusual when you compare to other Frameworks (eg: Flutter).
In my opinion, the ViewModel should stay, or the State should persist.
If I replace the ViewModel with a #State Property and use the int (in this example) directly it stays persisted and does not get recreated:
struct SomeView: View {
#State var state = 1
var body: some View {
// Code to read and write the State goes here
}
}
This does obviously not work for more complex States. And if I set a class for #State (like the ViewModel) more and more Things are not working as expected.
Question
Is there a way of not recreating the ViewModel every time?
Is there a way of replicating the #State Propertywrapper for #ObservedObject?
Why is #State keeping the State over the redraw?
I know that usually, it is bad practice to create a ViewModel in an inner View but this behavior can be replicated by using a NavigationLink or Sheet.
Sometimes it is then just not useful to keep the State in the ParentsViewModel and work with bindings when you think of a very complex TableView, where the Cells themself contain a lot of logic.
There is always a workaround for individual cases, but I think it would be way easier if the ViewModel would not be recreated.
Duplicate Question
I know there are a lot of questions out there talking about this issue, all talking about very specific use-cases. Here I want to talk about the general problem, without going too deep into custom solutions.
Edit (adding more detailed Example)
When having a State-changing ParentView, like a list coming from a Database, API, or cache (think about something simple). Via a NavigationLink you might reach a Detail-Page where you can modify the Data. By changing the data the reactive/declarative Pattern would tell us to also update the ListView, which would then "redraw" the NavigationLink, which would then lead to a recreation of the ViewModel.
I know I could store the ViewModel in the ParentView / ParentView's ViewModel, but this is the wrong way of doing it IMO. And since subscriptions are destroyed and/or recreated - there might be some side effects.
Finally, there is a Solution provided by Apple: #StateObject.
By replacing #ObservedObject with #StateObject everything mentioned in my initial post is working.
Unfortunately, this is only available in ios 14+.
This is my Code from Xcode 12 Beta (Published June 23, 2020)
struct ContentView: View {
#State var title = 0
var body: some View {
NavigationView {
VStack {
Button("Test") {
self.title = Int.random(in: 0...1000)
}
TestView1()
TestView2()
}
.navigationTitle("\(self.title)")
}
}
}
struct TestView1: View {
#ObservedObject var model = ViewModel()
var body: some View {
VStack {
Button("Test1: \(self.model.title)") {
self.model.title += 1
}
}
}
}
class ViewModel: ObservableObject {
#Published var title = 0
}
struct TestView2: View {
#StateObject var model = ViewModel()
var body: some View {
VStack {
Button("StateObject: \(self.model.title)") {
self.model.title += 1
}
}
}
}
As you can see, the StateObject Keeps it value upon the redraw of the Parent View, while the ObservedObject is being reset.
I agree with you, I think this is one of many major problems with SwiftUI. Here's what I find myself doing, as gross as it is.
struct MyView: View {
#State var viewModel = MyViewModel()
var body : some View {
MyViewImpl(viewModel: viewModel)
}
}
fileprivate MyViewImpl : View {
#ObservedObject var viewModel : MyViewModel
var body : some View {
...
}
}
You can either construct the view model in place or pass it in, and it gets you a view that will maintain your ObservableObject across reconstruction.
Is there a way of not recreating the ViewModel every time?
Yes, keep ViewModel instance outside of SomeView and inject via constructor
struct SomeView: View {
#ObservedObject var viewModel: SomeViewModel // << only declaration
Is there a way of replicating the #State Propertywrapper for #ObservedObject?
No needs. #ObservedObject is-a already DynamicProperty similarly to #State
Why is #State keeping the State over the redraw?
Because it keeps its storage, ie. wrapped value, outside of view. (so, see first above again)
You need to provide custom PassThroughSubject in your ObservableObject class. Look at this code:
//
// Created by Франчук Андрей on 08.05.2020.
// Copyright © 2020 Франчук Андрей. All rights reserved.
//
import SwiftUI
import Combine
struct TextChanger{
var textChanged = PassthroughSubject<String,Never>()
public func changeText(newValue: String){
textChanged.send(newValue)
}
}
class ComplexState: ObservableObject{
var objectWillChange = ObservableObjectPublisher()
let textChangeListener = TextChanger()
var text: String = ""
{
willSet{
objectWillChange.send()
self.textChangeListener.changeText(newValue: newValue)
}
}
}
struct CustomState: View {
#State private var text: String = ""
let textChangeListener: TextChanger
init(textChangeListener: TextChanger){
self.textChangeListener = textChangeListener
print("did init")
}
var body: some View {
Text(text)
.onReceive(textChangeListener.textChanged){newValue in
self.text = newValue
}
}
}
struct CustomStateContainer: View {
//#ObservedObject var state = ComplexState()
var state = ComplexState()
var body: some View {
VStack{
HStack{
Text("custom state View: ")
CustomState(textChangeListener: state.textChangeListener)
}
HStack{
Text("ordinary Text View: ")
Text(state.text)
}
HStack{
Text("text input: ")
TextInput().environmentObject(state)
}
}
}
}
struct TextInput: View {
#EnvironmentObject var state: ComplexState
var body: some View {
TextField("input", text: $state.text)
}
}
struct CustomState_Previews: PreviewProvider {
static var previews: some View {
return CustomStateContainer()
}
}
First, I using TextChanger to pass new value of .text to .onReceive(...) in CustomState View. Note, that onReceive in this case gets PassthroughSubject, not the ObservableObjectPublisher. In last case you will have only Publisher.Output in perform: closure, not the NewValue. state.text in that case would have old value.
Second, look at the ComplexState class. I made an objectWillChange property to make text changes send notification to subscribers manually. Its almost the same like #Published wrapper do. But, when the text changing it will send both, and objectWillChange.send() and textChanged.send(newValue). This makes you be able to choose in exact View, how to react on state changing. If you want ordinary behavior, just put the state into #ObservedObject wrapper in CustomStateContainer View. Then, you will have all the views recreated and this section will get updated values too:
HStack{
Text("ordinary Text View: ")
Text(state.text)
}
If you don't want all of them to be recreated, just remove #ObservedObject. Ordinary text View will stop updating, but CustomState will. With no recreating.
update:
If you want more control, you can decide while changing the value, who do you want to inform about that change.
Check more complex code:
//
//
// Created by Франчук Андрей on 08.05.2020.
// Copyright © 2020 Франчук Андрей. All rights reserved.
//
import SwiftUI
import Combine
struct TextChanger{
// var objectWillChange: ObservableObjectPublisher
// #Published
var textChanged = PassthroughSubject<String,Never>()
public func changeText(newValue: String){
textChanged.send(newValue)
}
}
class ComplexState: ObservableObject{
var onlyPassthroughSend = false
var objectWillChange = ObservableObjectPublisher()
let textChangeListener = TextChanger()
var text: String = ""
{
willSet{
if !onlyPassthroughSend{
objectWillChange.send()
}
self.textChangeListener.changeText(newValue: newValue)
}
}
}
struct CustomState: View {
#State private var text: String = ""
let textChangeListener: TextChanger
init(textChangeListener: TextChanger){
self.textChangeListener = textChangeListener
print("did init")
}
var body: some View {
Text(text)
.onReceive(textChangeListener.textChanged){newValue in
self.text = newValue
}
}
}
struct CustomStateContainer: View {
//var state = ComplexState()
#ObservedObject var state = ComplexState()
var body: some View {
VStack{
HStack{
Text("custom state View: ")
CustomState(textChangeListener: state.textChangeListener)
}
HStack{
Text("ordinary Text View: ")
Text(state.text)
}
HStack{
Text("text input with full state update: ")
TextInput().environmentObject(state)
}
HStack{
Text("text input with no full state update: ")
TextInputNoUpdate().environmentObject(state)
}
}
}
}
struct TextInputNoUpdate: View {
#EnvironmentObject var state: ComplexState
var body: some View {
TextField("input", text: Binding( get: {self.state.text},
set: {newValue in
self.state.onlyPassthroughSend.toggle()
self.state.text = newValue
self.state.onlyPassthroughSend.toggle()
}
))
}
}
struct TextInput: View {
#State private var text: String = ""
#EnvironmentObject var state: ComplexState
var body: some View {
TextField("input", text: Binding(
get: {self.text},
set: {newValue in
self.state.text = newValue
// self.text = newValue
}
))
.onAppear(){
self.text = self.state.text
}.onReceive(state.textChangeListener.textChanged){newValue in
self.text = newValue
}
}
}
struct CustomState_Previews: PreviewProvider {
static var previews: some View {
return CustomStateContainer()
}
}
I made a manual Binding to stop broadcasting objectWillChange. But you still need to gets new value in all the places you changing this value to stay synchronized. Thats why I modified TextInput too.
Is that what you needed?
My solution is use EnvironmentObject and don't use ObservedObject at view it's viewModel will be reset, you pass through hierarchy by
.environmentObject(viewModel)
Just init viewModel somewhere it will not be reset(example root view).