Picker choice changing the TextField in SwiftUI - swiftui

I want the user to be able to choose the currency they want to put in my app. But with this code:
import SwiftUI
struct ContentView: View {
#State private var amount = 0.0
#State private var currency = "USD"
let currencies = ["PLN", "USD", "EUR", "GBP", "JPY"]
var body: some View {
Form {
Picker("Currency", selection: $currency) {
ForEach(currencies, id: \.self) {
Text($0)
}
}
TextField("Amount", value: $amount, format: .currency(code: currency))
.keyboardType(.decimalPad)
}
}
}
The currency choices made with Picker don't change the currency type in the TextField (it stays USD at all times, even after deletion). How to push the chosen currency code to the TextField?

Format is not tracked to update TextField, you can do it forcefully using id depending on format parameter, like
TextField("Amount", value: $amount, format: .currency(code: currency))
.keyboardType(.decimalPad)
.id(currency) // << here !!
Tested with Xcode 13.2 / iOS 15.2

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: How to have a ForEach of editable TextFields?

I'm trying to have a component that basically starts with a single TextField for editing your home phone number, and then you can hit the add button to add different types of phone numbers to your account. for right now I just want to allow the user to edit the phone number, but in the future I'll probably make it so that there are actually two TextFields for each PhoneNumberListItem. 1 field for the editable name and 1 field for the editable phone itself. I'm coming from Android/Compose which is maybe where my line of thinking is stuck. appreciate any pointers.
func ListOfMyPhoneNumbers() -> some View {
#State var listOfMyPhones = [PhoneNumberListItem(name: "home", phone: "123")]
return VStack {
ForEach(listOfMyPhones) { i in
TextField(i.name, text: i.$phone).textFieldStyle(.roundedBorder)
}
Button("Add Phone") {
listOfMyPhones.append(PhoneNumberListItem(name: "other", phone: ""))
}.buttonStyle(.borderless)
Spacer()
}
.padding()
}
struct PhoneNumberListItem: Identifiable {
let id = UUID()
let name: String
#State var phone: String
}
Do not use #State var phone: String in your PhoneNumberListItem.
#State is only for use in a View. Just use a var.
Also use a struct for your view. With the following example code you will be able to edit the TextField in a ForEach, like this:
struct PhoneNumberListItem: Identifiable {
let id = UUID()
var name: String // <--- here
var phone: String // <--- here
}
struct ContentView: View {
var body: some View {
PhoneView() // <--- here
}
}
struct PhoneView: View { // <--- here
#State var listOfMyPhones = [PhoneNumberListItem(name: "home", phone: "123")]
var body: some View {
VStack {
// --- here
ForEach($listOfMyPhones) { $item in
TextField("phone", text: $item.phone)
TextField("name", text: $item.name)
}.textFieldStyle(.roundedBorder)
Button("Add Phone") {
listOfMyPhones.append(PhoneNumberListItem(name: "other", phone: ""))
}.buttonStyle(.borderless)
Spacer()
}
.padding()
}
}
As you progress with learning SwiftUI, you will want to use ObservableObject class to manage your data. Have a look at this link, it gives you some good examples of how to manage data in your app :
https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app

SwiftUI Picker selection not updated unless value shown in view (using Enum for selection)

import SwiftUI
enum TestEnum: String, CaseIterable {
case firstValue = "First Value"
case secondValue = "Second Value"
case thirdValue = "Third Value"
}
struct TestView: View {
#State private var testEnumSelection = TestEnum.allCases.first!
#State private var isShowingSheet = false
var body: some View {
VStack {
Picker("Test Enum Selection", selection: $testEnumSelection) {
ForEach(TestEnum.allCases, id: \.self) { testEnum in
Text(testEnum.rawValue)
}
}
//Text("Enum Selection: \(testEnumSelection.rawValue)") Enum value not updated if this line is not inlcuded
Button("Show Sheet", action: {
isShowingSheet = true
})
}
.padding()
.sheet(isPresented: $isShowingSheet) {
Text(testEnumSelection.rawValue)
.padding()
}
}
}
I am trying to use an enum value selected from a picker in a sheet view but the value from the picker is not being updated for the sheet. The value does get updated if I show the picker selection on screen elsewhere like in a Text object but I don't want to do that.
Could someone explain to me why I need to show the enum selection for it to be updated for the sheet and how to get around doing this?
The sheet content is created once, so it is not updated when state in parent is updated.
The possible solution is to separate sheet content into standalone view and use binding - bound variable will update view internals.
Here is a modified part (tested with Xcode 13.2 / macOS 12.1)
.padding()
.sheet(isPresented: $isShowingSheet) {
SheetContent(value: $testEnumSelection) // << here !!
}
}
}
struct SheetContent: View {
#Binding var value: TestEnum
var body: some View {
Text(value.rawValue)
.padding()
}
}

SwiftUI Picker unable to transfer data from Picker to Binding variable

Hopefully you can see what I'm trying to achieve from the code below but simply put, I'm trying to update .selectedTown which is binded to my Picker. The row tapped on will bind to .selectedTown which will then update the Text 'Your selected town is: [.selectedTown]'
However, the selected row is not binding and the text remains 'Your selected town is: '
struct ContentView: View {
struct Town: Identifiable {
let name: String
let id = UUID()
}
private var towns = [
Town(name: "Bristol"),
Town(name: "Oxford"),
Town(name: "Portsmouth"),
Town(name: "Newport"),
Town(name: "Glasgow"),
]
#State private var selectedTown: String = ""
var body: some View {
NavigationView {
VStack {
Form {
Section {
Picker("", selection: $selectedTown) {
ForEach(towns, id: \.id) {
Text("\($0.name)")
}
}
.pickerStyle(.inline)
.labelsHidden()
} header: {
Text("Random Towns")
}
}
Text("Your selected town is: \(selectedTown)")
.padding()
}
.navigationTitle("Random")
}
}
}
Hopefully this is just a small fix but I've tried for what seems a day to find a solutino and am now stuck. Any help would be gratefully received,
Simon
The types don't match. your array is a towns: [Town] and your selectedTown: String
Option 1 is to change the variable
#State private var selectedTown: Town = Town(name: "Sample")
Option 2 is to add a tag
Text("\($0.name)").tag($0.name)
Option 3 is change the variable and the tag
#State private var selectedTown: Town? = nil
Text("\($0.name)").tag($0 as? Town)
The "best" option depends on what you use selectedTown for.
The type of selection should be same as picked item or use tag, like below
Picker("", selection: $selectedTown) {
ForEach(towns, id: \.id) {
Text("\($0.name)").tag($0.name) // << here !!
}
}
Tested with Xcode 13.2 / iOS 15.2

Observing changes #FocusState variable not working as expected

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