Closure containing a declaration cannot be used with function builder 'ViewBuilder' - swift

I cant declare a variable inside swift ui view block
var body: some View {
let isHuman = false
Text("Levels \(isHuman)")
}

you shouldn't create a variable inside the SwiftUI builder block, you should create it out of the body scope,
var isPlaying = false
var body: some View {
Text("Levels \(isPlaying)")
}
Swift UI uses a function builders block that can only contain content that is understandable by the builder.
Then what you should write inside the builder block is only View Type and [View] 
However, if you want to declare a variable you could work around it by introducing it to a new subclass
The purpose of this feature is to enable the creation of embedded DSLs in Swift -- allowing you to define content that gets translated to something else down the line
Also, you could use.
#State var isPlaying: Bool = false
Note
review your code twice before doing the workaround, probably you have
a misunderstanding about how swift-UI works?

Not so bad, you can ... another question whether you really need it, but when you really need, you do know that you can. You just need to remember that view builder must return some View and it must be same type in all possible branches
var body: some View {
let value = "" // construct somewhere or something
return Text("Levels \(value)")
}

When SwiftUI was first released (in 2019), the Swift language didn't allow local declarations (like let isHuman = false) inside what were then called “function builders”. However, function builders evolved into what are now called “result builders” and do support local declarations as of Swift 5.4.
This behavior is documented in SE-0289 Result Builders:
Declaration statements
Local declarations are left alone by the transformation. This allows developers to factor out subexpressions freely to clarify their code, without affecting the result builder transformation.

my two cents for locals AND "Expression was too complex to be solved in reasonable time”
I DO precalc in a previous nested loop.
struct MyGridView : View {
var room: Room
let MaxRows = 2
let MaxCols = 2
var body: some View {
**// precalc, as GeometryReader calls its body loop twice**
let si = FirstLevelQuestionsManager.shared
var firstLevelQuestions = FirstLevelQuestions()
let devids = room.devids
for row in 0..<self.MaxRows {
for col in 0..<self.MaxCols {
let flq = si.titleAndTextAt(row: row, col: col, ncols: self.MaxCols)
firstLevelQuestions.append(flq)
}
}
let gr = GeometryReader { (geometry : GeometryProxy) in
VStack() {
ForEach(0..<self.MaxRows) { (row: Int) in
HStack {
ForEach(0..<self.MaxCols) { (col: Int) -> GridCellView in
let index = col + row * self.MaxCols
return GridCellView(
w: (geometry.size.width / CGFloat(self.MaxCols))
titleAndText: firstLevelQuestions[index],
room: self.room,
troubleShootings: troubleShootings[index] )
}
}
}
}
} // GeometryReader
return gr
}
}

Related

Why does calling a method via a pointer end up with a different result than calling via the method directly?

In the example following, I'm calling an instance method of a View using the method directly (saveTitle), and via a pointer to that method (saveAction).
When calling the method, I am passing in the current value of the title variable.
When I call it directly, the current value matches the value inside the method.
When I call it via a pointer to the method, the value inside is the value it was when the struct was first instantiated.
It is almost like there is a new instance of the Struct being created, but without the init method being called a second time.
I suspect this has something to do with how SwiftUI handles #State modifiers, but I'm hoping someone out there will have a better understanding, and be able to enlighten me a bit.
Thanks much for your consideration :)
import SwiftUI
struct EditContentView: View {
#Binding var isPresented: Bool
#State var title: String
var saveAction: ((String) -> Void)?
var body: some View {
TextField("new title", text: $title)
Button("save") {
print("calling saveText")
// this call works as expected, the title inside the saveTitle method is the same as here
saveTitle(title)
print("now calling saveAction, a pointer to saveTitle")
// with this call the title inside the method is different than the passed in title here
// even though they are theoretically the same member of the same instance
saveAction!(title)
isPresented = false
}
}
init(isPresented: Binding<Bool>, title: String) {
print("this method only gets called once")
self._isPresented = isPresented
self._title = State(initialValue: title)
saveAction = saveTitle
}
func saveTitle(_ expected: String) {
if (expected == title) {
print("the title inside this method is the same as before the call")
}
else {
print("expected: \(expected), but got: \(title)")
}
}
}
struct ContentView: View {
#State var title = "Change me"
#State var showingEdit = false
var body: some View {
Text(title)
.onTapGesture { showingEdit.toggle() }
.sheet(isPresented: $showingEdit) {
EditContentView(isPresented: $showingEdit, title: title)
}
}
}
I don't think this is related to #State. It is just a natural consequence of structs having value semantics, i.e.
struct Foo {
init() {
print("This is run only once!")
}
var foo = 1
}
var x = Foo() // Prints: This is run only once!
let y = x // x and y are now two copies of the same *value*
x.foo = 2 // changing one of the copies doesn't affect the other
print(y.foo) // Prints: 1
Your example is essentially just a little more complicated version of the above. If you understand the above, then you can easily understand your SwiftUI case, We can actually simplify your example to one without all the SwiftUI distractions:
struct Foo {
var foo = 1
var checkFooAction: ((Int) -> Void)?
func run() {
checkFoo(expectedFoo: foo)
checkFooAction!(foo)
}
init() {
print("This is run only once!")
checkFooAction = checkFoo
}
func checkFoo(expectedFoo: Int) {
if expectedFoo == foo {
print("foo is expected")
} else {
print("expected: \(expectedFoo), actual: \(foo)")
}
}
}
var x = Foo()
x.foo = 2 // simulate changing the text in the text field
x.run()
/*
Output:
This is run only once!
foo is expected
expected: 2, actual: 1
*/
What happens is that when you do checkFooAction = checkFoo (or in your case, saveAction = saveTitle), the closure captures self. This is like the line let y = x in the first simple example.
Since this is value semantics, it captures a copy. Then the line x.foo = 2 (or in your case, the SwiftUI framework) changes the other copy that the closure didn't capture.
And finally, when you inspect what foo (or title) by calling the closure, you see the unchanged copy, analogous to inspecting y.foo in the first simple example.
If you change Foo to a class, which has reference semantics, you can see the behaviour change. Because this time, the reference to self is captured.
See also: Value and Reference Types
Now you might be wondering, why does saveAction = saveTitle capture self? Well, notice that saveTitle is an instance method, so it requires an instance of EditContentView to call, but in your function type, (String) -> Void, there is no EditContentView at all! This is why it "captures" (a copy of) the current value of self, and says "I'll just always use that".
You can make it not capture self by including EditContentView as one of the parameters:
// this doesn't actually need to be optional
var saveAction: (EditContentView, String) -> Void
assign it like this:
saveAction = { this, title in this.saveTitle(title) }
then provide self when calling it:
saveAction(self, title)
Now you won't get different copies of self flying around.

Swift & SwiftUI - Conditional global var

I want to make a global variable in Swift, so that its Data is accessible to any view that needs it. Eventually it will be a var so that I can mutate it, but while trying to get past this hurdle I'm just using it as let
I can do that by putting this as the top of a file (seemingly any file, Swift is weird):
let myData: [MyStruct] = load("myDataFile.json)
load() returns a JSONDecoder(). MyStruct is a :Hashable, Codable, Identifiable struct
That data is then available to any view that wants it, which is great. However, I want to be able to specify the file that is loaded based on a condition - I'm open to suggestions, but I've been using an #AppStorage variable to determine things when inside a View.
What I'd like to do, but can't, is do something like:
#AppStorage("appStorageVar") var appStorageVar: String = "Condition1"
if(appStorageVar == "Condition2") {
let myData: [MyStruct] = load("myDataFile2.json")
}
else {
let myData: [MyStruct] = load("myDataFile.json")
}
I can do this inside a View's body, but then it's only accessible to that View and then I have to repeat it constantly, which can't possibly the correct way to do it.
You could change just change the global in an onChange on the AppStorage variable. This is an answer to your question, but you have the problem that no view is going to be updating itself when the global changes.
var myData: [MyStruct] = load("myDataFile.json)
struct ContentView: View {
#AppStorage("appStorageVar") var appStorageVar: String = "Condition1"
var body: some View {
Button("Change value") {
appStorageVar = "Condition2"
}
.onChange(of: appStorageVar) { newValue in
myData = load(newValue == "Condition1" ? "myDataFile.json" : "myDataFile2.json")
}
}
}

is possible to change #State property's value through its structure's method?

at Xcode's play ground I run code below:
import SwiftUI
struct Test {
#State var count: Int = 0 //to avoid "mutating" keyword, I use "#State" property wrapper
func countUp() {
self.count += 1
}
}
var test = Test()
test.countUp() // I want "count" property to be count up. but it did not
print(test.count) // print 0
Q) why test.count is still 0??
This may not be the intent of the question, but I will explain why the code in question does not work.(I believe this is the same thing the comment.)
You are using SwiftUI in a playground and not View.
The #State conforms to DynamicProperty.
https://developer.apple.com/documentation/swiftui/state
https://developer.apple.com/documentation/swiftui/dynamicproperty
The Documents say:
You should only access a state property from inside the view’s body, or from methods called by it.
An interface for a stored variable that updates an external property of a view.
and
The view gives values to these properties prior to recomputing the view’s body.
It has been suggested to use it with View and view’s body.
As mentioned above, your code does not use View or view’s body.
Since #State can only be used in a struct, if you want to do something similar, you can use a class and use #Published as shown below(simple use Combine, not SwiftUI):
import Combine
class Test {
#Published var count: Int = 0
func countUp() {
self.count += 1
}
}
var test = Test()
test.countUp()
print(test.count) // 1
However, your code has no one to subscribe to the changes, so this is just an experimental code.
Expressions are not allowed at the top level.
To test your case I created a View struct called Test1, and within its initialiser I created object for Test view. Check code below.
import SwiftUI
struct Test1:View {
init() {
let test = Test() // concentrate on this
test.countUp()
}
var body: some View {
Test() // Ignore for now
}
}
Test View-:
import SwiftUI
#propertyWrapper struct TestCount:DynamicProperty {
#State private var count = 0
var wrappedValue: Int {
get {
count
}
nonmutating set {
count = newValue
}
}
init(_ count: Int) {
self.count = count
}
}
struct Test: View {
#TestCount var count:Int
init(count:Int) {
_count = TestCount(count)
print(_count)
}
var body: some View {
Button {
countUp()
} label: {
Text("\(count)")
}
}
func countUp() {
count += 1
print(count)
}
}
For better understanding I created a custom propertyWrapper and we will initialise that inside Test view.
Now, when you run this code in simulator Test1 view is called first, and in it’s initialiser it will create object for Test() view.
Test view upon initialisation will create object for it’s property wrapper called TestCount, once created it will print description for TestCount instance, and notice the print output now-:
TestCount(_count: SwiftUI.State<Swift.Int>(_value: 0, _location: nil))
You can clearly see State variable count inside TestCount wrapper was assigned default value 0, but wasn’t allocated any SwiftUI.StoredLocation yet.
Now, this line is executed next test.countUp() in Test1 view, and because we are calling it from outside of body property and Test1 is also initialised outside of body property , compiler is smart enough to detect that and it will not provide SwiftUI storage location for this change to count variable, because there is no view update required for this change, hence you always see Output as 0 (default saved value).
To prove my point, now make following change in Test1 view.
import SwiftUI
struct Test1:View {
// init() {
// let test = Test(count: 0)
// test.countUp() // I want "count" property to be count up. but it did not
// }
var body: some View {
Test(count: 0)
}
}
Now again the initial print statement inside Test view is same -:
TestCount(_count: SwiftUI.State<Swift.Int>(_value: 0, _location: nil))
But, now you have a view with Button to change the count variable value on tap.
When you tap the button, again countUp() will be called, and this time check the output of print statement again, SwiftUI storage location is no more nil.
TestCount(_count: SwiftUI.State<Swift.Int>(_value: 0, _location:
Optional(SwiftUI.StoredLocation<Swift.Int>))).
You can debug the code further to get more better understanding, and I would also suggest you to read about PropertyWrappers in more detail, and how they work internally.

Change the options in a Picker dynamically using distinct arrays

I'm trying to get a Picker to update dynamically depending on the selection of the prior Picker. In order to achieve this, I'm using a multidimensional array. Unfortunately this seems to confuse my ForEach loop and I noticed the following message in the logs:
ForEach<Range<Int>, Int, Text> count (3) != its initial count (5).ForEach(:content:)should only be used for *constant* data. Instead conform data toIdentifiableor useForEach(:id:content:)and provide an explicitid!
This kinda makes sense, I'm guessing what is happening is that I'm passing it one array and it keeps referring to it, so as far as it is concerned, it keeps changing constantly whenever I pass it another array. I believe the way to resolve this is to use the id parameter that can be passed to ForEach, although I'm not sure this would actually solve it and I'm not sure what I would use. The other solution would be to somehow destroy the Picker and recreate it? Any ideas?
My code follows. If you run it, you'll notice that moving around the first picker can result in an out of bounds exception.
import SwiftUI
struct ContentView: View {
#State private var baseNumber = ""
#State private var dimensionSelection = 1
#State private var baseUnitSelection = 0
#State private var convertedUnitSelection = 0
let temperatureUnits = ["Celsius", "Fahrenheit", "Kelvin"]
let lengthUnits = ["meters", "kilometers", "feet", "yards", "miles"]
let timeUnits = ["seconds", "minutes", "hours", "days"]
let volumeUnits = ["milliliters", "liters", "cups", "pints", "gallons"]
let dimensionChoices = ["Temperature", "Length", "Time", "Volume"]
let dimensions: [[String]]
init () {
dimensions = [temperatureUnits, lengthUnits, timeUnits, volumeUnits]
}
var convertedValue: Double {
var result: Double = 0
let base = Double(baseNumber) ?? 0
if temperatureUnits[baseUnitSelection] == "Celsius" {
if convertedUnitSelection == 0 {
result = base
} else if convertedUnitSelection == 1 {
result = base * 9/5 + 32
} else if convertedUnitSelection == 2 {
result = base + 273.15
}
}
return result
}
var body: some View {
NavigationView {
Form {
Section {
TextField("Enter a number", text: $baseNumber)
.keyboardType(.decimalPad)
}
Section(header: Text("Select the type of conversion")) {
Picker("Dimension", selection: $dimensionSelection) {
ForEach(0 ..< dimensionChoices.count) {
Text(self.dimensionChoices[$0])
}
}.pickerStyle(SegmentedPickerStyle())
}
Group {
Section(header: Text("Select the base unit")) {
Picker("Base Unit", selection: $baseUnitSelection) {
ForEach(0 ..< self.dimensions[self.dimensionSelection].count) {
Text(self.dimensions[self.dimensionSelection][$0])
}
}.pickerStyle(SegmentedPickerStyle())
}
Section(header: Text("Select the unit to convert to")) {
Picker("Converted Unit", selection: $convertedUnitSelection) {
ForEach(0 ..< self.dimensions[self.dimensionSelection].count) {
Text(self.dimensions[self.dimensionSelection][$0])
}
}.pickerStyle(SegmentedPickerStyle())
}
}
Section(header: Text("The converted value is")) {
Text("\(convertedValue) \(dimensions[dimensionSelection][convertedUnitSelection])")
}
}.navigationBarTitle("Unit Converter")
}
}
}
I hate to answer my own question, but after spending some time on it, I think it's worth summarizing my findings in case it helps somebody. To summarize, I was trying to set the second Picker depending on what the selection of the first Picker was.
If you run the code that I pasted as is, you will get an out of bounds. This is only the case if I set #State private var dimensionSelection = 1 and the second array is larger than the first array. If you start with smaller array, you will be fine which you can observe by setting #State private var dimensionSelection = 0. There are a few ways to solve this.
Always start with the smallest array (Not great)
Instead of using an array of String, use an array of objects implementing Identifiable. this is the solution proposed by fuzz above. This got past the out of bound array exception. In my case though, I needed to specify the id parameter in the ForEach parameters.
Extend String to implement Identifiable as long as your strings are all different (which works in my trivial example). This is the solution proposed by gujci and his proposed solution looks much more elegant than mine, so I encourage you to take a look. Note that this to work in my own example. I suspect it might be due to how we built the arrays differently.
HOWEVER, once you get past these issues, it will still not work, You will hit an issue that appears be some kind of bug where the Picker keep adding new elements. My impression is that to get around this, one would have to destroy the Picker every time, but since I'm still learning Swift and SwiftUI, I haven't gotten round doing this.
So you'll want to make sure according to Apple's documentation that the array elements are Identifiable as you've mentioned.
Then you'll want to use ForEach like this:
struct Dimension: Identifiable {
let id: Int
let name: String
}
var temperatureUnits = [
Dimension(id: 0, name: "Celsius"),
Dimension(id: 1, name: "Fahrenheit"),
Dimension(id: 2, name: "Kelvin")
]
ForEach(temperatureUnits) { dimension in
Text(dimension.name)
}

Swift memoizing/caching lazy variable in a struct

I drank the struct/value koolaid in Swift. And now I have an interesting problem I don't know how to solve. I have a struct which is a container, e.g.
struct Foo {
var bars:[Bar]
}
As I make edits to this, I create copies so that I can keep an undo stack. So far so good. Just like the good tutorials showed. There are some derived attributes that I use with this guy though:
struct Foo {
var bars:[Bar]
var derivedValue:Int {
...
}
}
In recent profiling, I noticed a) that the computation to compute derivedValue is kind of expensive/redundant b) not always necessary to compute in a variety of use cases.
In my classic OOP way, I would make this a memoizing/lazy variable. Basically, have it be nil until called upon, compute it once and store it, and return said result on future calls. Since I'm following a "make copies to edit" pattern, the invariant wouldn't be broken.
But I can't figure out how to apply this pattern if it is struct. I can do this:
struct Foo {
var bars:[Bar]
lazy var derivedValue:Int = self.computeDerivation()
}
which works, until the struct references that value itself, e.g.
struct Foo {
var bars:[Bar]
lazy var derivedValue:Int = self.computeDerivation()
fun anotherDerivedComputation() {
return self.derivedValue / 2
}
}
At this point, the compiler complains because anotherDerivedComputation is causing a change to the receiver and therefore needs to be marked mutating. That just feels wrong to make an accessor be marked mutating. But for grins, I try it, but that creates a new raft of problems. Now anywhere where I have an expression like
XCTAssertEqaul(foo.anotherDerivedComputation(), 20)
the compiler complains because a parameter is implicitly a non mutating let value, not a var.
Is there a pattern I'm missing for having a struct with a deferred/lazy/cached member?
Memoization doesn't happen inside the struct. The way to memoize is to store a dictionary off in some separate space. The key is whatever goes into deriving the value and the value is the value, calculated once. You could make it a static of the struct type, just as a way of namespacing it.
struct S {
static var memo = [Int:Int]()
var i : Int
var square : Int {
if let result = S.memo[i] {return result}
print("calculating")
let newresult = i*i // pretend that's expensive
S.memo[i] = newresult
return newresult
}
}
var s = S(i:2)
s.square // calculating
s = S(i:2)
s.square // [nothing]
s = S(i:3)
s.square // calculating
The only way I know to make this work is to wrap the lazy member in a class. That way, the struct containing the reference to the object can remain immutable while the object itself can be mutated.
I wrote a blog post about this topic a few years ago: Lazy Properties in Structs. It goes into a lot more detail on the specifics and suggest two different approaches for the design of the wrapper class, depending on whether the lazy member needs instance information from the struct to compute the cached value or not.
I generalized the problem to a simpler one: An x,y Point struct, that wants to lazily compute/cache the value for r(adius). I went with the ref wrapper around a block closure and came up with the following. I call it a "Once" block.
import Foundation
class Once<Input,Output> {
let block:(Input)->Output
private var cache:Output? = nil
init(_ block:#escaping (Input)->Output) {
self.block = block
}
func once(_ input:Input) -> Output {
if self.cache == nil {
self.cache = self.block(input)
}
return self.cache!
}
}
struct Point {
let x:Float
let y:Float
private let rOnce:Once<Point,Float> = Once {myself in myself.computeRadius()}
init(x:Float, y:Float) {
self.x = x
self.y = y
}
var r:Float {
return self.rOnce.once(self)
}
func computeRadius() -> Float {
return sqrtf((self.x * self.x) + (self.y * self.y))
}
}
let p = Point(x: 30, y: 40)
print("p.r \(p.r)")
I made the choice to have the OnceBlock take an input, because otherwise initializing it as a function that has a reference to self is a pain because self doesn't exist yet at initialization, so it was easier to just defer that linkage to the cache/call site (the var r:Float)