Got rare behaviour in SwiftUI with checking boolean in ForEach with Struct elements in Array - foreach

I'm trying to understand why this code is working sometimes as expected but sometimes not. Im showing a List of Boxes that can be selected by a simple tap. When a user tapped a box, a Text changes.
I have a struct and a class:
struct SelectionBoxItemStruct: Identifiable
{
var id = UUID()
var title: String
var subtitle: String
var selected = false
}
class SelectionBoxItemClass: ObservableObject {
#Published var selectionBoxItems = [SelectionBoxItemStruct(title: "XXX", subtitle: "YYY"), SelectionBoxItemStruct(title: "XXX", subtitle: "YYY")]
}
In the main View I declared the class like this:
#ObservedObject var selectionBoxItemClass = SelectionBoxItemClass()
And later in a ForEach I do this:
ForEach($selectionBoxItemClass.selectionBoxItems) { $selection in
Text(selection.selected ? "True" : "False")
Rectangle() // Example of a Box
.frame(width: geometry.size.width/1.2, height: 100)
.onTapGesture(perform: {
selection.selected.toggle()
print(selection.selected)
})
}
It will print correctly after every tap "true, false, true, ...", but the displayed "TextView" shows only sometimes the right Text and changes, but sometimes only the correct value is printed but the Text doesn't change. In conclusion: every build works different.

Related

SWIFTUI TextView with real-time, programmatically-appendable text

I'm looking for a way to create a simple textview (command line like) that will show the user what is going on inside the app. It should be able to append elements programmatically in real time so user can monitor and copy text from it. Im writing app for macOS.
So far i done this:
class OutputElementStruct: Identifiable
{
var HEX: String
var Balance: String
init(HEX: String, Balance: String)
{
self.HEX = HEX
self.Balance = Balance
}
}
class OutputElements: ObservableObject
{
#Published var OutputElement = [OutputElementStruct]()
init()
{
self.OutputElement =
[
OutputElementStruct(HEX: "sometext", Balance: "sometext")
]
}
}
Then it shows to user
struct OutputView: View
{
#StateObject var OutputArray = OutputElements()
var body: some View
{
GeometryReader
{ Geo in
ZStack
{
BoxView(width: 600, height: 700, Name: "")
VStack
{
List
{
ForEach($OutputArray.OutputElement)
{ item in
Text(verbatim: item.HEX.wrappedValue)
}
}
}
}
}
.frame(width: 600, height: 700, alignment: .center)
}
}
and here is data added.
#StateObject var OutputArray = OutputElements()
.onTapGesture
{
print("button tapped")
OutputArray.OutputElement.append(OutputElementStruct(HEX: "sometext", Balance: "sometext"))
}
It is working, but not in real time. While i tapped the button it is prints "button tapped" in Xcode, but text view does not displays anything except firstly added item in init(). So how to make it work?
While i tapped the button it is prints "button tapped" in Xcode, but text view does not displays anything except firstly added item in init(). So how to make it work?
It looks like you've got two separate arrays that are both named OutputArray, and the one you're appending objects to isn't the one that your view is displaying. That's why you don't see any new objects show up even though you know you're adding them.
You need a single array that's used in both places. I think you've got the right idea by using #StateObject, but instead of creating two #StateObject variables, create just one and add it to the environment as explained in the StateObject documentation.

SwiftUI replace and copy text TextEditor

I have read countless articles and watched 10 hours of YouTube videos and still can't figure this out, maybe I'm just missing the term. I'm old school and still trying to learn this backwards swift stuff compared to vb or javascript.
I am using the default Mac OS document template in Xcode 13.1. it comes with a dialogue to open a file then dumps it into the TextEditor as utf8 string.
For my part I added another TextEditor and a button but keep getting error when I try to copy the text from the original texteditor to the new texteditor when button is pressed.
struct ContentView: View {
#Binding var document: test2Document
#State var newtexteditor: String = "press the button"
var body: some View {
VStack{
//main text editor
TextEditor(text: $document.text)
//divider
Divider()
//new text editor
TextEditor(text: $newtexteditor).padding()
//button
Button("test", action: dosomething)
}
}
func dosomething () {
$newtexteditor = $document
}
}
Also, is there any ways of giving objects a name or label and using that to say things like textbox1.text = textbox2.text like you could using storyboards. I obviously know that's not swift syntax but you get the point.
#State and #Binding vars are special kinds of variables. They actually contain 2 sub-variables: projectedValue and wrappedValue.
projectedValue is like a reference to the actual value, and allows the #State/#Binding to be modified from further down the view hierarchy.
You access this by saying $newTextEditor or $document.text.
This is only used for passing references down the view hierarchy, where you can later set the wrappedValue.
wrappedValue is the actual value — in your case, a String.
You access this by just saying newTextEditor or document.text. No $.
You're able to set this - for example, newTextEditor = "New text" or newTextEditor = document.text. The UI will automatically update to reflect the changes.
Here's an example:
struct ContentView: View {
#State var text = "Hello"
var body: some View {
VStack {
Text("The text is: \(text)") /// display the `wrappedValue`
SubView(text: $text) /// pass in the `projectedValue`
}
}
}
struct SubView: View {
#Binding var text: String
var body: some View {
Button("Click me to change text") {
text = "New text" /// set the `wrappedValue`
}
}
}
In your code, it would look something like this:
struct ContentView: View {
#Binding var document: Test2Document /// Side Note: structs like `Test2Document` should be Capitalized
#State var newTextEditor: String = "press the button" /// also use camel case
var body: some View {
VStack{
TextEditor(text: $document.text) /// pass in the `projectedValue`
Divider()
TextEditor(text: $newTextEditor) /// pass in the `projectedValue`
.padding()
Button("test", action: dosomething)
}
}
func dosomething() {
newTextEditor = document.text /// set the `wrappedValue`
}
}

Can I add a variable within a binding of a TextField within a ForEach loop?

I am very new to swiftUI and development in general. I am trying to create a dynamic list of TextFields that return strings.
ForEach((1...numberofNAdefects), id: \.self) {
TextField("\($0): Enter defect description", text: $nadefect1)
The problem i have is that I want the "$nadefect#" return string to increase dynamically with the foreachloop. Initially my thought would be to add text: $nadefect\($0) but that doesn't work.
Increasing the string like you suggested is impossible. Nevertheless, implementing a dynamic list of TextFields is quite easy in SwiftUI.
Instead of increasing strings you could use an array of strings:
struct ContentView: View {
#State var defects: [String] = ["", "", ""]
var body: some View {
ForEach(0..<defects.count, id: \.self) {
TextField("\($0 + 1): Enter defect description", text: $defects[$0])
}.padding()
}
}

SwiftUI, how to bind EnvironmnetObject Int property to TextField?

I have an ObservableObject which is supposed to hold my application state:
final class Store: ObservableObject {
#Published var fetchInterval = 30
}
now, that object is being in injected at the root of my hierarchy and then at some component down the tree I'm trying to access it and bind it to a TextField, namely:
struct ConfigurationView: View {
#EnvironmnetObject var store: Store
var body: some View {
TextField("Fetch interval", $store.fetchInterval, formatter: NumberFormatter())
Text("\(store.fetchInterval)"
}
}
Even though the variable is binded (with $), the property is not being updated, the initial value is displayed correctly but when I change it, the textfield changes but the binding is not propagated
Related to the first question, is, how would I receive an event once the value is changed, I tried the following snippet, but nothing is getting fired (I assume because the textfield is not correctly binded...
$fetchInterval
.debounce(for: 0.8, scheduler: RunLoop.main)
.removeDuplicates()
.sink { interval in
print("sink from my code \(interval)")
}
Any help is much appreciated.
Edit: I just discovered that for text variables, the binding works fine out of the box, ex:
// on store
#Published var testString = "ropo"
// on component
TextField("Ropo", text: $store.testString)
Text("\(store.testString)")
it is only on the int field that it does not update the variable correctly
Edit 2:
Ok I have just discovered that only changing the field is not enough, one has to press Enter for the change to propagate, which is not what I want, I want the changes to propagate every time the field is changed...
For anyone that is interested, this is te solution I ended up with:
TextField("Seconds", text: Binding(
get: { String(self.store.fetchInterval) },
set: { self.store.fetchInterval = Int($0.filter { "0123456789".contains($0) }) ?? self.store.fetchInterval }
))
There is a small delay when a non-valid character is added, but it is the cleanest solution that does not allow for invalid characters without having to reset the state of the TextField.
It also immediately commits changes without having to wait for user to press enter or without having to wait for the field to blur.
Do it like this and you don't even have to press enter. This would work with EnvironmentObject too, if you put Store() in SceneDelegate:
struct ContentView: View {
#ObservedObject var store = Store()
var body: some View {
VStack {
TextField("Fetch interval", text: $store.fetchInterval)
Text("\(store.fetchInterval)")
}
} }
Concerning your 2nd question: In SwiftUI a view gets always updated automatically if a variable in it changes.
how about a simple solution that works well on macos as well, like this:
import SwiftUI
final class Store: ObservableObject {
#Published var fetchInterval: Int = 30
}
struct ContentView: View {
#ObservedObject var store = Store()
var body: some View {
VStack{
TextField("Fetch interval", text: Binding<String>(
get: { String(format: "%d", self.store.fetchInterval) },
set: {
if let value = NumberFormatter().number(from: $0) {
self.store.fetchInterval = value.intValue
}}))
Text("\(store.fetchInterval)").padding()
}
}
}

swiftui forms not appearing inside list

I am developing a certain application where it requires a user to fill a form. a form is not a problem actually but problem is when i want more than just one form dynamically. The number of forms (of the same look) depends on a certain number. So i decided to create a model and put the form in the list. That way the number of forms will be equal to nth number. problem is they are not appearing. If i remove the form they appear, if i put a form they do not. Is it that a form is not allowed in the list?
Below is expected results
But when i add the form in the list i get
So here's my simple List:
struct UserTab: View {
let seats = ["A4", "B2", "C1", "D3"]
var body: some View {
List(seats, id: \.self){seat in
PassengerInfoModel()
}
}
The PassengerInfoModel is below:
struct PassengerInfoModel: View {
#State private var fullName: String = ""
var body: some View {
Form{
Text("Paasenger 1")
TextField("Full name", text: $fullName)
}
}
}
Thanks in advance.
Because the form frame is not determined, you get this result.
Try to add some frame to your form and it should be ok
struct PassengerInfoModel: View {
#State private var fullName: String = ""
var body: some View {
Form{
Text("Paasenger 1")
TextField("Full name", text: $fullName)
}.frame(height: 200)
}
}
In general you should avoid using Form inside Scrollview because both of them has scroll and not working as you want

Resources