Hide NavigationBar back button and reduce space - swiftui

I'm trying to hide the Back button above my Navigation title, But .navigationBarBackButtonHidden(true) isn't working, Is there any alternative way I can approach it to remove this back button and the space below it and my heading ?
import SwiftUI
struct ContentView: View {
init() {
let navBarAppearance = UINavigationBar.appearance()
navBarAppearance.largeTitleTextAttributes = [.foregroundColor: UIColor.orange]
navBarAppearance.titleTextAttributes = [.foregroundColor: UIColor.orange]
}
#AppStorage("hasOpened") private var hasOpened : Bool = false
var body: some View {
NavigationView(){
VStack(alignment: .leading, spacing: 0) {
Text("Hello, World!")
.onAppear{
hasOpened = false
}
}
.navigationBarBackButtonHidden(true)
.padding()
.navigationBarTitle("Home")
.foregroundColor(Color("Orange"))
.navigationBarItems(trailing:
Image("Logo")
.resizable()
.scaledToFit()
.frame(width: 100.0,height:100)
.padding(.top, 75)
)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Output:

Related

how can i make a conditional navigation in swiftui [duplicate]

I am trying to push from login view to detail view but not able to make it.even navigation bar is not showing in login view. How to push on button click in SwiftUI? How to use NavigationLink on button click?
var body: some View {
NavigationView {
VStack(alignment: .leading) {
Text("Let's get you signed in.")
.bold()
.font(.system(size: 40))
.multilineTextAlignment(.leading)
.frame(width: 300, height: 100, alignment: .topLeading)
.padding(Edge.Set.bottom, 50)
Text("Email address:")
.font(.headline)
TextField("Email", text: $email)
.frame(height:44)
.accentColor(Color.white)
.background(Color(UIColor.darkGray))
.cornerRadius(4.0)
Text("Password:")
.font(.headline)
SecureField("Password", text: $password)
.frame(height:44)
.accentColor(Color.white)
.background(Color(UIColor.darkGray))
.cornerRadius(4.0)
Button(action: {
print("login tapped")
}) {
HStack {
Spacer()
Text("Login").foregroundColor(Color.white).bold()
Spacer()
}
}
.accentColor(Color.black)
.padding()
.background(Color(UIColor.darkGray))
.cornerRadius(4.0)
.padding(Edge.Set.vertical, 20)
}
.padding(.horizontal,30)
}
.navigationBarTitle(Text("Login"))
}
To fix your issue you need to bind and manage tag with NavigationLink, So create one state inside you view as follow, just add above body.
#State var selection: Int? = nil
Then update your button code as follow to add NavigationLink
NavigationLink(destination: Text("Test"), tag: 1, selection: $selection) {
Button(action: {
print("login tapped")
self.selection = 1
}) {
HStack {
Spacer()
Text("Login").foregroundColor(Color.white).bold()
Spacer()
}
}
.accentColor(Color.black)
.padding()
.background(Color(UIColor.darkGray))
.cornerRadius(4.0)
.padding(Edge.Set.vertical, 20)
}
Meaning is, when selection and NavigationLink tag value will match then navigation will be occurs.
I hope this will help you.
iOS 16+
Note: Below is a simplified example of how to present a new view. For a more advanced generic example please see this answer.
In iOS 16 we can access the NavigationStack and NavigationPath.
Usage #1
A new view is activated by a simple NavigationLink:
struct ContentView: View {
var body: some View {
NavigationStack {
NavigationLink(value: "NewView") {
Text("Show NewView")
}
.navigationDestination(for: String.self) { view in
if view == "NewView" {
Text("This is NewView")
}
}
}
}
}
Usage #2
A new view is activated by a standard Button:
struct ContentView: View {
#State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
Button {
path.append("NewView")
} label: {
Text("Show NewView")
}
.navigationDestination(for: String.self) { view in
if view == "NewView" {
Text("This is NewView")
}
}
}
}
}
Usage #3
A new view is activated programmatically:
struct ContentView: View {
#State private var path = NavigationPath()
var body: some View {
NavigationStack(path: $path) {
Text("Content View")
.navigationDestination(for: String.self) { view in
if view == "NewView" {
Text("This is NewView")
}
}
}
.onAppear {
path.append("NewView")
}
}
}
iOS 13+
The accepted answer uses NavigationLink(destination:tag:selection:) which is correct.
However, for a simple view with just one NavigationLink you can use a simpler variant: NavigationLink(destination:isActive:)
Usage #1
NavigationLink is activated by a standard Button:
struct ContentView: View {
#State var isLinkActive = false
var body: some View {
NavigationView {
VStack(alignment: .leading) {
...
NavigationLink(destination: Text("OtherView"), isActive: $isLinkActive) {
Button(action: {
self.isLinkActive = true
}) {
Text("Login")
}
}
}
.navigationBarTitle(Text("Login"))
}
}
}
Usage #2
NavigationLink is hidden and activated by a standard Button:
struct ContentView: View {
#State var isLinkActive = false
var body: some View {
NavigationView {
VStack(alignment: .leading) {
...
Button(action: {
self.isLinkActive = true
}) {
Text("Login")
}
}
.navigationBarTitle(Text("Login"))
.background(
NavigationLink(destination: Text("OtherView"), isActive: $isLinkActive) {
EmptyView()
}
.hidden()
)
}
}
}
Usage #3
NavigationLink is hidden and activated programmatically:
struct ContentView: View {
#State var isLinkActive = false
var body: some View {
NavigationView {
VStack(alignment: .leading) {
...
}
.navigationBarTitle(Text("Login"))
.background(
NavigationLink(destination: Text("OtherView"), isActive: $isLinkActive) {
EmptyView()
}
.hidden()
)
}
.onAppear {
self.isLinkActive = true
}
}
}
Here is a GitHub repository with different SwiftUI extensions that makes navigation easier.
Another approach:
SceneDelegate
if let windowScene = scene as? UIWindowScene {
let window = UIWindow(windowScene: windowScene)
window.rootViewController = UIHostingController(rootView: BaseView().environmentObject(ViewRouter()))
self.window = window
window.makeKeyAndVisible()
}
BaseView
import SwiftUI
struct BaseView : View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
VStack {
if viewRouter.currentPage == "view1" {
FirstView()
} else if viewRouter.currentPage == "view2" {
SecondView()
.transition(.scale)
}
}
}
}
#if DEBUG
struct MotherView_Previews : PreviewProvider {
static var previews: some View {
BaseView().environmentObject(ViewRouter())
}
}
#endif
ViewRouter
import Foundation
import Combine
import SwiftUI
class ViewRouter: ObservableObject {
let objectWillChange = PassthroughSubject<ViewRouter,Never>()
var currentPage: String = "view1" {
didSet {
withAnimation() {
objectWillChange.send(self)
}
}
}
}
FirstView
import SwiftUI
struct FirstView : View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
VStack {
Button(action: {self.viewRouter.currentPage = "view2"}) {
NextButtonContent()
}
}
}
}
#if DEBUG
struct FirstView_Previews : PreviewProvider {
static var previews: some View {
FirstView().environmentObject(ViewRouter())
}
}
#endif
struct NextButtonContent : View {
var body: some View {
return Text("Next")
.foregroundColor(.white)
.frame(width: 200, height: 50)
.background(Color.blue)
.cornerRadius(15)
.padding(.top, 50)
}
}
SecondView
import SwiftUI
struct SecondView : View {
#EnvironmentObject var viewRouter: ViewRouter
var body: some View {
VStack {
Spacer(minLength: 50.0)
Button(action: {self.viewRouter.currentPage = "view1"}) {
BackButtonContent()
}
}
}
}
#if DEBUG
struct SecondView_Previews : PreviewProvider {
static var previews: some View {
SecondView().environmentObject(ViewRouter())
}
}
#endif
struct BackButtonContent : View {
var body: some View {
return Text("Back")
.foregroundColor(.white)
.frame(width: 200, height: 50)
.background(Color.blue)
.cornerRadius(15)
.padding(.top, 50)
}
}
Hope this helps!
Simplest and most effective solution is :
NavigationLink(destination:ScoresTableView()) {
Text("Scores")
}.navigationBarHidden(true)
.frame(width: 90, height: 45, alignment: .center)
.foregroundColor(.white)
.background(LinearGradient(gradient: Gradient(colors: [Color.red, Color.blue]), startPoint: .leading, endPoint: .trailing))
.cornerRadius(10)
.contentShape(Rectangle())
.padding(EdgeInsets(top: 16, leading: UIScreen.main.bounds.size.width - 110 , bottom: 16, trailing: 20))
ScoresTableView is the destination view.
In my opinion a cleaner way for iOS 16+ is using a state bool to present the view.
struct ButtonNavigationView: View {
#State private var isShowingSecondView : Bool = false
var body: some View {
NavigationStack {
VStack{
Button(action:{isShowingSecondView = true} ){
Text("Show second view")
}
}.navigationDestination(isPresented: $isShowingSecondView) {
Text("SecondView")
}
}
}
}
I think above answers are nice, but simpler way should be:
NavigationLink {
TargetView()
} label: {
Text("Click to go")
}

How to set the color of unsafe area in a NavigationView

I want the color above the titlebar of NavigationView to remain the same, even when I scroll the list. I've tried using UINavigationBar to set it, but it doesn't work in unsafe area, and tt still turns gray when sliding down.
import SwiftUI
struct ContentView: View {
init(){
UINavigationBar.appearance().backgroundColor = UIColor.blue // it doesn't works in unsafe area
}
var body: some View {
TabView {
NavigationView{
List{
ForEach((1...10).reversed(), id: \.self) {
Text("list \($0)")
}
}.toolbar(content: {
ToolbarItem(placement: .principal, content: {
Text("Title Bar")
.bold()
.frame(maxWidth: .infinity,maxHeight: .infinity, alignment: .center)
.background(Color.red) // it doesn't works
})})
.navigationBarTitleDisplayMode(.inline)
}.tabItem {
Image(systemName: "person")
Text("tag1")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
the preview
I refer to this link https://stackoverflow.com/a/62125578/6845277 to solve this problem.
It feels quite troublesome. I don't know a simple solution yet
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
NavigationView{
List{
ForEach((1...10).reversed(), id: \.self) {
Text("list \($0)")
}
}.toolbar(content: {
ToolbarItem(placement: .principal, content: {
Text("Title Bar")
})})
.navigationBarColor(backgroundColor: .blue, titleColor: .white)
.navigationBarTitleDisplayMode(.inline)
}.tabItem {
Image(systemName: "person")
Text("tag1")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct NavigationBarModifier: ViewModifier {
var backgroundColor: UIColor?
var titleColor: UIColor?
init(backgroundColor: UIColor?, titleColor: UIColor?) {
self.backgroundColor = backgroundColor
let coloredAppearance = UINavigationBarAppearance()
coloredAppearance.configureWithTransparentBackground()
coloredAppearance.backgroundColor = backgroundColor
coloredAppearance.titleTextAttributes = [.foregroundColor: titleColor ?? .white]
coloredAppearance.largeTitleTextAttributes = [.foregroundColor: titleColor ?? .white]
UINavigationBar.appearance().standardAppearance = coloredAppearance
UINavigationBar.appearance().compactAppearance = coloredAppearance
UINavigationBar.appearance().scrollEdgeAppearance = coloredAppearance
}
func body(content: Content) -> some View {
ZStack{
content
VStack {
GeometryReader { geometry in
Color(self.backgroundColor ?? .clear)
.frame(height: geometry.safeAreaInsets.top)
.edgesIgnoringSafeArea(.top)
Spacer()
}
}
}
}
}
extension View{
func navigationBarColor(backgroundColor: UIColor?, titleColor: UIColor?) -> some View {
self.modifier(NavigationBarModifier(backgroundColor: backgroundColor, titleColor: titleColor))
}
}
preview

SwiftUI - How to change the background Color of top safe area to gray?

I want to change the background color of top safe area from green to gray. I have looked everywhere but could not find any solution. The screen in preview looks like this.
My codes:
struct ContentView: View {
#State var name = ""
init() {
//Use this if NavigationBarTitle is with Large Font
UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: UIColor.red]
UINavigationBar.appearance().backgroundColor = .gray
}
var body: some View {
NavigationView {
ZStack{
VStack{
TextField("Name", text: $name)
.frame(height:200)
.padding()
.background(backgrounImage())
.overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.gray,lineWidth: 4))
.padding()
Spacer()
}.navigationTitle("Tanvir")
.background(Color.green.edgesIgnoringSafeArea(.all))
}
}
}
}
You can add another view on top of the ZStack:
var body: some View {
NavigationView {
ZStack(alignment: .top) { // <- Don't forget this
,,,
GeometryReader { reader in
Color.yellow
.frame(height: reader.safeAreaInsets.top, alignment: .top)
.ignoresSafeArea()
}
}
}
}
Don't forget the stack alignment!
Consistant Bar for the entire App
If you need it to be on all of your views, try putting the code somewhere more consistent like where you are providing the contentView:
#main
struct SwiftUIAppPlaygroundApp: App {
var body: some Scene {
WindowGroup {
ZStack {
ContentView()
GeometryReader { reader in
Color.yellow
.frame(height: reader.safeAreaInsets.top, alignment: .top)
.ignoresSafeArea()
}
}
}
}
}
Use this UIApplication extension to chagne your status bar color
extension UIApplication {
/**
Get status bar view
*/
var statusBarUIView: UIView? {
let tag = 13101996
if let statusBar = self.windows.first?.viewWithTag(tag) {
self.windows.first?.bringSubviewToFront(statusBar)
return statusBar
} else {
let statusBarView = UIView(frame: UIApplication.shared.windows.first?.windowScene?.statusBarManager?.statusBarFrame ?? .zero)
statusBarView.tag = tag
self.windows.first?.addSubview(statusBarView)
return statusBarView
}
}
}
Usage
struct ContentViewStatusBar: View {
#State var name = ""
init() {
//Use this if NavigationBarTitle is with Large Font
UINavigationBar.appearance().largeTitleTextAttributes = [.foregroundColor: UIColor.red]
UINavigationBar.appearance().backgroundColor = .gray
}
var body: some View {
NavigationView {
ZStack{
VStack{
TextField("Name", text: $name)
.frame(height:200)
.padding()
.background(backgrounImage())
.overlay(RoundedRectangle(cornerRadius: 20).stroke(Color.gray,lineWidth: 4))
.padding()
Spacer()
}.navigationTitle("Tanvir")
.background(Color.green.edgesIgnoringSafeArea(.all))
}
}.onAppear {
UIApplication.shared.statusBarUIView?.backgroundColor = .gray //<<=== Here
}
}
}

SwiftUI drag and drop on moving/animating view/shape

I am trying to implement drag and drop feature in SwiftUI. It seems when view is not animating the following code works fine. But when I am moving the view with offset then there is some issue with drag location and view like view location is not same as touch location and view shape is not same as original.
Dragging without offset animation(animate = false) works fine:
Dragging with offset animation(animate = true):
Code Snippet:
import SwiftUI
struct ContentView: View {
#ObservedObject var dropDelegate = CircleDropDelegate()
#State var animate = false
var body: some View {
VStack(alignment: .leading) {
Spacer()
Circle()
.fill(Color.green)
.offset(x: animate ? 100 : 0)
.animation(Animation.linear(duration: 5).repeatForever(autoreverses: true))
.frame(width: 50, height: 50)
.onDrag({
return NSItemProvider(object: "circle" as NSString)
})
Spacer()
Rectangle()
.fill(Color.green)
.frame(height: 150)
.onDrop(of: ["public.text"], delegate: dropDelegate)
Spacer()
}
.onAppear {
animate = true
}
.padding()
.background(Color.init(white: 0.7))
}
}
class CircleDropDelegate: ObservableObject, DropDelegate {
#Published var isEntered: Bool = false
func performDrop(info: DropInfo) -> Bool {
print("perform drop on box \(info)")
return true
}
func dropEntered(info: DropInfo) {
print("circle entered the box")
isEntered = true
}
func dropExited(info: DropInfo) {
print("circle exited the box")
isEntered = false
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Is there a way to fix this issue?

Using SwiftUI. My Slider/Side-menu launches new Views just fine when clicked but click <back> button and now all the options are 'dead'

Using SwiftUI and a slider/side menu tutorial that I have augmented in order to put actions on each of the side menu selections.
When the side menu is displayed and I tap a menu option, it works great and takes me to a new view with a menu item. But when i tap on and see the side menu still in place, all the menu items are not dead. The menu items still animate a click (with a flicker) but nothing happens. I have to close the side menu, reopen it, and then the menu items work once again - one time.
Can anyone tell me why this is happening?
Here is the pretty contentview, the mainview, and the sidemenu view.
//ContentView.swift
import SwiftUI
struct ContentView: View {
#State var showMenu = false
var body: some View {
let drag = DragGesture()
.onEnded {
if $0.translation.width < -100 {
withAnimation {
self.showMenu = false
}
}
}
return NavigationView {
GeometryReader { geometry in
ZStack(alignment: .leading) {
MainView(showMenu: self.$showMenu)
.frame(width: geometry.size.width, height: geometry.size.height)
.offset(x: self.showMenu ? geometry.size.width/2 : 0)
.disabled(self.showMenu ? true : false)
if self.showMenu {
MenuView()
.frame(width: geometry.size.width/2)
.transition(.move(edge: .leading))
}
}
.gesture(drag)
}
.navigationBarTitle("Side Menu", displayMode: .inline)
.navigationBarItems(leading: (
Button(action: {
withAnimation {
self.showMenu.toggle()
}
}) {
Image(systemName: "line.horizontal.3")
.imageScale(.large)
}
))
}
}
}
struct MainView: View {
#Binding var showMenu: Bool
var body: some View {
Button(action: {
withAnimation {
self.showMenu = true
}
}) {
Text("Show Menu")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
and here is the sidemenu view.
//MenuView.swift
import SwiftUI
struct PlayerView: View {
#State var showMenu = true
//#EnvironmentObject var session: SessionStore
var body: some View {
VStack{
//self.showMenu = true
Text("Manage Players Here").foregroundColor(.red)
}
}
}
struct MenuView: View {
#State var showMenu = true
var body: some View {
VStack(alignment: .leading) {
HStack() {
NavigationLink(destination: PlayerView()) {
HStack(){
Image(systemName: "person")
.foregroundColor(.gray)
.imageScale(.large)
Text("Players")
.foregroundColor(.gray)
.font(.headline)
}
}
}
.padding(.top, 100)
}
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color(red: 32/255, green: 32/255, blue: 32/255))
.edgesIgnoringSafeArea(.all)
}
}
struct MenuView_Previews: PreviewProvider {
static var previews: some View {
MenuView()
}
}
enter code here
1) Binding var in the MenuView
2) OnAppear{} with Zstack to turn off the showMenu
struct ContentView: View {
#State var showMenu = false
var body: some View {
let drag = DragGesture()
.onEnded {
if $0.translation.width < -100 {
withAnimation {
self.showMenu = false
}
}
}
return NavigationView {
GeometryReader { geometry in
ZStack(alignment: .leading) {
MainView(showMenu: self.$showMenu)
.frame(width: geometry.size.width, height: geometry.size.height)
.offset(x: self.showMenu ? geometry.size.width/2 : 0)
.disabled(self.showMenu ? true : false)
if self.showMenu {
MenuView(showMenu: self.$showMenu)
.frame(width: geometry.size.width/2)
.transition(.move(edge: .leading))
}
}
.gesture(drag).onAppear {
self.showMenu = false
}
}
.navigationBarTitle("Side Menu", displayMode: .inline)
.navigationBarItems(leading: (
Button(action: {
withAnimation {
self.showMenu.toggle()
}
}) {
Image(systemName: "line.horizontal.3")
.imageScale(.large)
}
))
}
}
}
struct MainView: View {
#Binding var showMenu: Bool
var body: some View {
Button(action: {
withAnimation {
self.showMenu = true
}
}) {
Text("Show Menu")
}
}
}
struct PlayerView: View {
#State var showMenu = true
//#EnvironmentObject var session: SessionStore
var body: some View {
VStack{
//self.showMenu = true
Text("Manage Players Here").foregroundColor(.red)
}
}
}
struct MenuView: View {
#Binding var showMenu: Bool // = true
var body: some View {
VStack(alignment: .leading) {
HStack() {
NavigationLink(destination: PlayerView()) {
HStack(){
Image(systemName: "person")
.foregroundColor(.gray)
.imageScale(.large)
Text("Players")
.foregroundColor(.gray)
.font(.headline)
}
}
}
.padding(.top, 100)
}
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.background(Color(red: 32/255, green: 32/255, blue: 32/255))
.edgesIgnoringSafeArea(.all)
}
}