2

How can the string from a TextField be stored in UserDefaults using SwiftUI?

I have seen this tutorial on how to save the state of a toggle in UserDefaults and that looks promising, but I can't figure out how to use the same idea for storing the content from a TextField: https://www.youtube.com/watch?v=nV-OdfQhStM&list=PLerlU8xuZ7Fk9zRMuh7JCKy1eOtdxSBut&index=3&t=329s

I still want to be able to update the string by typing new text in the TextField, but unless I do that, I want the string to be maintained in the view. Both when changing page and completely exitting the app.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
oscar
  • 33
  • 1
  • 3
  • Welcome to StackOverflow. Please show what research you've undertaken, what you've already tried, what didn't work, code samples etc. Read [ask] and [mcve] and update your question. – Ashley Mills Sep 21 '19 at 11:02
  • Look at my solution there is a link to YouTube example https://stackoverflow.com/a/57982560/4067700 – Victor Kushnerov Sep 22 '19 at 14:49

2 Answers2

6

For things like these I suggest you use the .debounce publisher.

import SwiftUI
import Combine

class TestViewModel : ObservableObject {
    private static let userDefaultTextKey = "textKey"
    @Published var text = UserDefaults.standard.string(forKey: TestViewModel.userDefaultTextKey) ?? ""
    private var canc: AnyCancellable!

    init() {
        canc = $text.debounce(for: 0.2, scheduler: DispatchQueue.main).sink { newText in
            UserDefaults.standard.set(newText, forKey: TestViewModel.userDefaultTextKey)            
        }
    }

    deinit {
        canc.cancel()
    }
}

struct ContentView: View {
    @ObservedObject var viewModel = TestViewModel()

    var body: some View {
        TextField("Type something...", text: $viewModel.text)
    }
}

The .debounce publisher documentation says:

Use this operator when you want to wait for a pause in the delivery of events from the upstream publisher. For example, call debounce on the publisher from a text field to only receive elements when the user pauses or stops typing. When they start typing again, the debounce holds event delivery until the next pause.

You don't really want to save the text in the UserDefaults each time the user types something (i.e. for every character he/she types). You just want the text to be ultimately saved to the UserDefaults. The debounce publisher waits for the user to stop typing according to the time you set in the debounce init (in the example above 0.2 secs) and then forwards the event to the sink subscriber that actually saves the text. Always use the debounce publisher when you have a lot of events (for example new text typed in a text field) that may trigger "heavy" operations (whatever you can think of: something related to CoreData, network calls and so forth).

superpuccio
  • 11,674
  • 8
  • 65
  • 93
  • Thank you for your comment! I have tested the code, but I still have the problem of the user input being deleted when the app restarts. Saving to the UserDefaults should be ok as the inputs for this app will happen once a week or less, so the data load shouldn't be too heavy. I would still like better solutions than saving to the UserDefaults if possible as I'm new to coding in SwiftUI. – oscar Sep 22 '19 at 01:16
  • @oscar I edited my answer in order for your users to see the text he/she has previously typed when the app starts. It's just a matter of reading the user default you've previously saved. – superpuccio Sep 22 '19 at 10:15
2

You can create a custom binding as described here and call UserDefaults.standard.set when the text binding is set.

struct ContentView: View {
    @State var location: String = ""

    var body: some View {
        let binding = Binding<String>(get: {
            self.location
        }, set: {
            self.location = $0
            // Save to UserDefaults here...
        })

        return VStack {
            Text("Current location: \(location)")
            TextField("Search Location", text: binding)
        }

    }
}

Copied from answer to 'TextField changes in SwiftUI'

KB-YYZ
  • 712
  • 7
  • 7
  • Please don't post link-only answers. While they may be correct, the integrity of your answer will be compromised should the link ever change/close. Try to include the essential parts from this link in your answer. – Ashley Mills Sep 21 '19 at 11:04
  • Thank you for your reply! My question is now what to write where you have written "Save to UserDefaults here...". This is the part I'm confused about. I would like the input data from the TextField to remain in the view, even after closing the app. – oscar Sep 21 '19 at 14:36