Observing changes #FocusState variable not working as expected - swiftui

I want to validate the user's changes in a specific textfield and thus need to hook in when this textfield loses focus. I am using this year's introduced .focused(_:equals:) modifier with an enum and a #FocusState.
Observing my optional #FocusState var, which is nil when the screen first loads, with .onChange(of:perform:), this is only called once, when #FocusState changes from nil to a value–but not when it changes to another enum value.
The latter is expected though, and once this happens I could check what the previous field was–at least that's my approach for now.
Why is this approach not working–and is there a better way to go about this?
struct FocusView: View {
#State private var nameLast = "Ispum"
#State private var phoneNumber = "+41 79 888 88 88"
enum Field {
case nameLast
case phoneNumber
}
#FocusState private var focusedField: Field?
#State private var prevFocusedField: Field?
var body: some View {
VStack {
TextField("Last Name", text: $nameLast)
.focused($focusedField, equals: .nameLast)
TextField("Phone Number", text: $phoneNumber, prompt: Text(""))
.focused($focusedField, equals: .nameLast)
}
.onChange(of: focusedField) { newFocusField in
//only called once–when `focusedField` changes its state from `nil` to a value
print("Old value: \(prevFocusedField)")
print("New value: \(focusedField)")
if prevFocusedField == .phoneNumber {
//Validation
}
prevFocusedField = newFocusField
}
.textFieldStyle(.roundedBorder)
.padding()
}
}

There is a typo in your code (probably copy-paste)
TextField("Phone Number", text: $phoneNumber, prompt: Text(""))
.focused($focusedField, equals: .nameLast) // << here !!
you should use different value for second field:
VStack {
TextField("Last Name", text: $nameLast)
.focused($focusedField, equals: .nameLast)
TextField("Phone Number", text: $phoneNumber, prompt: Text(""))
.focused($focusedField, equals: .phoneNumber) // << fix !!
}
Tested with Xcode 13 / iOS 15

Related

in SwifUI how to define variables which depends of an input Entity in the init of a view and then populate different pickers

I have a CoreData entity called PokSession which contains multiple attributes (date, currency, period, nbheure).
I want to design a view which display those informations (and allow me to modify them) for a given entity.
So basically from a previous view, I call an other view like this:
#FetchRequest(entity: PokSession.entity(), sortDescriptors: [
NSSortDescriptor(keyPath: \PokSession.date, ascending: false)
]) var poksessions: FetchedResults<PokSession>
ForEach(poksessions, id: \.date) { session in
DetailSessionPokUIView (session: session)
}
.onDelete(perform: deleteSessions)
which leads to the following view:
struct DetailSessionPokUIView: View {
#Environment(\.managedObjectContext) var moc
#ObservedObject var session: PokSession
#State private var date: Date
#State private var currency: String
#State private var periode: String
#State private var nbheure: Double
let liste_currency = ["CAD", "EUR", "USD", "GBP"]
let liste_periode = ["matinée", "après-midi", "soirée"]
init(session: PokSession) {
date = session.date!
currency = session.currency!
periode = session.periode!
nbheure = session.nbheure
}
var body: some View {
NavigationView {
Form {
Section {
DatePicker(selection: $date , displayedComponents: .date){
Text("Date")
}
HStack{
Picker("Devise", selection: $currency) {
ForEach(liste_currency, id: \.self) { currency in
Text(currency)
}
}
}
HStack{
Picker("Période de jeu", selection: $periode) {
ForEach(liste_periode, id: \.self) { periode in
Text(periode)
}
}
}
HStack{
Text("Temps de jeu (h)")
.multilineTextAlignment(.leading)
Slider(value: $nbheure, in: 0...24, step: 1)
Text("\(nbheure, specifier: "%.0f")")
Image(systemName: "clock")
}
}
} // Form
} // NavigationView
}
}
But I am having error message inside my init(), saying " Variable 'self.session' used before being initialized".
I dont really understand why as "session" is an input in my init().
How can I use attributes of the selected PokSection entity to populate my DatePicker and my other Pickers default value
It shouldn't be difficult I guess but I am struggling...
Basically, I just want to have my Pickers set with the value coming from the selected PokSession.
And I want to see it in a Picker because I want to be able to modify it.
Thanks for your help

SwiftUI picker with TextField for optional entry

Picker with a choice of names. If ‘other’ is selected a TextField appears. User enters something into the TextField and the user entered value needs to also be reassigned to the Picker variable. I've searched and tried a 100 different options, nothing. (Also, I'm sure this isn't the best way, but don't know any better at this stage...) Thanks.
(Code simplified from actual)
import SwiftUI
struct ContentView: View {
#State private var playerList = ["sally", "bob", "mary"]
#State private var player1 = "tester"
#State private var player1other = ""
var body: some View {
NavigationView{
VStack{
List{
Picker("Player 1:", selection: $player1) {
ForEach(playerList, id: \.self) {
Text($0)
}
Divider()
Text("Non-roster player").tag("other")
}
if player1 == "other" {
TextField("Player name", text: $player1other)
}
//Now need something here that updates player1 to be whatever player1other is
// something like player1 = player1other
//that doesn't create an error - Type '()' cannot conform to 'View'
}
}
}
}
}
Edit: I should also address that NoeOnJupiter is correct in that your attempt to add player1 = player1other doesn't work because that's not a SwiftUI View but a statement. SwiftUI is declarative so you can't throw functions (for lack of a better term) in the middle of where you're building your view unless a view uses it (i.e. a button or the .onChange modifier)
One problem here is you have a static group of names that is supposed to be used to display the selected name (i.e. if the Picker selection is bob, bob will be shown by the Picker) but if there's a new name how will it be displayed by the Picker without being in the Picker's dataset?
I recommend doing one of two things:
Add new names to the dataset
Have a different Text element to display the player1 name
Approach 1:
Usage: Tap "sally", choose non-roster, type a name, submit. You'll notice that name gets added to the list.
struct ContentView: View {
#State private var playerList = ["sally", "bob", "mary"]
#State private var player1 = /**"tester"*/ "sally" // recommended by Swift Playgrounds to only use valid selections as a value for this
#State private var player1other = ""
var body: some View {
NavigationView{
VStack{
List{
Picker("Player 1:", selection: $player1) {
ForEach(playerList, id: \.self) {
Text($0)
}
Divider()
Text("Non-roster player").tag("other")
}
if player1 == "other" {
HStack {
TextField("Player name", text: $player1other)
Button("Submit") {
playerList.append(player1other)
player1 = player1other
player1other = "" // reset for next selection
}
}
}
//Now need something here that updates player1 to be whatever player1other is
// something like player1 = player1other
//that doesn't create an error - Type '()' cannot conform to 'View'
}
}
}
}
}
Approach 2:
Usage: Tap "sally", choose non-roster, start typing a name in the text-field. You'll notice the player name at the top of the screen updates in real time but will not be saved if the user changes names again.
struct ContentView: View {
#State private var playerList = ["sally", "bob", "mary"]
#State private var selectedFromList = "sally" // keeps track of picker selection, but not player
#State private var player1 = "tester"
#State private var player1other = ""
var body: some View {
NavigationView{
VStack{
// Displays current player name
Text(player1)
.font(.largeTitle)
List{
Picker("Player 1:", selection: $selectedFromList) {
ForEach(playerList, id: \.self) {
Text($0)
}
Divider()
Text("Non-roster player").tag("other")
}
// NEW
.onChange(of: selectedFromList) { newValue in
if selectedFromList != "other" {
player1 = selectedFromList
}
}
// ENDNEW
if selectedFromList == "other" { // Edited for new var
HStack {
TextField("Player name", text: $player1other)
// NEW
.onChange(of: player1other) { newValue in
player1 = player1other
}
//ENDNEW
}
}
//Now need something here that updates player1 to be whatever player1other is
// something like player1 = player1other
//that doesn't create an error - Type '()' cannot conform to 'View'
}
}
}
}
}
The reason you're getting
Type '()' cannot conform to 'View'
is because body is a ViewBuilder. Meaning it expects any type conforming to View, whereas player1 = player1other is of type Void != View.
Here's what you should do, depending on what you need:
Changing player1 to player1other when the user presses return (Recommended):
TextField("Player name", text: $player1other)
.onSubmit {
player1 = player1other
}
Directly assigning the text of TextField to be player1:
TextField("Player name", text: $player1)
Realtime updating player1other from player1:
TextField("Player name", text: $player1other)
.onChange(of: player1other) { newValue in
player1 = newValue
}

TextField swiftui xcode 13

I have been working with xcode 12 and swiftui. In my app I have textFiel with a localizable placeholder in Spanish and English, I switch to xcode 13 and it doesn't show me my localizable placeholder
this only happens in TextField, with SecureField it does not happen even with Text
this is my code
struct ContentView: View {
#State var email:String = ""
var body: some View {
VStack () {
TextField("login5", text: self.$email)
.autocapitalization(.none)
.padding()
.background(RoundedRectangle(cornerRadius: 50).stroke(Color("grayColor")))
}.padding(.horizontal, 20)
}
}
Localizable.strings
"login5" = "Correo eléctronico";
with SecureField in ios15, you can use the prompt parameter to get your localized string:
SecureField("purpose", text: $password, prompt: Text("login6"))
or using the label:
SecureField(text: $password) {
Text("login6")
}
EDIT1:
This is the test code I'm using to show a working localized TextField and SecureField.
import SwiftUI
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
#State var email = ""
#State var password = ""
#State var isVisible = false
var body: some View {
VStack (spacing: 55) {
Button(action: { isVisible.toggle() }) {
Text("Toggle isVisible")
}
TextField("login5", text: $email).border(.black)
if isVisible {
TextField("login6", text: $password).border(.green)
} else {
SecureField("password", text: $password, prompt: Text("login6")).border(.red)
}
}.padding(.horizontal, 20)
}
}
Test Localizable.strings file.
"login5" = "hola login5";
"login6" = "contraseña";
EDIT2: alternative approach of manually using LocalizedStringKey,
TextField(LocalizedStringKey("login5"), text: $email)
Your main Problem is, like workingdog already said, you need to use text: $variable.
That means for you declare your variable as #State var password = "" and use it like this..
struct ContentView: View {
#State var password = ""
...
if self.visible{
TextField("login6", text: $password)
....
} else {
SecureField("login6", text: $password)
....
}
}
Btw. next time post your code as code not as picture. Its easier to help you :)
Hope I understand your problem correctly and this will be your solution.
I've had the exact same problem, going from Xcode 12 to 13. All of a sudden some (not all) of my text fields no longer show localized string. I was able to fix the problem by forcing:
TextField(LocalizedString("usernameLabel"), text: $username)
Instead of
Textfield("usernameLabel", text: $username)

How to select an item by a button or tapGesture in my SwiftUI List and transfer it to another view

In my app I´m needing two players (only 2 names/strings), selected from an array built in a List/ForEach SwiftUI-code, which are used in another view.
What is the way to bring the name into a string for my Text(item)?
Can I select two items out of the list?
Thx for any help.
Franz
My code (modified, found by Ale Patron,Tutorial using UserDefaults with encoding and decoding the array/list ):
#State private var allTeams: [PlayerItem] = []
#State private var newPlayer = ""
#State private var selectedPlayer = ""
#State private var selection: String?
struct PlayerItem: Identifiable {
var id = UUID()
let player: String
}
var body: some View {
VStack{
HStack {
TextField("Add Players/Teams...", text: $newPlayer)
.textFieldStyle(RoundedBorderTextFieldStyle())
Button(action: {
self.allTeams.append(PlayerItem(player: self.newPlayer))
self.newPlayer = ""
}) {
Image(systemName: "plus")
}
.padding(.leading, 5)
}.padding()
List{
ForEach(allTeams) { playerItem in
Text(playerItem.player)
}
.onTapGesture {
print("How can I select my first und my second player")
}
}
Text("Selected Player: \(selectedPlayer)")
}
}
}
You should use indices for what you are trying to do.
Try this:
ForEach(allTeams.indices) { i in
Text(allTeams[i].player)
.onTapGesture {
print("How can I select my first und my second player")
print("The selected player is \(allTeams[i].player). The second player is \(allTeams[i + 1].player)"
}
}
Make sure to also check if the selected player is the last one in the array and to catch this. Otherwise, you may encounter an out-of-bounds error.

String Interpolation with Text View and Textfield in Swiftui

Hi I'm wondering if there's any way to have string interpolation with a textfield and Text in Swiftui. Like
Text("hi \(TextField("Enter your name", $name)")
You can try below code
struct ContentView: View {
#State private var name = ""
var body: some View {
VStack{
TextField("Enter your name", text: $name)
Text("Hi \(name)")
}
}
}
Hope this is what you want
If you really want to do that you can put your statements in an Hstack like so:
import SwiftUI
struct SwiftUIView: View {
#State var name = ""
var body: some View {
HStack {
Text("Hi")
TextField("Name", text: $name)
.frame(width: (name.isEmpty ? 45 : 0) + CGFloat(name.count) * 9)
Text("blabla")
}
}
}
Note: This gives you a dynamic change, but not a perfect one Because the size of each character is different you will get a bigger whitespace at the end. I just choose 9 here, for char width. I still think having a extra Textfield and using the variable then later, is the better option.