how to bind to both nullable and nonnullable values in swiftui - swiftui

I have the following view, which works fine:
struct Decimal_text_field_nullable : View {
#State var name : String
#State var place_holder : String
#Binding var value : Double?
var format_as_decimal: NumberFormatter {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
return formatter
}
var body: some View {
VStack(alignment: .leading) {
Text(name)
.font(.headline)
TextField( place_holder, value : $value, formatter: format_as_decimal )
.add_close_button()
.keyboardType(.decimalPad)
.padding(.all)
.background(Color(red: 239.0/255.0, green: 243.0/255.0, blue: 244.0/255.0, opacity: 0.2))
}
.padding(.horizontal, 15)
}
however I have to have a Decimal_text_field_nullable and a Decimal_text_field with exactly the same code except for a question mark on the definition of value - because I can't bind a Double value to a Double? value.
Is there any way to make a decimal_text_field where this can work:
VStack()
{
Decimal_text_field("non optional value", value = $non_nullable_double )
Decimal_text_field("optional_value", value = $nullable_double )
}

You can use this approach for both bindings. This code used for both.
struct Decimal_text_field: View {
#State private var name: String
#State private var place_holder: String
#Binding private var value: Double?
init(name : String, place_holder : String, value : Binding<Double?>) {
self.name = name
self.place_holder = place_holder
self._value = value
}
init(name : String, place_holder : String, value : Binding<Double>) {
self.name = name
self.place_holder = place_holder
self._value = Binding(get: {Optional(value.wrappedValue)}, set: {value.wrappedValue = $0 ?? 0})
}
var format_as_decimal: NumberFormatter {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
return formatter
}
}
Note: Try to avoid under score on the var name and struct name.

Related

Picker view is not changing when you try to select a different option in swiftui

I am using in swiftUI. When select picker, it is not changing. Here is code..
Here is datamodel:
struct SourceAccountModel:Codable,Identifiable{
var id: Int
let accountNumber: String
let accountTitle: String
let priaryAccount: String
init(id:Int=0,accountNumber: String, accountTitle: String, priaryAccount: String) {
self.id = id
self.accountNumber = accountNumber
self.accountTitle = accountTitle
self.priaryAccount = priaryAccount
}
}
Here is my code
struct Test2: View {
#State private var selectedOption = "Option 1"
#State private var sourceAccountList = [SourceAccountModel]()
var body: some View {
VStack{
ZStack {
RoundedRectangle(cornerRadius: 8)
.fill(Color.white)
.shadow(radius: 2)
Picker(selection: $selectedOption,label: EmptyView()) {
ForEach (0..<sourceAccountList.count,id: \.self) {
Text(sourceAccountList[$0].accountNumber)
}
}
.padding(8)
}
.frame(maxWidth: .infinity)
}.onAppear{
intitializeValue()
}
}
func intitializeValue(){
self.sourceAccountList.append(SourceAccountModel(id:1,accountNumber: "Option 1", accountTitle: "", priaryAccount: ""))
self.sourceAccountList.append(SourceAccountModel(id:2,accountNumber: "Option 2", accountTitle: "", priaryAccount: ""))
}
}
Always select first value. What is the wrong with my code?
selectedOption is a String, but your ForEach iterates over Range<Int>.
You can fix this by changing selectedOption to Int, e.g.
#State private var selectedOption = 0
You might find it easier to store the actual object in selectedOption: SourceAccountModel, iterate over the sourceAccountList, and tag each row:
struct SourceAccountModel: Identifiable, Hashable {
let id: Int
let accountNumber: String
init(id: Int, accountNumber: String) {
self.id = id
self.accountNumber = accountNumber
}
}
struct ContentView: View {
init() {
let sourceAccountList = [SourceAccountModel(id: 1, accountNumber: "Option 1"),
SourceAccountModel(id: 2, accountNumber: "Option 2")]
_sourceAccountList = State(wrappedValue: sourceAccountList)
_selectedOption = State(wrappedValue: sourceAccountList[0])
}
#State private var selectedOption: SourceAccountModel
#State private var sourceAccountList = [SourceAccountModel]()
var body: some View {
VStack {
Picker("Select", selection: $selectedOption) {
ForEach(sourceAccountList) { model in
Text(model.accountNumber).tag(model)
}
}
}
}
}

Change in View does not update object/model

I have a problem where a change in the View is not updating the underlying object in the model. Idea here is to generate a dynamic list of attributes of all different types (string, date, bool) and in the GUI all looks fine but when hitting the Save button, I can see that the data is not updated. What am I missing here?
Full working demo project below:
//
// ContentView.swift
// AttributeDemo
//
// Created by Max on 28.05.22.
//
import SwiftUI
public enum AttributeType: Codable, CaseIterable{
case int
case string
case datetime
case decimal
case double
case boolean
var stringValue: String {
switch self {
case .int: return "Full numbers"
case .string: return "Text"
case .datetime: return "Date"
case .decimal: return "Decimal"
case .double: return "Double"
case .boolean: return "Yes/No"
}
}
}
public class Attribute: Identifiable, Codable, ObservableObject {
public var id: UUID = UUID()
public var bkey: String = ""
public var tmp_create: Date = Date()
public var itemBkey: String = ""
public var attrType: AttributeType = .string
public var name: String = ""
public var description: String = ""
public var value_int: Int = 0
public var value_string: String = ""
public var value_datetime: Date = Date()
public var value_decimal: Decimal = 0.0
public var value_double: Double = 0.0
public var value_boolean: Bool = false
var userBkey: String = ""
var userToken: String = ""
}
struct ContentView: View {
#State private var attributes: [Attribute] = []
#State private var showingAttributeTypes = false
var body: some View {
VStack{
Button("Add attribute"){
self.showingAttributeTypes.toggle()
}
.confirmationDialog("Select a color", isPresented: $showingAttributeTypes, titleVisibility: .visible) {
Button("Text") {
addAttribute(attributeType: .string)
}
Button("Number") {
addAttribute(attributeType: .decimal)
}
Button("Date") {
addAttribute(attributeType: .datetime)
}
Button("Yes/No") {
addAttribute(attributeType: .boolean)
}
}
ForEach(self.attributes){value in
AttributeView(attribute: value)
}
Button("Save"){
self.attributes.forEach{value in
print(value.attrType.stringValue)
print(value.value_string)
print(value.value_datetime)
print(value.value_boolean)
print("--------------------------------")
}
}
}
}
func addAttribute(attributeType: AttributeType){
var attribute = Attribute()
attribute.attrType = attributeType
self.attributes.append(attribute)
}
}
struct AttributeView: View {
#ObservedObject var attribute: Attribute = Attribute()
#State private var description: String = ""
#State private var value_boolean: Bool = false
#State private var value_string: String = ""
#State private var value_decimal: Decimal = 0.0
#State private var value_double: Double = 0.0
#State private var value_datetime: Date = Date()
var body: some View {
HStack{
FormField(fieldName: "Description", fieldValue: $description)
.keyboardType(.default)
Spacer()
switch(attribute.attrType){
case .boolean:
Toggle(isOn: $value_boolean) {
Label("", image: "")
}
case .string:
TextField("", text: $value_string)
.keyboardType(.default)
case .datetime:
DatePicker(selection: $value_datetime, displayedComponents: .date, label: { Text("") })
case .decimal:
TextField("", value: $value_decimal, format: .number)
.keyboardType(.decimalPad)
case .double:
TextField("", value: $value_double, format: .number)
.keyboardType(.decimalPad)
default:
EmptyView()
}
}
}
}
struct FormField: View {
var fieldName = ""
#Binding var fieldValue: String
var body: some View{
TextField(fieldName, text: $fieldValue)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
To make changes in the View update the underlying object in the model, you could try a small re-structure of your code, where you make Attribute a struct, use an ObservableObject model to keep your array of Attributes, and use them like in this example code:
public struct Attribute: Identifiable, Codable { // <-- here
public var id: UUID = UUID()
public var bkey: String = ""
public var tmp_create: Date = Date()
public var itemBkey: String = ""
public var attrType: AttributeType = .string
public var name: String = ""
public var description: String = ""
public var value_int: Int = 0
public var value_string: String = ""
public var value_datetime: Date = Date()
public var value_decimal: Decimal = 0.0
public var value_double: Double = 0.0
public var value_boolean: Bool = false
var userBkey: String = ""
var userToken: String = ""
}
public class AttributeModel: ObservableObject { // <-- here
#Published var attributes: [Attribute] = [] // <-- here
}
struct ContentView: View {
#StateObject var model = AttributeModel() // <-- here
#State private var showingAttributeTypes = false
var body: some View {
VStack{
Button("Add attribute"){
self.showingAttributeTypes.toggle()
}
.confirmationDialog("Select a color", isPresented: $showingAttributeTypes, titleVisibility: .visible) {
Button("Text") {
addAttribute(attributeType: .string)
}
Button("Number") {
addAttribute(attributeType: .decimal)
}
Button("Date") {
addAttribute(attributeType: .datetime)
}
Button("Yes/No") {
addAttribute(attributeType: .boolean)
}
}
ForEach($model.attributes){ $value in // <-- here
AttributeView(attribute: $value)
}
Button("Save"){
model.attributes.forEach { value in
print("---> \(value)") // <-- here
print("--------------------------------")
}
}
}
}
func addAttribute(attributeType: AttributeType){
var attribute = Attribute()
attribute.attrType = attributeType
model.attributes.append(attribute)
}
}
struct AttributeView: View {
#Binding var attribute: Attribute // <-- here
var body: some View {
HStack{
FormField(fieldName: "Description", fieldValue: $attribute.description)
.keyboardType(.default)
Spacer()
switch(attribute.attrType){
case .boolean:
Toggle(isOn: $attribute.value_boolean) { // <-- here etc...
Label("", image: "")
}
case .string:
TextField("", text: $attribute.value_string)
.keyboardType(.default)
case .datetime:
DatePicker(selection: $attribute.value_datetime, displayedComponents: .date, label: { Text("") })
case .decimal:
TextField("", value: $attribute.value_decimal, format: .number)
.keyboardType(.decimalPad)
case .double:
TextField("", value: $attribute.value_double, format: .number)
.keyboardType(.decimalPad)
default:
EmptyView()
}
}
}
}

Link #Binding to #Published with SwiftUI

I'm trying to figure out how to link the #Binding passed into a custom View to an #Published from that view's model. Essentially I'm trying to create a reusable integer only TextField. I'm using the below code, which works to set the integer value into the text field, but what I can't figure out is how to update the binding when the text changes.
private class IntegerTextFieldValue: ObservableObject {
#Published var value = "" {
didSet {
let numbersOnly = value.filter { $0.isNumber }
if value != numbersOnly {
value = numbersOnly
}
}
}
}
struct IntegerTextField: View {
#Binding var value: Int?
#StateObject private var fieldValue = IntegerTextFieldValue()
var placeholder = ""
var body: some View {
TextField(placeholder, text: $fieldValue.value)
.keyboardType(.numberPad)
.onAppear {
if let value = value {
fieldValue.value = "\(value)"
}
}
}
}
If I understand you correctly
.onChange (of: fieldValue.value) { vl in
value = vl
}
this modifier updates the binding value to $fieldValue.value
Here is modified code to demo a possible approach (tested with Xcode 12.1 / iOS 14.1):
private class IntegerTextFieldValue: ObservableObject {
#Published var value = "" {
didSet {
let numbersOnly = value.filter { $0.isNumber }
if value != numbersOnly {
value = numbersOnly
}
if let number = Int(value) {
numberValue = number
}
}
}
#Published var numberValue: Int = 0
}
struct IntegerTextField: View {
#Binding var value: Int?
#StateObject private var fieldValue = IntegerTextFieldValue()
var placeholder = ""
var body: some View {
TextField(placeholder, text: $fieldValue.value)
.keyboardType(.numberPad)
.onAppear {
if let value = value {
fieldValue.value = "\(value)"
}
}
.onChange(of: fieldValue.numberValue) {
if $0 != self.value {
self.value = $0
}
}
}
}

TextField Copies all Fields the same in SwiftUI

When I type some text in one of the text fields, somehow it adds to all the text fields the same text at the same time. I am trying to separate them from each other so I can assign them correctly.
struct CardInfo : View {
#State var creditCard : CreditCard
#State var isSaved: Bool = false
#State private(set) var text = ""
var body: some View {
VStack {
CustomTextField(data: $text, tFtext: "Kartin Uzerindeki Isim", tFImage: "user")
.textContentType(.givenName)
.onReceive(Just(text)) { data in
self.creditCard.cardOwnerName = self.text
}
CustomTextField(data: $text, tFtext: "Kredi Kart Numarasi", tFImage: "credit")
.textContentType(.oneTimeCode)
.keyboardType(.numberPad)
.onReceive(Just(text)) { data in
self.creditCard.cardNumber = self.text
}
struct CustomTextField: View {
#Binding var data : String
var tFtext: String = ""
var tFImage: String = ""
var body: some View {
HStack {
Image(tFImage)
.resizable()
.frame(width: 20, height: 20)
.padding()
TextField(tFtext, text: $data)
.padding()
.font(Font.custom("SFCompactDisplay", size: 16))
.foregroundColor(.black)
}
.background(RoundedRectangle(cornerRadius: 10))
.foregroundColor(Color(#colorLiteral(red: 0.9647058824, green: 0.9725490196, blue: 0.9882352941, alpha: 1)))
}
}
You need to use separate #State variables for each TextField:
struct CardInfo : View {
#State var creditCard : CreditCard
#State var isSaved: Bool = false
#State private(set) var cardOwnerName = ""
#State private(set) var cardNumber = ""
var body: some View {
VStack {
CustomTextField(data: $cardOwnerName, tFtext: "Kartin Uzerindeki Isim", tFImage: "user")
.textContentType(.givenName)
.onReceive(Just(cardOwnerName)) { data in
self.creditCard.cardOwnerName = data
}
CustomTextField(data: $cardNumber, tFtext: "Kredi Kart Numarasi", tFImage: "credit")
.textContentType(.oneTimeCode)
.keyboardType(.numberPad)
.onReceive(Just(cardNumber)) { data in
self.creditCard.cardNumber = data
}
...
}
}
}

How to set the style of the textField part of a DatePicker

I used a DatePicker inside a Form, and It looks like the following image ,
Now,I hope it display "2020-4-19 " instead of "4/19/20 ".
Someone knows how to do it?
I could not find a way with the default DatePicker, so I've taken the code from
Change selected date format from DatePicker SwiftUI
made some changes to make it work. This CustomDatePicker should do you what you asked for even within a Form.
struct CustomDatePicker: View {
#State var text: String = "Date"
#Binding var date: Date
#State var formatString: String = "yyyy-MM-dd"
#State private var disble: Bool = false
#State private var showPicker: Bool = false
#State private var selectedDateText: String = "Date"
let formatter = DateFormatter()
private func setDateString() {
formatter.dateFormat = formatString
self.selectedDateText = formatter.string(from: self.date)
}
var body: some View {
VStack {
HStack {
Text(text).frame(alignment: .leading)
Spacer()
Text(self.selectedDateText)
.onAppear() {
self.setDateString()
}
.foregroundColor(.blue)
.onTapGesture {
self.showPicker.toggle()
}.multilineTextAlignment(.trailing)
}
if showPicker {
DatePicker("", selection: Binding<Date>(
get: { self.date},
set : {
self.date = $0
self.setDateString()
}), displayedComponents: .date)
.datePickerStyle(WheelDatePickerStyle())
.labelsHidden()
}
}
}
}
struct ContentView: View {
#State var date = Date()
var body: some View {
Form {
Section {
CustomDatePicker(text: "my date", date: $date, formatString: "yyyy-MM-dd")
Text("test")
}
}
}