Create an arbitrary number of state variables in SwiftUI - ios

I'm trying to create a survey using SwiftUI, where the survey can have an arbitrary number of questions.
I'm trying to capture what values the user inputted through state variables such as:
#State var answer: String = ""
ForEach(survey) { surveyQuestion in
Text(surveyQuestion.question)
TextField(surveyQuestion.placeholder, text: $answer)
}
However, since I don't know how many questions are going to be in the survey beforehand, I don't know how many of these state variables to store the answers to make. I could create the variables on the fly inside the ForEach loop but then the variables would be out of scope when I actually go to submit the survey (since the submitting would happen outside of the ForEach loop).
How do I create an arbitrary number of state variables to capture the user's answers to the survey?
EDIT: I had tried making my answers variable a dictionary, where the keys are the IDs to the questions. My code looked like:
#State var answers: [String:String] = [:]
ForEach(survey) { surveyQuestion in
Text(surveyQuestion.question)
TextField(surveyQuestion.placeholder, text: $answers[surveyQuestion.id!])
}
However, I kept getting the error:
Cannot convert value of type 'Binding<String?>' to expected argument type 'Binding<String>'
So then I tried replacing $answers[surveyQuestion.id!] with $(answers[surveyQuestion.id!]!) but then the system gets confused and responds with:
'$' is not an identifier; use backticks to escape it
I had also tried adjusting my question model so that there's a field for an answer in the same struct. My code looked like this:
TextField(surveyQuestion.placeholder, text: $surveyQuestion.answer)
I kept getting the error:
Cannot find '$surveyQuestion' in scope

Using the strategy in the edit that you added, with the Dictionary, you could provide a custom Binding, like this:
func bindingForID(id: String) -> Binding<String> {
.init {
answers[id] ?? ""
} set: { newValue in
answers[id] = newValue
}
}
And you could use it like this:
TextField(surveyQuestion.placeholder, text: bindingForID(id: surveyQuestion.id))
In terms of adding this data to Firestore, you could trigger Firestore updates in the set closure from the custom binding. However, I'd probably recommend moving this logic to a #Published property on a view model (ObservableObject) where you could use Combine to do things like Debouncing before you send the data to Firestore (probably a little beyond the scope of this question).

Create a struct that holds id, question, and answer. Your #State var should be an array of those.
struct QA: Identifiable {
id: String
question: String
answer: String
}
…
#State private var myQAs: [QA] = myQAs() // or populate them in init, onAppear(if asynchronous) or however you see fit
…
ForEach(myQAs) { qa in
Text(qa.question)
TextField(placeholder, text: $qa.answer)
}

Related

Eliminating quicktype accessory methods when parsng JSON to Swift

I've noticed that while Quicktype.io does a very good job of parsing JSON into SWIFt, occasionally it resorts to a lot of auxiliary functions and methods. For the following, it generated about 15 additional methods. Some of these are familiar such as NSNull, however, two are unfamiliar to me such as JSONAny and it seems like there ought to be a way around them. The JSONAny class for example, has about 12 functions in it and it is used to parse just one field that is not that important to me.
Here is what the JSON looks like:
[{"name":"Afghanistan","topLevelDomain":[".af"],"alpha2Code":"AF","alpha3Code":"AFG","callingCodes":["93"],"capital":"Kabul","altSpellings":["AF","Afġānistān"],"region":"Asia","subregion":"Southern Asia","population":27657145,"latlng":[33.0,65.0],"demonym":"Afghan","area":652230.0,"gini":27.8,"timezones":["UTC+04:30"],"borders":["IRN","PAK","TKM","UZB","TJK","CHN"],"nativeName":"افغانستان","numericCode":"004","currencies":[{"code":"AFN","name":"Afghan afghani","symbol":"؋"}],"languages":[{"iso639_1":"ps","iso639_2":"pus","name":"Pashto","nativeName":"پښتو"},{"iso639_1":"uz","iso639_2":"uzb","name":"Uzbek","nativeName":"Oʻzbek"},{"iso639_1":"tk","iso639_2":"tuk","name":"Turkmen","nativeName":"Türkmen"}],"translations":{"de":"Afghanistan","es":"Afganistán","fr":"Afghanistan","ja":"アフガニスタン","it":"Afghanistan","br":"Afeganistão","pt":"Afeganistão","nl":"Afghanistan","hr":"Afganistan","fa":"افغانستان"},"flag":"https://restcountries.eu/data/afg.svg","regionalBlocs":[{"acronym":"SAARC","name":"South Asian Association for Regional Cooperation","otherAcronyms":[],"otherNames":[]}],"cioc":"AFG"}]
I wont' give all the code that that struct is one level down from the main struct:
struct CountryReturnedElement: Codable {
//...various fields
let regionalBlocs: [RegionalBloc]
}
// MARK: - RegionalBloc
struct RegionalBloc: Codable {
let acronym, name: String
let otherAcronyms, otherNames: [JSONAny]
}
Which is designed to decode merely the following JSON:
"regionalBlocs":[{"acronym":"SAARC","name":"South Asian Association for Regional Cooperation","otherAcronyms":[],"otherNames":[]}]
Is there a simple way to parse the above without resorting to auxiliary classes with literally fifteen functions and methods. I all likelihood, otherAcronyms and otherNames are strings so I guess I could go [String?]. However, I guess I don't know that with 100% certainty, more like 95% certainty.
Thanks for any suggestions.
If you're certain the otherAcronyms and otherNames keys return [String?] you can modify the RegionalBloc struct to accept [String?].
struct RegionalBloc: Codable {
let acronym, name: String
let otherAcronyms, otherNames: [String?]
}
You can simply try it out and if the JSONDecoder doesn't throw any error you're good and can continue with [String?]. Otherwise, you can check the error and print it out onto the console to check the incoming type and set it.

Kitura gives me ambiguous type of expression in nested structs

I'm starting to use the Kitura framework to migrate my current node.js backend to swift and I'm starting by implementing all the routes with mocked data.
Anyways, right now im at a very early stage and started to develop some mocked endpoints and deployed on IBM Cloud without any problems, they work perfectly. One example of a working endpoint is the following:
app.router.post("/getStats") { (request, response, next) in
let mocks = Mocks()
response.send(json: mocks.dashboard)
next()
}
The Mocks() its a class that holds the dashboard struct with all fields predefined. Those fields are plain types (strings and numbers)
When I started to develop a more advanced endpoint, the workflow is the same, I register the route with the following code
app.router.post("/generateTest") { (request, response, next) in
let mocks = Mocks()
response.send(json: mocks.getTest())
next()
}
Instead of a Codable struct with plain types, this mocked struct will have nested codable structs:
struct Test: Codable {
var totalTime: Int
var questions: [Question]
}
extension Test {
struct Question: Codable {
let id: String?
let pregunta: String
var selectedAnswer: Answer? = nil
let respuestas: [Answer]
let idConcepto: Int
let stats: Stats?
let categorias: [String]
}
struct Answer: Codable {
let respuesta: String
let correcta: Bool
}
struct Stats: Codable {
let globalStats: GlobalStats?
}
struct GlobalStats: Codable {
let timesFailed, timesUnanswered, timesAnswered: Int?
}
}
Everything works fine when I build in localhost the project. When I navigate throught those endpoints in Postman, they return me the expected response.
The problem comes when I try to deploy this into IBM Cloud (also tried on Heroku with the same problem).
The problem it gives me its the following
/tmp/app/Sources/Application/Mocks/UserMock.swift:29:44: error: type of expression is ambiguous without more context
let questionFirst: Test.Question = Test.Question(id: "1", pregunta: "UNO", respuestas: answers, idConcepto: 1, stats: nil, categorias: cats)
^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
I dont know how to solve this, Xcode didnt raise any errors and compiled the project locally witouth any problem, I tried to cast types and all possible combinations to generate the struct, the error is always the same.
Has somebody faced the same problem? Is this a bug with Kitura or am I missing something? In the documentation they didn't say anything about nested Structs, the only thing I found relating to nested Struct, is that Kitura ORM doesnt support nested structs when working with ORM and SQL database, which is not my case.
I would be very happy if we can find a solution
Thanks for your help
I managed to solve the problem. As i suspected, it was a dumb thing. In Test.Question struct, the field selectedAnswer was initialized with a default value equal to nil
When i created the struct i didnt wrote that field because it has already a default value, but it looks like Kitura or deploying Swift to a server didnt support this. I instantiate the structure with the default value right now and works without any problem.
I hope this can help anyone in a future with similar problem

Adding elements to an empty array throws errors

I am trying to added elements to an empty string array and I tried to follow this post add-value-to empty-array but none the options are helping me as they result in Xcode throwing errors each time.
here is the code if have tired:
var tasks = [String]()
tasks += ["something"]
This gave me 6 errors on x code with the first being Consecutive declaration on a line must be separated by ; then it says it's an invalid redeclaration of tasks followed by a bunch of errors saying to make it a func. When I try the .append func instead of += it gives the same errors
Now if I try this:
var tasks = [String]()
var tasks = ["Something"]
it only gives me the invalid redeclaration error but I do not believe this the correct way to add elements to the array
Hopefully, this helps explain my issue and sorry for the weird beginner question but thank for the help in advance
I looked at the code in your pastebin and the issue is that you had both the declaration and assignment on separate lines in the class definition.
class TableViewController: UITableViewController {
//temp list of tasks
var tasks = [Sting]()
//giving some default values in the cell
tasks.append(["something"])
You also spelled String wrong, but that is not relevant for the fix.
Another issue is a type mis-match. You declare an array of String, which would be [String]. However, you are attempting to add an array of String to an another array of String, which is wrong.
tasks.append(["something"])
Instead, you should have
tasks.append("something")
This now adds an element of String to your array of Strings.
Finally, you can do one of two things:
Assign the array at creation
var tasks = ["something"]
or assign it inside a function, like your ViewDidLoad
You can't use += with a [String] (array of Strings) and String.
Here's an example I ran in a playground:
var array: [String] = []
array.append("A")
print(array)
It prints ["A"]. Without seeing your code it will be hard to diagnose if there is another problem.
Update after looking at your code:
var tasks = [Sting]() // Should be String
tasks.append(["something"])
You can't append in the declaration, you'll need to add the append to a function (try viewDidLoad or viewWillAppear to test). ["something"] is an array of String, not a String. You'll need to use "something" instead.

Swift 2 - Are arrays being copied during setting?

I have such class setup. In my methods I want to work with specific array depending on what parameter is passed. My question is : is "array" variable a copy of selected array or a reference to it? If it is a copy, how does one pass a reference to an array? I don't want to copy it becuase it is quite long.
I heard that in times of Swift 1 arrays were copied only when needed (compiler decides when). How things are now in Swift 2?
class ... {
private var currentVertexes = [CCVertex]()
private var mainVertexes : [CCVertex]!
private var leftVertexes : [CCVertex]!
private var rightVertexes : [CCVertex]!
private var topVertexes : [CCVertex]!
private var bottomVertexes : [CCVertex]!
...
internal func method(var factor: Float) {
let array = factor < 0.0 ? leftVertexes : rightVertexes
...
}
Depends on whether CCVertex is a struct or a class. If it's a struct it will be copied, not if it's a class.
From Apple's documentation (and with good examples too):
Copying an array also copies all of the elements of that array that are value types. This means that changing one of the elements of an array does not change the elements of any of the copies of the array
If the elements in an array are instances of classes, changing the class does affect other copies, because classes have reference semantics
Arrays are value types, but use copy-on-write to prevent unnecessary copies when you merely access them in a read-only fashion (see SwiftDocs)
So if the rest of your method only reads from the array, then you don't need to worry about copies (irrespective of whether CVVertex is a struct or a class).

DRY struct tags in Go

I'm parsing XML, and at most every level of the document, there's a description.
Here's an toy example:
<obj>
<description>outer object</description>
<subobjA>
<description>first kind of subobject</description>
<foo>some goop</foo>
</subobjA>
<subobjB>
<description>second kind of subobject</description>
<bar>some other goop</bar>
</subobjB>
</obj>
This means that every struct involved has an identical Description member, with an identical tag `xml:"description,omitempty"`.
Here's functioning code: http://play.golang.org/p/1-co6Qcm8d
I'd rather the Description tags be DRY. The obvious thing to want to do is something like:
type Description string `xml:"description,omitempty"`
and then use the type Description throughout. However, only struct members can have tags. See http://play.golang.org/p/p83UrhrN4u for what I want to write; it doesn't compile.
One could create a Description struct and embed it repeatedly, but that adds a layer of indirection when accessing.
Is there another way to go about this?
Embedding a re-factored Description struct (as you already suggested) is the way to go:
(Playground)
type describable struct{
Description string `xml:"description"`
}
type subobjA struct {
describable
XMLName xml.Name `xml:"subobjA"`
}
type subobjB struct {
describable
XMLName xml.Name `xml:"subobjB"`
}
type obj struct {
XMLName xml.Name `xml:"obj"`
A subobjA
B subobjB
}
The mentioned layer of indirection does not exist. To cite the spec on that topic:
A field or method f of an anonymous field in a struct x is called promoted if x.f is a legal selector that denotes that field or method f.
Promoted fields act like ordinary fields of a struct except that they cannot be used as field names in composite literals of the struct.
So you can do this:
err := xml.Unmarshal([]byte(sampleXml), &sampleObj)
fmt.Println(sampleObj.Description)
fmt.Println(sampleObj.A.Description)
sampleObj.describable.Description is promoted to be sampleObj.Description, so no further layer of indirection here.

Resources