How to detect if keyboard is present in swiftui - swift

I want to know if the keyboard is present when the button is pressed. How would I do this? I have tried but I don't have any luck. Thanks.

Using this protocol, KeyboardReadable, you can conform to any View and get keyboard updates from it.
KeyboardReadable protocol:
import Combine
import UIKit
/// Publisher to read keyboard changes.
protocol KeyboardReadable {
var keyboardPublisher: AnyPublisher<Bool, Never> { get }
}
extension KeyboardReadable {
var keyboardPublisher: AnyPublisher<Bool, Never> {
Publishers.Merge(
NotificationCenter.default
.publisher(for: UIResponder.keyboardWillShowNotification)
.map { _ in true },
NotificationCenter.default
.publisher(for: UIResponder.keyboardWillHideNotification)
.map { _ in false }
)
.eraseToAnyPublisher()
}
}
It works by using Combine and creating a publisher so we can receive the keyboard notifications.
With an example view of how it can be applied:
struct ContentView: View, KeyboardReadable {
#State private var text: String = ""
#State private var isKeyboardVisible = false
var body: some View {
TextField("Text", text: $text)
.onReceive(keyboardPublisher) { newIsKeyboardVisible in
print("Is keyboard visible? ", newIsKeyboardVisible)
isKeyboardVisible = newIsKeyboardVisible
}
}
}
You can now read from the isKeyboardVisible variable to know if the keyboard is visible.
When the TextField is active with the keyboard showing, the following prints:
Is keyboard visible? true
When the keyboard is then hidden upon hitting return, the following prints instead:
Is keyboard visible? false
You can use keyboardWillShowNotification/keyboardWillHideNotification to update as soon as they keyboard starts to appear or disappear, and the keyboardDidShowNotification/keyboardDidHideNotification variants to update after the keyboard has appeared or disappeared. I prefer the will variant because the updates are instant for when the keyboard shows.

iOS 15:
You can use the focused(_:) view modifier and #FocusState property wrapper to know whether a text field is editing, and also change the editing state.
#State private var text: String = ""
#FocusState private var isTextFieldFocused: Bool
var body: some View {
VStack {
TextField("hello", text: $text)
.focused($isTextFieldFocused)
if isTextFieldFocused {
Button("Keyboard is up!") {
isTextFieldFocused = false
}
}
}
}

My little improvement #George's answer.
Implement publisher right inside the View protocol
extension View {
var keyboardPublisher: AnyPublisher<Bool, Never> {
Publishers
.Merge(
NotificationCenter
.default
.publisher(for: UIResponder.keyboardWillShowNotification)
.map { _ in true },
NotificationCenter
.default
.publisher(for: UIResponder.keyboardWillHideNotification)
.map { _ in false })
.debounce(for: .seconds(0.1), scheduler: RunLoop.main)
.eraseToAnyPublisher()
}
}
I also added debounce operator in order to prevent true - false toggle when you have multiple TextFields and user moves between them.
Use in any View
struct SwiftUIView: View {
#State var isKeyboardPresented = false
#State var firstTextField = ""
#State var secondTextField = ""
var body: some View {
VStack {
TextField("First textField", text: $firstTextField)
TextField("Second textField", text: $secondTextField)
}
.onReceive(keyboardPublisher) { value in
isKeyboardPresented = value
}
}
}

Related

SwiftUI Issue with State with Toggle/Sheet

Intro
Takes this simple view as an example.
#State private var isOn: Bool = false
#State private var isPresented: Bool = false
var body: some View {
VStack(content: {
Button("Present", action: { isPresented = true })
Toggle("Lorem Ipsum", isOn: $isOn)
})
.padding()
.sheet(isPresented: $isPresented, content: {
Text(String(isOn))
.onAppear(perform: { print("> \(isOn)") })
})
}
A simple VStack displays a Button that presents a Sheet, and a Toggle that modifies a local property. VStack has a Sheet modifier applied to it, that simply displays property modified by the Toggle.
Sounds simple, but there are issues in certain conditions.
Different App Runs
App Run 1 (No Bug):
Don't press Toggle (set to false)
Open Sheet
Text shows "false" and console logs "false"
App Run 2 (Bug):
Press Toggle (true)
Open Sheet
Text shows "false" and console logs "true"
App Run 3 (No Bug):
Press Toggle (true)
Open Sheet
Close Sheet
Press Toggle (false)
Press Toggle (true)
Open Sheet
Text shows "true" and console logs "true"
In the second run, Text in Sheet displays "false", while console logs "true". But closing the sheet and re-toggling the Toggle fixes the issue.
Also, console logs the following warning:
invalid mode 'kCFRunLoopCommonModes' provided to CFRunLoopRunSpecific - break on _CFRunLoopError_RunCalledWithInvalidMode to debug. This message will only appear once per execution.
Weird Fix
Adding same Text inside the VStack as well seems to fix the issue.
#State private var isOn: Bool = false
#State private var isPresented: Bool = false
var body: some View {
VStack(content: {
Button("Present", action: { isPresented = true })
Toggle("Lorem Ipsum", isOn: $isOn)
Text(String(isOn)) // <--
})
.padding()
.sheet(isPresented: $isPresented, content: {
Text(String(isOn))
.onAppear(perform: { print("> \(isOn)") })
})
}
Problem can also be fixed by using onChange modifier.
#State private var isOn: Bool = false
#State private var isPresented: Bool = false
var body: some View {
VStack(content: {
Button("Present", action: { isPresented = true })
Toggle("Lorem Ipsum", isOn: $isOn)
})
.padding()
.sheet(isPresented: $isPresented, content: {
Text(String(isOn))
.onAppear(perform: { print("> \(isOn)") })
})
.onChange(of: isOn, perform: { _ in }) // <--
}
Other UI Components
I have two custom made Toggle and BottomSheet components that are build in SwiftUI from scratch. I have also used them in the test.
Using native Toggle with my BottomSheet causes problem.
Using my Toggle with native Sheet DOESN'T cause problem.
Using my Toggle with my Sheet DOESN'T cause problem.
Swapping out native Toggle with native Button also causes the same issue:
#State private var isOn: Bool = false
#State private var isPresented: Bool = false
var body: some View {
VStack(content: {
Button("Present", action: { isPresented = true })
Button("Toggle", action: { isOn.toggle() }) // <--
})
.padding()
.sheet(isPresented: $isPresented, content: {
Text(String(isOn))
.onAppear(perform: { print("> \(isOn)") })
})
}
Update
As suggested in the comments, using Sheet init with Binding item seems so solve the issue:
private struct Sheet: Identifiable {
let id: UUID = .init()
let isOn: Bool
}
#State private var presentedSheet: Sheet?
#State private var isOn: Bool = false
var body: some View {
VStack(content: {
Button("Present", action: { presentedSheet = .init(isOn: isOn) })
Toggle("Lorem Ipsum", isOn: $isOn)
})
.padding()
.sheet(item: $presentedSheet, content: { sheet in
Text(String(sheet.isOn))
})
}
However, as other older threads suggested, this may be a bug in SwiftUI, introduced in 2.0.
Another way of fixing the issue that doesn't require creating a new object and doing additional bookkeeping is just leaving an empty onChange modifier: .onChange(of: isOn, perform: { _ in }).
extension View {
func bindToModalContext<V>(
_ value: V
) -> some View
where V : Equatable
{
self
.onChange(of: value, perform: { _ in })
}
}
Other threads:
SwiftUI #State and .sheet() ios13 vs ios14
https://www.reddit.com/r/SwiftUI/comments/l744cb/running_into_state_issues_using_sheets/
https://developer.apple.com/forums/thread/661777
https://developer.apple.com/forums/thread/659660
The sample code demonstrating the issue can be simplified to this:
struct SheetTestView: View {
#State private var isPresented: Bool = false
var body: some View {
Button("Present") {
isPresented = true
}
.sheet(isPresented: $isPresented) {
Text(isPresented.description)
}
}
}
The thing you have to understand first is part of SwiftUI's magic is dependency tracking. Where body is only called if it actually uses a var that changes. Unfortunately that behavior does not extend to the code within blocks passed to view modifiers like sheet. So in the code above, SwiftUI is asking itself does any of the Views inside body display the value of isPresented? And that answer is no, so when the value of isPresented is changed, it does not need to call body and it doesn't.
So when body is called the first time, the block that is created for the sheet is using the original value of isPresented which is false. When the Button is pressed and isPresented is set to true, the block is called immediately and thus it still is using the value false.
Now you understand what is happening, a workaround is to make body actually use the value of isPresented, e.g.
struct SheetTestView: View {
#State private var isPresented: Bool = false
var body: some View {
Button("Present (isPresented: \(isPresented))") { // now body has a dependency on it
isPresented = true
}
.sheet(isPresented: $isPresented) {
Text(isPresented.description)
}
}
}
Now SwiftUI, does detect a dependency for body on the value of isPresented so the behaviour is now different. When the Button is pressed, instead of the sheet block being called immediately, actual body is called first, and thus a new sheet block is created and this one now uses the new value of isPresented which is true and the problem is fixed.
This workaround may be undesirable, so a way to ensure the problem doesn't happen is to use a feature of Swift called a capture list, this makes a dependency on the value of isPresented without actually having to display it, e.g.
struct SheetTestView: View {
#State private var isPresented: Bool = false
var body: some View {
Button("Present (isPresented: \(isPresented))") {
isPresented = true
}
.sheet(isPresented: $isPresented) { [isPresented] in // capture list
Text(isPresented.description)
}
}
}
This trick does make body depend on isPresented in SwiftUI's eyes, so body is called when the Button action changes isPresented and then a new block is created that is passed to sheet and does have the correct value, problem solved!

SwiftUI: How to only run code when the user stops typing in a TextField?

so I'm trying to make a search bar that doesn't run the code that displays the results until the user stops typing for 2 seconds (AKA it should reset a sort of timer when the user enters a new character). I tried using .onChange() and an AsyncAfter DispatchQueue and it's not working (I think I understand why the current implementation isn't working, but I'm not sure I'm even attack this problem the right way)...
struct SearchBarView: View {
#State var text: String = ""
#State var justUpdatedSuggestions: Bool = false
var body: some View {
ZStack {
TextField("Search", text: self.$text).onChange(of: self.text, perform: { newText in
appState.justUpdatedSuggestions = true
DispatchQueue.main.asyncAfter(deadline: .now() + 3, execute: {
appState.justUpdatedSuggestions = false
})
if justUpdatedSuggestions == false {
//update suggestions
}
})
}
}
}
The possible approach is to use debounce from Combine framework. To use that it is better to create separated view model with published property for search text.
Here is a demo. Prepared & tested with Xcode 12.4 / iOS 14.4.
import Combine
class SearchBarViewModel: ObservableObject {
#Published var text: String = ""
}
struct SearchBarView: View {
#StateObject private var vm = SearchBarViewModel()
var body: some View {
ZStack {
TextField("Search", text: $vm.text)
.onReceive(
vm.$text
.debounce(for: .seconds(2), scheduler: DispatchQueue.main)
) {
guard !$0.isEmpty else { return }
print(">> searching for: \($0)")
}
}
}
}
There are usually two most common techniques used when dealing with delaying search query calls: throttling or debouncing.
To implement these concepts in SwiftUI, you can use Combine frameworks throttle/debounce methods.
An example of that would look something like this:
import SwiftUI
import Combine
final class ViewModel: ObservableObject {
private var disposeBag = Set<AnyCancellable>()
#Published var text: String = ""
init() {
self.debounceTextChanges()
}
private func debounceTextChanges() {
$text
// 2 second debounce
.debounce(for: 2, scheduler: RunLoop.main)
// Called after 2 seconds when text stops updating (stoped typing)
.sink {
print("new text value: \($0)")
}
.store(in: &disposeBag)
}
}
struct ContentView: View {
#StateObject var viewModel = ViewModel()
var body: some View {
TextField("Search", text: $viewModel.text)
}
}
You can read more about Combine and throttle/debounce in official documentation: throttle, debounce

Value from #State variable does not change

I have created a View that provides a convinient save button and a save method. Both can then be used inside a parent view.
The idea is to provide these so that the navigation bar items can be customized, but keep the original implementation.
Inside the view there is one Textfield which is bound to a #State variable. If the save method is called from within the same view everthing works as expected. If the parent view calls the save method on the child view, the changes to the #State variable are not applied.
Is this a bug in SwiftUI, or am I am missing something? I've created a simple playbook implementation that demonstrates the issue.
Thank you for your help.
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
// Create the child view to make the save button available inside this view
var child = Child()
var body: some View {
NavigationView {
NavigationLink(
destination: child.navigationBarItems(
// Set the trailing button to the one from the child view.
// This is required as this view might be inside a modal
// sheet, and we need to add the cancel button as a leading
// button:
// leading: self.cancelButton
trailing: child.saveButton
)
) {
Text("Open")
}
}
}
}
struct Child: View {
// Store the value from the textfield
#State private var value = "default"
// Make this button available inside this view, and inside the parent view.
// This makes sure the visibility of this button is always the same.
var saveButton: some View {
Button(action: save) {
Text("Save")
}
}
var body: some View {
VStack {
// Simple textfield to allow a string to change.
TextField("Value", text: $value)
// Just for the playground to change the value easily.
// Usually it would be chnaged through the keyboard input.
Button(action: {
self.value = "new value"
}) {
Text("Update")
}
}
}
func save() {
// This always displays the default value of the state variable.
// Even after the Update button was used and the value did change inside
// the textfield.
print("\(value)")
}
}
PlaygroundPage.current.setLiveView(ContentView())
I think a more SwiftUi way of doing it:
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
var body: some View {
return NavigationView {
// tell the child view where to render it's navigation item
// Instead of configuring navigation items.
NavigationLink(destination: Child(navigationSide: .left)) {
Text("Open")
}
}
}
}
struct Child: View {
enum NavigationSide { case left, right }
// If you really want to encapsulate all state in this view then #State
// is a good choice.
// If the parent view needs to read it, too, #Binding would be your friend here
#State private var value: String = "default"
// no need for #State as it's never changed from here.
var navigationSide = NavigationSide.right
// wrap in AnyView here to make ternary in ui code easier readable.
var saveButton: AnyView {
AnyView(Button(action: save) {
Text("Save")
})
}
var emptyAnyView: AnyView { AnyView(EmptyView()) }
var body: some View {
VStack {
TextField("Value", text: $value)
Button(action: {
self.value = "new value"
}) {
Text("Update")
}
}
.navigationBarItems(leading: navigationSide == .left ? saveButton : emptyAnyView,
trailing: navigationSide == .right ? saveButton : emptyAnyView)
}
func save() {
print("\(value)")
}
}
TextField will only update your value binding when the return button is pressed. To get text changes that occur during editing, set up an observed object on Child with didSet. This was the playground I altered used from your example.
struct ContentView: View {
var child = Child()
var body: some View {
NavigationView {
NavigationLink(
destination: child.navigationBarItems(
trailing: child.saveButton
)
) {
Text("Open")
}
}
}
}
class TextChanges: ObservableObject {
var completion: (() -> ())?
#Published var text = "default" {
didSet {
print(text)
}
}
}
struct Child: View {
#ObservedObject var textChanges = TextChanges()
var saveButton: some View {
Button(action: save) {
Text("Save")
}
}
var body: some View {
VStack {
TextField("Value", text: $textChanges.text).multilineTextAlignment(.center)
Button(action: {
print(self.textChanges.text)
}) {
Text("Update")
}
}
}
func save() {
print("\(textChanges.text)")
}
}
PlaygroundPage.current.setLiveView(ContentView())
Inside Child: value is mutable because it's wrapped with #State.
Inside ContentView: child is immutable because it's not wrapped with #State.
Your issue can be fixed with this line: #State var child = Child()
Good luck.
Child view needs to keep its state as a #Binding. This works:
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
#State var v = "default"
var body: some View {
let child = Child(value: $v)
return NavigationView {
NavigationLink(
destination: child.navigationBarItems(trailing: child.saveButton)
) {
Text("Open")
}
}
}
}
struct Child: View {
#Binding var value: String
var saveButton: some View {
Button(action: save) {
Text("Save")
}
}
var body: some View {
VStack {
TextField("Value", text: $value)
Button(action: {
self.value = "new value"
}) {
Text("Update")
}
}
}
func save() {
print("\(value)")
}
}
PlaygroundPage.current.setLiveView(ContentView())
Based on this commend from #nine-stones (thank you!) I implemented a more SwiftUI way so solve my problem. It does not allow the customization of the navigation items as I planned, but that was not the problem that needed to be solved. I wanted to use the Child view in a navigation link, as well as inside a modal sheet. The problem was how to perform custom cancel actions. This is why I removed the button implementation and replaced it with a cancelAction closure. Now I can display the child view wherever and however I want.
One thing I still do not know why SwiftUI is not applying the child context to the button inside the saveButton method.
Still, here is the code, maybe it helps someone in the future.
import SwiftUI
import PlaygroundSupport
struct ContentView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
NavigationView {
NavigationLink(
destination: Child(
// Instead of defining the buttons here, I send an optional
// cancel action to the child. This will make it possible
// to use the child view on navigation links, as well as in
// modal dialogs.
cancelAction: {
self.presentationMode.wrappedValue.dismiss()
}
)
) {
Text("Open")
}
}
}
}
struct Child: View {
// Store the value from the textfield
#State private var value = "default"
#Environment(\.presentationMode) var presentationMode
var cancelAction: (() -> Void)?
// Make this button available inside this view, and inside the parent view.
// This makes sure the visibility of this button is always the same.
var saveButton: some View {
Button(action: save) {
Text("Save")
}
}
var body: some View {
VStack {
// Simple textfield to allow a string to change.
TextField("Value", text: $value)
// Just for the playground to change the value easily.
// Usually it would be chnaged through the keyboard input.
Button(action: {
self.value = "new value"
}) {
Text("Update")
}
}
.navigationBarItems(
leading: self.cancelAction != nil ? Button(action: self.cancelAction!, label: {
Text("Cancel")
}) : nil,
trailing: self.saveButton
)
}
func save() {
// This always displays the default value of the state variable.
// Even after the Update button was used and the value did change inside
// the textfield.
print("\(value)")
}
}
PlaygroundPage.current.setLiveView(ContentView())

How to Add Max length for a character for Swift UI

Hi i am creating a to do application and i am facing a problem when a user entering some characters to a UIText field i remember there was a way in SWIFT 5 to put a max length but i can't find one in SWIFT UI can someone send me a link or guide me step by step HOW CAN I ADD A MAX LENTGH TO A SWIFT UI PROJECT TO THE TEXT FIELD! THANKS
I tried to find it Everywhere but i can't
struct NewTaskView: View {
var taskStore: TaskStore
#Environment(\.presentationMode) var presentationMode
#State var text = ""
#State var priority: Task.Priority = .Низкий
var body: some View {
Form {
TextField("Название задания", text: $text)
VStack {
Text("Приоритет")
.multilineTextAlignment(.center)
Picker("Priority", selection: $priority.caseIndex) {
ForEach(Task.Priority.allCases.indices) { priorityIndex in
Text(
Task.Priority.allCases[priorityIndex].rawValue
.capitalized
)
.tag(priorityIndex)
}
}
.pickerStyle( SegmentedPickerStyle() )
}
I want to put max length to a text field where is written TextField("Название задания", text: $text)
It seems like this can be achieved with Combine, by creating a wrapper around the text and opening a 2 way subscription, with the text subscribing to the TextField and the TextField subscribing to the ObservableObject. I'd say the way it works its quite logical from a Reactive point of view but would have liked to find a cleaner solution that didn't require another object to be created.
import SwiftUI
import Combine
class TextBindingManager: ObservableObject {
#Published var text = "" {
didSet {
if text.count > characterLimit && oldValue.count <= characterLimit {
text = oldValue
}
}
}
let characterLimit = 5
}
struct ContentView: View {
#ObservedObject var textBindingManager = TextBindingManager()
var body: some View {
TextField("Placeholder", text: $textBindingManager.text)
}
}
I read this article. please check here
This is my whole code. I don't use EnvironmentObject.
struct ContentView: View {
#ObservedObject private var restrictInput = RestrictInput(5)
var body: some View {
Form {
TextField("input text", text: $restrictInput.text)
}
}
class RestrictInput: ObservableObject {
#Published var text = ""
private var canc: AnyCancellable!
init (_ maxLength: Int) {
canc = $text
.debounce(for: 0.5, scheduler: DispatchQueue.main)
.map { String($0.prefix(maxLength)) }
.assign(to: \.text, on: self)
}
deinit {
canc.cancel()
}
}

SwiftUI - Form with error message on button press and navigation

I have the following scenario. I have a text field and a button, what I would need is to show an error message in case the field is empty and if not, navigate the user to the next screen.
I have tried showing the error message conditionally by using the field value and checking if it is empty on button press, but then, I don't know how to navigate to the next screen.
struct SomeView: View {
#State var fieldValue = ""
#State var showErrorMessage = false
var body: some View {
NavigationView {
VStack {
TextField("My Field", text: $fieldValue).textFieldStyle(RoundedBorderTextFieldStyle())
if showErrorMessage {
Text("Error, please enter value")
}
Button(action: {
if self.fieldValue.isEmpty {
self.showErrorMessage = true
} else {
self.showErrorMessage = false
//How do I put navigation here, navigation link does not work, if I tap, nothing happens
}
}) {
Text("Next")
}
}
}
}
}
Using UIKit would be easy since I could use self.navigationController.pushViewController
Thanks to part of an answer here, here's some working code.
First, I moved everything into an EnvronmentObject to make things easier to pass to your second view. I also added a second toggle variable:
class Model: ObservableObject {
#Published var fieldValue = ""
#Published var showErrorMessage = false
#Published var showSecondView = false
}
Next, change two things in your ContentView. I added a hidden NavigationLink (with a isActive parameter) to actually trigger the push, along with changing your Button action to execute a local function:
struct ContentView: View {
#EnvironmentObject var model: Model
var body: some View {
NavigationView {
VStack {
TextField("My Field", text: $model.fieldValue).textFieldStyle(RoundedBorderTextFieldStyle())
NavigationLink(destination: SecondView(), isActive: $model.showSecondView) {
Text("NavLink")
}.hidden()
Button(action: {
self.checkForText()
}) {
Text("Next")
}
.alert(isPresented: self.$model.showErrorMessage) {
Alert(title: Text("Error"), message: Text("Please enter some text!"), dismissButton: .default(Text("OK")))
}
}
}
}
func checkForText() {
if model.fieldValue.isEmpty {
model.showErrorMessage.toggle()
} else {
model.showSecondView.toggle()
}
}
}
Toggling showErrorMessage will show the Alert and toggling `showSecondView will take you to the next view.
Finally, the second view:
struct SecondView: View {
#EnvironmentObject var model: Model
var body: some View {
ZStack {
Rectangle().fill(Color.green)
// workaround
.navigationBarBackButtonHidden(true) // not needed, but just in case
.navigationBarItems(leading: MyBackButton(label: "Back!") {
self.model.showSecondView = false
})
Text(model.fieldValue)
}
}
func popSecondView() {
model.showSecondView.toggle()
}
}
struct MyBackButton: View {
let label: String
let closure: () -> ()
var body: some View {
Button(action: { self.closure() }) {
HStack {
Image(systemName: "chevron.left")
Text(label)
}
}
}
}
This is where the above linked answer helped me. It appears there's a bug in navigation back that still exists in beta 6. Without this workaround (that toggles showSecondView) you will get sent back to the second view one more time.
You didn't post any details on the second view contents, so I took the liberty to add someText into the model to show you how to easily pass things into it can be using an EnvironmentObject. There is one bit of setup needed to do this in SceneDelegate:
var window: UIWindow?
var model = Model()
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let contentView = ContentView()
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: contentView.environmentObject(model))
self.window = window
window.makeKeyAndVisible()
}
}
I noticed a slight change in this, depending on when your project was created (beta 6 declares an instance of contentView where older versions do not). Either way, declare an instance of model and then add the envoronmentObject modifier to contentView.
Another approach is to make the "Next" button conditionally a Button when the fieldValue is empty and a NavigationLink when the fieldValue is valid. The Button case will trigger your error message view and the NavigationLink will do the navigation for you. Keeping this close to your sample, the following seems to do the trick.
struct SomeView: View {
#State var fieldValue = ""
#State var showErrorMessage = false
var body: some View {
NavigationView {
VStack {
TextField("My Field", text: $fieldValue).textFieldStyle(RoundedBorderTextFieldStyle())
if showErrorMessage {
Text("Please Enter Data")
}
if fieldValue == "" {
Button(action: {
if self.fieldValue == "" {
self.showErrorMessage = true
}
}, label: {
Text("Next")
})
} else {
// move on case
NavigationLink("Next", destination: Text("Next View"))
}
}
}
}
}
By using this code we can display the alert if the fields are empty else . it will navigate.
struct SomeView: View {
#State var userName = ""
#State var password = ""
#State var showErrorMessage = false
var body: some View {
NavigationView {
VStack {
TextField("Enter Username", text: $userName).textFieldStyle(RoundedBorderTextFieldStyle())
SecureField("Enter Your Password", text: $password)
.textFieldStyle(RoundedBorderTextFieldStyle())
if userName == "" || password == "" {
Button(action: {
if self.userName == "" || self.password == "" {
self.showErrorMessage = true
}
}, label: {
Text("Login")
})
} else {
// move case
NavigationLink("Login", destination: Text("Login successful"))
}
}.alert(isPresented: $showErrorMessage) { () -> Alert in
Alert(title: Text("Important Message"), message: Text("Please Fill all the Fields"), primaryButton: .default(Text("Ok")), secondaryButton: .destructive(Text("Cancel")))
}
}
}
}