Push, pop view controller equivalent in SwiftUI - swift

What's equivalent to the Push and Pop of a view controller in SwiftUI?

Root :
window.rootViewController = UIHostingController(rootView: ContentView().environmentObject(Model()))
iOS version 13.1 :
class Model: ObservableObject {
#Published var pushed = false
}
struct ContentView: View {
#EnvironmentObject var model: Model
var body: some View {
NavigationView {
VStack {
Button("Push") {
self.model.pushed = true
}
NavigationLink(destination: DetailView(), isActive: $model.pushed) { EmptyView() }
}
}
}
}
struct DetailView: View {
#EnvironmentObject var model: Model
var body: some View {
Button("Bring me Back") {
self.model.pushed = false
}
}
}
Removing the default back button and adding our own will let us get through, until the bug gets fixed by Apple.
class Model: ObservableObject {
#Published var pushed = false
}
struct ContentView: View {
#EnvironmentObject var model: Model
var body: some View {
NavigationView {
VStack {
Button("Push") {
self.model.pushed = true
}
NavigationLink(destination: DetailView(), isActive: $model.pushed) { EmptyView() }
}
}
}
}
struct DetailView: View {
#EnvironmentObject var model: Model
var body: some View {
Button("Bring me Back") {
self.model.pushed = false
}
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: MyBackButton(label: "Back!") {
self.model.pushed = false
})
}
}
struct MyBackButton: View {
let label: String
let closure: () -> ()
var body: some View {
Button(action: { self.closure() }) {
HStack {
Image(systemName: "chevron.left")
Text(label)
}
}
}
}
more to refer

Related

ConfirmationDialog cancel bug in swiftui

When I jump to the settings page and click Cancel in the pop-up dialog box, it will automatically return to the home page. How can I avoid this problem?
import SwiftUI
struct ContentView: View {
#State private var settingActive: Bool = false
var body: some View {
NavigationView {
TabView {
VStack {
NavigationLink(destination: SettingView(), isActive: $settingActive) {
EmptyView()
}
Button {
settingActive.toggle()
} label: {
Text("Setting")
}
}
.padding()
}
}
}
}
struct SettingView: View {
#State private var logoutActive: Bool = false
var body: some View {
VStack {
Button {
logoutActive.toggle()
} label: {
Text("Logout")
}
.confirmationDialog("Logout", isPresented: $logoutActive) {
Button("Logout", role: .destructive) {
}
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
This seems to be an issue with using TabView inside NavigationView.
You can solve this by moving TabView to be your top level object (where it really should be), or replacing NavigationView with the new NavigationStack.
Here's an implementation that also removes the deprecated NavigationLink method:
enum Router {
case settings
}
struct ContentView: View {
#State private var path = NavigationPath()
var body: some View {
TabView {
NavigationStack(path: $path) {
VStack {
Button {
path.append(Router.settings)
} label: {
Text("Setting")
}
}
.navigationDestination(for: Router.self) { router in
switch router {
case .settings:
SettingView(path: $path)
}
}
.padding()
}
}
}
}
struct SettingView: View {
#Binding var path: NavigationPath
#State private var logoutActive: Bool = false
var body: some View {
VStack {
Button {
logoutActive = true
} label: {
Text("Logout")
}
.confirmationDialog("Logout", isPresented: $logoutActive) {
Button("Logout", role: .destructive) {
}
}
}
}
}

Save onboarding in storage using Swift

So I need to utilize app storage to save the state of onboarding, but I can't seem to figure it out with a #Published variable which I have, so I wanted to reach out and see if anyone know what I can do to switch things up.
So here is the code:
class MainViewModel: ObservableObject {
#Published var showOnboarding = true
}
#main
struct MapGlider: App {
#StateObject private var model = MainViewModel()
var body: some Scene {
WindowGroup {
MainScreenContainer(
model: model,
showOnboarding: $model.showOnboarding
)
}
}
}
struct MainScreenContainer: View {
#ObservedObject var model: MainViewModel
#Binding var showOnboarding: Bool
var body: some View {
if showOnboarding {
OnboardingView(
model: model,
showOnboarding: $model.showOnboarding
)
} else {
MainView(model: model)
}
}
}
struct OnboardingView: View {
#Binding var showOnboarding: Bool
var body: some View {
NavigationStack {
ZStack(alignment: .top) {
VStack {
LazyVGrid(columns: [GridItem(), GridItem(), GridItem(alignment: .topTrailing)], content: {
Button(action: {
showOnboarding = false
}, label: {
Image(systemName: "xmark")
})
})
}
}
}
}
}
I want to be able to click on the button inside OnboardingView and then set the app storage of showOnboarding to false, so next time the app runs, it can check MainScreenContainer and go directly to MainView if the storage is set to false.
In your use case i would actually use #AppStorage property wrapper link which is a wrapper over UserDefaults, and i would get rid of that ViewModel. The code would look something like this:
struct MainScreenContainer: View {
#AppStorage("show_onboarding") var showOnboarding: Bool = false
var body: some View {
if showOnboarding {
OnboardingView(
model: model
)
} else {
MainView(model: model)
}
}
}
struct OnboardingView: View {
#AppStorage("show_onboarding") var showOnboarding: Bool = false
var body: some View {
NavigationStack {
ZStack(alignment: .top) {
VStack {
LazyVGrid(columns: [GridItem(), GridItem(), GridItem(alignment: .topTrailing)], content: {
Button(action: {
showOnboarding = false
}, label: {
Image(systemName: "xmark")
})
})
}
}
}
}
}

Navigate to tabview's subview from another tabview's subview

I have:
TabView {
ViewA()
ViewB()
}
In ViewA I have a SubViewA() and
In ViewB I have a SubViewB()
How do I navigate from SubViewA() to SubViewB() using NavigationLink (or other solution) ? and have SubViewB() a back button that navigates to ViewB()
This is what I have so far:
ViewA()
struct ViewA: View {
NavigationView {
NavigationLink(destination: SubViewA()){}
}
}
SubViewA()
struct SubViewA: View {
NavigationLink(destination: SubViewB()){}
.isDetailLink(false)
}
In SubViewA:
NavigationLink(destination: ViewB(toSubView: true)) {
Text("Navigate")
}
In ViewB:
struct ViewB: View {
#State var toSubView: Bool
var body: some View {
//Content...
NabigationLink(destination: SubViewB(), isActive: $toSubView) {
}.hidden()
}
}
Or;
Now for multiple view just add your view to the Master Enum and the subviews to the Sub Enum. And for navigating use the navigate function with or without presentationMode.wrappedValue.dismiss(). Preview
struct SubB: View {
#Environment(\.presentationMode) var presentationMode
#EnvironmentObject var navigation: Navigation
var body: some View {
VStack {
Button(action: {
withAnimation() {
navigation.navigate(to: .subViewC)
presentationMode.wrappedValue.dismiss()
}
}) {
Text("Sub View C")
}
}.navigationBarTitle(Text("Sub B"))
}
}
struct SubA: View {
#EnvironmentObject var navigation: Navigation
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
Button(action: {
withAnimation() {
navigation.navigate(to: .subViewB)
presentationMode.wrappedValue.dismiss()
}
}) {
Text("Sub View B")
}
}.navigationBarTitle(Text("Sub A"))
}
}
struct SubC: View {
#EnvironmentObject var navigation: Navigation
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack {
Button(action: {
withAnimation() {
navigation.navigate(to: .subViewA)
presentationMode.wrappedValue.dismiss()
}
}) {
Text("Sub View A")
}
}.navigationBarTitle(Text("Sub C"))
}
}
struct C: View {
#EnvironmentObject var navigation: Navigation
#Environment(\.presentationMode) var presentationMode
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: SubC(), tag: Sub.subViewC, selection: $navigation.subView) {
Text("Sub View C")
}
}.navigationBarTitle(Text("View C"))
}
}
}
struct A: View {
#EnvironmentObject var navigation: Navigation
var body: some View {
NavigationView {
VStack {
Text("A")
NavigationLink(destination: SubA(), tag: Sub.subViewA, selection: $navigation.subView) {
Text("SubView A")
}
}.navigationBarTitle(Text("View A"))
}
}
}
struct B: View {
#EnvironmentObject var navigation: Navigation
var body: some View {
NavigationView {
VStack {
Text("B")
NavigationLink(destination: SubB(), tag: Sub.subViewB, selection: $navigation.subView) {
Text("Sub B")
}
}.navigationBarTitle(Text("View B"))
}
}
}
struct Root: View {
#EnvironmentObject var navigation: Navigation
var body: some View {
TabView(selection: $navigation.selected.int) {
A()
.tabItem {
Image(systemName: "capsule.portrait.fill")
Text("A")
}.tag(Master.viewA.rawValue)
B()
.tabItem {
Image(systemName: "capsule.fill")
Text("B")
}.tag(Master.viewB.rawValue)
C()
.tabItem {
Image(systemName: "capsule")
Text("C")
}.tag(Master.viewC.rawValue)
}
}
}
class Navigation: ObservableObject {
#Published var selected = Master.viewA
#Published var subView: Sub?
func navigate(to sub: Sub) {
withAnimation() {
guard let master = Master.init(rawValue: sub.int) else {return}
selected = master
subView = sub
}
}
}
enum Sub: Equatable {
case subViewA
case subViewB
case subViewC
var int: Int {
switch self {
case .subViewA = 0
case .subViewB = 1
case .subViewB = 2
//here when adding subsubsubViews, it's value must be equal to it's master view i.e subsubViewA = 0
}
}
}
enum Master: Int, Equatable {
case viewA = 0
case viewB = 1
case viewC = 2
var int: Int {
get {
switch self {
case .viewA:
return 0
case .viewB:
return 1
case .viewC:
return 2
}
}
set {
switch newValue {
case 0:
self = .viewA
case 1:
self = .viewB
case 2:
self = .viewC
default:
break
}
}
}
}

Swiftui connect command to value in main view

Does someone know how to connect commands to the rest of the project?
For example: I want to toggle the AddNew variable in the content view to show the add new item sheet by using the command.
struct SampleApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.commands {
CommandGroup(after: CommandGroupPlacement.newItem) {
Button("Add new", action: {
self.AddNew.toggle() // should toggle variable in content View
})
}
}
}
}
struct ContentView: View {
#State var AddNew = false
var body: some View {
Button(action: {
self.AddNew.toggle()
}) {
Text("Show Detail")
}.sheet(isPresented: $AddNew) {
AddNew(dimiss: $AddNew)
}
}
}
A solution could be to have a #Published var in a class conforming to ObservableObject.
You would toggle the boolean in the class and access it from wherever you want (as an #EnvironmentObject for example).
Like this:
class AppModel: ObservableObject {
#Published var addNew: Bool = false
}
struct SampleApp: App {
#ObservedObject var model = AppModel()
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(model)
}
.commands {
CommandGroup(after: CommandGroupPlacement.newItem) {
Button("Add new", action: {
self.model.addNew.toggle()
})
}
}
}
}
struct ContentView: View {
#EnvironmentObject var model: AppModel
var body: some View {
Button(action: {
self.model.addNew.toggle()
}) {
Text("Show Detail")
}.sheet(isPresented: $model.addNew) {
AddNew(dimiss: $model.addNew)
}
}
}

SwiftUI Conditional View Transitions are not working

Consider the following code:
class ApplicationHostingView: ObservableObject {
#Published var value: Bool
}
struct ApplicationHostingView: View {
// view model env obj
var body: some View {
Group {
if applicationHostingViewModel.value {
LoginView()
.transition(.move(edge: .leading)) // <<<< Transition for Login View
} else {
IntroView()
}
}
}
}
struct IntroView: View {
// view model env obj
var body: some View {
Button(action: { applicationHostingViewModel.value = true }) {
Text("Continue")
}
}
}
struct LoginView: View {
var body: some View {
Text("Hello World")
}
}
ISSUE
In this case, I see my transition from IntroView to LoginView work fine except for any of the animations. Animations inside IntroView based on the conditionals seem to be working fine but transitions that change the entire screen don't seem to work.
change group to ZStack
add animation somewhere.
class ApplicationHostingViewModel: ObservableObject {
#Published var value: Bool = false
}
struct ApplicationHostingView: View {
// view model env obj
#ObservedObject var applicationHostingViewModel : ApplicationHostingViewModel
var body: some View {
ZStack {
if applicationHostingViewModel.value {
LoginView()
.transition(.move(edge: .leading))
} else {
IntroView(applicationHostingViewModel:applicationHostingViewModel)
}
}
}
}
struct IntroView: View {
// view model env obj
#ObservedObject var applicationHostingViewModel : ApplicationHostingViewModel
var body: some View {
Button(action: {
withAnimation(.default){
self.applicationHostingViewModel.value = true} }) {
Text("Continue")
}
}
}
struct LoginView: View {
var body: some View {
Text("Hello World").frame(maxWidth: .infinity, maxHeight: .infinity)
}
}