Trouble getting EnvironmentObject to update the UI - swift

I originally posted another question asking this in the context of a project I was trying to develop, but I can't even get it to work in a vacuum so I figured I'd start with the basics. As the title suggests, my EnvironmentObjects don't update the UI as they should; in the following code, the user enters text on the ContentView and should be able to see that text in the next screen SecondView.
EDITED:
import SwiftUI
class NameClass: ObservableObject {
#Published var name = ""
}
struct ContentView: View {
#StateObject var myName = NameClass()
var body: some View {
NavigationView {
VStack {
TextField("Type", text: $myName.name)
NavigationLink(destination: SecondView()) {
Text("View2")
}
}
}.environmentObject(myName)
}
}
struct SecondView: View {
#EnvironmentObject var myself: NameClass
var body: some View {
Text("\(myself.name)")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(NameClass())
}
}
However, the SecondView doesn't show the text that the user has written, but the default value of name (blank). What am I doing wrong here?

class NameClass: ObservableObject {
#Published var name = ""
}
struct ContentView: View {
#StateObject var myName = NameClass()
var body: some View {
NavigationView {
VStack {
TextField("Type", text: $myName.name)
NavigationLink(destination: SecondView()) {
Text("View2")
}
}
}.environmentObject(myName)
}
}
struct SecondView: View {
#EnvironmentObject var myself: NameClass
var body: some View {
Text("\(myself.name)")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(NameClass())
}
}

Related

How to pass data between SwiftUI views using NavigationLink

I am engaging in a small SwiftUI exercise to learn how to pass data between views using NavigationLink. I set up ContentView to send a message to the SecondView after tapping the ContentView NavigationLink. Tapping NavigationLink in the SecondView then sends the message to the ThirdView. However, I am noticing a strange UI occurrence by the time I get to ThirdView. See the screenshot below:
Any idea why this NavigationView issue is occurring? Is it related to having NavigationView in all 3 views?
Here is my code:
ContentView
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination: SecondView(message: "Hello from ContentView")) {
Text("Go to Second View")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
SecondView
struct SecondView: View {
var message: String
var body: some View {
Text("\(message)")
NavigationView {
NavigationLink(destination: ThirdView(message: self.message)) {
Text("Go to Third View")
}
}
}
}
struct SecondView_Previews: PreviewProvider {
static var previews: some View {
SecondView(message: String())
}
}
ThirdView
struct ThirdView: View {
var message: String
var body: some View {
NavigationView {
Text("\(message)")
}
}
}
struct ThirdView_Previews: PreviewProvider {
static var previews: some View {
ThirdView(message: String())
}
}
Feedback is appreciated. Thanks!
remove the second navigation view
struct SecondView: View {
var message: String
var body: some View {
VStack(spacing: 100 ) {
Text("\(message)")
NavigationLink(destination: ThirdView(message: self.message)) {
Text("Go to Third View")
}
}
}
}
struct ThirdView: View {
var message: String
var body: some View {
Text("\(message)")
}
}

Missing argument for parameter 'message' in call

I'm back with another question. I was following this guide: https://medium.com/#fs.dolphin/passing-data-between-views-in-swiftui-793817bba7b1
Everything worked from it, but the SecondView_Previews is throwing an error Missing argument for parameter 'message' in call. Here is my ContentView and SecondView
// ContentView
import SwiftUI
struct ContentView: View {
#State private var showSecondView = false
#State var message = "Hello from ContentView"
var body: some View {
VStack {
Button(action: {
self.showSecondView.toggle()
}){
Text("Go to Second View")
}.sheet(isPresented: $showSecondView){
SecondView(message: self.message)
}
Button(action: {
self.message = "hi"
}) {
Text("click me")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
import SwiftUI
struct SecondView: View {
#State var message: String
var body: some View {
Text("\(message)")
}
}
struct SecondView_Previews: PreviewProvider {
static var previews: some View {
SecondView() // Error here: Missing argument for parameter 'message' in call.
}
}
It tried changing it to SecondView(message: String) and the error changes to "Cannot convert value of type 'String.Type' to expected argument type 'String'"
Can someone please explain what I'm doing wrong, or how to correctly set up the preview. It all works fine when there's no preview. Thanks in advance!
struct ContentView: View {
#State var message: String //Define type here
var body: some View {
Text("\(message)")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(message: "Some text") //Passing value here
}
}

Why doesn't the Text update when using #Binding?

The Working works as expected.
But using the #Binding in the NotWorking example, doesn't seem to update the Text control. Why doesn't the #Binding version work, what am I missing here?
Initial Launch:
After Typing:
struct Working: View {
//Binding from #State updates both controls
#State private var text = "working"
var body: some View {
VStack {
TextField("syncs to label...", text: $text)
Text($text.wrappedValue)
}
}
}
struct NotWorking: View {
//Using the #Binding only updates the TextField
#Binding var text: String
var body: some View {
//This does not works
VStack {
TextField("won't sync to label...", text: $text)
Text($text.wrappedValue)
}
}
}
struct Working_Previews: PreviewProvider {
#State static var text = "not working"
static var previews: some View {
VStack {
Working()
NotWorking(text: $text)
}
}
}
Static #States don't work. It's the fact that it being static means that the struct Working_Previews isn't mutated when text is changed, so it won't refresh.
We can test this by changing from a PreviewProvider to an actual View:
struct ContentView: View {
#State static var text = "not working"
var body: some View {
VStack {
Working()
NotWorking(text: ContentView.$text)
}
}
}
This code gives the following runtime message:
Accessing State's value outside of being installed on a View. This will result in a constant Binding of the initial value and will not update.
Thanks to #George_E. I define #State in a wrapper view and display that for the preview. The WrapperView simply displays the control that I want to preview but it contains the State.
struct Working_Previews: PreviewProvider {
//Define the State in a wrapper view
struct WrapperView: View {
#State var text = "Preview is now working!!!"
var body: some View {
NotWorking(text: $text)
}
}
static var previews: some View {
VStack {
Working()
//Don't display the view that needs the #Binding
//NotWorking(text: $text)
//Use the WrapperView that has #State and displays the view I want to preview.
WrapperView()
}
}
}

Difference between toggle() and Toggle

I am learning Modals in SwiftUI and the code is below:
ContentView.swift:
import SwiftUI
struct ContentView: View {
#State private var showingAddUser = false
var body: some View {
return VStack {
Text("Modal View")
}.onTapGesture {
self.showingAddUser.toggle()
print(self.showingAddUser) //for console
}
.sheet(isPresented: $showingAddUser) {
Addview(isPresented: self.$showingAddUser)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
AddView.swift:
import SwiftUI
struct Addview: View {
#Binding var isPresented: Bool
var body: some View {
Button("Dismiss") {
self.isPresented = false
}
}
}
struct Addview_Previews: PreviewProvider {
static var previews: some View {
Addview(isPresented: .constant(false))
}
}
When I try to run the code for the first time and check the print output in console, boolean value changes to true however if I initialise #State variable showingAddUser with true the console output is unchanged that is it remains true. Should't toggle() flip the boolean value to false?
Is this toggle() different from Toggle switch from a concept point of view?
The toggle() is a mutating function on value type Bool. If you set the initial value of showingAddUser as true it will display the AddUser View when launched initially and it's not if set to false, that's the difference.
Toggle is a SwiftUI View. It can be used as any other View in SwiftUI body, like this:
struct ContentView: View {
#State var bool: Bool
var body: some View {
Toggle(isOn: $bool) {
Text("Hello world!")
}
}
}
There is no need for isPresented Boolean in Add View
Try This
ContentView.swift
import SwiftUI
struct ContentView: View {
#State private var showingAddUser = false
var body: some View {
return VStack {
Text("Modal View")
}.onTapGesture {
self.showingAddUser = true
print(self.showingAddUser) //for console
}
.sheet(isPresented: $showingAddUser) {
Addview()
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
AddView.swift
import SwiftUI
struct AddView: View {
var body: some View {
Button(action:
// Do Your Things
) {
Text("MyButton")
}
}
}
struct Addview_Previews: PreviewProvider {
static var previews: some View {
Addview()
}

Switching Views With Observable Objects in SwiftUI

I am practicing trying to switch views using observable objects in SwiftUI, but my code isn't working. I know I can do this with #State, but I would like to get this working with observable objects. When I click on the image in the content view, the image doesn't change. Can someone help me?
ContentView.swift
import SwiftUI
struct ContentView: View {
#ObservedObject var viewRouter: ViewRouter
var body: some View {
VStack {
Button(action: {self.viewRouter.currentPage = "Page2"}) {
Image(viewRouter.currentPage)
}
if viewRouter.currentPage == "Page1" {
Page1(viewRouter: viewRouter)
}else if viewRouter.currentPage == "Page2" {
Page2(viewRouter: viewRouter)
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView(viewRouter: ViewRouter())
}
}
ViewRouter.swift
import Foundation
import SwiftUI
import Combine
class ViewRouter: ObservableObject {
let objectWillChange = PassthroughSubject<ViewRouter, Never>()
#Published var currentPage: String = "Page1"
}
Page1.swift
import SwiftUI
struct Page1: View {
#ObservedObject var viewRouter:ViewRouter
var body: some View {
VStack {
Image("ET-LondonBridge")
}
}
}
struct Page1_Previews: PreviewProvider {
static var previews: some View {
Page1(viewRouter: ViewRouter())
}
}
Page2.swift
import SwiftUI
struct Page2: View {
#ObservedObject var viewRouter:ViewRouter
var body: some View {
VStack {
Image("BigBen")
.aspectRatio(contentMode: .fit)
}
}
}
struct Page2_Previews: PreviewProvider {
static var previews: some View {
Page2(viewRouter: ViewRouter())
}
}
You don't need this line to make all things work. Just comment out this line
//let objectWillChange = PassthroughSubject<ViewRouter, Never>()