I have a modal built with SwitftUI which has a TextField with onCommit: code which saves user input from #State variable to file when user taps "return" on keyboard.
However, if user types something inside TextField and then dismisses the modal without pressing "return", the onCommit: code doesn't fire and user input stays unsaved. How do I fire some code accessing inner variable of my modal View when it is dismissed?
Try the following:
Instead having a private #State var on your modal, make it an internal #Binding that you pass into the modal from the call site. This way the modified bound variable is available on both the caller and the modal view.
import SwiftUI
struct ContentView: View {
#State var dismiss = false
#State var txt = ""
#State var store = ""
var body: some View {
VStack {
Text("modal").sheet(isPresented: $dismiss, onDismiss: {
self.store = self.txt
}) {
TextField("txt", text: self.$txt) {
self.store = self.txt
}.padding().border(Color.red)
}.onTapGesture {
self.dismiss.toggle()
}
Text(store)
}
}
}
struct ContetView_Preview: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Related
As in the code below, the choosenKeyboardKnowledge is a #State variable and was initiated as the first object read from the cache. Then in the body, I iterate each object and wrap it into a Button so that when clicked it leads to the corresponding sheet view. But each time after I run the preview and click on whichever button in the list view it always shows the first default view (set in the initializer), and if I dismiss it and click on another line it shows the correct view.
struct KeyboardKnowledgeView: View {
var keyboardKnowledges: [KeyboardKnowledge]
#State private var choosenKeyboardKnowledge: KeyboardKnowledge
#State private var showSheet: Bool = false
init() {
keyboardKnowledges = KeyboardKnowledgeCache.getKeyboardKnowledges()
_choosenKeyboardKnowledge = State(initialValue: keyboardKnowledges[0])
}
var body: some View {
ZStack {
Color.bgGreen.ignoresSafeArea()
List(keyboardKnowledges) { knowledge in
Button(action: {
self.choosenKeyboardKnowledge = knowledge
self.showSheet.toggle()
}) {
Text(knowledge.name)
}
.sheet(isPresented: $showSheet) {
KeyboardKnowledgeDetailsView(keyboardKnowledge: choosenKeyboardKnowledge)
}
}
}
}
}
I'm trying to figure out how to switch between views using #State and fullScreenCover in SwiftUI
I have a main view and 2 simple views (View1 and View2). View1 must open on the tap of the "Show view 1" button, and View2 when typing on the "Show view 2" button. I also created 2 #State variables for this to work and initialized it with a .View1 (ActiveFullscreenview enum):
#State private var showFullscreenView = false
#State private var activeFullscreenView: ActiveFullscreenView = .View1
The problem is that it constantly showing the View1 even when I tap on the Show view 2 Button.
I thought that when tapping on the button and giving the activeFullscreenView variable a value of .View2 , it should change the variable and show the fullScreenView.
I supposed that it was because I called the showFullscreenView and it changed the variable and after that the default value was .View1 so it didn't change it.
tried this too:
self.showFullscreenView = true
self.activeFullscreenView = .View2
The second assumption was that I shouldn't give this variable the initial value. But in this case it adds some additional initialisation code that is not convenient:
#State private var activeFullscreenView: ActiveFullscreenView = .View1
Currently I have the following code that has the described issue:
import SwiftUI
struct MainView: View {
enum ActiveFullscreenView {
case View1, View2
}
#State private var showFullscreenView = false
#State private var activeFullscreenView: ActiveFullscreenView = .View1
var body: some View {
ZStack {
VStack {
// Button to show View 1 in FullScreen
Button("Show view 1", action: {
self.activeFullscreenView = .View1
self.showFullscreenView = true
})
// Button to show View 2 in FullScreen
Button("Show view 2", action: {
self.activeFullscreenView = .View2
self.showFullscreenView = true
})
}
}
.fullScreenCover(isPresented: $showFullscreenView) {
switch self.activeFullscreenView {
case .View1:
Text("View1")
case .View2:
Text("View2")
}
}
}
}
struct MainView_Previews: PreviewProvider {
static var previews: some View {
MainView()
}
}
Any help related to solving the problem appreciated. Convenient ways ways about how to achieve the same results of showing separate views using fullscreenCover appreciated.
Working in Xcode 12.4, latest iOS 14
With the help of #jnpdx who provided
This Answer that is Similar to My problem the problem is solved. Here is the code. As long as fullScreenCover and sheet are both modals, I renamed enum to ModalView:
struct MainView: View {
// enum to decide which view to present:
enum ModalView: String, Identifiable { // Identifiable
case View1, View2
var id: String {
return self.rawValue
}
}
#State var activeModalView : ModalView? = nil
var body: some View {
ZStack {
VStack {
// Button to show View 1
Button("Show view 1", action: {
self.activeModalView = .View1
})
// Button to show View 2
Button("Show view 2", action: {
self.activeModalView = .View2
})
}
} // use fullScreenCover or sheet depending on your needs
.fullScreenCover(item: $activeModalView) { activeModalValue in
switch activeModalValue {
case .View1:
Text("View1")
case .View2:
Text("View2")
}
}
}
}
The Working works as expected.
But using the #Binding in the NotWorking example, doesn't seem to update the Text control. Why doesn't the #Binding version work, what am I missing here?
Initial Launch:
After Typing:
struct Working: View {
//Binding from #State updates both controls
#State private var text = "working"
var body: some View {
VStack {
TextField("syncs to label...", text: $text)
Text($text.wrappedValue)
}
}
}
struct NotWorking: View {
//Using the #Binding only updates the TextField
#Binding var text: String
var body: some View {
//This does not works
VStack {
TextField("won't sync to label...", text: $text)
Text($text.wrappedValue)
}
}
}
struct Working_Previews: PreviewProvider {
#State static var text = "not working"
static var previews: some View {
VStack {
Working()
NotWorking(text: $text)
}
}
}
Static #States don't work. It's the fact that it being static means that the struct Working_Previews isn't mutated when text is changed, so it won't refresh.
We can test this by changing from a PreviewProvider to an actual View:
struct ContentView: View {
#State static var text = "not working"
var body: some View {
VStack {
Working()
NotWorking(text: ContentView.$text)
}
}
}
This code gives the following runtime message:
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.
Thanks to #George_E. I define #State in a wrapper view and display that for the preview. The WrapperView simply displays the control that I want to preview but it contains the State.
struct Working_Previews: PreviewProvider {
//Define the State in a wrapper view
struct WrapperView: View {
#State var text = "Preview is now working!!!"
var body: some View {
NotWorking(text: $text)
}
}
static var previews: some View {
VStack {
Working()
//Don't display the view that needs the #Binding
//NotWorking(text: $text)
//Use the WrapperView that has #State and displays the view I want to preview.
WrapperView()
}
}
}
I have a simple detail view where users can input data. After pressing the save button, I would like the app to navigate back to the previous list view. The detail view is opened through a NavigationLink. I assume the action for the button needs to be adjusted in some way. Any advice is appreciated.
In order to do this you need to grab an Environment variable in your detail view:
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
and then call it's dismiss method in your button's action like this:
self.presentationMode.wrappedValue.dismiss()
A more detailed example:
struct DetailView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
#State var someData = ""
var body: some View {
TextField("Placeholder", text: $someData)
Button(action: {
saveData()
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Save data and go back")
}
}
}
I am using Swift-UI for creating my app.
There is an AccountView is listing user's attributes and you can update it.
Once you click an Update button on the user's variable row of the list, navigate to EditVariableView, where you can change the variable with Text Field.
Of course, the text field has a validation of the inputted text, and you can commit the change by the Submit button on the right-up corner of EditVariableView.
For validation of the input, I use onCommit, detecting the change of the input, but here is a problem.
When you touch the text field, the keyboard comes out, and also you can input the text. But onCommit emits an event only when you close the keyboard.
If you input the text and click the Submit button without closing the keyboard, certainly onCommit does not emit an event for the validation. So, of course, the validation won't be done.
I want you to tell me, how to detect the input change on every text change.
You can disable Submit button if TextField is in editing state
import SwiftUI
struct ContentView: View {
#State var txt: String = ""
#State var editingFlag = false
var body: some View {
VStack {
TextField("text", text: $txt, onEditingChanged: { (editing) in
self.editingFlag = editing
}) {
print("commit")
}.padding().border(Color.red)
Button(action: {
print("submit")
}) {
Text("SUBMIT")
}.disabled(editingFlag)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
SwiftUI 2.0
With the Xcode 12 and from iOS 14, macOS 11, or any other OS contains SwiftUI 2.0, there is a new modifier called 'onChange' that detects any change of the given state and can be performed on any view. So with some minor refactoring:
struct ContentView: View {
#State var txt: String = ""
#State var editingFlag = false
var body: some View {
VStack {
TextField("text", text: $txt)
.onChange(of: txt) {
print("Changed to :\($0)")
}
}
.padding()
.border(Color.red)
Button("SUBMIT") {
print("submit")
}
.disabled(editingFlag)
}
}
from: hacking with with Swift. Paul Hudson
struct ContentView : View {
#State private var name = ""
var body: some View {
TextField("Enter your name:", text: $name)
.textFieldStyle(RoundedBorderTextFieldStyle())
.onChange(of: name) { newValue in
print("Name changed to \(name)!")
}
}
}