Reputation: 615
I wish to add/present an onboarding screen/walkthrough after user's have signed up. However, I'm already using .environmentObject()
in my SceneDelegate
. The reason being is I wish to check the Auth state of a user (I'm using FireStore).
So this is my SceneDelegate:
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: InitialView().environmentObject(SessionStore()))
self.window = window
window.makeKeyAndVisible()
}
This is my InitialView:
struct InitialView: View {
@EnvironmentObject var session: SessionStore
func listen() {
session.listenAuthenticationState()
}
var body: some View {
Group {
if session.isLoggedIn {
MainView()
} else {
SignInView()
}
}.onAppear(perform: listen)
}
}
My SignInView()
is pretty self explanatory. And my MainView()
is essentially my TabView
struct MainView: View {
var body: some View {
ZStack() {
Color(SYSTEM_BACKGROUND_COLOUR)
.edgesIgnoringSafeArea(.all)
TabView{
HomeView().tabItem({
Image(systemName: "house.fill")
})
DiscoverSearchView().tabItem({
Image(systemName: "magnifyingglass")
})
CameraView().tabItem({
Image(systemName: "camera.fill")
})
ActivityView().tabItem({
Image(systemName: "heart.fill")
})
ProfileView().tabItem({
Image(systemName: "person.crop.circle")
})
}.accentColor(Color(SYSTEM_ACCENT_COLOUR))
}
}
}
So right now I have an OnboardingView, but I am not too sure how to present it:
struct OnboardingContentView: View {
@State private var step = 1
var body: some View {
ZStack {
Color(SYSTEM_BACKGROUND_COLOUR)
.edgesIgnoringSafeArea(.all)
VStack {
Text("Welcome to").font(.caption).foregroundColor(Color(SYSTEM_FONT_COLOUR))
.padding(.top)
Text("Anexis").bold().font(.largeTitle).foregroundColor(Color(SYSTEM_FONT_COLOUR))
GeometryReader { gr in
HStack {
VStack(spacing: 40) {
Image("logo")
Text("1")
.padding()
.animation(Animation.interpolatingSpring(stiffness: 40, damping: 7).delay(0.1))
}.frame(width: gr.frame(in: .global).width)
VStack(spacing: 40) {
Image("logo")
Text("2")
.padding().fixedSize(horizontal: false, vertical: true)
.animation(Animation.interpolatingSpring(stiffness: 40, damping: 7).delay(0.1))
}.frame(width: gr.frame(in: .global).width)
VStack(spacing: 40) {
Image("logo")
Text("3")
.padding().fixedSize(horizontal: false, vertical: true)
.animation(Animation.interpolatingSpring(stiffness: 40, damping: 7).delay(0.1))
}.frame(width: gr.frame(in: .global).width)
}.multilineTextAlignment(.center)
.foregroundColor(Color(SYSTEM_FONT_COLOUR)).font(.title).padding(.vertical, 60).frame(width: gr.frame(in: .global).width * 3)
.frame(maxHeight: .infinity)
.offset(x: self.step == 1 ? gr.frame(in: .global).width : self.step == 2 ? 0 : -gr.frame(in: .global).width)
.animation(Animation.interpolatingSpring(stiffness: 40, damping: 8))
}
HStack(spacing: 20) {
Button(action: {self.step = 1}) {
Image(systemName: "1.circle")
.padding().scaleEffect(self.step == 1 ? 1 : 0.65)
}
Button(action: {self.step = 2}) {
Image(systemName: "2.circle")
.padding().scaleEffect(self.step == 2 ? 1 : 0.65)
}
Button(action: {self.step = 3}) {
Image(systemName: "3.circle")
.padding().scaleEffect(self.step == 3 ? 1 : 0.65)
}
}
.animation(.spring(response: 0.4, dampingFraction: 0.5)).font(.largeTitle).accentColor(Color(SYSTEM_ACCENT_COLOUR))
Button(action: {
NavigationView {
HomeView()
}
}) {
HStack {
Text("Continue")
Image(systemName: "chevron.right")
}.padding(.horizontal)
.padding().background(Capsule().fill(Color(SYSTEM_ACCENT_COLOUR))).accentColor(Color(SYSTEM_BACKGROUND_COLOUR)).opacity(step == 3 ? 1 : 0)
.animation(.none).scaleEffect(step == 3 ? 1 : 0.01).animation(Animation.interpolatingSpring(stiffness: 50, damping: 10, initialVelocity: 10))
}
}.padding()
}
}
}
I've tried to add another .environmentObject()
to my SceneDelegate but I get errors. So I thought I can use UserDefaults
but I get an error
No ObservableObject of type ViewRouter found
So how could I go about checking if the user has installed the app for the first time, present the onboarding view and also how would I dismiss the onboarding view? Use a NavigationLink
to my HomeView
when the Continue
button is clicked?
Edit :
class ViewRouter: ObservableObject {
@Published var currentPage: String
init() {
if !UserDefaults.standard.bool(forKey: "didLaunchBefore") {
UserDefaults.standard.set(true, forKey: "didLaunchBefore" )
currentPage = "onboardingView"
} else {
currentPage = "homeView"
}
}
}
Upvotes: 1
Views: 908
Reputation: 54466
You can create a top level view used for routing - displaying views conditionally:
class ViewRouter: ObservableObject {
@Published var currentPage = "onboardingView"
init() {
if !UserDefaults.standard.bool(forKey: "didLaunchBefore") {
UserDefaults.standard.set(true, forKey: "didLaunchBefore")
currentPage = "onboardingView"
} else {
currentPage = "initialView"
}
}
}
struct RoutingView: View {
@EnvironmentObject private var viewRouter: ViewRouter
var body: some View {
VStack {
if viewRouter.currentPage == "onboardingView" {
OnboardingView()
} else {
InitialView()
}
}
}
}
struct OnboardingView: View {
@EnvironmentObject private var viewRouter: ViewRouter
var body: some View {
VStack {
Text("OnboardingView")
Button("Continue") {
self.viewRouter.currentPage = "initialView"
}
}
}
}
You also need to replace the root view with:
RoutingView()
.environmentObject(SessionStore())
.environmentObject(ViewRouter())
Note: I assumed you want to present the InitialView and not the HomeView (as the user may not be logged in). If that's not true you can easily replace InitialView
with HomeView
.
Upvotes: 2