I have to use large title (navigationBarTitleDisplayMode = .large) and .searchable in View 2.
But in that case, the height of the navigation bar in View 3 is set strangely.
I think, the sum of the height of the navigation bar and the height of the search bar in View 2 is applied to the height of the navigation bar in View 3.
Is there a way to set the height of the navigation bar in view 3 to the height it would have when .inline?
import SwiftUI
struct ContentView: View {
init() {
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = UIColor.red
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().compactAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
}
var body: some View {
NavigationView {
ScrollView {
VStack {
NavigationLink("View 2") {
SecondView()
}
}
}
.navigationTitle("View 1")
.navigationBarTitleDisplayMode(.large)
}
}
}
struct SecondView: View {
#State var text = ""
var body: some View {
ScrollView {
VStack {
NavigationLink("View 3") {
ThirdView()
}
}
}
.navigationTitle("View 2")
.navigationBarTitleDisplayMode(.large)
.searchable(text: $text)
}
}
struct ThirdView: View {
var body: some View {
Text("View 3")
.navigationTitle("View 3")
.navigationBarTitleDisplayMode(.inline)
}
}
If you remove the first and second .navigationBarTitleDisplayMode(.large) & change your .searchable to .searchable(text: $text, placement: .navigationBarDrawer(displayMode: .always)) it should work.
Tested and working code:
struct FirstView: View {
init() {
let appearance = UINavigationBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = .red
UINavigationBar.appearance().standardAppearance = appearance
UINavigationBar.appearance().compactAppearance = appearance
UINavigationBar.appearance().scrollEdgeAppearance = appearance
}
var body: some View {
NavigationStack {
List {
NavigationLink("View 2") {
SecondView()
}
}.navigationBarTitle("View 1")
}
}
}
struct SecondView: View {
#State var text: String = ""
var body: some View {
List {
NavigationLink("View 3") {
ThirdView()
}
}.navigationTitle("View 2")
.searchable(text: $text, placement: .navigationBarDrawer(displayMode: .always))
}
}
struct ThirdView: View {
var body: some View {
Text("View 3")
.navigationTitle("View 3")
.navigationBarTitleDisplayMode(.inline)
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView()
}
}
Related
I am using tab bar for with three tab for showing the data form api call . I added the required property for showing the navigation at top with title . Any reason it not showing it at top of the app .
Here is my content view code .
import SwiftUI
struct ContentView: View {
#State private var selection = 0
#EnvironmentObject private var viewModel: FruitsViewModel
var body: some View {
TabView(selection: $selection) {
NavigationView { TabListView(fruit: viewModel.fruits)}.tabItem {
Image(systemName: "house.fill")
Text("List View")
} .navigationBarTitle("Fruit List ", displayMode: .inline)
.accentColor(.red)
.onAppear() {
UITabBar.appearance().barTintColor = .white
}
.tag(0)
NavigationView {GridListView(fruit: viewModel.fruits)}.tabItem {
Image(systemName: "bookmark.circle.fill")
Text("Collection View")
}.navigationBarTitle("Fruit List ", displayMode: .inline)
.accentColor(.red)
.onAppear() {
UITabBar.appearance().barTintColor = .white
}
.tag(1)
NavigationView {WebListView()}.tabItem {
Image(systemName: "person.crop.circle")
Text("Web View")
}.navigationBarTitle("Fruit List ", displayMode: .inline)
.accentColor(.red)
.onAppear() {
UITabBar.appearance().barTintColor = .white
}
}.tag(2)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Here is the TabList code .
import SwiftUI
struct TabListView: View {
let fruit: [Fruits]
#EnvironmentObject private var viewModel: FruitsViewModel
var body: some View {
VStack {
List {
ForEach(viewModel.fruits) { fruits in
NavigationLink(destination: FruitDetailsView(fruit: fruits)) {
FruitsRowList(fruit: fruits)
}
}
}
}
.navigationTitle("Fruit List")
.onAppear {
Task {
await viewModel.getFruits()
}
}
}
}
Here is the result on simulator .. It just collapsed with entire view when I scroll down it .
Order is very important in SwiftUI, Each tab should have its own NavigationView or NavigationStack.
tabItem should be attached to the NavigationView and navigationTitle should go inside the NavigationView.
struct ContentView: View {
#State private var selection = 0
//#EnvironmentObject private var viewModel: FruitsViewModel
var body: some View {
TabView(selection: $selection) {
NavigationView {
Text("TabListView(fruit: viewModel.fruits)")
.navigationBarTitle("Fruit List ", displayMode: .inline)
}
.tabItem {
Image(systemName: "house.fill")
Text("List View")
}
.accentColor(.red)
.onAppear() {
UITabBar.appearance().barTintColor = .white
}
.tag(0)
NavigationView {
Text("GridListView(fruit: viewModel.fruits)")
.navigationBarTitle("Fruit List ", displayMode: .inline)
}.tabItem {
Image(systemName: "bookmark.circle.fill")
Text("Collection View")
}
.accentColor(.red)
.onAppear() {
UITabBar.appearance().barTintColor = .white
}
.tag(1)
NavigationView {
Text("WebListView()")
.navigationBarTitle("Fruit List ", displayMode: .inline)
}.tabItem {
Image(systemName: "person.crop.circle")
Text("Web View")
.accentColor(.red)
.onAppear() {
UITabBar.appearance().barTintColor = .white
}
}
.tag(2)
}
}
}
I have a question about the new NavigationStack in IOS 16. I have a problem setting navigationTitle in ContentView, I set navigationTitle but it is the same for all tabs. Can I set it somehow so that I can edit it for each different tab? Using tag ? thank you very much
struct ContentView: View {
#State var selection = 1
var body: some View {
NavigationStack {
TabView(selection: $selection) {
LaunchView()
.badge(2)
.tabItem {
Label("Received", systemImage: "tray.and.arrow.down.fill")
}
.tag(1)
DeView()
.tabItem {
Label("Sent", systemImage: "tray.and.arrow.up.fill")
}
.tag(2)
DeView()
.tabItem {
Label("Sent", systemImage: "tray.and.arrow.up.fill")
}
.tag(3)
}
.navigationTitle("test")
}
}
}
You can create a function that returns the title for every selection:
func title() -> String {
if selection == 1 {
return title
}else if selection == 2 {
return some Title
}else if selection == 3 {
return some other Title
}
}
Or my personal best way: Enums!
Create an enum that holds the tabs, then create a title property for each tab:
struct ContentView: View {
#State var selection = Tab.received
var body: some View {
NavigationStack {
TabView(selection: $selection) {
Text("hello")
.badge(2)
.tabItem {
Label("Received", systemImage: "tray.and.arrow.down.fill")
}
.tag(Tab.received)
Text("hello3")
.tabItem {
Label("Sent", systemImage: "tray.and.arrow.up.fill")
}
.tag(Tab.sent)
Text("hello5")
.tabItem {
Label("Sent", systemImage: "tray.and.arrow.up.fill")
}
.tag(Tab.sennt)
}.navigationTitle(selection.title)
}
}
}
enum Tab: Int {
case received = 1
case sent = 2
case sennt = 3
var title: String {
switch self {
case .received:
return "Hello"
case .sent:
return "Hello World"
case .sennt:
return "Hello, World!"
}
}
}
Plus it’s easier to work with than Ints.
Edit: To hide the TabBar for DeviceView:
struct Test: View {
#State var selection = 1
#State var devicePresented = false
var body: some View {
NavigationStack {
//Content
.navigationDestination(isPresented: $devicePresented) {//present DeviceView when devicePresented is true
DeviceView()
}
}
}
}
struct SettingsView: View {
#Binding var devicePresented: Bool
var body: some View {
List {
Button(action: {
devicePresented.toggle()
}) {
Text("Go to device")
}
}
}
}
I'm trying to add different toolbars to each of my tabs but they are not displayed. The app will mostly be used on a landscape iPad and I can add the toolbars to the TabView itself and they display but then I don't know how to pass the button press down the navigation stack to the individual views/view-models to be handled locally.
I've tried adding new NavigationViews (including .stack navigationViewStyles) but this still just adds another column to the view.
This is some barebones, working code:
import SwiftUI
#main
struct NavTabTestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
MasterView()
}
}
struct MasterView: View {
var body: some View {
NavigationView {
List {
ForEach(0..<20) { index in
NavigationLink(
destination: DetailView(index: index)
.navigationTitle("Row \(index)")
) {
Text("\(index) th row")
}.tag(index)
}
}.navigationTitle(Text("Ratty"))
}
}
}
struct DetailView: View {
var index: Int
#State var selectedTab = 1
var body: some View {
TabView(selection: $selectedTab) {
Tab1(index: index).tabItem { Label("Tab1", systemImage: "list.dash") }
Tab2(index: index).tabItem { Label("Tab2", systemImage: "aqi.medium") }
Tab3(index: index).tabItem { Label("Tab3", systemImage: "move.3d") }
}
}
}
struct Tab1: View {
var index: Int
var body: some View {
Text("This is \(index) in tab 1")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button("Bingo") { print("Bingo") }
}
}
}
}
struct Tab2: View {
var index: Int
var body: some View {
Text("This is \(index) in tab 2")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button("Bongo") { print("Bongo") }
}
}
}
}
struct Tab3: View {
var index: Int
var body: some View {
Text("This is \(index) in tab 3")
.toolbar {
ToolbarItem(placement: .primaryAction) {
Button("Banjo") { print("Banjo") }
}
}
}
}
I'm starting to wonder if this is even possible and whether it would be better to just implement my own view with buttons at the top of each tab.
EDIT:
Not sure if this will help but it does go over some interesting concepts with the toolbar in the nav view.
link: Stewart Lynch
You need to use only one level NavigationView. In other words, you should not nest NavigationViews. Have a look at this answer.
Only Back Button Visible on Custom Navigation Bar SwiftUI
Use a NavgationView for the MasterView as you have used.
Use a NavigationView for each of the Tab# veiws.
Switch between MasterView and DetailsView.
Use Button instead of NavigationLink in MasterView (You can customise it to look like a NavigationLink)
Use a custom back button in each of the Tab# veiws.
This controls which one of MasterView and DetailsView should be shown.
class BaseViewModel: ObservableObject {
#Published var userFlow: UserFlow = .masterView
init(){
userFlow = .masterView
}
enum UserFlow {
case masterView, detailsView
}
}
This view to be used either in ContentView or instead of it. When you are in MasterView and click on one of the list row, set appState.userFlow = .detailView. When you click the back buttons, set appState.userFlow = .masterView.
Doing so, you switch between the two views and one NavigationView is shown at a time.
As of now, it does not have animation. Use if you wish so
https://developer.apple.com/tutorials/swiftui/animating-views-and-transitions
struct BaseView: View {
#EnvironmentObject var appState: BaseViewModel
#State var index: Int = 0
var body: some View {
Group {
switch appState.userFlow {
case .masterView:
MasterView(index: $index)
default:
DetailView(index: index)
}
}
.edgesIgnoringSafeArea(.bottom)
}
}
Complete code
struct ContentView: View {
var body: some View {
BaseView().environmentObject(BaseViewModel())
}
}
class BaseViewModel: ObservableObject {
#Published var userFlow: UserFlow = .masterView
init(){
userFlow = .masterView
}
enum UserFlow {
case masterView, detailsView
}
}
struct BaseView: View {
#EnvironmentObject var appState: BaseViewModel
#State var index: Int = 0
var body: some View {
Group {
switch appState.userFlow {
case .masterView:
MasterView(index: $index)
default:
DetailView(index: index)
}
}
.edgesIgnoringSafeArea(.bottom)
}
}
struct MasterView: View {
#EnvironmentObject var appState: BaseViewModel
#Binding var index: Int
var body: some View {
NavigationView {
List {
ForEach(0..<20) { index in
Button(action: {
appState.userFlow = .detailsView
$index.wrappedValue = index
}, label: {
HStack {
Text("\(index) th row")
Spacer()
Image(systemName: "greaterthan")
}
})
.tag(index)
}
}.navigationTitle(Text("Ratty"))
}
}
}
struct DetailView: View {
var index: Int
#State var selectedTab = 1
var body: some View {
TabView(selection: $selectedTab) {
Tab1(index: index).tabItem { Label("Tab1", systemImage: "list.dash") }
Tab2(index: index).tabItem { Label("Tab2", systemImage: "aqi.medium") }
Tab3(index: index).tabItem { Label("Tab3", systemImage: "move.3d") }
}
}
}
struct Tab1: View {
#EnvironmentObject var appState: BaseViewModel
var index: Int
var body: some View {
NavigationView {
Text("This is \(index) in tab 1")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("back") { appState.userFlow = .masterView }
}
ToolbarItem(placement: .navigationBarTrailing) {
Button("Bingo") { print("Bingo") }
}
}
}
}
}
struct Tab2: View {
#EnvironmentObject var appState: BaseViewModel
var index: Int
var body: some View {
NavigationView {
Text("This is \(index) in tab 2")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("back") { appState.userFlow = .masterView }
}
ToolbarItem(placement: .primaryAction) {
Button("Bongo") { print("Bongo") }
}
}
}
}
}
struct Tab3: View {
#EnvironmentObject var appState: BaseViewModel
var index: Int
var body: some View {
NavigationView {
Text("This is \(index) in tab 3")
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button("back") { appState.userFlow = .masterView }
}
ToolbarItem(placement: .primaryAction) {
Button("Banjo") { print("Banjo") }
}
}
}
}
}
I have customised xtwistedx 's solution.
I created a NavigationView and it works, but everytime I click on a NavigationLink, SwiftUI recreates the destination view and resets every property in it. See this example:
struct NavView: View {
#State var selectionIndex: Int? = nil
let views = [
DestinationView(viewNumber: 1),
DestinationView(viewNumber: 2),
DestinationView(viewNumber: 3)
]
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: self.views[0], tag: 0, selection: $selectionIndex) {
Text("View 1")
}
NavigationLink(destination: self.views[1], tag: 1, selection: $selectionIndex) {
Text("View 2")
}
NavigationLink(destination: self.views[2], tag: 0, selection: $selectionIndex) {
Text("View 3")
}
}
self.views[0]
}
}
}
struct DestinationView: View {
#State var viewNumber: Int
#State var count = 0
var body: some View {
VStack {
Text("View \(self.viewNumber)")
.font(.largeTitle)
Text("Count: \(self.count)")
Button(action: {
self.count += 1
}) {
Text("Increase counter")
}
}
}
}
In this example clicking for example on the View1 link opens the View 1. Pressing the button increases the count property. If you then click on the View2 button and then back the View1 button, the count property will be zero again.
EDIT
I also tried not to store the views and create them directly in body, as suggested in the comments. Doesn't work either :(
struct NavView: View {
#State var selectionIndex: Int? = nil
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DestinationView(viewNumber: 1), tag: 0, selection: $selectionIndex) {
Text("View 1")
}
NavigationLink(destination: DestinationView(viewNumber: 2), tag: 1, selection: $selectionIndex) {
Text("View 2")
}
NavigationLink(destination: DestinationView(viewNumber: 3), tag: 2, selection: $selectionIndex) {
Text("View 3")
}
}
}
}
}
EDIT 2
I also tried to store the views globally, I also tried with and without tag: and selection:
Issue in your code is that every time you are by default initializing count to 0 whenever we are navigating to DestinationView so trick is we have to update count value and save it some where in NavView.
First of all you have to create DestinationViewModel to keep track of your updated count values like below
class DestinationViewModel: ObservableObject {
#Published var viewNumber: Int
#Published var count: Int
init(_ viewNumber: Int, count: Int) {
self.viewNumber = viewNumber
self.count = count
}
}
Then you can access your DestinationViewModel in your DestinationView
struct DestinationView: View {
#EnvironmentObject var viewModel: DestinationViewModel
var body: some View {
VStack {
Text("View \(viewModel.viewNumber)")
.font(.largeTitle)
Text("Count: \(viewModel.count)")
Button(action: {
self.viewModel.count += 1
}) {
Text("Increase counter")
}
}
}
}
Below is your updated NavView
class DestinationViewModelContainer: ObservableObject {
#Published var viewModels: [DestinationViewModel]
init(_ viewModels: [DestinationViewModel]) {
self.viewModels = viewModels
}
}
struct NavView: View {
#EnvironmentObject var viewModelContainer: DestinationViewModelContainer
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DestinationView().environmentObject(viewModelContainer.viewModels[0])) {
Text("View 1")
}
NavigationLink(destination: DestinationView().environmentObject(viewModelContainer.viewModels[1])) {
Text("View 2")
}
NavigationLink(destination: DestinationView().environmentObject(viewModelContainer.viewModels[2])) {
Text("View 3")
}
}
}
}
}
and Here you can call your NavView from below code
let contentView = NavView().environmentObject(DestinationViewModelContainer([DestinationViewModel(1, count: 0), DestinationViewModel(2, count: 0),DestinationViewModel(3, count: 0)]))
Hope it will help you.
I defined 2 popovers and one sheet in the View Line().
Using this view in a VStack, everything works fine.
Using it inside a List, the wrong popovers /sheets are displayed when the corresponding text or Button is tapped.
What's going wrong here?
struct ContentView: View {
var body: some View {
VStack {
Line()
List {
Line()
Line()
Line()
}
}
}
}
struct Line: View {
#State private var showPopup1 = false
#State private var showPopup2 = false
#State private var showSheet2 = false
var body: some View {
VStack {
Text("popover 1")
.onTapGesture { self.showPopup1 = true}
.popover(isPresented: $showPopup1, arrowEdge: .trailing )
{ Popover1(showSheet: self.$showPopup1) }
.background(Color.red)
Text("popover 2")
.onTapGesture { self.showPopup2 = true }
.popover(isPresented: $showPopup2, arrowEdge: .trailing )
{ Popover2(showSheet: self.$showPopup2) }
.background(Color.yellow)
Button("Sheet2"){self.showSheet2 = true}
.sheet(isPresented: self.$showSheet2, content: { Sheet2()})
}
}
}
struct Popover1: View {
#Binding var showSheet: Bool
var body: some View {
VStack {
Text("Poppver 1 \(self.showSheet ? "T" : "F")")
Button("Cancel"){ self.showSheet = false }
}
}
}
struct Popover2: View {
#Binding var showSheet: Bool
var body: some View {
VStack {
Text("Poppver 2")
Button("Cancel"){ self.showSheet = false }
}
}
}
struct Sheet2: View {
#Environment(\.presentationMode) var presentation
var body: some View {
VStack {
Text("Sheet 2")
Button("Cancel"){ self.presentation.wrappedValue.dismiss() }
}
}
}
Just don't use Button for .sheet. List detects buttons in row and activate entire row (not sure about bug, let it be as designed). So using only and for everywhere in sub-elements gestures, makes your code work.
Tested with Xcode 11.2 / iOS 13.2
var body: some View {
VStack {
Text("popover 1")
.onTapGesture { self.showPopup1 = true}
.popover(isPresented: $showPopup1, arrowEdge: .trailing )
{ Popover1(showSheet: self.$showPopup1) }
.background(Color.red)
Text("popover 2")
.onTapGesture { self.showPopup2 = true }
.popover(isPresented: $showPopup2, arrowEdge: .trailing )
{ Popover2(showSheet: self.$showPopup2) }
.background(Color.yellow)
Text("Sheet2") // << here !!!
.onTapGesture {self.showSheet2 = true} // << here !!!
.sheet(isPresented: self.$showSheet2, content: { Sheet2()})
}
}