Delegate #State value to a child component - swift

A Swift new guy here. Trying to understand SwiftUI.
I'm trying to create a "Field" component that wraps a Text and TextField. The idea is to have less code and have a control that can display the field title and its corresponding value.
I can't figure out how to assign the value of the model on my control.
This is my model:
import Foundation
class EmployeeModel {
var FullName: String = "John Doe"
var JobStartDate: String = ""
var BornDate: String = ""
var DepartmentId: Int = 0
var DepartmentName: String = ""
var isBossDepartment: Bool = false
var JobPositionId: Int = 0
var JobPositionName: String = ""
var PersonalDocNumber: String = ""
var Password: String = ""
init() {
}
}
In some part of the view...
struct EmployeeView : View {
#State private var Employee = EmployeeModel()
var body : some View {
Field("Full Name", $Employee.FullName)
}
}
This is my custom component that want to implement.
struct Field : View {
private var caption: String = ""
#State private var controlValue: String = ""
init(caption: String, value: String) {
self.caption = caption
controlValue = value
}
var body : some View {
VStack {
Text(self.caption)
TextField($controlValue)
.textFieldStyle(.roundedBorder)
}
}
}
Currently I got a message of
'Binding' is not convertible to 'String' on my Field implementation of the EmployeeView

Before going into the details of your problem, please be advised that by convention, types (classes, structs, enums, etc) should begin with a capital letter, while objects and values should start with a lower case (or underscore). By not following that convention, you make the code hard to read for others, as everyone is expecting it.
Now, on your code, there are several improvements:
controlValue must be declared as #Binding.
#Binding properties should not have an initial value, as they are supposed to be passed by the caller.
If you declare your properties non-private, you wouldn't need an initializer. Nothing wrong with that, but if you do use an initializer, there are multiple changes you need to perform (see the code below).
Your TextField is using a deprecated and discontinued initializer in beta 5.
struct EmployeeModel {
var fullName: String = "John Doe"
var jobStartDate: String = ""
var bornDate: String = ""
var departmentId: Int = 0
var departmentName: String = ""
var isBossDepartment: Bool = false
var jobPositionId: Int = 0
var jobPositionName: String = ""
var personalDocNumber: String = ""
var password: String = ""
init() {
}
}
struct EmployeeView : View {
#State private var employee = EmployeeModel()
var body : some View {
Field(caption: "Full Name", value: $employee.fullName)
}
}
struct Field : View {
private var caption: String = ""
#Binding private var controlValue: String
init(caption: String, value: Binding<String>) {
self.caption = caption
self._controlValue = value
}
var body : some View {
VStack {
Text(self.caption)
TextField("", text: $controlValue)
.textFieldStyle(.roundedBorder)
}
}
}

Related

Why am I getting this error "Cannot assign to property: 'blurb' is a get-only property" - SwiftUI

I'm trying to assign values to a Data Object from a static array in a file called FeaturedData, but I keep getting this annoying error: "Cannot assign to property: 'blurb' is a get-only property"
Here's my Model:
struct Featured: Codable {
var tagline: String
var title: String
var blurb: String
}
Here's my ViewModel:
import SwiftUI
class FeaturedViewModel: ObservableObject {
#Published var features = [FeaturedObjectViewModel]()
var data = FeaturedData()
func setFeatured() {
for featured in data.featureds {
let featureObject = FeaturedObjectViewModel(featured: featured)
featureObject.blurb = featured.blurb
self.features.append(featureObject)
}
}
}
struct FeaturedObjectViewModel {
let featured: Featured
var tagline: String {
self.featured.tagline
}
var title: String {
self.featured.title
}
var blurb: String {
self.featured.blurb
}
}
Here's the line that's prompting the error:
featureObject.blurb = featured.blurb
I don't get what wrong.
blurb Is a computed property. You can not assign the any values. It’s always returns.
Use init for FeaturedObjectViewModel
struct FeaturedObjectViewModel {
let featured: Featured
var tagline: String = “”
var title: String = “”
var blurb: String = “”
init(featured: Featured){
self.featured = featured
self.tagline = featured.tagline
self.title = featured.title
self.blurb = featured.blurb
}
}
Note : By doing this you don’t need to do this,
featureObject.blurb = featured.blurb
It will automatically assign to object value by init.
Also, in your code with computed property, no need to reassign value to featureObject var. When you get it, it will get featured.blurb value.

Filtering multidimensional array in uitableview - swift

Here is my model
class BusinessProfile: NSObject {
var title: String?
var website: String?
var associatedOrganization: String?
var companyName: String?
var productList: [BusinessProfileProduct]?
}
class BusinessProfileProduct: NSObject{
var productName: Double?
var modelNumber: String?
var hsCode: String?
}
Here are my array variables in view controller.
var businessProfileArray = [BusinessProfile]()
var tempBusinessProfileArray = [BusinessProfile]()
I already have filtered businessProfileArray on the basis of companyName like below:
tempBusinessProfileArray = businessProfileArray.filter({ (BusinessProfile) -> Bool in
return (BusinessProfile.companyName!.lowercased().contains(searchText.lowercased()))
})
But I am unable to filter businessProfileArray on the basis of productName or hsCode from nested array of BusinessProfileProduct.
Note: businessProfileArray contains array of businessProfileProduct
Any help from anyone? Thanks in advance.
You can do something similar to this
func filterArr(searchText:String) -> [BusinessProfile] {
var filteredArr = [BusinessProfile]()
for profile in businessProfileArray {
var isMatched = false
if let matchedProduct = profile.productList.filter ({$0.companyName.lowercased().contains(searchText.lowercased())}).first {
isMatched = true
print(matchedProduct.companyName)
}
if isMatched {
filteredArr.append(profile)
}
}
return filteredArr
}
this will return all the profiles in which there is a match of searchText with product's companyName however it will not remove the extra products which does not match the searchText

How to modify a user input inside a SwiftUI form loop

I'm developing a simple SwiftUI app in Xcode 11. I want to have a form that loops through multiple user input strings and displays a form with a button. When the user presses the button it modifies the input value - specifically increment or decrement it.
However when passing an array of references like UserInput().foo where UserInput is a published observable object I cannot modify the value inside a ForEach because the ForEach is passed a copy as oppose to the original reference (at least that's my basic understanding). How do I then try to achieve it? I read about inout and everybody says to avoid it but surely this must be a relatively common issue.
I've made an simple example of what I'm trying to do but I can't quite work it out:
import SwiftUI
class UserInput: ObservableObject {
#Published var foo: String = ""
#Published var bar: String = ""
}
struct ContentView: View {
#ObservedObject var input = UserInput()
var body: some View {
LoopInputs()
}
func LoopInputs() -> AnyView?{
var userinputs = [
[UserInput().foo, "Foo"],
[UserInput().bar, "Bar"]
]
var inputs: some View{
VStack(){
ForEach(userinputs, id: \.self){userinput in
Text("\(userinput[1]): \(String(userinput[0]))")
Button(action: {
increment(input: String(userinput[0]))
}){
Text("Increase")
}
}
}
}
return AnyView(inputs)
}
func increment(input: String){
var lead = Int(input) ?? 0
lead += 1
// input = String(lead)
}
}
As I understood, when adding a value to userinputs, the ForEach values doesn't change.
Well, if that's the case, first of all, you could try creating a struct and in it, you declare foo and bar, then just declare a variable of type the struct. It'll look like this:
struct Input: Identifiable {
var id = UUID()
var foo: String
var bar: String
}
class UserInput: ObservableObject {
#Published var inputs: [Input] = [Input]()
}
//ContentView
struct ContentView: View {
#ObservedObject var input = UserInput()
var body: some View {
LoopInputs()
}
func LoopInputs() -> AnyView? {
var inputs: some View {
VStack {
ForEach(input.inputs) { userinput in
Text("\(userinput.bar): \(String(userinput.foo))")
Button(action: {
increment(input: String(userinput.foo))
}) {
Text("Increase")
}
}
}
}
return AnyView(inputs)
}
func increment(input: String) {
var lead = Int(input) ?? 0
lead += 1
// input = String(lead)
}
}
Wouldn't this be easier and more elegant?

Create a Picker that returns a String

Hello everyone
I am creating a form that allows me to modify the data of #EnvironmentObject variable.
Therefore, I would like to be able to create a Picker that returns a String. However, after many unsuccessful attempts, I have the impression that a Picker cannot return a String.
Anyone would have an idea for a Picker that returns a String (maybe through UIKit ?).
Here's my code :
struct UserStruct {
var firstName: String
var lastName: String
var birthDate: Int
var city: String
var postalCode: Int
var street: String
var streetCode: String
var country: String
}
class User: ObservableObject {
#Published var userProfile = UserStruct()
// Other stuff here
}
// Then in my FormView:
// I declare the object as #EnvironmentObject
#EnvironmentObject var userStore: User
// I declare an array which contains all the country for the picker
let country = ["France", "Russie", "USA"]
// In my var body: some View...
// Trying to change the value of country of the userStore object
Picker(selection: $userStore.userProfile.country, label: Text("Make a choice")) {
ForEach(0 ..< country.count) { index in
Text(self.country[index]).tag(index)
}
Thank you all for your help.
you could try something like this:
let country = ["France", "Russie", "USA"]
#State var countrySelection = 0
Picker(selection: Binding(get: {
self.countrySelection
}, set: { newVal in
self.countrySelection = newVal
self.userStore.userProfile.country = self.country[self.countrySelection]
}), label: Text("Make a choice")) {
ForEach(0 ..< country.count) { index in
Text(self.country[index]).tag(index)
}
}

Getting the error "Unable to infer complex closure return type; add explicit type to disambiguate" when trying to recreate the SwiftUI List demo

I'm trying to recreate the SwiftUI demo with a difference being I want to use my own object Item.
Item:
class Item {
var company: String = ""
var item_class: String = ""
var name: String = ""
var stock: Int = 0
var average_cost: Decimal = 0.00
var otc_price: Decimal = 0.00
var dealer_price: Decimal = 0.00
var ctc_price: Decimal = 0.00
class var _API_LIST_EP: String {return "api/inventory/items/"}
// Init and Funcs
// JToken is an extended typealias for [String : Any] that makes parsing easier
required init(_ jt: JToken) {
company = jt.string(forKey: "company")
item_class = jt.string(forKey: "item_class")
name = jt.string(forKey: "name")
stock = jt.int(forKey: "stock")
average_cost = jt.decimal(forKey: "average_cost")
otc_price = jt.decimal(forKey: "otc_price")
dealer_price = jt.decimal(forKey: "dealer_price")
ctc_price = jt.decimal(forKey: "ctc_price")
}
}
In a similar question, it was noted that the issue stemmed from one of the object's variables being initialized in an ambiguous state however after filling up all of my object's variables, it still comes up
Problematic code:
struct ContentView : View {
var itemList: [Item] = []
var body: some View {
List(itemList) { item in
Image(systemName: "photo")
VStack(alignment: .leading) {
Text(item.name)
Text(item.company)
.font(.subheadline)
.color(.gray)
}
}
}
}
Screenshot of the error:
The error message is misleading.
List(itemList) { item in ... }
requires that the element type of itemList conforms to the Identifiable protocol. For classes (as in your case) it is sufficient to declare protocol conformance
class Item: Identifiable {
// ...
}
because there is a default implementation (based on the object identifier).