SwiftUI incorrect navigation behavior in iOS 14.2 - swiftui

The view navigation hierarchy of my code is as follows:
ColorsView
WarmColorsView
RedView
CoolColorsView
Which is ColorsView can navigate directly to WarmColorsView and CoolColorView, and WarmColorsView can navigate directly to RedView.
Here is code (very simple):
import SwiftUI
class Model: ObservableObject {
#Published var tagNavToWarmOrCool: String?
#Published var isNavToRed = false
}
struct RedView: View {
#EnvironmentObject var model: Model
var body: some View {
VStack {
Button("to cool colors"){
model.isNavToRed = false
model.tagNavToWarmOrCool = "cool"
}
}
}
}
struct CoolColorsView: View {
var body: some View {
VStack {
}
.navigationTitle("Cool Colors")
}
}
struct WarmColorsView: View {
#EnvironmentObject var model: Model
var body: some View {
NavigationLink("red", destination: RedView(), isActive: $model.isNavToRed)
.navigationTitle("Warm Colors")
}
}
struct ColorsView: View {
#EnvironmentObject var model: Model
var body: some View {
VStack {
NavigationLink("to warm colors", destination: WarmColorsView(), tag: "warm", selection: $model.tagNavToWarmOrCool)
NavigationLink("to cool colors", destination: CoolColorsView(), tag: "cool", selection: $model.tagNavToWarmOrCool)
}
.navigationTitle("Colors")
}
}
struct ContentView: View {
#StateObject var model = Model()
var body: some View {
NavigationView {
ColorsView()
}
.navigationViewStyle(StackNavigationViewStyle())
.environmentObject(model)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
.environmentObject(Model())
}
}
My intention is to go to the RedView, and then click the button to navigate to the CoolColorsView.
Running in iOS 14.2, however it ends up navigating to the ColorsView, I tried to change NavigationView's style to default, but it didn't work.
There is no such problem in iOS 15.4.1!
So how to navigate from RedView to CoolColorsView by click button in RedView in iOS 14.2? Thanks a lot! :)

Related

Two Column Navigation macOS SwiftUI

I am building a macOS SwiftUI app in which I need to perform navigation from my View A to View B. Here is a small screenshot.
Here is my implementation:
struct ViewA: View {
var body: some View {
VStack {
Text("VIEW A")
Button("Go to View B") {
// I want to go to View B and also pass some data to View B
}
}
}
}
struct ViewB: View {
let data: String
var body: some View {
VStack {
Text("VIEW B")
}
}
}
struct ContentView: View {
#EnvironmentObject var appState: AppState
var body: some View {
NavigationView {
SideBar()
ViewA()
}
}
}
In iOS I would use NavigationLink, but for macOS apps NavigationLink is not working as expected. Any ideas?

How can I navigate to a detail view of an item by using an #EnvironmentObject to route the views?

I have the following code in SwiftUI. I am expecting it to navigate from the list view to the PetView() with the proper name showing when tapping on one of the items in the ForEach loop or the button that says "Go to first pet". However, when I tap on an item or the button, the app doesn't do anything. What am I doing wrong? Thank you for your help!
import SwiftUI
#main
struct TestListAppApp: App {
var body: some Scene {
WindowGroup {
ContentView().environmentObject(ViewRouter())
}
}
}
import SwiftUI
struct ContentView: View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
ForEach(viewRouter.pets) { pet in
NavigationLink(
destination: PetView(),
tag: pet,
selection: $viewRouter.selectedPet,
label: {
Text(pet.name)
}
)
}
Button("Go to first pet.") {
viewRouter.selectedPet = viewRouter.pets[0]
}
}
}
import Foundation
class ViewRouter: ObservableObject {
#Published var selectedPet: Pet? = nil
#Published var pets: [Pet] = [Pet(name: "Louie"), Pet(name: "Fred"), Pet(name: "Stanley")]
}
import SwiftUI
struct PetView: View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
Text(viewRouter.selectedPet!.name)
}
}
import Foundation
struct Pet: Identifiable, Hashable {
var name: String
var id: String { name }
}
try this:
#main
struct TestListAppApp: App {
#StateObject var viewRouter = ViewRouter()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(viewRouter)
}
}
}
struct PetView: View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
if let pet = viewRouter.selectedPet {
Text(pet.name)
} else {
EmptyView()
}
}
}
struct ContentView: View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
NavigationView {
List {
ForEach(viewRouter.pets) { pet in
NavigationLink(destination: PetView(),
tag: pet,
selection: $viewRouter.selectedPet,
label: {
Text(pet.name)
})
}
Button("Go to first pet.") {
viewRouter.selectedPet = viewRouter.pets[0]
}
}
}
}
}

How can I use a common variable in a Tab VIew?

I'm currently developing an application using SwiftUI.
This app has 3 structs
①ContentView
②FirstView
③SecondView
These 3 structs do page transition in Tab View.
And this app has a common variable type of Bool using ObservableObject annotation.
I want to change to appear and disappear Text View in the FirstView and the SecondView depends on the condition of the variable, but the FirstView doesn't change a view as I expected...
How can I solve this situation?
Here are the codes:
ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
FirstView()
.tabItem {
Text("First")
}.tag(1)
SecondView()
.tabItem {
Text("Second")
}.tag(2)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
FirstView.swift
import SwiftUI
struct FirstView: View {
#ObservedObject var firstCheck: ViewModel = ViewModel()
var body: some View {
VStack{
if firstCheck.check == true{
Text("checked")
}
}
}
}
struct FirstView_Previews: PreviewProvider {
static var previews: some View {
FirstView()
}
}
SecondView.swift
import SwiftUI
struct SecondView: View {
#ObservedObject var secondCheck = ViewModel()
var body: some View {
VStack{
Toggle(
isOn: $secondCheck.check
){
Text("change")
}
if self.secondCheck.check == true{
Text("checked")
}
}
}
}
struct SecondView_Previews: PreviewProvider {
static var previews: some View {
SecondView()
}
}
ViewModel.swift
import Foundation
final class ViewModel: ObservableObject {
#Published var check: Bool = false
}
Xcode: Version 11.7
Keep object in one place, can be parent view
struct ContentView: View {
#ObservedObject var viewModel = ViewModel()
// #StateObject var viewModel = ViewModel() // SwiftUI 2.0
var body: some View {
TabView {
// .. other code here
}
.environmentObject(viewModel) // << inject here
}
}
and then use in both views like (for second the same)
struct FirstView: View {
#EnvironmentObject var firstCheck: ViewModel // declare only
// will be injected by type
var body: some View {
VStack{
if firstCheck.check == true{
Text("checked")
}
}
}
}

SwiftUI: How can I pop to the root view for watchOS?

I need to pop to the root view from a deep detail view. And while the following solution using isDetailLink and isActive works quite well for iOS, it does not work for watchOS. The isDetailLink command is unavailable in watchOS.
'isDetailLink' is unavailable in watchOS
import SwiftUI
class AppState : ObservableObject {
#Published var showState : Bool = false
}
struct MoreTests: View {
#EnvironmentObject var appState : AppState // injected from SceneDelegate
var body: some View {
NavigationView {
NavigationLink(
destination: MoreView1(),
isActive: $appState.showState, // required to work
label: { Text("Go to MoreView1") }
).isDetailLink(false) // required to work
}.navigationBarTitle("Root")
}
}
struct MoreView1: View {
var body: some View {
NavigationLink(
destination: MoreView2(),
label: { Text("Go to MoreView2") }
)
.navigationBarTitle("MoreView1")
}
}
struct MoreView2: View {
var body: some View {
NavigationLink(
destination: MoreView3(),
label: { Text("Go to MoreView3") }
)
.navigationBarTitle("MoreView2")
}
}
struct MoreView3: View {
#Environment(\.presentationMode) var presentationMode
#EnvironmentObject var appState : AppState
var body: some View {
VStack {
Button(action: {
self.appState.showState = false // required
}) {
Text("Dismiss to root")
}
}.navigationBarTitle("MoreView3")
}
}
The iOS solution came from How can I pop to the Root view using SwiftUI?.
An alternative is to (re)set the id of the NavigationView
Replace AppState with
class AppState : ObservableObject {
#Published var appID = UUID()
}
In MoreTests remove the isActive parameter and add this modifier right before setting the navigationBarTitle
NavigationView {...
}
.id(appState.appID)
In MoreView3 assign a new UUID to the appID
Button(action: {
self.appState.appID = UUID()
}) {
Text("Dismiss to root")
}
Fixed with Xcode 13.4 / watchOS 8.5
Create Project from template.
Just copy-pasted above code
Commented .isDetailLink(false)
Use MoreTests as content of scene
Build & Run

A View.environmentObject(_:) for may be missing as an ancestor of this view

I just updated to Xcode 11.4 and it's broken my code. I am storing some user settings in an ObservableObject as follows:
class UserSettings: ObservableObject {
#Published var cardOrder = UserDefaults.standard.integer(forKey: "Card Order")
#Published var cardTheme = UserDefaults.standard.integer(forKey: "Card Theme")
#Published var translation = UserDefaults.standard.integer(forKey: "Translation")
#Published var overdueFirst = UserDefaults.standard.bool(forKey: "Overdue First")
#Published var randomNum = 0
}
This is my main menu, the settings environment object is successfully passed down to the Settings view where I'm able to save and retrieve user selections.
struct ContentView: View {
#State var settings = UserSettings()
var body: some View {
SubView().environmentObject(settings)
}
}
struct SubView: View {
#EnvironmentObject var settings: UserSettings
var body: some View {
List {
NavigationLink (destination: Flashcard()){
HStack {
Image(systemName: "rectangle.on.rectangle.angled")
Text(verbatim: "Study")
}
}
NavigationLink (destination: Settings()) {
HStack {
Image(systemName: "gear")
Text(verbatim: "Settings")
}
}
}
}
}
But in my flashcard view, I am getting an error: Fatal error: No ObservableObject of type UserSettings found. A View.environmentObject(_:) for UserSettings may be missing as an ancestor of this view.: file SwiftUI, line 0
The error is on line 13 where I initiate Frontside. In the original code, I just called the Frontside subview, but I thought to solve the error I had to add .environmentObject(settings), but even after adding it my app compiles but crashes as soon I go to the Flashcard view.
struct Flashcard: View {
#EnvironmentObject var settings: UserSettings
#State var colour = UserDefaults.standard.integer(forKey: "Card Theme") * 6
#State private var showResults: Bool = false
#State private var fullRotation: Bool = false
#State private var showNextCard: Bool = false
var body: some View {
let zstack = ZStack {
Frontside(id: $settings.randomNum, sheet: $showingSheet, rotate: $fullRotation, invis: $showNextCard, col: $colour).environmentObject(self.settings)
//
Backside(id: $settings.randomNum, sheet: $showingSheet, bookmark: $bookmarked, results: $showResults, rotate: $fullRotation, invis: $showNextCard, col: $colour, trans: $translation).environmentObject(self.settings)
//
}
}
Does anyone know what I'm doing wrong? This code compiled and ran fine in the previous Xcode.
I think you should pass settings object to FlashCard and Settings as well.
try this:
struct ContentView: View {
#State var settings = UserSettings()
var body: some View {
SubView().environmentObject(settings)
}
}
struct SubView: View {
#EnvironmentObject var settings: UserSettings
var body: some View {
List {
NavigationLink (destination: Flashcard().environmentObject(settings)){
HStack {
Image(systemName: "rectangle.on.rectangle.angled")
Text(verbatim: "Study")
}
}
NavigationLink (destination: Settings().environmentObject(settings)) {
HStack {
Image(systemName: "gear")
Text(verbatim: "Settings")
}
}
}
}
}
An #EnvironmentObject has to be filled with an #StateObject, an #ObservedObject or an ObservableObject directly NOT an #State
struct ContentView: View {
//#ObservedObject
#StateObject var settings = UserSettings()
var body: some View {
SubView().environmentObject(settings)
}
}
Note: UserSettings has to be an ObservableObject
Apple documentation on managing model data
struct BookReader: App {
#StateObject var library = Library()
var body: some Scene {
WindowGroup {
LibraryView()
.environmentObject(library)
}
}
}
struct LibraryView: View {
#EnvironmentObject var library: Library
// ...
}
I'm running iOS 14.3 in the simulator and in my case the error was about my environmentObject NavigationController. It was resolved by modifying ContentView() with .environmentObject(NavigationController()) in the SceneDelegate and, if you want the preview to work, also in ContentView_Previews.
import SwiftUI
#main
// there is a file with the name of your "projectApp" (JuegosSwiftUIApp in my case)
struct JuegosSwiftUIApp: App {
var body: some Scene {
WindowGroup {
DatosIniciales() // any view
.environmentObject(Datos()) // this solved it (Datos() is class type Observableobject)
}
}
}