SwiftUI - Add values from two textfields using var or let - ios

How to add values of two textfields in SwiftUI?
I have this code:
import SwiftUI
struct ContentView: View {
#State private var value1 = ""
#State private var value2 = ""
private var sumValues = (Int(value1) ?? 0) + (Int(value2) ?? 0)
var body: some View {
VStack {
TextField("type value 1 here", text: $value1)
.keyboardType(.numberPad)
TextField("type value 2 here", text: $value2)
.keyboardType(.numberPad)
Text("sum: \(sumValues)")
// I need to have a var or let, so I cannot use something like this:
//Text("sum: \((Int(value1) ?? 0) + (Int(value2) ?? 0))")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
I am getting this error on line with private var sumValues...:
Cannot use instance member 'value1' within property initializer;
property initializers run before 'self' is available
Cannot use instance member 'value2' within property initializer;
property initializers run before 'self' is available

Use a computed-property.
private var sumValues: Int { (Int(value1) ?? 0) + (Int(value2) ?? 0) }

The possible approach is to move all this logic (and might be all other) into view model, like below - so keep engine separated of view and let standard observed dynamic property take care of view updates:
Here is simple demo. Tested with Xcode 12 / iOS 14
class CalcViewModel: ObservableObject {
#Published var value1 = "" {
didSet { update() }
}
#Published var value2 = "" {
didSet { update() }
}
#Published var sum: Int = 0
private func update() {
self.sum = (Int(value1) ?? 0) + (Int(value2) ?? 0)
}
}
struct ContentView: View {
#ObservedObject var vm = CalcViewModel()
var body: some View {
VStack {
TextField("type value 1 here", text: $vm.value1)
.keyboardType(.numberPad)
TextField("type value 2 here", text: $vm.value2)
.keyboardType(.numberPad)
Text("sum: \(vm.sum)")
}
}
}

I think you can change sumValues to a computed property:
private var sumValues: Int {
get {
(Int(value1) ?? 0) + (Int(value2) ?? 0)
}
}

Related

SwiftUI: Changing a private var with a slider

I have a private variable in a struct which I can only access using a setter and getter. I want to change this variable using a slider, so I am attempting to bind a different var with this var using willSet:
struct MyStruct {
private var myVar: Float? = nil
mutating func setMyVar(newVal: Float) {
if (SomeCondition) { // always true when the slider is in use
myVar = newVal
} else {
// stuff
}
}
func getMyVar() -> Float {
myVar == nil ? 0.0 : myVar!
}
}
struct MyView: View {
#State var myStructToEdit = MyStruct()
#State var tempVar: Double = 0.0 {
willSet {
myStructToEdit.setMyVar(newVal: Float(newValue))
}
}
var body: some View {
VStack {
Text(String(tempVar))
Text(String(myStructToEdit.getMyVar()))
Slider(value: $tempVar, in: 1.0...20.0, step: 1.0)
}
}
}
As the slider moves, tempVar changes but MyVar doesn't. What is the correct way to achieve this binding?
Property observers won't work with SwiftUI #State variables.
Use .onChange(of:) to act upon changes to tempVar:
struct MyView: View {
#State var myStructToEdit = MyStruct()
#State var tempVar: Double = 0.0
var body: some View {
VStack {
Text(String(tempVar))
Text(String(myStructToEdit.getMyVar()))
Slider(value: $tempVar, in: 1.0...20.0, step: 1.0)
.onChange(of: tempVar) { value in
myStructToEdit.setMyVar(newVal: Float(value))
}
}
}
}
Use Binding(get:set:) to directly set and get the value in your struct:
You don't need tempVar. You can directly set and get the value to and from your struct.
struct ContentView: View {
#State var myStructToEdit = MyStruct()
var body: some View {
VStack {
Text(String(myStructToEdit.getMyVar()))
Slider(value: Binding(get: {
myStructToEdit.getMyVar()
}, set: { value in
myStructToEdit.setMyVar(newVal: value)
}), in: 1.0...20.0, step: 1.0)
}
}
}
or assign the Binding to a let to make it cleaner:
struct ContentView: View {
#State var myStructToEdit = MyStruct()
var body: some View {
let myVarBinding = Binding(
get: { myStructToEdit.getMyVar() },
set: { value in myStructToEdit.setMyVar(newVal: value) }
)
VStack {
Text(String(myStructToEdit.getMyVar()))
Slider(value: myVarBinding, in: 1.0...20.0, step: 1.0)
}
}
}
Note: Since myVarBinding is already a binding, you do not need to use a $ to turn it into a binding.

SwiftUI Core Data Binding TextFields in DetailView

I have a SwiftUI app with SwiftUI App lifecycle that includes a master-detail type
list driven from CoreData. I have the standard list in ContentView and NavigationLinks
to the DetailView. I pass a Core Data entity object to the Detailview.
My struggle is setting-up bindings to TextFields in the DetailView for data entry
and for editing. I tried to create an initializer which I could not make work. I have
only been able to make it work with the following. Assigning the initial values
inside the body does not seem like the best way to do this, though it does work.
Since the Core Data entities are ObservableObjects I thought I should be able to
directly access and update bound variables, but I could not find any way to reference
a binding to Core Data in a ForEach loop.
Is there a way to do this that is more appropriate than my code below?
Simplified Example:
struct DetailView: View {
var thing: Thing
var count: Int
#State var localName: String = ""
#State private var localComment: String = ""
#State private var localDate: Date = Date()
//this does not work - cannot assign String? to State<String>
// init(t: Thing) {
// self._localName = t.name
// self._localComment = t.comment
// self._localDate = Date()
// }
var body: some View {
//this is the question - is this safe?
DispatchQueue.main.async {
self.localName = self.thing.name ?? "no name"
self.localComment = self.thing.comment ?? "No Comment"
self.localDate = self.thing.date ?? Date()
}
return VStack {
Text("\(thing.count)")
.font(.title)
Text(thing.name ?? "no what?")
TextField("name", text: $localName)
Text(thing.comment ?? "no comment?")
TextField("comment", text: $localComment)
Text("\(thing.date ?? Date())")
//TextField("date", text: $localDate)
}.padding()
}
}
And for completeness, the ContentView:
struct ContentView: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(sortDescriptors: [NSSortDescriptor(keyPath: \Thing.date, ascending: false)])
private var things : FetchedResults<Thing>
#State private var count: Int = 0
#State private var coverDeletedDetail = false
var body: some View {
NavigationView {
List {
ForEach(things) { thing in
NavigationLink(destination: DetailView(thing: thing, count: self.count + 1)) {
HStack {
Image(systemName: "gear")
.resizable()
.frame(width: 40, height: 40)
.onTapGesture(count: 1, perform: {
updateThing(thing)
})
Text(thing.name ?? "untitled")
Text("\(thing.count)")
}
}
}
.onDelete(perform: deleteThings)
if UIDevice.current.userInterfaceIdiom == .pad {
NavigationLink(destination: WelcomeView(), isActive: self.$coverDeletedDetail) {
Text("")
}
}
}
.navigationTitle("Thing List")
.navigationBarItems(trailing: Button("Add Task") {
addThing()
})
}
}
private func updateThing(_ thing: FetchedResults<Thing>.Element) {
withAnimation {
thing.name = "Updated Name"
thing.comment = "Updated Comment"
saveContext()
}
}
private func deleteThings(offsets: IndexSet) {
withAnimation {
offsets.map { things[$0] }.forEach(viewContext.delete)
saveContext()
self.coverDeletedDetail = true
}
}
private func addThing() {
withAnimation {
let newThing = Thing(context: viewContext)
newThing.name = "New Thing"
newThing.comment = "New Comment"
newThing.date = Date()
newThing.count = Int64(self.count + 1)
self.count = self.count + 1
saveContext()
}
}
func saveContext() {
do {
try viewContext.save()
} catch {
print(error)
}
}
}
And Core Data:
extension Thing {
#nonobjc public class func fetchRequest() -> NSFetchRequest<Thing> {
return NSFetchRequest<Thing>(entityName: "Thing")
}
#NSManaged public var comment: String?
#NSManaged public var count: Int64
#NSManaged public var date: Date?
#NSManaged public var name: String?
}
extension Thing : Identifiable {
}
Any guidance would be appreciated. Xcode 12.2 iOS 14.2
You already mentioned it. CoreData works great with SwiftUI.
Just make your Thing as ObservableObject
#ObservedObject var thing: Thing
and then you can pass values from thing as Binding. This works in ForEach aswell
TextField("name", text: $thing.localName)
For others - note that I had to use the Binding extension above since NSManagedObjects are optionals. Thus as davidev stated:
TextField("name", text: Binding($thing.name, "no name"))
And ObservedObject, not Observable

How to set textfield character limit SwiftUI?

I'm using SwiftUi version 2 for my application development. I'm facing issue with textfield available in SwiftUI. I don't want to use UITextField anymore. I want to limit the number of Characters in TextField. I searched a lot and i find some answer related to this but those answer doesn't work for SwiftUI version 2.
class textBindingManager: ObservableObject{
let characterLimit: Int
#Published var phoneNumber = "" {
didSet {
if phoneNumber.count > characterLimit && oldValue.count <= characterLimit {
phoneNumber = oldValue
}
}
}
init(limit: Int = 10) {
characterLimit = limit
}
}
struct ContentView: View {
#ObservedObject var textBindingManager = TextBindingManager(limit: 5)
var body: some View {
TextField("Placeholder", text: $textBindingManager.phoneNumber)
}
}
No need to use didSet on your published property. You can add a modifier to TextField and limit the string value to its prefix limited to the character limit:
import SwiftUI
struct ContentView: View {
#ObservedObject var textBindingManager = TextBindingManager(limit: 5)
var body: some View {
TextField("Placeholder", text: $textBindingManager.phoneNumber)
.padding()
.onChange(of: textBindingManager.phoneNumber, perform: editingChanged)
}
func editingChanged(_ value: String) {
textBindingManager.phoneNumber = String(value.prefix(textBindingManager.characterLimit))
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
class TextBindingManager: ObservableObject {
let characterLimit: Int
#Published var phoneNumber = ""
init(limit: Int = 10){
characterLimit = limit
}
}
The following should be the simpliest. It limits the number of characters to 10.
struct ContentView: View {
#State var searchKey: String = ""
var body: some View {
TextField("Enter text", text: $searchKey)
.onChange(of: searchKey) { newValue in
if newValue.count > 10 {
self.searchKey = String(newValue.prefix(10))
}
}
}
}
This solution wraps everything up in a new Component. You could adapt this to perform other parsing / pattern checking quite easily.
struct ContentView : View {
#State private var myTextValue: String = ""
var body: some View {
LimitedTextField(value: $myTextValue, charLimit: 2)
}
}
struct LimitedTextField : View {
#State private var enteredString: String = ""
#Binding var underlyingString: String
let charLimit : Int
init(value: Binding<String>, charLimit: Int) {
_underlyingString = value
self.charLimit = charLimit
}
var body: some View {
HStack {
TextField("", text: $enteredString, onCommit: updateUnderlyingValue)
.onAppear(perform: { updateEnteredString(newUnderlyingString: underlyingString) })
.onChange(of: enteredString, perform: updateUndelyingString)
.onChange(of: underlyingString, perform: updateEnteredString)
}
}
func updateEnteredString(newUnderlyingString: String) {
enteredString = String(newUnderlyingString.prefix(charLimit))
}
func updateUndelyingString(newEnteredString: String) {
if newEnteredString.count > charLimit {
self.enteredString = String(newEnteredString.prefix(charLimit))
underlyingString = self.enteredString
}
}
func updateUnderlyingValue() {
underlyingString = enteredString
}
}

How to extract String value from Observed Object in Swift

I want to extract String value from Observed Object
This is example code
import SwiftUI
import Combine
class SetViewModel : ObservableObject {
private static let userDefaultTextKey = "textKey"
#Published var text: String = UserDefaults.standard.string(forKey: SetViewModel.userDefaultTextKey) ?? ""
private var canc: AnyCancellable!
init() {
canc = $text.debounce(for: 0.2, scheduler: DispatchQueue.main).sink { newText in
UserDefaults.standard.set(newText, forKey: SetViewModel.userDefaultTextKey)
}
}
deinit {
canc.cancel()
}
}
struct SettingView: View {
#ObservedObject var viewModel = SettingViewModel()
var body: some View {
ZStack {
Rectangle().foregroundColor(Color.white).edgesIgnoringSafeArea(.all).background(Color.white)
VStack {
TextField("test", text: $viewModel.text).textFieldStyle(BottomLineTextFieldStyle()).foregroundColor(.red)
Text($viewModel.text) //I want to get String Value from $viewModel.text
}
}
}
}
I want to use "$viewModel.text"'s String value. How can I do this?
Here is fix
Text(viewModel.text) // << use directly, no $ needed, it is for binding
try this:
struct SettingView: View {
#ObservedObject var viewModel = SetViewModel()
var body: some View {
ZStack {
Rectangle().foregroundColor(Color.white).edgesIgnoringSafeArea(.all).background(Color.white)
VStack {
TextField("test", text: self.$viewModel.text)
.textFieldStyle(PlainTextFieldStyle())
.foregroundColor(.red)
Text(viewModel.text) //I want to get String Value from $viewModel.text
}
}
}
}

How to observe a TextField value with SwiftUI and Combine?

I'm trying to execute an action every time a textField's value is changed.
#Published var value: String = ""
var body: some View {
$value.sink { (val) in
print(val)
}
return TextField($value)
}
But I get below error.
Cannot convert value of type 'Published' to expected argument type 'Binding'
This should be a non-fragile way of doing it:
class MyData: ObservableObject {
var value: String = "" {
willSet(newValue) {
print(newValue)
}
}
}
struct ContentView: View {
#ObservedObject var data = MyData()
var body: some View {
TextField("Input:", text: $data.value)
}
}
In your code, $value is a publisher, while TextField requires a binding. While you can change from #Published to #State or even #Binding, that can't observe the event when the value is changed.
It seems like there is no way to observe a binding.
An alternative is to use ObservableObject to wrap your value type, then observe the publisher ($value).
class MyValue: ObservableObject {
#Published var value: String = ""
init() {
$value.sink { ... }
}
}
Then in your view, you have have the binding $viewModel.value.
struct ContentView: View {
#ObservedObject var viewModel = MyValue()
var body: some View {
TextField($viewModel.value)
}
}
I don't use combine for this. This it's working for me:
TextField("write your answer here...",
text: Binding(
get: {
return self.query
},
set: { (newValue) in
self.fetch(query: newValue) // any action you need
return self.query = newValue
}
)
)
I have to say it's not my idea, I read it in this blog: SwiftUI binding: A very simple trick
If you want to observe value then it should be a State
#State var value: String = ""
You can observe TextField value by using ways,
import SwiftUI
import Combine
struct ContentView: View {
#State private var Text1 = ""
#State private var Text2 = ""
#ObservedObject var viewModel = ObserveTextFieldValue()
var body: some View {
//MARK: TextField with Closures
TextField("Enter text1", text: $Text1){
editing in
print(editing)
}onCommit: {
print("Committed")
}
//MARK: .onChange Modifier
TextField("Enter text2", text: $Text2).onChange(of: Text2){
text in
print(text)
}
//MARK: ViewModel & Publisher(Combine)
TextField("Enter text3", text: $viewModel.value)
}
}
class ObserveTextFieldValue: ObservableObject {
#Published var value: String = ""
private var cancellables = Set<AnyCancellable>()
init() {
$value.sink(receiveValue: {
val in
print(val)
}).store(in: &cancellables)
}
}
#Published is one of the most useful property wrappers in SwiftUI, allowing us to create observable objects that automatically announce when changes occur that means whenever an object with a property marked #Published is changed, all views using that object will be reloaded to reflect those changes.
import SwiftUI
struct ContentView: View {
#ObservedObject var textfieldData = TextfieldData()
var body: some View {
TextField("Input:", text: $textfieldData.data)
}
}
class TextfieldData: ObservableObject{
#Published var data: String = ""
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Resources