Using #State resolves into 'self' used before all stored properties are initialized - ios

I ran into an issue when using the #State property.
My ContentView.swift looks like this:
import SwiftUI
struct ContentView: View {
#State var showText: Bool = true
var Mod: Modifier
init() {
Mod = Modifier(showText: $showText) // Throws error -> 'self' used before all stored properties are initialized ('self.Mod' not initialized)
}
var body: some View {
VStack {
if showText == true {
Text("Hello, World!")
}
Mod
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
And my Modifier.swift from which the Modifier view is called has following code:
import SwiftUI
struct Modifier: View {
#Binding var showText: Bool
var body: some View {
VStack {
Button("Hide Text") {
self.showText.toggle()
}
}
}
}
I created this simplified code from my actual project that my problem is easier to understand.
Problem
The problem is that the code in the init function results into an error and I don't know how to resolve it.
What I tried and what I would need
Because this is just a simplified version of my actual code there are some requirements I need to my code:
Mod can't be a computed variable
I somehow need the Modifier view as a variable called Mod in my ContentView
When I remove the #State property and the #Binding property and the $ the code works and results with 0 errors. But I need to use the #State property (which unfortunately results into errors with my code)
Also the button to hide and show the text should work
I would be very thankful if anyone could give me a hint. I really appreciate your help!

I did actually find a way to do this. I'm not sure whether it'll be suitable but here are the details.
The problem was that SwiftUI didn't seem to allow setting the Binding outside of body. So this solution returns a new instance of Modifier
struct Modifier: View {
#Binding var showText: Bool
var body: some View {
VStack {
Button("Hide Text") {
self.showText.toggle()
}
}
}
// this function returns a new instance with the binding
func bind(to binding: Binding<Bool>) -> Self {
return Modifier(showText: binding)
}
}
And the code for ContentView, where we can call this function from within body:
struct ContentView: View {
#State var showText: Bool = true
var Mod: Modifier
init() {
Mod = Modifier(showText: .constant(true)) // .constant() gives a placeholder Binding
}
var body: some View {
return VStack {
if showText == true {
Text("Hello, World!")
}
Mod.bind(to: $showText)
}
}
}
Tested and the text can be hidden/shown. Hope this can help.

Mod = Modifier(showText: _showText.projectedValue)
You can make it let instead of var if you'd like.

Use views inside body context
struct ContentView: View {
#State var showText: Bool = true
var body: some View {
VStack {
if showText == true {
Text("Hello, World!")
}
Modifier(showText: $showText)
}
}
}

Related

SwiftUI - "Argument passed to call that takes no arguments"?

I have an issue with the coding for my app, where I want to be able to scan a QR and bring it to the next page through navigation link. Right now I am able to scan a QR code and get a link but that is not a necessary function for me. Below I attached my code and got the issue "Argument passed to call that takes no arguments", any advice or help would be appreciated :)
struct QRCodeScannerExampleView: View {
#State private var isPresentingScanner = false
#State private var scannedCode: String?
var body: some View {
VStack(spacing: 10) {
if let code = scannedCode {
//error below
NavigationLink("Next page", destination: PageThree(scannedCode: code), isActive: .constant(true)).hidden()
}
Button("Scan Code") {
isPresentingScanner = true
}
Text("Scan a QR code to begin")
}
.sheet(isPresented: $isPresentingScanner) {
CodeScannerView(codeTypes: [.qr]) { response in
if case let .success(result) = response {
scannedCode = result.string
isPresentingScanner = false
}
}
}
}
}
Page Three Code
import SwiftUI
struct PageThree: View {
var body: some View {
Text("Hello, World!")
}
}
struct PageThree_Previews: PreviewProvider {
static var previews: some View {
PageThree()
}
}
You forgot property:
struct PageThree: View {
var scannedCode: String = "" // << here !!
var body: some View {
Text("Code: " + scannedCode)
}
}
You create your PageThree View in two ways, One with scannedCode as a parameter, one with no params.
PageThree(scannedCode: code)
PageThree()
Meanwhile, you defined your view with no initialize parameters
struct PageThree: View {
var body: some View {
Text("Hello, World!")
}
}
For your current definition, you only can use PageThree() to create your view. If you want to pass value while initializing, change your view implementation and consistently using one kind of initializing method.
struct PageThree: View {
var scannedCode: String
var body: some View {
Text(scannedCode)
}
}
or
struct PageThree: View {
private var scannedCode: String
init(code: String) {
scannedCode = code
}
var body: some View {
Text(scannedCode)
}
}
This is basic OOP, consider to learn it well before jump-in to development.
https://docs.swift.org/swift-book/LanguageGuide/Initialization.html

How to observe change in #StateObject in SwiftUI?

I am having property with #StateObject, I am trying to observe change in viewmodel, I am able to print correct result but can not able to show on screen as view is not refreshing.
Tried using binding but not worked because of #StateObject
import SwiftUI
struct AbcView: View {
#StateObject var abcViewModel: AbcViewModel
init(abcViewModel: AbcViewModel) {
self._abcViewModel = StateObject(wrappedValue: abcViewModel)
}
var body: some View {
VStack(alignment: .leading) {
ZStack(alignment: .top) {
ScrollView {
Text("some txt")
}
.overlay(
VStack {
TopView(content: classViews(data: $abcViewModel.somedata, abcViewModel: abcViewModel))
Spacer()
}
)
}
}
}
}
func classViews(data: Binding<[SomeData]>, abcViewModel: AbcViewModel) -> [AnyView] {
var views: [AnyView] = []
for element in data {
views.append(
VStack(spacing: 0) {
HStack {
print("\(abcViewModel.title(Id: Int(element.dataId.wrappedValue ?? "")) )") // printing correct value
Text(abcViewModel.title(Id: Int(element.dataId.wrappedValue ?? ""))) // want to observe change here
}
}
.convertToAnyView())
}
return views
}
If you are injecting your AbcViewModel into AbcView you should use #ObserverdObject instead of #StateObject , full explanation here Also you should conform tour AbcViewModel to ObservableObject and make your desired property #Published if you want to trigger the change in View . Here is simplified code example:
Making AbcViewModel observable:
class AbcViewModel: ObservableObject {
#Published var dataID: String = "" //by changing the #Published proprty you trigger change in View using it
}
store AbcViewModel as #ObserverdObject:
struct AbcView: View {
#ObservedObject var abcViewModel: AbcViewModel
init(abcViewModel: AbcViewModel) {
self.abcViewModel = abcViewModel
}
var body: some View {
//...
}
}
If you now use your AbcViewModel dataID property anywhere in the project, and you change its value, the property will publish the change and your View (struct) will be rebuilded. Use the same pattern for creating TopView and assigning AbcViewModel to it the same way.

SwiftUI: Why onReceive run duplicate when binding a field inside ObservableObject?

This is my code and "print("run to onReceive (text)")" run twice when text change (like a image). Why? and thank you!
import SwiftUI
class ContentViewViewModel : ObservableObject {
#Published var text = ""
}
struct ContentView: View {
#StateObject private var viewModel = ContentViewViewModel()
var body: some View {
ZStack {
TextField("pla", text: $viewModel.text)
.padding()
}
.onReceive(viewModel.$text) { text in
print("run to onReceive \(text)")
}
}
}
I think it's because the view is automatically updated as your #Published property in your ViewModel changes and the .onReceive modifier updates the view yet again due to the 2 way binding created by viewModel.$text resulting in the view being updated twice each time.
If you want to print the text as it changes you can use the .onChange modifier instead.
class ContentViewViewModel: ObservableObject {
#Published var text = ""
}
struct ContentView: View {
#StateObject private var viewModel = ContentViewViewModel()
var body: some View {
ZStack {
TextField("pla", text: $viewModel.text)
.padding()
}.onChange(of: viewModel.text) { newValue in
print("run to onChange \(newValue)")
}
}
}
onChanged in SwiftUI
Because you have a #Published variable inside the #StateObject of your view, the changes in that variable will automatically update the view.
If you add the .onReceive() method, you will:
update the view because you have the #Published var
update it again when the .onReceive() method listens to the change
Just delete the .onReceive() completely and it will work:
class ContentViewViewModel : ObservableObject {
#Published var text = ""
}
struct ContentView: View {
#StateObject private var viewModel = ContentViewViewModel()
var body: some View {
ZStack {
TextField("pla", text: $viewModel.text)
.padding()
}
// It still works without this modifier
//.onReceive(viewModel.$text) { text in
// print("run to onReceive \(text)")
//}
}
}

How to pass static values in SwiftUI from one View to another?

I'm new to learning SwiftUI and XCode and am unable to figure out how to pass a variable from view to another. I read on #State and #Binding variables but from what I can tell that is for values that change. I have a static value that I calculate based on the date when the user opens the app.
The variable is the current moon phase and is stored locally in my main ContentView. I want to pass this variable to a second view that's accessed by clicking a NavigationLink.
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
let currentMoonPhaseArray = calculateMoonPhase()
let moonPhase = currentMoonPhaseArray[0]
NavigationView{
ScrollView(.vertical, showsIndicators:true) {
VStack(spacing:3){
NavigationLink(destination: MoonPhaseView()){
Text("Moon Phase - " + moonPhase)
}
}
}
.frame(maxWidth: .infinity)
.navigationTitle("MySky")
.navigationBarTitleDisplayMode(.inline)
}
}
}
MoonPhaseView.swift
import SwiftUI
struct MoonPhaseView: View {
var body: some View {
HStack{
Text("MoonPhaseView!")
}
}
}
struct MoonPhaseView_Previews: PreviewProvider {
static var previews: some View {
MoonPhaseView()
}
}
My goal is to have the calculated moon phase from ContentView.swift be passed to the MoonPhaseView.swift. I believe that bindings are the correct approach from what I've read, but all binding implementations seem to be for updating views often.
Thanks for any help or pointers!
You haven't shown what the type of moonPhase is, so I'm just going to use String as an example.
struct ContentView: View {
func calculateMoonPhase() -> [String] {
return ["Full","Waxing","Waning"]
}
var body: some View {
let currentMoonPhaseArray = calculateMoonPhase()
let moonPhase = currentMoonPhaseArray[0]
NavigationView{
ScrollView(.vertical, showsIndicators:true) {
VStack(spacing:3){
NavigationLink(destination: MoonPhaseView(phase: moonPhase)){
Text("Moon Phase - " + moonPhase)
}
}
}
.frame(maxWidth: .infinity)
.navigationTitle("MySky")
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct MoonPhaseView: View {
var phase : String
var body: some View {
HStack{
Text("MoonPhaseView!")
Text(phase)
}
}
}
struct MoonPhaseView_Previews: PreviewProvider {
static var previews: some View {
MoonPhaseView(phase: "Full")
}
}
Note that every time MoonPhaseView is used, you must provide a phase parameter so that it has a value to fill var phase : String. You could provide a default value, but that doesn't seem like it would do much good here.
Not directly related to your question, but I might suggest that calculating the phase in the body might lead to undesirable results, especially if it's an expensive calculation or it has to contact an API or something. You might want to consider doing this in onAppear and keeping it in a #State variable in your ContentView, or perhaps even using an ObservableObject as a view model and storing the phase there.
You can use "Environment" to pass system-wide settings to views and child views.
For example:
#main
struct TestApp: App {
let moonPhaseValue = "Waxing" // <--- some value
var body: some Scene {
WindowGroup {
ContentView().environment(\.moonPhase, moonPhaseValue) // <--- pass it around
}
}
}
struct ContentView: View {
#Environment(\.moonPhase) var moonPhase // <--- the value again
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: MoonPhaseView()) {
Text("Moon Phase - " + moonPhase)
}
}
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct MoonPhaseView: View {
#Environment(\.moonPhase) var moonPhase // <--- the value again
var body: some View {
HStack{
Text("MoonPhaseView is \(moonPhase)")
}
}
}
// create your own EnvironmentKey
struct MoonPhaseKey: EnvironmentKey {
static let defaultValue: String = "Full"
}
// create your own EnvironmentValues
extension EnvironmentValues {
var moonPhase: String {
get { return self[MoonPhaseKey] }
set { self[MoonPhaseKey] = newValue }
}
}

How to use #State if #Binding not provided in the initializer

Imagine a view with some #Binding variables:
init(isEditing: Binding<Bool>, text: Binding<Bool>)
How can we have the selection working with an internal #State if it is not provided in the initializer?
init(text: Binding<Bool>)
This is how to make TextField become first responder in SwiftUI
Note that I know we can pass a constant like:
init(isEditing: Binding<Bool> = .constant(false), text: Binding<Bool>)
But!
This will kill the dynamicity of the variable and it won't work as desire. Imagine re-inventing the isFirstResponder of the UITextField.
It can't be .constant(false). The keyboard will be gone on each view update.
It can't be .constant(true). The view will take the keyboard on each view update.
Maybe! Apple is doing it somehow with TabView.
One solution is to pass an optional binding and use a local state variable if the binding is left nil. This code uses a toggle as an example (simpler to explain) and results in two interactive toggles: one being given a binding and the other using local state.
import SwiftUI
struct ContentView: View {
#State private var isOn: Bool = true
var body: some View {
VStack {
Text("Special toggle:")
SpecialToggle(isOn: $isOn)
.padding()
SpecialToggle()
.padding()
}
}
}
struct SpecialToggle: View {
/// The binding being passed from the parent
var isOn: Binding<Bool>?
/// The fallback state if the binding is left `nil`.
#State private var defaultIsOn: Bool = true
/// A quick wrapper for accessing the current toggle state.
var toggleIsOn: Bool {
return isOn?.wrappedValue ?? defaultIsOn
}
init(isOn: Binding<Bool>? = nil) {
if let isOn = isOn {
self.isOn = isOn
}
}
var body: some View {
Toggle(isOn: isOn ?? $defaultIsOn) {
Text("Dynamic label: \(toggleIsOn.description)")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
You may create separate #State and #Binding properties and sync them using onChange or onReceive:
struct TestView: View {
#State private var selectionInternal: Bool
#Binding private var selectionExternal: Bool
init() {
_selectionInternal = .init(initialValue: false)
_selectionExternal = .constant(false)
}
init(selection: Binding<Bool>) {
_selectionInternal = .init(initialValue: selection.wrappedValue)
_selectionExternal = selection
}
var body: some View {
if #available(iOS 14.0, *) {
Toggle("Selection", isOn: $selectionInternal)
.onChange(of: selectionInternal) {
selectionExternal = $0
}
} else {
Toggle("Selection", isOn: $selectionInternal)
.onReceive(Just(selectionInternal)) {
selectionExternal = $0
}
}
}
}
struct ContentView: View {
#State var selection = false
var body: some View {
VStack {
Text("Selection: \(String(selection))")
TestView(selection: $selection)
TestView()
}
}
}

Resources