I have a textfield where a user can manually enter a location string. This is bound to my observed object AppState
which i’ve re-labelled (app
) which has a @Published var
called destination
. This works fine and I am able to bind the manually typed text in the text field like so:
TextField("Destination", text: $app.destination)
I have started trying to implement a favourites system where the user can save and change up to 4 destinations to quickly enter without re-typing (Only 1 is entered into the textfield at a time).
When the user presses on one of their favourites, the favourite appears in the textfield, there they can change it by typing a new one an saving it, or pressing a new favourite which replaces the one in the textField. The user must also have the option of not entering a favourite at all and be able to enter a random one too.
I have got this part working all reasonably well so far and am happy with the functionality.
My issue here is that I can’t figure out how to bind whatever is in the textfield to my ObservedObject
app.destination
. After some trouble shooting I have found that only the manually entered text will store in app.destination
. Having a favourite appear in the textfield does not bind.
I have tried variations on .onChange()
and .onTapGesture()
and .focused()
to run an if/else block which would simply assign the text in the textfield to app.destination
but it is still buggy and you can’t then save a new favourite…
I’m going around in circles trying to figure this out and would greatly appreciate some help.
I have a minimum reproducible example for you to copy and paste. Thank you
Main View
struct ContentView: View {
@StateObject private var favoritesViewModel = FavoritesViewModel()
@ObservedObject var app = AppState()
@State private var newLocation = ""
@State private var selectedLocationIndex = 0
@State private var userSelectedFavourite: String = ""
@State private var favouritePressed: Bool = false
@FocusState private var isFocused: Bool
var body: some View {
VStack {
HStack {
//.........THIS IS THE ISSUE TEXTFIELD
TextField("Destination", text: (favouritePressed && !isFocused) ? $favoritesViewModel.favoriteLocations[selectedLocationIndex] : $newLocation)
//.....................................
.focused($isFocused)
.contentShape(.rect)
.textInputAutocapitalization(.characters)
.disableAutocorrection(true)
.padding()
.frame(width: 280, height: 60)
.overlay(
RoundedRectangle(cornerRadius: 10)
.stroke(Color.gray, lineWidth: 2)
)
Button("Save") {
saveLocation(locationIndex: selectedLocationIndex)
}
}
Picker("Favorite Locations", selection: $selectedLocationIndex) {
ForEach(favoritesViewModel.favoriteLocations.indices, id: .self) { index in
Text(favoritesViewModel.favoriteLocations[index])
.tag(index)
}
}
.onChange(of: selectedLocationIndex) {
favouritePressed = true
isFocused = false
}
.pickerStyle(.palette)
Spacer()
}
.navigationTitle("Favorite Locations")
}
func saveLocation(locationIndex: Int) {
print("Saving...")
if !newLocation.isEmpty {
favoritesViewModel.addFavoriteLocation(newLocation, locationIndex: locationIndex)
newLocation = ""
}
}
}
This one just stores favourites:
class FavoritesViewModel: ObservableObject {
// Key for UserDefaults
let favoritesKey = "favoriteLocations"
// UserDefaults instance
var defaults: UserDefaults {
UserDefaults.standard
}
// Published property to manage favorite locations
@Published var favoriteLocations: [String] = []
// Initialize with default values
init() {
self.favoriteLocations = loadFavorites()
}
// Load favorite locations from UserDefaults
func loadFavorites() -> [String] {
return defaults.stringArray(forKey: favoritesKey) ?? []
}
// Save favorite locations to UserDefaults
func saveFavorites() {
defaults.set(favoriteLocations, forKey: favoritesKey)
}
// Add a new favorite location
func addFavoriteLocation(_ location: String, locationIndex: Int) {
// Check if location is already in favorites and limit to 4 locations
if !favoriteLocations.contains(location) {
if favoriteLocations.count < 4 {
favoriteLocations.insert(location, at: locationIndex)
} else {
favoriteLocations.remove(at: locationIndex)
favoriteLocations.insert(location, at: locationIndex)
}
saveFavorites() // Save after adding or updating favorites
}
}
}
And just the Observed class:
class AppState: ObservableObject {
// THIS IS THE VARIABLE IM TRYING TO SAVE TEXT TO
@Published var destination: String = ""
}