first i have NavigationView and dont working swipe back, how in UIKit NavigationController, why?
Second, how to correctly create the navigation flow for my situation:
I have AuthView(), SelectedLanguageView() and OnboardingView().
At first start user show SelectedLanguageView(), next OnboardingView() and in the end AuthView()
User dont can with OnboardingView() return on SelectedLanguageView() and with AuthView() return AuthView()
Just NavigationLink dont working without NavigationView, why?
if this is not the first launch, and user not authorized, then show AuthView(), else if user authorize, then show MainView(),
MainView() include tab bar for 4 items, every item have NavigationView
#State private var navBarHidden = true
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
let languagSettings = UserLanguageSettings()
UserDefaults.standard.set(false, forKey: "isNotFirstLaunchApp")
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
if UserDefaults.standard.bool(forKey: "isNotFirstLaunchApp") == false {
window.rootViewController = UIHostingController(rootView: OnboardingView(navBarHidden: $navBarHidden).environmentObject(languagSettings))
} else {
window.rootViewController = UIHostingController(rootView: AuthorizationView(navBarHidden: $navBarHidden.not, isAddNavView: true).environmentObject(languagSettings))
}
self.window = window
window.makeKeyAndVisible()
}
}
condition if the user is authorized has not added yet
var isAddNavView: Bool = false
var body: some View {
if isAddNavBar {
return NavigationView {
ZStack {
VStack {
Text("")
}
}
}
} else {
return ZStack {
VStack {
Text("")
}
}
}
}
i have error, if return NavigationView, if delete NavigationView and return ZStack, then all right. I think there is a better solution.
ideas?
NavigationLink(destination: SignInView(), isActive: $isShowingSignInView) {
EmptyView()
}
Button(action: {
self.isShowingSignInView.toggle()
}) {
Text("isShowingSignInView")
}
all navigation links in the project i use like this
next - i tried the following:
in all project i delete all NavigationView and create modal show screens (.sheet)
in AuthorizationView() i have variables #State
Example, on AuthorizationView() i have
#State var isShowRegistration: Bool = false
and tap button Registration showing flow Registration include 4 screens which are shown with .sheet
RegistrationView(registrationView: $isShowRegistration)
//screens below include #Binding var registrationView: Bool
VerificationView(registrationView: $registrationView)
SetupPasswordView(registrationView: $registrationView)
EndRegistrationViewAndAuthorization(registrationView: $registrationView)
on EndRegistrationViewAndAuthorization i have button and in ideal me need closed all previous screens and call func Auth() Which will open me the main screen of the application with the tab bar each item of which includes NavigationView. Now my button action {self.registrationView.toogle()} return me RegistrationView() but should return to AuthorizationView()
i have error, if return NavigationView, if delete NavigationView and return ZStack, then all right. I think there is a better solution. ideas?
For SwiftUI 1.0 the following should work
var body: some View {
Group {
if isAddNavBar {
NavigationView {
ZStack {
VStack {
Text("")
}
}
}
} else {
ZStack {
VStack {
Text("")
}
}
}
}
}
Related
How can I accommodate user accessibility sizes with a bottomBar button?
With accessibility1 or larger user-configured in iOS, a bottomBar button fails to trigger. Smaller font sizes work. Configuring the toolbar placement to .navigation works.
Details: Navigate from ContentView > FirstView > SecondView, then back to the FirstView via the "Goodbye" bottomBar button displays the FirstView. Then FirstView's "Goodbye" button does not trigger. There is a user-workaround: in this situation, scroll the FirstView down to hide the Goodbye button, release, it returns, then press and it works. Code is below.
struct ContentView: View {
#State var showFirstView: Bool = false
var body: some View {
NavigationStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundColor(.accentColor)
Text("Hello, world!")
Button("First View") { showFirstView = true }
}
.sheet(isPresented: $showFirstView) {
FirstView()
}
}
struct FirstView: View {
#Environment(\.dismiss) var dismiss
#State var showSecondView: Bool = false
var body: some View {
NavigationStack {
VStack {
Text("First View")
Button("Second View") { showSecondView = true }
}
.toolbar {
// On return from SecondView with accessibility1
// or large text size configured, the button below
// does not trigger (even with a programmatically limited
// font size via .dynamicTypeSize).
// However, scroll the view down slightly to hide the button,
// let it return on screen, then the Goodbye button works.
ToolbarItem(placement: .bottomBar) {
Button("Goodbye") { dismiss() }
}
}
}
.sheet(isPresented: $showSecondView) {
SecondView()
}
}
struct SecondView: View {
#Environment(\.dismiss) var dismiss
var body: some View {
NavigationStack {
VStack { Text("Second View") }
.toolbar {
ToolbarItem(placement: .bottomBar) {
Button("Goodbye") { dismiss() }
}
}
}
}
The technique #Asperi shared in the post, StackOverflow Toolbar disappears when going back, resolves my problem. See Update (1), Update (2), and Update (3) below.
struct FirstView: View {
#Environment(\.dismiss) var dismiss
#State var showSecondView: Bool = false
#State private var refresh = UUID() // <- Update (1)
var body: some View {
NavigationStack {
VStack {
Text("First View")
Button("Second View") { showSecondView = true }
}
.toolbar {
// On return from SecondView with accessibility1
// or larger text size configured, the button below
// does not trigger (even with a programmatically limited
// font size via .dynamicTypeSize).
// However, scroll the view down slightly to hide the button,
// let it return on screen, then the Goodbye button works.
// Or, change placement to .navigation and it works as expected.
ToolbarItem(placement: .bottomBar) {
Button("Goodbye") { dismiss() }
}
}.id(refresh) // <- Update (2)
}
.sheet(isPresented: $showSecondView) {
SecondView()
.onDisappear { refresh = UUID() } // <- Update (3)
}
}
}
My app has a multilevel layout
AView -> BView -> CView -> DView.
I change
window.rootViewController
to BView in order to "pop" 2 top views but for some reason when i come back to BView it's NavigationLink is not clickable.
Any ideas on how to fix this? It seems like BView doesn't know that it became visible..
AView.swift:
import SwiftUI
struct AView: View {
init() {
print("AView init")
}
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: BView()) {
Text("This is View A, now go to View B.")
}
}
}
}
}
struct BView: View {
init() {
print("BView init")
}
var body: some View {
NavigationLink(destination: CView()) {
Text("This is View B, now go to View C.")
}
}
}
struct CView: View {
init() {
print("CView init")
}
var body: some View {
NavigationLink(destination: DView()) {
Text("This is View C, now go to View D.")
}
}
}
struct DView: View {
init() {
print("DView init")
}
var body: some View {
Button(action: {
print("button pressed")
(UIApplication.shared.connectedScenes.first?.delegate as? SceneDelegate)?.toBView()
},
label: {
Text("Back!")
})
}
}
SceneDelegate.swift:
import UIKit
import SwiftUI
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Create the SwiftUI view that provides the window contents.
let aView = AView()
// Use a UIHostingController as window root view controller.
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: aView)
self.window = window
window.makeKeyAndVisible()
}
}
func toBView() {
let bView = BView()
window?.rootViewController = UIHostingController(rootView: bView)
}
}
Asperi already stated the problem.. you are setting BView as root level. Actually changing that, shouldn't be a big problem.
But back to your question why does the NavigationLink won't work anymore. Thats because ViewA, which contains the NavigationView is not in the RootLevel anymore. Hence you will need to provide a new NavigationView, but only and only when BView is the root view.
So inside BView, add a parameter isRootView, which you will set only to true when you call it from your SceneDelegate
struct BView: View {
init(isRootView: Bool = false) {
print("BView init")
self.isRootView = isRootView
}
var isRootView : Bool = false
var body: some View {
if isRootView {
NavigationView {
NavigationLink(destination: CView()) {
Text("This is View B, now go to View C.")
}
}
}
else
{
NavigationLink(destination: CView()) {
Text("This is View B, now go to View C.")
}
}
}
}
And here the call from SceneDelegate
func toBView() {
let bView = BView(isRootView: true)
window?.rootViewController = UIHostingController(rootView: bView)
}
I'm trying to add a NavigationLink at the top of the screen, but once I click it, it prompts me to the result and the Back button disappears.
SwiftUI Code:
NavigationView {
VStack {
NavigationLink (destination: Text("COOL")) {
Text("COOL")
}
Spacer()
}
.navigationBarHidden(true)
.navigationBarTitle(Text("Home"))
//.edgesIgnoringSafeArea([.top, .bottom])
}
The back button disappears after clicking on the NavigationLink: https://gyazo.com/9d39936c849f570a05687e41096ddeca
There is some glitch IMHO, when you use both .navigationBarHidden(true) and .navigationBarTitle(Text("Some text)). If you remove the last one, back button works as usual. Nevertheless I tried to return back button in your code snippet. It still has glitch while returning to first view, but back button don't disappear. I hope it will help and you will go further from here:
struct NotHiddenBackButton: View {
#State var hiddingNavBar = true
#State var goToSecondView = false
var body: some View {
NavigationView {
NavigationLink(destination: ViewWithBackButton(hiddingNavBar: $hiddingNavBar), isActive: $goToSecondView) {
VStack {
Text("COOL")
.onTapGesture {
self.hiddingNavBar = false
self.goToSecondView = true
}
Spacer()
}
}
.navigationBarHidden(hiddingNavBar)
.navigationBarTitle(Text("Home"))
}
}
}
struct ViewWithBackButton: View {
#Binding var hiddingNavBar: Bool
var body: some View {
Text("Second view")
.navigationBarTitle("Second view")
.onDisappear() {
self.hiddingNavBar = true
}
}
}
I believe this was a bug that has now been fixed in iOS 14
if I set a custom Back Button (which everyone wants, hiding the ugly text ;-) ) and using .navigationBarBackButtonHidden, the standard Swipe Back gesture on the navigation controller does not work. Is there a way to get this back and having a custom back button?
For Example:
NavigationView {
NavigationLink(destination: DummyViewer())
{
Text("Go to next view"
}
}
struct DummyViewer: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
Text("Hello, World!").navigationBarBackButtonHidden(true)
.navigationBarItems(leading:
Button(action: { self.presentationMode.wrappedValue.dismiss()}) {
Text("Custom go back")
}
)
}
}
If I do so, I cannot swipe back to the previous view, seems the gesture is then disabled... How to get it back?
BR
Steffen
Nothing I found about creating a custom NavigationView worked but I found that by extending UINavigationController I was able to have a custom back button and the swipe back gesture.
extension UINavigationController: UIGestureRecognizerDelegate {
override open func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = self
}
public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return viewControllers.count > 1
}
}
I would like to integrate the answer given by Nick Bellucci to make the code also works in other circumstances, e.g. when the child view of the NavigationView is a ScrollView, or a View that is listening for Drag gestures.
extension UINavigationController: UIGestureRecognizerDelegate {
override open func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = self
}
public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return viewControllers.count > 1
}
// To make it works also with ScrollView
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
true
}
}
I've just created a hack which will not animate view but it works
extension View {
func onBackSwipe(perform action: #escaping () -> Void) -> some View {
gesture(
DragGesture()
.onEnded({ value in
if value.startLocation.x < 50 && value.translation.width > 80 {
action()
}
})
)
}
}
Usage
struct TestView: View {
#Environment (\.presentationMode) var mode
var body: some View {
VStack {
Color.red
}
.onBackSwipe {
mode.wrappedValue.dismiss()
}
}
}
You can set the title to an empty string. So back bar button title will be empty:
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination: Text("Here you are")) {
Text("Next").navigationBarTitle("")
}
}
}
}
You can set the title onAppear or onDisappear if you need to.
If it's still actual, here I answered, how to set custom back button and save swipe back gesture.
I would like to be able to show a new view when a button is pressed on one of my views.
From the tutorials I have looked at and other answered questions here it seems like everyone is using navigation button within a navigation view, unless im mistaken navigation view is the one that gives me a menu bar right arrows the top of my app so I don't want that. when I put the navigation button in my view that wasn't a child of NavigationView it was just disabled on the UI and I couldn't click it, so I guess I cant use that.
The other examples I have seen seem to use presentation links / buttons which seem to show a sort of pop over view.
Im just looking for how to click a regular button and show another a view full screen just like performing a segue used to in the old way of doing things.
Possible solutions
1.if you want to present on top of current view(ex: presentation style in UIKit)
struct ContentView: View {
#State var showingDetail = false
var body: some View {
Button(action: {
self.showingDetail.toggle()
}) {
Text("Show Detail")
}.sheet(isPresented: $showingDetail) {
DetailView()
}
}
}
2.if you want to reset current window scene stack(ex:after login show home screen)
Button(action: goHome) {
HStack(alignment: .center) {
Spacer()
Text("Login").foregroundColor(Color.white).bold()
Spacer()
}
}
func goHome() {
if let window = UIApplication.shared.windows.first {
window.rootViewController = UIHostingController(rootView: HomeScreen())
window.makeKeyAndVisible()
}
}
3.push new view (ex: list->detail, navigation controller of UIKit)
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DetailView()) {
Text("Show Detail View")
}.navigationBarTitle("Navigation")
}
}
}
}
4.update the current view based on #state property, (ex:show error message on login failure)
struct ContentView: View {
#State var error = true
var body: some View {
...
... //login email
.. //login password
if error {
Text("Failed to login")
}
}
}
For simple example you can use something like below
import SwiftUI
struct ExampleFlag : View {
#State var flag = true
var body: some View {
ZStack {
if flag {
ExampleView().tapAction {
self.flag.toggle()
}
} else {
OtherExampleView().tapAction {
self.flag.toggle()
}
}
}
}
}
struct ExampleView: View {
var body: some View {
Text("some text")
}
}
struct OtherExampleView: View {
var body: some View {
Text("other text")
}
}
but if you want to present more view this way looks nasty
You can use stack to control view state without NavigationView
For Example:
class NavigationStack: BindableObject {
let didChange = PassthroughSubject<Void, Never>()
var list: [AuthState] = []
public func push(state: AuthState) {
list.append(state)
didChange.send()
}
public func pop() {
list.removeLast()
didChange.send()
}
}
enum AuthState {
case mainScreenState
case userNameScreen
case logginScreen
case emailScreen
case passwordScreen
}
struct NavigationRoot : View {
#EnvironmentObject var state: NavigationStack
#State private var aligment = Alignment.leading
fileprivate func CurrentView() -> some View {
switch state.list.last {
case .mainScreenState:
return AnyView(GalleryState())
case .none:
return AnyView(LoginScreen().environmentObject(state))
default:
return AnyView(AuthenticationView().environmentObject(state))
}
}
var body: some View {
GeometryReader { geometry in
self.CurrentView()
.background(Image("background")
.animation(.fluidSpring())
.edgesIgnoringSafeArea(.all)
.frame(width: geometry.size.width, height: geometry.size.height,
alignment: self.aligment))
.edgesIgnoringSafeArea(.all)
.onAppear {
withAnimation() {
switch self.state.list.last {
case .none:
self.aligment = Alignment.leading
case .passwordScreen:
self.aligment = Alignment.trailing
default:
self.aligment = Alignment.center
}
}
}
}
.background(Color.black)
}
}
struct ExampleOfAddingNewView: View {
#EnvironmentObject var state: NavigationStack
var body: some View {
VStack {
Button(action:{ self.state.push(state: .emailScreen) }){
Text("Tap me")
}
}
}
}
struct ExampleOfRemovingView: View {
#EnvironmentObject var state: NavigationStack
var body: some View {
VStack {
Button(action:{ self.state.pop() }){
Text("Tap me")
}
}
}
}
In my opinion this bad way, but navigation in SwiftUI much worse