import SwiftUI
struct ConditionalNavigationParentView: View {
@Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass?
@Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass?
var body: some View {
NavigationView {
// iPhone Portrait
if horizontalSizeClass == .compact && verticalSizeClass == .regular {
VStack {
Text("This is the Parent View.")
.foregroundColor(Color.black)
.font(.headline)
NavigationLink(destination: ConditionalNavigationChildView()) {
Text("Navigate to Child View.")
.font(.body)
}
}
}
// iPhone Landscape
if verticalSizeClass == .compact {
HStack {
Text("This is the Parent View.")
.foregroundColor(Color.black)
.font(.headline)
NavigationLink(destination: ConditionalNavigationChildView()) {
Text("Navigate to Child View.")
.font(.body)
}
}
}
}
}
}
struct ConditionalNavigationChildView: View {
var body: some View {
Text("This is the Child View.")
.font(.headline)
}
}In the above code, we have a Parent View and a Child View. The parent view holds a conditional title and navigation link. The child view holds a title. When you navigate to the child view, navigation works fine. However, once the condition that holds the navigation link in the parent view is no longer met (by rotating your device from portrait to landscape), the connection keeping the child and parent views navigatable breaks, and forces navigation back to the parent view. This appears to be because SwiftUI as it is now needs to redraw the components from the parent view. With the example above, one solution would be to make only the text condtional, and have the navgation link be held in both a vstack and hstack simultaneously, like this:import SwiftUI
struct ConditionalNavigationParentView: View {
@Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass?
@Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass?
var body: some View {
NavigationView {
VStack {
// iPhone Portrait
if horizontalSizeClass == .compact && verticalSizeClass == .regular {
Text("This is the Parent View.")
.foregroundColor(Color.black)
.font(.headline)
}
HStack {
// iPhone Landscape
if verticalSizeClass == .compact {
Text("This is the Parent View.")
.foregroundColor(Color.black)
.font(.headline)
}
NavigationLink(destination: ConditionalNavigationChildView()) {
Text("Navigate to Child View.")
.font(.body)
}
}
}
}
}
}
struct ConditionalNavigationChildView: View {
var body: some View {
Text("This is the Child View.")
.font(.headline)
}
}While this does fix the navigation issue in this case, other cases with multiple navigation links seem to be completely impossible. For example, say you replace the title with another navigation link. The second one will still work fine, as it is not conditional, but the first one, and all of its subviews, will get pushed back to the parent view on rotation.In UIKit, this would never be a problem, as conditional navigation doesn't push back to the parent view like that. So is this a SwiftUI bug? Or is it intended behavior? If it is intended behavior, what solution is there for a view with multiple conditional navigation links?
Post
Replies
Boosts
Views
Activity
So say we have a set of views, which uses an enum and Environment Objects in order to navigate globally, like the following (if copying remember to add .environmentObject(StatusNavigation()) to SceneDelegate)://
// ResetNavigation.swift
// SwiftUI Playground Project
//
// Created by O'Donnell, Troy(AWF) on 12/30/19.
// Copyright © 2019 O'Donnell, Troy(AWF). All rights reserved.
//
import SwiftUI
enum StatusViews {
case firstView
case secondView
case thirdView
case emptyView
}
final class StatusNavigation: ObservableObject {
@Published var statusNavigation: StatusViews = StatusViews.firstView
}
struct UniversalNavigation: View {
@EnvironmentObject var statusNavigation: StatusNavigation
var body: some View {
if statusNavigation.statusNavigation == .firstView {
return AnyView(FirstView())
}
else if statusNavigation.statusNavigation == .secondView {
return AnyView(SecondView())
}
else if statusNavigation.statusNavigation == .thirdView {
return AnyView(ThirdView())
}
else if statusNavigation.statusNavigation == .emptyView {
return AnyView(EmptyView())
}
else {
return AnyView(FirstView())
}
}
}
struct FirstView: View {
@EnvironmentObject var statusNavigation: StatusNavigation
var body: some View {
NavigationView {
VStack(spacing: 0) {
Text("First View")
.font(.title)
.fontWeight(.bold)
.foregroundColor(Color.black)
Text("Go to Second View")
.foregroundColor(Color.blue)
.onTapGesture {self.statusNavigation.statusNavigation = .secondView}
NavigationLink(destination: ChildView(idxView: "First")) {
Text("Navigate to Child View")
}
}
}
}
}
struct SecondView: View {
@EnvironmentObject var statusNavigation: StatusNavigation
var body: some View {
NavigationView {
VStack(spacing: 0) {
Text("Second View")
.font(.title)
.fontWeight(.bold)
.foregroundColor(Color.black)
Text("Go to Third View")
.foregroundColor(Color.blue)
.onTapGesture {self.statusNavigation.statusNavigation = .thirdView}
NavigationLink(destination: ChildView(idxView: "Second")) {
Text("Navigate to Child View")
}
}
}
}
}
struct ThirdView: View {
@EnvironmentObject var statusNavigation: StatusNavigation
var body: some View {
NavigationView {
VStack(spacing: 0) {
Text("Third View")
.font(.title)
.fontWeight(.bold)
.foregroundColor(Color.black)
Text("Go to First View")
.foregroundColor(Color.blue)
.onTapGesture {self.statusNavigation.statusNavigation = .firstView}
NavigationLink(destination: ChildView(idxView: "Third")) {
Text("Navigate to Child View")
}
}
}
}
}
struct ChildView: View {
var idxView: String
@EnvironmentObject var statusNavigation: StatusNavigation
var body: some View {
VStack(spacing: 0) {
Text("\(idxView) Child View")
.font(.title)
.fontWeight(.bold)
.foregroundColor(Color.black)
Text("Go back to First Parent View")
.foregroundColor(Color.blue)
.onTapGesture {self.statusNavigation.statusNavigation = .firstView}
Text("Go back to Second Parent View")
.foregroundColor(Color.blue)
.onTapGesture {self.statusNavigation.statusNavigation = .secondView}
Text("Go back to Third Parent View")
.foregroundColor(Color.blue)
.onTapGesture {self.statusNavigation.statusNavigation = .thirdView}
}
}
}As it is now, we can rotate between the three main views just fine, but I also want to be able to navigate back from the children (and more importantly, children of children!) in order to have a more dynamic and flexible navigation system. For example, if we added more children views to the already-existing children views, and set self.statusNavigation.statusNavigation to the first, second or third views, we can move back to programmer-defined views more easily. However, I have had one problem with this. Say we are in the first child view. We can navigate to the second and third parent view using Universal Navigation just fine, since we aren't in those places right now, but since the first child view is the child of the first parent view, setting the environment object to .firstView again doesn't change it, and thus doesn't update it. What I've done to fix that is by changing the ChildView slightly, like this:struct ChildView: View {
var idxView: String
@EnvironmentObject var statusNavigation: StatusNavigation
var body: some View {
VStack(spacing: 0) {
Text("\(idxView) Child View")
.font(.title)
.fontWeight(.bold)
.foregroundColor(Color.black)
Text("Go back to \(idxView) Parent View")
.foregroundColor(Color.blue)
.onTapGesture {self.statusNavigation.statusNavigation = .emptyView; DispatchQueue.main.asyncAfter(deadline: .now() + 0.001) {
self.statusNavigation.statusNavigation = self.changeDestination(idx: self.idxView)}}
}
}
func changeDestination(idx: String) -> StatusViews {
if idx == "First" {
return .firstView
}
else if idx == "Second" {
return .secondView
}
else if idx == "Third" {
return .thirdView
}
else {
return .firstView
}
}
}Now while this works, A. It's kinda hacky, with using the DispatchMainQueue, and B. Delays aren't a great solution as it will eat up the phone's memory really quick. My questions is this: Is there some function we can call in between the first and second variable change, to make SwiftUI recognize the first change before it sees the second one?
So currently what I have been doing for determining what device type and orientation I am in for SwiftUI is using sizeClasses, usually something like the following:struct SizeClassView: View {
@Environment(\.verticalSizeClass) var verticalSizeClass: UserInterfaceSizeClass?
@Environment(\.horizontalSizeClass) var horizontalSizeClass: UserInterfaceSizeClass?
var body: some View {
if horizontalSizeClass == .compact && verticalSizeClass == .regular {
Text("iPhone Portrait")
}
else if horizontalSizeClass == .regular && verticalSizeClass == .compact {
Text("iPhone Landscape")
}
else if horizontalSizeClass == .regular && verticalSizeClass == .regular {
Text("iPad Portrait/Landscape")
}
}
}What I'd like to know is: Is there any better way to do this in SwiftUI? The main problem I'm having with this method is that I can't differentiate between iPad Portrait and Landscape, but at least I can use it to differentiate between iPhone Portrait and iPhone Landscape and iPad... Any help and insight is greatly appreciated!
I have a SwiftUI application in development, and for most screens, I'm fine with them being either landscape or portrait, and making designs for each orientation. However, for some screens, I would like to only allow the portrait orientation, and a few in only landscape. Essentially, something like the following StackOverflow post, but for SwiftUI:https://stackoverflow.com/questions/25606442/how-to-lock-portrait-orientation-for-only-main-view-using-swiftAnything like this for SwiftUI yet?