Switching Views With Observable Objects in SwiftUI - swift

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>()

Related

Configure preview for ObservedObject and State object

So I am still learning Swift and for some reason, I am having the hardest trouble with the previews and how to configure them.
So I have the following code:
struct MainView: View {
// The app's model that the containing scene passes in
#ObservedObject var model: MainViewModel
#State var activeTab = 0
var body: some View {
VStack {
TabView(selection: $activeTab) {
Group {
WorldView(model: model, activeTab: $activeTab)
.tabItem {
Label(Tabs.explore.rawValue, systemImage: Tabs.explore.icon)
.environment(\.symbolVariants, .none)
}
.tag(0)
ListView(model: model, activeTab: $activeTab)
.tabItem {
Label(Tabs.list.rawValue, systemImage: Tabs.list.icon)
.environment(\.symbolVariants, .none)
}
.tag(1)
FavoritesView(activeTab: $activeTab)
.tabItem {
Label(Tabs.favorite.rawValue, systemImage: Tabs.favorite.icon)
.environment(\.symbolVariants, .none)
}
.tag(2)
ProfileView(model: model, activeTab: $activeTab)
.tabItem {
Label(Tabs.profile.rawValue, systemImage: Tabs.profile.icon)
.environment(\.symbolVariants, .none)
}
.tag(3)
}
.environmentObject(model)
}
.tint(.accentColor)
.onChange(of: activeTab, perform: { value in
log.info("\n 🟢: (MainView: 46) - User has selected tab: \(value).")
print("")
})
}
.onAppear() {
model.fetchPlaces()
}
}
}
Then I have the preview, as such:
struct MainView_Previews: PreviewProvider {
static var previews: some View {
MainView(model: model, activeTab: activeTab)
}
}
I am getting the two errors on the previews:
Cannot find 'activeTab' in scope
Cannot find 'model' in scope
If I define it as such:
struct MainView_Previews: PreviewProvider {
#ObservedObject var model: MainViewModel
#State var activeTab = 0
static var previews: some View {
MainView(model: model, activeTab: activeTab)
}
}
I get the following errors:
Instance member 'activeTab' cannot be used on type 'MainView_Previews'
Instance member 'model' cannot be used on type 'MainView_Previews'
Does anyone know how I can configure the preview so that it works properly and doesn't crash?
This happens because you are either passing in non existing paremeters in Previews, or because you cannot initialize objects in previews. Instead, do this:
struct MyExamplePreviews: PreviewProvider{
static var previews: some View {
MainView(model: MainViewModel(), activeTab: 0)
}
}
This will allow you to preview the UI. What this does:
Creates a new model that is passed in at the top level - again, you cannot create this anywhere but right here in the previews
Makes it so that activeTab will be set to 0 - you can have multiple preview devices with different tabs if needed. See the docs for Previews to learn more.
I recommend the below approach, then you can use #EnvironmentObject var model: Model in any View that needs it and don't have to pass it into every View.
class Model: ObservableObject {
#Published var items: [Item] = []
static var shared = Model()
static var preview = Model(preview: true)
init(preview: Bool) {
if preview {
items = // set some test items
return
}
load()
}
fun load(){
// load items from disk
}
}
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(Model.shared)
}
}
}
struct ContentView: View {
#EnvironmentObject var: model: Model
var body: some View {
NavigationStack {
List {
ForEach($model.items) $item in {
TextField("Title" text: $item.title)
}
.navigationTitle("Items")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(Model.preview)
}
}

Propertly break down and pass data between views

So I'm still learning Swift and I wanted to cleanup some code and break down views, but I can't seem to figure out how to pass data between views, so I wanted to reach out and check with others.
So let's say that I have MainView() which previously had this:
struct MainView: View {
#ObservedObject var model: MainViewModel
if let item = model.selectedItem {
HStack(alignment: .center, spacing: 3) {
Text(item.title)
}
}
}
Now I created a SecondView() and changed the MainView() content to this:
struct MainView: View {
#ObservedObject var model: MainViewModel
if let item = model.selectedItem {
SecondView(item: item)
}
}
Inside SecondView(), how can I access the item data so that I can use item.title inside SecondView() now?
In order to pass item to SecondView, declare item as a let property and then when you call it with SecondView(item: item), SecondView can refer to item.title.
Here is a complete example expanding on your code:
import SwiftUI
struct Item {
let title = "Test Title"
}
class MainViewModel: ObservableObject {
#Published var selectedItem: Item? = Item()
}
struct MainView: View {
#ObservedObject var model: MainViewModel
var body: some View {
if let item = model.selectedItem {
SecondView(item: item)
}
}
}
struct SecondView: View {
let item: Item
var body: some View {
Text(item.title)
}
}
struct ContentView: View {
#StateObject private var model = MainViewModel()
var body: some View {
MainView(model: model)
}
}

Content preview unable to load

Any idea why the preview doesn't work here??
When I use the Text field, the preview comes up with a following error:
MessageSendFailure: Message send failure for update
==================================
| MessageError: Connection interrupted
import SwiftUI
import UIKit
struct ContentView: View {
#EnvironmentObject var userSettings: UserSettings
#State var loginmode = false
#State private var showingAlert = false
#State var selectedTab = "Home"
#State var showMenu = false
#State var visible = false
#State var color = Color.black.opacity(0.7)
var body: some View {
TextField("Enter your email address", text: $userSettings.email)
}
}
struct RegisterView: View {
var body: some View {
Text("Hello World")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Content of another file:
import Foundation
import SwiftUI
import Combine
class UserSettings: ObservableObject
{
#Published var email = String()
}
The content of main file is as below:
import SwiftUI
#main
struct kokoburraApp: App {
#StateObject var userSettings = UserSettings()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(userSettings)
}
}
}
Previews do not work without the necessary environment objects.
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(UserSettings())
}
}

Trouble getting EnvironmentObject to update the UI

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())
}
}

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()
}