I have an array of views stored in a variable that I want to render. This seems pretty straightforward and simple to me -- however, SwiftUI fails to render it. Does anyone know why this is so?
struct TestView: View {
#State private var currentSelectedView = 0
#State private var quote = "Hello 123"
var body: some View { // Failed to produce diagnostic for expression
let viewArray = [FirstTextView(quote:$quote), SecondTextView(quote:$quote)]
viewArray[0]
viewArray[1]
}
}
struct FirstTextView: View {
#Binding var quote:String
var body: some View {
Text(quote)
}
}
struct SecondTextView: View {
#Binding var quote:String
var body: some View {
Text(quote)
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
Try this. The problem was the elements of array were different types.
var body: some View {
let viewArray = [AnyView(FirstTextView(quote:$quote)), AnyView(SecondTextView(quote:$quote))]
viewArray[0]
viewArray[1]
}
Related
I want to have a mother view that displays a different child view based on a #State bool.
However I get two errors when doing that.
On the start of the body closure:
Struct 'ViewBuilder' requires that 'EmptyCommands' conform to 'View'
Return type of property 'body' requires that 'EmptyCommands' conform
to 'View'
And inside the control flow statement:
Closure containing control flow statement cannot be used with result
builder 'CommandsBuilder'
struct ResultView: View {
#State var resultViewSuccess = false
let resultViewModel: ResultViewModel
var body: some View {
Group {
if let showresultView = resultViewSuccess {
ViewOne(viewModel: resultViewModel)
} else {
ViewTwo(
resultViewSuccess: $resultViewSuccess,
viewModel: resultViewModel,
)
}
}
}
}
struct ViewTwo: View {
#Binding var resultViewSuccess: Bool
#StateObject var viewModel: ResultViewModel
var body: some View {
NavigationView {
ButtonResult(
resultViewSuccess: $resultViewSuccess,
viewModel: viewModel)
}
}
struct ButtonResult: View {
#Binding var resultViewSuccess: Bool
#StateObject var viewModel: ResultViewModel
var body: some View {
Button(action: {
self.resultViewSuccess = true
}) {
Text("View Results")
}
}
}
I am using SwiftUi 3.0 and I am new to it . I am learning about ObservedObjects . What I am trying to do is update the count of a variable every time that I close a view . This is the entire small app . The screen starts at DataUpdateView view when I click Next View I go to DataUpdateView2 view . Once I close DataUpdateView2 and go back to the original view I want to have the
Text("Score Count \(progress.score)")
score number increase by 1 since in the second view I do a +1 every time that I close that view . Any suggestions would be great
import SwiftUI
class UserProgress: ObservableObject {
#Published var score = 0
}
struct DataUpdateView: View {
#State var nextView = false
#StateObject var progress = UserProgress()
var body: some View {
VStack {
Text("Score Count \(progress.score)")
Text("Next View")
.onTapGesture {
nextView = true
}.fullScreenCover(isPresented: $nextView, content: {
DataUpdateView2()
})
}
}
}
struct DataUpdateView_Previews: PreviewProvider {
static var previews: some View {
DataUpdateView()
}
}
struct DataUpdateView2: View {
#ObservedObject var progress = UserProgress()
#Environment(\.presentationMode) var presentationMode
var body: some View {
Text("Back")
.onTapGesture {
progress.score += 1
self.presentationMode.wrappedValue.dismiss()
}
}
}
struct DataUpdateView2_Previews: PreviewProvider {
static var previews: some View {
DataUpdateView2()
}
}
You're probably not seeing the first view update since both views are instantiating their own UserProgress(). You need to pass the object you already created in the first view along to the second in the initializer
So In DataUpdateView:
.fullScreenCover(isPresented: $nextView, content: {
DataUpdateView2(progress: progress)
})
}
And then in DataUpdateView2:
struct DataUpdateView2: View {
#ObservedObject var progress: UserProgress
#Environment(\.presentationMode) var presentationMode
// ...
}
So now the second view is receiving the object from the first rather than creating its own.
Note: If you are not using an ObservableObject, then take a look at the second part.
In this specific situation, you don't even need a Binding variable, you can just use the .onDisappear method. .onDisappear Documentation.
import SwiftUI
class UserProgress: ObservableObject {
#Published var score = 0
}
struct DataUpdateView: View {
#State var nextView = false
#StateObject var progress = UserProgress()
var body: some View {
VStack {
Text("Score Count \(progress.score)")
Text("Next View")
.onTapGesture {
nextView = true
}.fullScreenCover(isPresented: $nextView, content: {
DataUpdateView2()
})
}
}
}
struct DataUpdateView_Previews: PreviewProvider {
static var previews: some View {
DataUpdateView()
}
}
struct DataUpdateView2: View {
#ObservedObject var progress = UserProgress()
#Environment(\.presentationMode) var presentationMode
var body: some View {
Text("Back")
.onTapGesture{
presentationMode.wrappedValue.dismiss()
print("Dismissed!")
}
.onDisappear{
//This is called when the view disappears.
progress.score += 1
}
}
}
struct DataUpdateView2_Previews: PreviewProvider {
static var previews: some View {
DataUpdateView2()
}
}
Second Part
If you want the variable to update when the view closes, you could use the .onDisappear method and a Binding value. An example implementation of this is below:
struct ViewOne: View{
#State var number = 0
var body: some View{
VStack{
Text("Number: \(number)")
NavigationLink(destination: ViewTwo(variable: $number)){
Text("Go To View Two")
}
}
}
}
struct ViewTwo: View{
#Binding var variable: Int
var body: some View{
//Content of view 2 here
Text("View Two")
.onDisappear{
//This is called when the view disappears
variable += 1
}
}
}
In short you need to use same view model in both views. A possible and seems simplest approach in your code is to inject view model from first view to second via environment object, like
#StateObject var progress = UserProgress()
var body: some View {
VStack {
Text("Score Count \(progress.score)")
Text("Next View")
.onTapGesture {
nextView = true
}.fullScreenCover(isPresented: $nextView, content: {
DataUpdateView2()
.environmentObject(progress) // << here !!
})
and use it internally, like
struct DataUpdateView2: View {
#EnvironmentObject var progress: UserProgress // << injected automatically !!
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)
}
}
I think I'm close but also missing something fundamental here with SwiftUI and passing data.
I have a top-level Color var called "masterColor" which I house in my "DataModel" struct.
Then in my view "NightLight", I have a system "ColorPicker", where I use a local var "localColor" to reflects whatever value the ColorPicker has.
Finally I have a "BackgroundControllerView", which sets a background color (which I would like to read the dataModel.masterColor)
What I'm trying to do set the dataModel.masterColor (which my whole app can see) equal to my localColor, which only NightLight can see. I've simplified the structure here a bit but the thrust of the question is about how to take local data and set something global equal to it for the rest of the app to see.
struct DataModel {
#State var masterColor = Color.red
}
struct NightLight: View {
#Binding var dataModel: DataModel
#State var localColor = Color.blue
var body: some View {
ColorPicker("Pick Color", selection: $localColor)
// Question: somehow set dataModel.masterColor = localColor ??
}
}
struct BackgroundControllerView: View {
#Binding var dataModel: DataModel
var body: some View {
Rectangle()
.fill(dataModel.masterColor)
}
}
Very much appreciate any help!
There's no need to have a separate localColor. You can directly pass in $dataModel.masterColor to the picker.
struct NightLight: View {
#Binding var dataModel: DataModel /// no need for extra `localColor`
var body: some View {
ColorPicker("Pick Color", selection: $dataModel.masterColor)
}
}
Also, remove the #State from masterColor -- you want the #State to be applied to DataModel, not the properties inside.
struct DataModel {
var masterColor = Color.red /// no #State here
}
struct NightLight: View {
#Binding var dataModel: DataModel
var body: some View {
ColorPicker("Pick Color", selection: $dataModel.masterColor)
}
}
struct BackgroundControllerView: View {
#Binding var dataModel: DataModel
var body: some View {
Rectangle()
.fill(dataModel.masterColor)
}
}
struct ContentView: View {
#State var dataModel = DataModel() /// use `#State` here instead
var body: some View {
VStack {
NightLight(dataModel: $dataModel)
BackgroundControllerView(dataModel: $dataModel)
}
}
}
Result:
I want to know if there is simple or proper way to read a State variable value from a different View, I know the usage of .onChange or Binding or ObservableObject(class) and ..., but I like to know is there any other better way?
For example in this code I have a View called TextView which has a State value, and I am calling this View inside my ContentView, Now I put a Text in my ContentView which I want to read the State Value of TextView. Is there a spacial method for this job?
struct ContentView: View {
#State var readStringOfTextView: String = ""
var body: some View {
TextView()
Text(readStringOfTextView)
.foregroundColor(Color.blue)
}
}
struct TextView: View {
#State var stringOfText: String = "Hello, world!"
var body: some View {
Text(stringOfText)
.padding()
.foregroundColor(Color.red)
}
}
The entire point of State is that it's internal to a View. If you're trying to read it elsewhere, something has gone wrong in your design. The tool you want in this case is #Binding. ContentView should pass a Binding to TextView. Any changes in TextView will be seen by ContentView (in your example, this doesn't make sense, because stringOfText can't change, but I assume that the rest of your code changes it somehow). In your example, that would look something like this:
struct ContentView: View {
#State var readStringOfTextView: String = ""
var body: some View {
TextView(stringOfText: $readStringOfTextView)
Text(readStringOfTextView)
.foregroundColor(Color.blue)
}
}
struct TextView: View {
#Binding var stringOfText : String
var body: some View {
Text(stringOfText)
.padding()
.foregroundColor(Color.red)
.onAppear {
stringOfText = "Hello, world!"
}
}
}
In is possible to directly pass data up the view hierarchy using Preferences, but it's much more complicated, and not the right tool for the problem you've described. Even so, this is what it would look like:
Create a PreferenceKey to pass the data
Set the PreferenceKey in the child view(s) using .preference
Read the PreferenceKey in the parent view using .onPreferenceChange or .overlayPreferenceValue or .backgroundPreferenceValue.
struct TextPreference: PreferenceKey {
static var defaultValue = "default"
static func reduce(value: inout String, nextValue: () -> String) {
value = nextValue()
}
}
struct ContentView: View {
#State var readStringOfTextView: String = ""
var body: some View {
VStack {
TextView()
.onPreferenceChange(TextPreference.self) { value in
readStringOfTextView = value
}
Text(readStringOfTextView)
.foregroundColor(Color.blue)
}
}
}
struct TextView: View {
#State var stringOfText : String = "Hello, world!"
var body: some View {
Text(stringOfText)
.padding()
.foregroundColor(Color.red)
.preference(key: TextPreference.self, value: stringOfText)
}
}