Onboarding screen at launch of SwiftUI document-based app - ios

Is there anyway at all, to show an onboarding screen at the launch of a SwiftUI document-based app, before the file picker screen appears?
What I can do now, is to show the onboarding screen AFTER user creates a new file, or opens an existing file, using UserDefaults:
import SwiftUI
struct ContentView: View {
#Binding var document: AnyDocument
#State var onboarding: Bool
var body: some View {
if onboarding {
OnBoarding(showOnboarding: $onboarding)
} else {
// Normal document view
...
}
}
}

Related

SwiftUI forcing individual views to different orientation for iPad

I am listening for existing orientation changes by using UIDevice.orientationDidChangeNotification, but is there a way to lock a SwiftUI View into a particular orientation?
I do not want to introduce AppDelegate.
Example:
import SwiftUI
struct TestView: View {
var text: String
var body: some View {
Text(text)
}
}

How to programmatically present a menu when single-tap a table view row, like in the iOS 16 Calendar app?

In the iOS 16 Calendar app, there is a new drop-down menu style for options like "repeat", when tapping any place of the row, a menu appeared. And there is a chevron up and chevron down icon at the right side of the table view cell.
How to do this in iOS 16? The context menu is triggered by long press, but this new style is by single-tap.
It seems this is SwiftUI only. I can't find changes in iOS 16 UIKit to achieve this. In SwiftUI:
import SwiftUI
struct SettingsView: View {
#State private var selectedFlavor: Flavor = .chocolate
var body: some View {
List {
Picker("Flavor", selection: $selectedFlavor) {
Text("Chocolate").tag(Flavor.chocolate)
Text("Vanilla").tag(Flavor.vanilla)
Text("Strawberry").tag(Flavor.strawberry)
}
}
}
}
struct SettingsView_Previews: PreviewProvider {
static var previews: some View {
SettingsView()
}
}
enum Flavor: String, CaseIterable, Identifiable {
case chocolate, vanilla, strawberry
var id: Self { self }
}

Using SwiftUI view in WatchKit and Storyboard

I'm building a watch app with WatchKit and storyboard.
I have a SwiftUI view that I want to use as a subview beside the other Storyboard items in a WKInterfaceController.
The SwiftUI view:
struct SliderView: View {
#State private var speed = 50.0
var body: some View {
CompactSlider(value: $speed, in: 0...100, step: 5) {
Text("Speed")
Spacer()
Text("\(Int(speed))")
}
}
}
This SwiftUI view lays just fine on a separate controller using WKHostingController, but this is not what i want.
class HostingController: WKHostingController<SliderView> {
override var body: SliderView {
return SliderView()
}
}
How can I use this SwiftUI view on Storyboard or WKInterfaceController?

SwiftUI Clear Navigation Stack

In my iOS 14 SwiftUI app, when user is not logged in, I make him go through a few setup screens before presenting the main logged in screen. At the last setup screen, I use a NavigationLink to present the main logged in screen. How do I clear the entire navigation stack such that the main logged in screen becomes the root / first screen in the navigation stack?
Out of curiosity I started thinking about a solution to clear everything down to my root view and remembered that setting the .id() of a view will force a reload.
I'm posting this as a discussion point in first place not as an actual answer since I'm interested in your opinion if this is a legit approach.
Code is kept to the minimum to demonstrate the general idea and I haven't considered any memory leaks yet
import SwiftUI
// MARK: - SessionManager
class SessionManager: ObservableObject {
var isLoggedIn: Bool = false {
didSet {
rootId = UUID()
}
}
#Published
var rootId: UUID = UUID()
}
// MARK: - ContentView
struct ContentView: View {
#ObservedObject
private var sessionManager = SessionManager()
var body: some View {
NavigationView {
NavigationLink("ContentViewTwo", destination: ContentViewTwo().environmentObject(sessionManager))
}.id(sessionManager.rootId)
}
}
// MARK: - ContentViewTwo
struct ContentViewTwo: View {
#EnvironmentObject
var sessionManager: SessionManager
var body: some View {
NavigationLink("ContentViewTwo", destination: ContentViewThree().environmentObject(sessionManager))
}
}
// MARK: - ContentViewThree
struct ContentViewThree: View {
#EnvironmentObject
var sessionManager: SessionManager
var body: some View {
NavigationLink("ContentViewThree", destination: ContentViewFour().environmentObject(sessionManager))
}
}
// MARK: - ContentViewFour
struct ContentViewFour: View {
#EnvironmentObject
var sessionManager: SessionManager
var body: some View {
Button(action: {
sessionManager.isLoggedIn.toggle()
}, label: {
Text("logout")
})
}
}
You can start with .sheet() or .fullScreenCover() on root / first screen,
and then stack with NaviagtionLink,
and at the end self.presentationMode.wrappedValue.dismiss()
Now that I am aware of .sheet() and .fullscreenCover(), I would prefer that answer, but being new to swiftUI this was my initial approach.
You could use a #State variable to control the behaviour. Create two navigation views, that are enclosed in a if condition based on your bool.
#State var showOnBoarding = true
if $showOnBoarding {
OnboardingNavigationView()
} else {
CoreNavigationView()
}
One with on-boarding links and the other containing your core navigation links. One for your onboarding and then one for logged in.
Each of your navigation links would also be initialized with the isActive Binding parameter.
struct OnboardingNavigationView: View {
var body: some View {
NavigationView {
NavigationLink("Step 1",destination: NextStepView(), isActive: $showOnBoarding)
//etc..
}
}
}
struct CoreNavigationView: View {
var body: some View {
NavigationView {
NavigationLink("Login Screen",destination: AccountView(), isActive: $showOnBoarding)
//etc..
}
}
}
Once the user logs in, you'd toggle that var. Each of your NavigationLinks would use the isActive property bound to showOnBoarding. So once logged in they won't be able go back to the inactive onboarding screens and will be in your 'new' navigation stack where your login screen is the root screen.
See section 'Presenting a Destination View with Programmatic Activation'
SwiftUI NavigationLink

How to present a view full-screen in SwiftUI?

I worked to a login view, now I want to present the view after login, but I do not want the user to have the possibility to return to the login view. In UIkit I used present(), but it seems in SwiftUI presentation(_ modal: Modal?) the view does not take the entire screen. Navigation also isn't an option.
Thank you!
I do not want the user to have the possibility to return to the login view
In that case you shouldn't be presenting away from the login view but replacing it entirely.
You could do this by conditionally building the login view or the "app view".
Something like this...
// create the full screen login view
struct LoginView: View {
// ...
}
//create the full screen app veiw
struct AppView: View {
// ...
}
// create the view that swaps between them
struct StartView: View {
#EnvironmentObject var isLoggedIn: Bool // you might not want to use this specifically.
var body: some View {
isLoggedIn ? AppView() : LoginView()
}
}
By using a pattern like this you are not presenting or navigating away from the login view but you are replacing it entirely so it is no longer in the view hierarchy at all.
This makes sure that the user cannot navigate back to the login screen.
Equally... by using an #EnvironmentObject like this you can edit it later (to sign out) and your app will automatically be taken back to the login screen.
struct ContentView: View {
#EnvironmentObject var userAuth: UserAuth
var body: some View {
if !userAuth.isLoggedin {
return AnyView(LoginView())
} else {
return AnyView(HomeView())
}
}
}
Encapsulate the body in a Group to eliminate compiler errors:
struct StartView: View {
#EnvironmentObject var userAuth: UserAuth
var body: some View {
Group {
if userAuth.isLoggedin {
AppView()
} else {
LoginView()
}
}
}
I made this extension for myself. Any feedback/ideas are welcome. :)
https://github.com/klemenkosir/SwiftUI-FullModal
Usage
struct ContentView: View {
#State var isPresented: Bool = false
var body: some View {
NavigationView {
Button(action: {
self.isPresented.toggle()
}) {
Text("Present")
}
.navigationBarTitle("Some title")
}
.present($isPresented, view: ModalView(isPresented: $isPresented))
}
}
struct ModalView: View {
#Binding var isPresented: Bool
var body: some View {
Button(action: {
self.isPresented.toggle()
}) {
Text("Dismiss")
}
}
}

Resources