How to navigate to different views in a side bar in swift? - swiftui

How do I navigate to other views I have created in swiftui?
below Is some code for the side bar I tried doing myself. the issue I was having is a white screen shows up as a huge white button.
VStack {
NavigationView {
HStack {
NavigationLink(destination: SettingsView()) {
Text("Settings")
.font(.system(size:20))
.foregroundColor(.black)
}
}
NavigationLink(destination: Settings()) {
Text("Settings")
.font(.title2)
}
}
NavigationLink(destination: AboutUs()) {
Text("About us")
.font(.title2)
}
}
}
}

Unless you're within a NavigationView, which has very specific appearances on iOS and macOS, you wouldn't be using NavigationLink. Since you're making your own sidebar, that means you won't be using Navigation View/Link.
Instead, you can use a #State variable or ObservableObject with a #Published property that keeps track of what view is active. I chose the latter in this example:
enum ViewTypes {
case main
case settings
case aboutUs
}
class SidebarNavigationManager : ObservableObject {
#Published var viewType : ViewTypes = .main
}
struct ContentView: View {
#StateObject var navigationManager = SidebarNavigationManager()
var body: some View {
HStack(alignment: .top) {
SidebarView(navigationManager: navigationManager)
.frame(width: 100)
.frame(maxHeight: .infinity)
.border(Color.green)
//Main content
VStack {
switch navigationManager.viewType {
case .main:
MainView()
case .settings:
SettingsView()
case .aboutUs:
AboutUsView()
}
}.frame(maxWidth: .infinity)
}.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
struct SidebarView : View {
#ObservedObject var navigationManager : SidebarNavigationManager
var body: some View {
//Sidebar
VStack {
Button(action: { navigationManager.viewType = .main }) {
Text("Main")
}
Button(action: { navigationManager.viewType = .settings }) {
Text("Settings")
}
Button(action: { navigationManager.viewType = .aboutUs }) {
Text("About Us")
}
}
}
}
struct MainView : View {
var body: some View {
Text("Main")
}
}
struct SettingsView : View {
var body: some View {
Text("Settings")
}
}
struct AboutUsView : View {
var body: some View {
Text("About Us")
}
}

Related

Overlay views one by one in SwiftUI

I have the following code with a struct and two views. On tap of the firstScreenOverlay button i want to show the secondScreenOverlay and hide the previous one and so on. Any help appreciated!
import SwiftUI
struct ContentView: View {
var body: some View {
Text("hello there")
.overlay(firstScreenOverlay, alignment: .center)
}
}
private var firstScreenOverlay: some View {
ZStack {
Color.blue
.opacity(0.5)
Button {} label: {
Text("Next")
.fullWidth()
}
}
}
private var secondScreenOverlay: some View {
ZStack {
Color.red
.opacity(0.5)
}
}
You just need a way to keep track of what is showing. You can use a variable and an enum
import SwiftUI
struct DynamicOverlay: View {
//Keeps track of what is showing
#State var selectedOverlay: OverlayViews = .none
var body: some View {
VStack{
Text("hello there")
//Button changes what is being displayed
Button("first", action: {
selectedOverlay = .first
})
}
//Displays the selected view
.overlay(selectedOverlay.view($selectedOverlay), alignment: .center)
}
}
enum OverlayViews{
case first
case second
case none
//Holds all the options for the views
#ViewBuilder func view(_ selectedView: Binding<OverlayViews>) -> some View{
switch self{
case .first:
ZStack {
Color.blue
.opacity(0.5)
Button {
selectedView.wrappedValue = .second
} label: {
Text("Next")
}
}
case .second:
ZStack {
Color.red
.opacity(0.5)
Button("home") {
selectedView.wrappedValue = .none
}
}
case .none:
EmptyView()
}
}
}
struct DynamicOverlay_Previews: PreviewProvider {
static var previews: some View {
DynamicOverlay()
}
}

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")
}

Show BottomPlayerView above TabView in SwiftUI

I'm learning swiftUI and I want to make a music app.
I created a view which going to be above the tabView, but I want it to be shown only if user start playing a music.
My App, I use ZStack for bottomPlayer, and I share the bottomPlayer variable through .environmentObject(bottomPlayer) so the child views can use it:
class BottomPlayer: ObservableObject {
var show: Bool = false
}
#main
struct MyCurrentApp: App {
var bottomPlayer: BottomPlayer = BottomPlayer()
var audioPlayer = AudioPlayer()
var body: some Scene {
WindowGroup {
ZStack(alignment: Alignment(horizontal: .center, vertical: .bottom)) {
TabBar()
if bottomPlayer.show {
BottomPlayerView()
.offset(y: -40)
}
}
.environmentObject(bottomPlayer)
}
}
}
The BottomPlayerView (above the TabView)
struct BottomPlayerView: View {
var body: some View {
HStack {
Image("cover")
.resizable()
.frame(width: 50, height: 50)
VStack(alignment: .leading) {
Text("Artist")
.foregroundColor(.orange)
Text("Song title")
.fontWeight(.bold)
}
Spacer()
Button {
print("button")
} label: {
Image(systemName: "play")
}
.frame(width: 60, height: 60)
}
.frame(maxWidth: .infinity, maxHeight: 60)
.background(Color.white)
.onTapGesture {
print("ontap")
}
}
}
My TabView:
struct TabBar: View {
var body: some View {
TabView {
AudiosTabBarView()
VideosTabBarView()
SearchTabBarView()
}
}
}
And In my SongsView, I use the EnvironmentObject to switch on the bottomPlayerView
struct SongsView: View {
#EnvironmentObject var bottomPlayer: BottomPlayer
var body: some View {
NavigationView {
VStack {
Button {
bottomPlayer.show = true
} label: {
Text("Show Player")
}
}
.listStyle(.plain)
.navigationBarTitle("Audios")
}
}
}
The problem is the bottomPlayer.show is actually set to true, but doesn't appear ...
Where I am wrong?
In your BottomPlayer add theĀ #Published attribute before the show boolean.
This creates a publisher of this type.
apple documentation

.confirmationDialog inside of .swipeActions does not work, iOS 15

With regards to iOS 15, Xcode 13; I am wondering if this is a bug, not properly implemented, or a planned non-functional feature...
With a list that has a .swipeActions that calls a .confirmationDialog the confirmation dialog does not show.
See example:
import SwiftUI
struct ContentView: View {
#State private var confirmDelete = false
var body: some View {
NavigationView {
List{
ForEach(1..<10) {_ in
Cell()
}
.swipeActions(edge: .trailing) {
Button(role: .destructive) {
confirmDelete.toggle()
} label: {
Label("Delete", systemImage: "trash")
}
.confirmationDialog("Remove this?", isPresented: $confirmDelete) {
Button(role: .destructive) {
print("Removed!")
} label: {
Text("Yes, Remove this")
}
}
}
}
}
}
}
struct Cell: View {
var body: some View {
Text("Hello")
.padding()
}
}
Misconfiguration:
The view modifier .confirmationDialog needs to be added to the view that is outside of the .swipeActions view modifier. It works when configured properly as shown below:
import SwiftUI
struct ContentView: View {
#State private var confirmDelete = false
var body: some View {
NavigationView {
List{
ForEach(1..<10) {_ in
Cell()
}
.swipeActions(edge: .trailing) {
Button(role: .destructive) {
confirmDelete.toggle()
} label: {
Label("Delete", systemImage: "trash")
}
}
//move outside the scope of the .swipeActions view modifier:
.confirmationDialog("Remove this?", isPresented: $confirmDelete) {
Button(role: .destructive) {
print("Removed!")
} label: {
Text("Yes, Remove this")
}
}
}
}
}
}
struct Cell: View {
var body: some View {
Text("Hello")
.padding()
}
}

When click the image button, open new view in Swift UI

I would like to open new View after clicking on a image button in SwiftUI, any idea?
Button(action: {}) {
Image("gift")
Text("Send")
.padding(.horizontal)
}
.padding()
.foregroundColor(.white)
.background(Color.gray)
.cornerRadius(.infinity)
If you want it in a NavigationView, you can use a NavigationLink instead of a Button:
struct ContentView : View {
var body: some View {
NavigationView {
NavigationLink(destination: DetailView()) {
Image("gift")
Text("Send")
.padding(.horizontal)
.padding()
.foregroundColor(.white)
.background(Color.gray)
.cornerRadius(.infinity)
}
}
}
}
struct DetailView : View {
var body: some View {
Text("Detail")
}
}
If you want a sheet presented, you can use the sheet modifier and a #State variable:
struct ContentView : View {
#State private var sheetPresented = false
var body: some View {
Button(action: {
sheetPresented = true
}) {
Image("gift")
Text("Send")
.padding(.horizontal)
}
.padding()
.foregroundColor(.white)
.background(Color.gray)
.cornerRadius(.infinity)
.sheet(isPresented: $sheetPresented) {
DetailView()
}
}
}
struct DetailView : View {
var body: some View {
Text("Detail")
}
}