Given this context:
struct MyView: View {
#State private var myString: String
}
I first tried to initialize it this way:
init(_ string: String) {
self.myString = string
}
But I got this error:
Variable 'self.myString' used before being initialized
Then I did this:
init(_ string: String) {
self._myString = State(initialValue: string)
}
It works fine, but since declarative programming and state are a new thing to me, I don't really understand the difference and what's going on here.
Using #State var name: String is a property wrapper, which is a nice wrapper around a variable called _name of type State<String>. Effectively, this:
struct MyView: View {
#State private var myString: String
}
translates to this:
struct MyView: View {
private var _myString: State<String>
private var $myString: Binding<String> {
_myString.projectedValue
}
private var myString: String {
get { _myString.wrappedValue }
set { _myString.wrappedValue = $0 }
}
}
which should explain your problem (using myString before _myString is initialized).
Related
I have a view like this:
struct View1: View {
#Binding var myVariable: Bool
init() {
_myVariable = Binding.constant(true) // It works but myVariable is immutable, so I can't edit myVariable
}
init(myVariable: Binding<Bool>) {
_myVariable = myVariable
}
var body: some View {
Button("Change") {
myVariable.toggle()
}
}
}
struct View2: View {
var body: some View {
View1()
}
}
struct View3: View {
#State var myVariable = false
var body: some View {
View1(myVariable: $myVariable)
}
}
And I want to make this: If there is a parameter provided, set this to myVariable like second init in View1. Else, set the first value of myVariable like in first init.
I tried to use Binding.constant(value) but it is immutable. And I can't edit the variable. So, I need a mutable Binding initializer like Binding.constant(value). But I can't find it.
How can I solve this problem?
To avoid over-complicating View1, you can create an intermediate view with that name, and then have a private 'internal' view which has the actual implementation.
Code:
private struct View1Internal: View {
#Binding var myVariable: Bool
var body: some View {
Button("Change") {
myVariable.toggle()
}
}
}
struct View1: View {
private enum Kind {
case state
case binding(Binding<Bool>)
}
#State private var state = true
private let kind: Kind
init() {
kind = .state
}
init(myVariable: Binding<Bool>) {
kind = .binding(myVariable)
}
var body: some View {
switch kind {
case .state: View1Internal(myVariable: $state)
case .binding(let binding): View1Internal(myVariable: binding)
}
}
}
I need to save an instance of a child view into a variable, so I can call a method on it afterward.
However, I need to pass a binding into this child view when its initialized. How do I do that?
struct EditImageView: View {
#State private var currentSelectedText:String
#State private var currentSelectedFilter:Filter
var imageCanvasView: ImageCanvasView
init() {
currentSelectedText = "Hello"
currentSelectedFilter = Filter.noFilter
imageCanvasView = ImageCanvasView(imageText: $currentSelectedText, filter: $currentSelectedFilter)
//Error: 'self' used before all stored properties are initialized
}
var body: some View {
imageCanvasview
Button("Take screenshot") {
imageCanvasview.takeScreenshot()
}
}
}
One way is to declare imageCanvasView in body, like:
struct EditImageView: View {
#State private var currentSelectedText = "Hello"
#State private var currentSelectedFilter = Filter.noFilter
var body: some View {
let imageCanvasView = ImageCanvasView(imageText: $currentSelectedText, filter: $currentSelectedFilter)
VStack {
imageCanvasView
Button("Take screenshot") {
imageCanvasView.takeScreenshot()
}
}
}
}
All you need to do is to change the property wrapper prefix. For example, if you wanted to pass your currentSelectedText you would pass it like so.
var currentSelectedText: Binding<String>
// Effectively is the equivalent of `#State`
The same can be done in your init()
init(someString: Binding<String>) { ....
Probably a better way is to use a view model which both EditImageView and ImageCanvasView use, something like:
class EditImageViewModel: ObservableObject {
#Published var currentSelectedText: String = "Hello"
#Published var currentSelectedFilter = Filter.noFilter
func takeScreenshot() {
}
}
struct ImageCanvasView: View {
#EnvironmentObject var editImage: EditImageViewModel
var body: some View {}
}
struct EditImageView: View {
#StateObject var editImage = EditImageViewModel()
var body: some View {
VStack {
ImageCanvasView()
Button("Take screenshot") {
editImage.takeScreenshot()
}
}
.environmentObject(editImage)
}
}
So I'm doing some refactoring and I ran across this line of code that I wanted to refactor:
struct MyView: View {
#State private var myArrayOfCustomObjects = [CustomObject]
let text: String
var body: some View {
Text(text)
}
}
Then when I wanted to refactor the view as so..
struct ExtractedView: View {
#Binding var customObjects: [CustomObject]
let text: String
init(customObjects: Binding<Array<CustomObject>>, text: String) {
self.customObjects = customObjects // Error: 'self' used before all stored properties are initialized
// Also tried _customObjects = customObjects
self.text = text
}
var body: some View {
Text(text)
}
}
This code is simplified of course but I fear I may be getting that error due to some complexity I'm not exposing in the example. Any feedback is welcome
What am I doing wrong??
( I also have an Environment instance (managedObjectContext) and a coreData class - which has some logic inside of the init that are being initialized too but didn't think it was relevant for this code example )
This will work! also try clean your build folder and build your project first.
struct ExtractedView: View {
#Binding var customObjects: [CustomObject]
let text: String
init(customObjects: Binding<Array<CustomObject>>, text: String) {
self._customObjects = customObjects
self.text = text
}
var body: some View {
Text(text)
}
}
struct CustomObject { }
I have this code and would expected a b as Text.
Result: a a -> see screenshot. What am I doing wrong?
import SwiftUI
class PublishString : ObservableObject {
init(string: String) {
self.string = string
print(self.string)
}
#Published var string : String = "a"
}
struct ContentView: View {
#EnvironmentObject var text1 : PublishString
#EnvironmentObject var text2 : PublishString
var body: some View {
VStack {
Text(text1.string)
Text(text2.string)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environmentObject(PublishString(string: "a"))
.environmentObject(PublishString(string: "b"))
}
}
and ...this works:
class PublishString : ObservableObject {
init(string: String) {
self.string = string
print(self.string)
}
#Published var string : String = "a"
}
class PublishString2 : ObservableObject {
init(string: String) {
self.string = string
print(self.string)
}
#Published var string : String = "a"
}
struct ContentView: View {
#EnvironmentObject var text1 : PublishString
#EnvironmentObject var text2 : PublishString2
var body: some View {
VStack {
Text(text1.string)
Text(text2.string)
}
}
}
As noted by Asperi in the comment, SwiftUI identifies Environment Objects by the type (the class definition you have used). It looks for an object of that type and uses the first one it finds.
One option is to have multiple properties on the one object that you can access (this would mean two separate String properties in your case.
Further information is available on the Apple documentation.
The accepted answer is perfectly fine and correct and answers the question.
The following is a simple workaround, if you must use two EnvironmentObjects of the same type to be passed around within your app, and stumble upon this question:
You can make a second class that inherits everything from the first class. Therefore you avoid redundancy and can use both EnvironmentObjects separately.
class PublishString : ObservableObject {
init(string: String) {
self.string = string
print(self.string)
}
#Published var string : String = "a"
}
class PublishString2 : PublishString {}
struct ContentView: View {
#EnvironmentObject var text1 : PublishString
#EnvironmentObject var text2 : PublishString2
var body: some View {
VStack {
Text(text1.string)
Text(text2.string)
}
}
}
instantiation:
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView().environmentObject(PublishString(string: "a"))
.environmentObject(PublishString2(string: "b"))
}
}
So I have been trying to fix this problem that has been already discussed here for a few times but I cannot seem to truly understand where the issue comes from and how to fix it in my application. Apologies if this one is obvious but I've picked up SwiftUI a week ago.
Basically what I am doing here is that I have a function called countStrokes() where I have an array of strings as an input. First of all I convert the array to an int array, then compute the sum of the array and return the sum as a String.
After that I declare a new lazy var called strokes and initialise it by calling the countStrokes() function.
All I want to do in the View is to print out the strokes value using the Text() module.
Any ideas of how to modify my existing code will be much appreciated.
import SwiftUI
struct Skore: View {
#State var skore: [String]
lazy var strokes: String = countStrokes(array: skore)
var body: some View {
Text(strokes)
}
}
func countStrokes(array: [String]) -> String {
let newArray = array.compactMap{Int($0)}
let total = newArray.reduce(0, +)
let totalString = String(total)
return totalString
}
The simplest is just use function inline. As soon as your view depends on skore state and countStrokes depends on it, once skore will be modified the corresponding Text will be recalculated and shows correct result.
struct Skore: View {
#State var skore: [String]
var body: some View {
Text(countStrokes(array: skore))
}
}
what you can do is this:
struct Skore: View {
#State var skore: [String]
#State var strokes: String = ""
var body: some View {
Text(strokes).onAppear(perform: loadData)
}
func loadData() {
self.strokes = countStrokes(array: skore)
}
}
func countStrokes(array: [String]) -> String {
let newArray = array.compactMap{Int($0)}
let total = newArray.reduce(0, +)
let totalString = String(total)
return totalString
}
struct ContentView: View {
#State var skore = ["1","2","3"]
var body: some View {
Skore(skore: skore)
}
}