How to know whether the SecureField is editing - ios

I'm using SecureField to build a login page. However, I notice there is no onEditingChanged, so we can't know whether it's become first responder or not. Is there any solution to monitor this event?

You can create a model class that holds the variable for secureText value (In your case it should be LoginModel or something like that). Then bind the value to secureField text and add didSet on the Published var in the model. Here is an example:
Model:
class SecureFieldModel: ObservableObject {
#Published var secureText = "" {
didSet {
print(secureText)
}
}
}
View:
#ObservedObject var model = SecureFieldModel()
struct LoginView: View {
var body: some View {
SecureField("Type your text here", text: $model.secureText)
}
}

I think you may misunderstand my question. If the textfield is just
focus, no text yet, we can't get the event. – RayChen 3 hours ago
Mantas answer slightly modified. "firing" when the input text has content or it is active:
struct ContentView: View {
#ObservedObject var model = SecureFieldModel()
var body: some View {
// No info when focus removed
SecureField("Type your text here", text: $model.secureText)
.onTapGesture { // when active
self.model.textFieldActivated()
}
// Do something when focus removed
SecureField("Type your text here", text: $model.secureText, onCommit: {print("Commited and focus removed")})
.onTapGesture { // when active
self.model.textFieldActivated()
}
}
}
class SecureFieldModel: ObservableObject {
#Published var secureText = "" {
didSet {
// if text
if secureText.count >= 1 {
print(secureText)
}
}
}
func textFieldActivated() {
if secureText.count >= 1 {
print(secureText)
} else {
print("is active")
}
}
}

Related

Using optional binding when SwiftUI says no

The problem
TL;DR: A String I'm trying to bind to inside TextField is nested in an Optional type, therefore I cannot do that in a straightforward manner. I've tried various fixes listed below.
I'm a simple man and my use case is rather simple - I want to be able to use TextField to edit my object's name.
The difficulty arises due to the fact that the object might not exist.
The code
Stripping the code bare, the code looks like this.
Please note that that the example View does not take Optional into account
model
struct Foo {
var name: String
}
extension Foo {
var sampleData: [Foo] = [
Foo(name: "Bar")
]
}
view
again, in the perfect world without Optionals it would look like this
struct Ashwagandha: View {
#StateObject var ashwagandhaVM = AshwagandhaVM()
var body: some View {
TextField("", text: $ashwagandhaVM.currentFoo.name)
}
}
view model
I'm purposely not unwrapping the optional, making the currentFoo: Foo?
class AshwagandhaVM: ObservableObject {
#Published var currentFoo: Foo?
init() {
self.currentFoo = Foo.sampleData.first
}
}
The trial and error
Below are the futile undertakings to make the TextField and Foo.name friends, with associated errors.
Optional chaining
The 'Xcode fix' way
TextField("", text: $ashwagandhaVM.currentFoo?.name)
gets into the cycle of fixes on adding/removing "?"/"!"
The desperate way
TextField("Change chatBot's name", text: $(ashwagandhaVM.currentFoo!.name) "'$' is not an identifier; use backticks to escape it"
Forced unwrapping
The dumb way
TextField("", text: $ashwagandhaVM.currentFoo!.name)
"Cannot force unwrap value of non-optional type 'Binding<Foo?>'"
The smarter way
if let asparagus = ashwagandhaVM.currentFoo.name {
TextField("", text: $asparagus.name)
}
"Cannot find $asparagus in scope"
Workarounds
My new favorite quote's way
No luck, as the String is nested inside an Optional; I just don't think there should be so much hassle with editing a String.
The rationale behind it all
i.e. why this question might be irrelevant
I'm re-learning about the usage of MVVM, especially how to work with nested data types. I want to check how far I can get without writing an extra CRUD layer for every property in every ViewModel in my app. If you know any better way to achieve this, hit me up.
Folks in the question comments are giving good advice. Don't do this: change your view model to provide a non-optional property to bind instead.
But... maybe you're stuck with an optional property, and for some reason you just need to bind to it. In that case, you can create a Binding and unwrap by hand:
class MyModel: ObservableObject {
#Published var name: String? = nil
var nameBinding: Binding<String> {
Binding {
self.name ?? "some default value"
} set: {
self.name = $0
}
}
}
struct AnOptionalBindingView: View {
#StateObject var model = MyModel()
var body: some View {
TextField("Name", text: model.nameBinding)
}
}
That will let you bind to the text field. If the backing property is nil it will supply a default value. If the backing property changes, the view will re-render (as long as it's a #Published property of your #StateObject or #ObservedObject).
I think you should change approach, the control of saving should remain inside the model, in the view you should catch just the new name and intercept the save button coming from the user:
class AshwagandhaVM: ObservableObject {
#Published var currentFoo: Foo?
init() {
self.currentFoo = Foo.sampleData.first
}
func saveCurrentName(_ name: String) {
if currentFoo == nil {
Foo.sampleData.append(Foo(name: name))
self.currentFoo = Foo.sampleData.first(where: {$0.name == name})
}
else {
self.currentFoo?.name = name
}
}
}
struct ContentView: View {
#StateObject var ashwagandhaVM = AshwagandhaVM()
#State private var textInput = ""
#State private var showingConfirmation = false
var body: some View {
VStack {
TextField("", text: $textInput)
.padding()
.textFieldStyle(.roundedBorder)
Button("save") {
showingConfirmation = true
}
.padding()
.buttonStyle(.bordered)
.controlSize(.large)
.tint(.green)
.confirmationDialog("are you sure?", isPresented: $showingConfirmation, titleVisibility: .visible) {
Button("Yes") {
confirmAndSave()
}
Button("No", role: .cancel) { }
}
//just to check
if let name = ashwagandhaVM.currentFoo?.name {
Text("in model: \(name)")
.font(.largeTitle)
}
}
.onAppear() {
textInput = ashwagandhaVM.currentFoo?.name ?? "default"
}
}
func confirmAndSave() {
ashwagandhaVM.saveCurrentName(textInput)
}
}
UPDATE
do it with whole struct
struct ContentView: View {
#StateObject var ashwagandhaVM = AshwagandhaVM()
#State private var modelInput = Foo(name: "input")
#State private var showingConfirmation = false
var body: some View {
VStack {
TextField("", text: $modelInput.name)
.padding()
.textFieldStyle(.roundedBorder)
Button("save") {
showingConfirmation = true
}
.padding()
.buttonStyle(.bordered)
.controlSize(.large)
.tint(.green)
.confirmationDialog("are you sure?", isPresented: $showingConfirmation, titleVisibility: .visible) {
Button("Yes") {
confirmAndSave()
}
Button("No", role: .cancel) { }
}
//just to check
if let name = ashwagandhaVM.currentFoo?.name {
Text("in model: \(name)")
.font(.largeTitle)
}
}
.onAppear() {
modelInput = ashwagandhaVM.currentFoo ?? Foo(name: "input")
}
}
func confirmAndSave() {
ashwagandhaVM.saveCurrentName(modelInput.name)
}
}
There is a handy Binding constructor that converts an optional binding to non-optional, use as follows:
struct ContentView: View {
#StateObject var store = Store()
var body: some View {
if let nonOptionalStructBinding = Binding($store.optionalStruct) {
TextField("Name", text: nonOptionalStructBinding.name)
}
else {
Text("optionalStruct is nil")
}
}
}
Also, MVVM in SwiftUI is a bad idea because the View data struct is better than a view model object.
I've written a couple generic optional Binding helpers to address cases like this. See this thread.
It lets you do if let unwrappedBinding = $optional.withUnwrappedValue { or TestView(optional: $optional.defaulting(to: someNonOptional).

Not able to use the Toggle Switch with if else conditions in swift ui

I am not able to access use the switch. I want if the switch is on, it should come up with a text field and if it is off the value of the variable should be zero. Can anyone help me with this. I have tried to use two different methods. One by using .Onchange and one without using .Onchange. When I use .Onchange, it comes up with a waning that the result of text field is unused. And when I don't use .onAppear it doesn't accept (userSettings.load = 0) but the text field works fine then. I don't understand what I am doing wrong here.The variables are defined as :
struct TwoView: View {
#EnvironmentObject var userSettings: UserSettings
#State var load: Bool = false
var body: some View {
NavigationView {
VStack {
Form {
Toggle("Casual loading", isOn: $load)
.onChange(of: load) { value in
if load == false
{
userSettings.loadrate = 0
}
else
{
TextField("Casual Loading", value: $userSettings.loadrate, format: .number)
}
}
}
}
}
}
}
class UserSettings: ObservableObject
{
#Published var loadrate = Float()
}
Right now, you're using TextField outside of the view hierarchy, and just inside the onChange. In fact, as you mentioned, Xcode is giving you a warning about the fact that it is unused.
To solve this, you can use an if clause inside the hierarchy itself:
struct ContentView : View {
var body: some View {
TwoView().environmentObject(UserSettings())
}
}
struct TwoView: View {
#EnvironmentObject var userSettings: UserSettings
#State var load: Bool = false
var body: some View {
NavigationView {
VStack {
Form {
Toggle("Casual loading", isOn: $load)
.onChange(of: load) { value in
//only imperative, non-View hierarchy code should go in this block
if load == false
{
userSettings.loadrate = 0
}
}
if load { //<-- Here
TextField("Casual Loading", value: $userSettings.loadrate, format: .number)
}
}
}
}
}
}
class UserSettings: ObservableObject
{
#Published var loadrate = Float()
}
TextField is a view element and it shouldn't be inside a closure. It should be a child of a view.
Similarly, assignment is not a view element, so it is not accepted to be in a view.
So, what you need to do is put userSettings.loadrate = 0 into .onChange, and put TextField outside .onChange.
I'm not sure what exactly is your expected result, but here is an example.
NavigationView {
VStack {
Form {
Toggle("Casual loading", isOn: $load)
.onChange(of: load) { value in
// Assignment should be inside a closure
if load == false {
userSettings.loadrate = 0
}
}
if load == true {
// View element should be a child of a view.
TextField("Casual Loading", value: $userSettings.loadrate, format: .number)
}
}
}
}

#State property keeping initial value instead of updating

From a settings page, I want to :
Navigate to a child view
Let the user input update some value in a textfield
Save this value in the user defaults
Navigate back to the settings
If the user opens the child view again, pre-fill the textfield with the previously saved value
Given the following (simple) code :
// User defaults wrapper
class SettingsProvider: ObservableObject {
static let shared = SettingsProvider()
var savedValue: String {
get { UserDefaults.standard.string(forKey: "userdefaultskey") ?? "Default value" }
set {
UserDefaults.standard.setValue(newValue, forKey: "userdefaultskey")
objectWillChange.send()
}
}
}
struct SettingsView: View {
var body: some View {
NavigationView {
NavigationLink("Open child", destination: ChildView())
}
}
}
struct ChildView: View {
#ObservedObject var settingsProvider = SettingsProvider.shared
#State var text: String = SettingsProvider.shared.savedValue
var body: some View {
Text("Value is \(settingsProvider.savedValue)")
TextField("Enter value", text: $text).background(Color.gray)
Button("Save value") {
settingsProvider.savedValue = text
}
}
}
I'm having the following behaviour : video
Can somebody explain to me why the TextField contains Default value the second time I open it ?
Is it a bug in SwiftUI that I should report, or am I missing something ?
If I kill & re-open the app, the textfield will contain (as expected) Other value.
You can just add an onAppear { text = SettingsProvider.shared.savedValue } under the Button like this:
var body: some View {
Text("Value is \(settingsProvider.savedValue)")
TextField("Enter value", text: $text).background(Color.gray)
Button("Save value") {
settingsProvider.savedValue = text
}
.onAppear {
text = SettingsProvider.shared.savedValue // <= add this
}
}

How to detect what changed a #Published value in SwiftUI?

I have an ObservableObject with a #Published value, how can I detect if the value was changed via TextField view or it was set directly (When Button is tapped for instance)?
struct ContentView: View {
#ObservedObject var model = Model()
var body: some View {
VStack {
Button("set value") {
self.model.value = "user set value"
}
TextField("value", text: $model.value)
}
}
}
class Model: ObservableObject {
#Published var value = ""
var anyCancellable: AnyCancellable?
init() {
anyCancellable = $value.sink { val in
// if changed by Button then ...
// if changed by TextField then ...
}
}
}
My real case scenario sounds like this: when the user changes the value a request have to be sent to the server with the new value, but the server can also respond with a new value (in that case a new request to server should not be sent), so I have to distinguish between the case when the user changes the value (via TextField) and the case when the server changes the value.
You can do this with #Binding easily. I don't think you need to use #Published
If you still want to use it you can try this code
struct ContentView: View {
#ObservedObject var model = Model()
var body: some View {
VStack {
Button("set value") {
DispatchQueue.main.async{
self.model.value = "user set value"
}
}
TextField("value", text: $model.value)
}
}
}
You can pass an onEditingChanged closure to the TextField initializer. You should use the closure to trigger the server request.
struct ContentView: View {
#ObservedObject var model = Model()
var body: some View {
VStack {
Button("set value") {
self.model.value = "user set value"
}
TextField(
"value",
text: $model.value,
onEditingChanged: { _ in self.model.sendServerRequest() )
}
}
}

How to initialize derived variables in body methods in SwiftUI (or alternate approach)

I'm trying to figure out the right way to initialized derived variables in the body method for a SwiftUI view. An example would the string value for an editable integer which would then be edited in a TextField. The integer could for example be part of an #ObservedObject. I cannot figure out any remotely clean way to do this.
I've looked into using custom initializers but this does not seem like the right thing to do. I'm not even sure this code would be run at the appropriate time.
I've also tried using the .onAppear method for TextField, but this method does not appear to be re-executed when the view is rebuilt.
simplified example:
final class Values : ObservableObject {
#Published var count: Int = 0;
}
var sharedValues = Values()
struct ContentView : View {
#ObservedObject var values = sharedValues
var body: some View {
VStack {
Button(
action: { self.add() },
label: { Text("Plus")}
)
InnerView()
}
}
func add() { values.count += 1 }
}
struct InnerView : View {
#ObservedObject var values = sharedValues
#State private var text = ""
var body: some View {
// text = String(value.count) - what I want to do
TextField("", text: $text, onEditingChanged: updateCount)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
func updateCount(updated: Bool) { /* this isn't important in this context */}
}
I would hope to be able to update sharedValues externally and see the update in MyView. In this example, I would want pressing the button to update the text field with the updated text value. But I can't figure a way to have the string representation of the count value computed at the appropriate point in the execution of the code.
I've tried multiple approaches to achieving this type of result, but have come up short.
I'm not sure if I'm understanding your question correctly, but if you are just trying to be able to change a number with a button, have the number be displayed in a text field, and then be able to edit it there, you don't need an ObserverableObject or multiple views.
Here is an example of how you can do it:
struct ContentView: View {
#State var count = 0
#State var countStr = ""
var body: some View {
VStack {
Button(action: {
self.count += 1
self.countStr = "\(self.count)"
}) {
Text("Plus")
}
TextField("", text: $countStr, onEditingChanged: updateCount)
}
}
func updateCount(updated: Bool) { /* this isn't important in this context */ }
}
Use value init method of TextField. This take the value as 2 way Binding. So it automatically update count from both text field and buttons.
import SwiftUI
import Combine
final class Values : ObservableObject {
#Published var count: Int = 0;
}
var sharedValues = Values()
struct AndrewVoelkel : View {
#ObservedObject var values = sharedValues
var body: some View {
HStack {
InnerView()
VStack{
Button(
action: { self.add() },
label: { Text("+")}
)
Button(
action: { self.sub() },
label: { Text("-")}
)
}.font(.headline)
}.padding()
}
func add() { values.count += 1 }
func sub() { values.count -= 1 }
}
struct InnerView : View {
#ObservedObject var values = sharedValues
var body: some View {
TextField("", value: $values.count, formatter: NumberFormatter())
.textFieldStyle(RoundedBorderTextFieldStyle())
}
}

Resources