I'm trying to pass data from one view to another and update the slider.
I understand you can do this with #State and #Binding property wrappers however it does not appear to be updating.
On my content view I have:
#State var startNumber: Double = 0
#State var endNumber: Double = 5000
Slider(value: $startNumber, in: 0...10000, step: 0.1)
On my second view I have:
#Binding var startNumber: Double
#Binding var endNumber: Double
#State var number: String = ""
#State var number2: String = ""
FirstResponderTextField(text: $number2, placeholder: "\(endNumber)")
Button(action: {
startNumber = Double(number) ?? 0
endNumber = Double(number2) ?? 0
print("\(startNumber)")
print("\(number)")
print("\(endNumber)")
print("\(number2)")
self.presentationMode.wrappedValue.dismiss()
}
I can see that the value doesn't update the #Binding as it stays at 0.
So I can't even get the variable to update, would someone know why?
Then I understand for the value to show on the content view that I would need to call onAppear after the Hstack for it to show the updated value?
HStack {
}onAppear {
self.startNumber = $startNumber
}
However, this doesn't seem to work either?
Related
i am learning swiftui now and I am newbie for stackoverflow, I find a question,this is my code. I want to change the #State nopubName in sink ,but it's not work,the print is always "Nimar", I don't know why
struct ContentView: View {
#State var nopubName: String = "Nimar"
private var cancellable: AnyCancellable?
var stringSubject = PassthroughSubject<String, Never>()
init() {
cancellable = stringSubject.sink(receiveValue: handleValue(_:))
}
func handleValue(_ value: String) {
print("handleValue: '\(value)'")
self.nopubName = value
print("in sink "+nopubName)
}
var body: some View {
VStack {
Text(self.nopubName)
.font(.title).bold()
.foregroundColor(.red)
Spacer()
Button("sink"){
stringSubject.send("World")
print(nopubName)
}
}
}
}
You should only access a state property from inside the view’s body, or from methods called by it.
https://developer.apple.com/documentation/swiftui/state
You can get that functionality working in an ObservableObject and update an #Published To keep the UI updated
https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app
You don't need to use Combine, If you are within the View, you can change the value of #State variables directly
struct ContentView: View {
#State var nopubName: String = "Nimar"
var body: some View {
VStack {
Text(self.nopubName)
.font(.title).bold()
.foregroundColor(.red)
Spacer()
Button("sink"){
nopubName = "World"
}
}
}
}
I have tried much time on it but I couldn't figure out why it is not working.
The problem is when I tap the button, the new value inside the sheet is not updated. It always show the same value which is set up in start.
#State var value:String = "empty"
#State var explorePageIsEnabled:Bool = false
VStack{
Button("tap me"){
value = "the new one"
exploreStatusIsEnabled.toggle()
}
.sheet(isPresented: $exploreStatusIsEnabled, content: {
Text(value)
})
}
Deployment target is IOS 14+
Create a separate struct view for text and use Binding.
struct SheetView: View {
#Binding var value: String
var body: some View {
Text(value)
}
}
struct ContentView: View {
#State private var value: String = "empty"
#State private var explorePageIsEnabled: Bool = false
var body: some View {
VStack{
Button("tap me"){
value = "the new one"
explorePageIsEnabled.toggle()
}
.sheet(isPresented: $explorePageIsEnabled, content: {
SheetView(value: $value)
})
}
}
}
I'm trying to figure out how to navigate to my secondary screen through a button alert. Firstly, the user inputs some fields through textfields in the primary screen.
At the bottom of this screen the user presses a submit button which then pops up an alert asking whether they would like to be taken to the secondary screen or cancel to not be taken. All the fields that have been entered through the primary screen then get passed onto the secondary screen. The user than has the option to navigate back to the primary screen if necessary once in the secondary screen.
Here's what I've tried:
struct View1: View {
#State var txtField1 : String = ""
#State var txtField2: String = ""
#State var txtField3: String = ""
#State var txtField4: String = ""
#State var txtField5 : String = ""
#State private var showingAlert = false
#State private var showingView = false
var body: some View {
HStack{
Button(action: {
self.showingAlert = true
}) {
Text("Submit")
.alert(isPresented:$showingAlert) {
Alert(title: Text("Would you like to go to second screen?"), message: Text("The second screen will pass all data from the first screen."), primaryButton:.destructive(Text("Continue")){
self.showingView = true
}, secondaryButton: .cancel(Text("Cancel")))
}
}
}.popover(isPresented: $showingView){
NavigationView{
View2(txtField1: self.$txtField1, txtField2: self.$txtField2, txtField3: self.$txtField4, txtField5: self.$txtField5)
}
}
When using the code above it does navigate to my secondary screen (View2) however its like a sheet. View2 has no navigation properties back to View1 and this is what I'm trying to achieve. Any help on this matter is greatly appreciated, Thank you!
To get the Back button you need a NavigationLink vs a popover. You can just "hide" the NavigationLink next to your Button
import SwiftUI
struct ConfirmNavView: View {
#State var txtField1 : String = ""
#State var txtField2: String = ""
#State var txtField3: String = ""
#State var txtField4: String = ""
#State var txtField5 : String = ""
#State private var showingAlert = false
#State private var showingView = false
var body: some View {
NavigationView{
HStack{
Button(action: {
self.showingAlert = true
}) {
Text("Submit")
.alert(isPresented:$showingAlert) {
Alert(title: Text("Would you like to go to second screen?"), message: Text("The second screen will pass all data from the first screen."), primaryButton:.destructive(Text("Continue")){
self.showingView = true
}, secondaryButton: .cancel(Text("Cancel")))
}
}
NavigationLink("View2", destination: View2(txtField1: self.$txtField1,
txtField2: self.$txtField2,
txtField3: self.$txtField3,
txtField4: self.$txtField4,
txtField5: self.$txtField5), isActive: $showingView).hidden().frame(width: 0, height: 0)
}
}
}
}
struct View2: View {
#Binding var txtField1 : String
#Binding var txtField2: String
#Binding var txtField3: String
#Binding var txtField4: String
#Binding var txtField5 : String
var body: some View {
VStack{
TextField("txtField1", text: $txtField1)
TextField("txtField2", text:$txtField2)
TextField("txtField3", text:$txtField3)
TextField("txtField4", text:$txtField4)
TextField("txtField5", text:$txtField5)
}
}
}
struct ConfirmNavView_Previews: PreviewProvider {
static var previews: some View {
ConfirmNavView()
}
}
I don’t understand why the Text Value doesn’t change. if I remove the TextField, the Text value change :/ is there something about combine or SwiftUI I am missing ?
struct ContentView2: View{
#State private var numTouches: Int = 0
#State private var num: String = ""
var body: some View {
VStack{
Button("Touch me pls"){
self.numTouches += 1
}
Text("\(numTouches)")
TextField("Hello enter a number", text: $num)
}.onReceive(Just(num)) { newValue in
if newValue == "" {
self.numTouches = 0
} else {
self.numTouches = Int.init(newValue)!
}
}
}
}
What happens is that when a button is touched, it increases numTouches, which causes the view's body to re-render. .onReceive subscribes to the Just publisher that immediately publishes the value num, which is empty "" in the beginning, and that sets numTouches back to 0.
It sounds that you have really just a single variable, which is being updated from two places:
via TextField
via Button's action
So, keep it as single #State var numTouches: Int:
struct ContentView2: View{
#State private var numTouches: Int = 0
var body: some View {
VStack{
Button("Touch me pls"){
self.numTouches += 1
}
Text("\(numTouches)")
TextField("Hello enter a number",
text: $numTouches, formatter: NumberFormatter()))
// .keyboardType(.numberPad) // uncomment for number pad keyboard
}
}
}
I have a control that sets an #State variable to keep track of the selected tabs in a custom tab View. I can set the #State variable in code by setting the following:
#State var selectedTab: Int = 1
How do I set the initial value programmatically so that I can change the selected tab when the view is created?
I have tried the following:
1:
#State var selectedTab: Int = (parameter == true ? 1 : 2)
2:
init(default: Int) {
self.$selectedTab = default
}
Example of how I set initial state values in one of my views:
struct TodoListEdit: View {
var todoList: TodoList
#State var title = ""
#State var color = "None"
init(todoList: TodoList) {
self.todoList = todoList
self._title = State(initialValue: todoList.title ?? "")
self._color = State(initialValue: todoList.color ?? "None")
}
Joe Groff: "#State variables in SwiftUI should not be initialized from data you pass down through the initializer. The correct thing to do is to set your initial state values inline:"
#State var selectedTab: Int = 1
You should use a Binding to provide access to state that isn't local to your view.
#Binding var selectedTab: Int
Then you can initialise it from init and you can still pass it to child views.
Source: https://forums.swift.org/t/state-messing-with-initializer-flow/25276/3
This was so simple to solve once I discovered the answer!
You simply remove the initial value from the initialization by changing the declaration from:
#State var selectedTab: Int = 1
to:
#State var selectedTab: Int
and then the selectedTab variable automatically becomes a parameter in the instantiation statement. So the initialization would be:
TabBarContentView(selectedTab: 2)
Its that simple!!!!!