SwiftUI : Using NavigationView leads to image and text gone - swift

What I'm gonna do is..
If I click a menu, then it goes to a detailed menu-description page.
but It seems to something wrong. the code is the below.
[contentview.swift]
var body: some View {
VStack(alignment:.leading){
List{
Text("Menu")
.font(.title)
.fontWeight(.heavy)
ForEach(menu){ section in
Section(header: Text(section.name) , content: {
ForEach(section.items.indices)
{
index in ItemRow(item:section.items[index])
}
})
}
}.listStyle(GroupedListStyle())
}
}
[ItemRow.swift]
struct ItemRow: View {
let item: MenuItem
var body: some View {
VStack{
//NavigationView{
NavigationLink(destination: ItemDetail(item:item)) {
HStack{
Image(item.thumbnailImage)
VStack(alignment:.leading){
Text(item.name)
.font(.headline)
Text("$\(item.price)")
}
}
}
//}
}
}
}
I intentionally commented out the 'NaviagationView'.
because If I activate that comment relating to NavigationNiew..
See? It seems weird. I still wonder what i did wrong..
Before adding the navigation code, the menu was shown on the Preview well.
Could you give me some tips?
===============================
My Problem has been solved!
[ContentView.swift]
struct ContentView: View {
var body: some View {
ItemList()
}
}
[ItemList.swift]
struct ItemList: View {
var body: some View {
NavigationView{
VStack(alignment:.leading){
List{
Text("Menu")
.font(.title)
.fontWeight(.heavy)
ForEach(menu){ section in
Section(header: Text(section.name) , content: {
ForEach(section.items.indices)
{
index in
//ItemRow(item:section.items[index])
NavigationLink(destination: ItemDetail(item:section.items[index])) {
HStack{
Image(section.items[index].thumbnailImage)
VStack(alignment:.leading){
Text(section.items[index].name)
.font(.headline)
Text("$\(section.items[index].price)")
}
}
}
}
})
}
}.listStyle(GroupedListStyle())
}
}
}
}

There is usually only one NavigationView in an app. It is usually the outer most View.
In this case put it above the VStack in the ContentView.
One of the most common exceptions are sheets.

Related

Multiple NavigationViews in SwiftUI - How to get rid of multiple toolbar items (i.e the back-button on the left corner)?

I am making an app where the first view the users see is a home screen with buttons that takes them to a second view. One of the second views present the user with a list of items. When the user clicks on one of these items the user comes to a detailed view of the item. When the user comes to the detailed view he is unfortunately presented with two toolbar buttons in the corner as can be seen here:
.
I know that one of the solutions is to only have one navigationview and that solves my problem. But I need to have toolbar items in my listview to be able to add more items, sort the list and have the list searchable which I'm not able to do without navigationView. I Have tried using scrollView and NavigationStack but it comes out blank.
Does anyone have an idea how to work with mulitple views, not getting double up "back buttons" on the toolbar and still have other toolbar items?
View one: (Home Screen):
NavigationView {
ZStack {
VStack {
Text(title)
.font(.custom("Glacial Indifference", size: 34, relativeTo: .headline).weight(.bold))
.multilineTextAlignment(.leading)
.foregroundColor(.white)
.tracking(10)
.padding(8)
.background(
Rectangle()
.fill(.gray)
.frame(width: 1000, height: 150)
.ignoresSafeArea()
.opacity(0.5))
Spacer()
}
VStack {
NavigationLink {
MapView()
} label: {
Buttons(str: "Cheese Map")
}
.padding(.bottom, 200)
}
VStack {
NavigationLink {
TabView()
} label: {
Buttons(str: "Cheese List")
}
.padding(.bottom, 400)
}
Second View (list):
NavigationView {
List {
ForEach(items, id: \.id) { item in
NavigationLink {
ItemView(item: item)
} label: {
ListItem(item: item)
}
}
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
showingAddItem = true
} label: {
Image(systemName: "plus")
Text("Add Item")
.font(.footnote)
.italic()
}
}
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Menu("Sort") {
Picker("Filter Options", selection: $selectedSort) {
ForEach(sortOptions, id: \.self) {
value in
Text(value)
.tag(value)
}
}
}
.onChange(of: selectedSort) { _ in
let sortBy = sorts[sortOptions.firstIndex(of: selectedSort)!]
items.sortDescriptors = sortBy.descriptors
}
}
}
.sheet(isPresented: $showingAddItems) {
AddItemsView(items: Items())
}
.navigationTitle("Item List")
.searchable(text: $searchText)
}
}
}
DetailView:
ScrollView {
ZStack {
VStack {
//More code...
Both .toolbar and .searchable find the nearest enclosing NavigationView automatically. You do not need a NavigationView in your list view.
Here's a self-contained demo. It looks like this:
Here's the code:
import SwiftUI
import PlaygroundSupport
struct HomeScreen: View {
var body: some View {
NavigationView {
List {
NavigationLink("Cheese Map") { Text("Map") }
NavigationLink("Cheese List") { ListView() }
}
.navigationTitle("Home Screen")
}
.navigationViewStyle(.stack)
}
}
struct ListView: View {
#State var items = ["Cheddar", "Swiss", "Edam"]
#State var search: String = ""
var filteredItems: [String] {
return items.filter {
search.isEmpty
|| $0.localizedCaseInsensitiveContains(search)
}
}
var body: some View {
List(filteredItems, id: \.self) {
Text($0)
}
.searchable(text: $search)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
withAnimation {
items.append("Gouda")
}
} label: {
Label("Add Item", systemImage: "plus")
}
.disabled(items.contains("Gouda"))
}
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Menu("Sort") {
Button("Ascending") {
withAnimation {
items.sort()
}
}
Button("Descending") {
withAnimation {
items.sort()
items.reverse()
}
}
}
}
}
.navigationTitle("Cheese List")
}
}
PlaygroundPage.current.setLiveView(HomeScreen())

Swiftui invisible bar on the top of the screen

I have the following in problem.
If I try to give a view a color (Color.red for example)
I get the following output:
I can just add .edgesignoressafearea(.top) and the top also gets red. But when I want to add an clickable item, the user won't be able to click it since there still is this invisible bar on the top of the screen. Does Anyone know what my problem is? The problem is in all the tabable views(Timeline, Favorits, Discover, Account). So It must be in either the first code or in tabview (second code) that I send in this post.
When the user clicks on the app first they get this view sending the user to the signin view or the app itself:
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: tabView().navigationBarHidden(true), isActive: $tabview, label: { EmptyView() })
NavigationLink(destination: loginView().navigationBarHidden(true), isActive: $login, label: { EmptyView() })
if tabview == false && login == false {
Text("loading")
.onAppear(perform: checklogin)
}
}
.navigationBarBackButtonHidden(true)
.navigationBarHidden(true)
}
}
Then the apps sends them to tabview:
var body: some View {
TabView(selection: $selection) {
Timeline()
.tabItem {
Label("timeline", systemImage: "house")
}
.tag(0)
Favorits()
.tabItem {
Label("favorits", systemImage: "heart")
}
.tag(1)
Discover()
.tabItem {
Label("discover", systemImage: "network")
}
.tag(2)
Account()
.tabItem {
Label("account", systemImage: "person")
}
.tag(3)
}
.navigationBarBackButtonHidden(true)
.navigationBarHidden(true)
}
The problem happens on all of this views.
This is the view where I made the screenshot of:
var body: some View {
ZStack {
Color.red
Text("Hello fav!")
}
.navigationBarBackButtonHidden(true)
.navigationBarHidden(true)
}
add .edgesIgnoringSafeArea(.top) and remove your navigation bar if you don't want to be able to get back to your login view anytime.
That's the full code with a login page, now it depends what you want on that login page :
import SwiftUI
struct ContentView: View {
#State var showLoginView: Bool = false
var body: some View {
VStack {
if showLoginView {
MainView()
} else {
Button("Login") {
self.showLoginView = true
}
}
}
}
}
struct MainView: View {
var body: some View {
TabView {
TimeLine()
.tabItem {
Label("timeline", systemImage: "house")
}
Favorits()
.tabItem {
Label("favorits", systemImage: "heart")
}
Discover()
.tabItem {
Label("discover", systemImage: "network")
}
Account()
.tabItem {
Label("account", systemImage: "person")
}
}
}
}
struct TimeLine: View {
var body: some View {
Color.blue
.edgesIgnoringSafeArea(.top)
}
}
struct Favorits: View {
var body: some View {
ZStack {
Color.red
.edgesIgnoringSafeArea(.top)
Text("Hello fav!")
}
}
}
struct Discover: View {
var body: some View {
Color.yellow
.edgesIgnoringSafeArea(.top)
}
}
struct Account: View {
var body: some View {
Color.purple
.edgesIgnoringSafeArea(.top)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
You need to set the navigation Title to "" because your View is embedded in NavigationView
Create a ViewModifier like this and apply it to your VStack
struct HiddenNavBarModifier: ViewModifier {
func body(content: Content) -> some View {
content
.navigationBarTitle("", displayMode: .inline)
.navigationBarHidden(true)
}
}

SwiftUI NavigationView Starting Inside Itself

So I've got a NavigationView embedded inside of a TabView that is in the Page View Style. Upon first load the NavigationView will start inside itself, and then once reloaded it shows normally. I am unsure as to what is causing this. And I've made a GIF to better illustrate the problem:
And here is my code:
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
SettingsView()
EmptyView()
EmptyView()
EmptyView()
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
}
}
struct SettingsView: View {
var body: some View {
NavigationView {
List {
Section(header: Text("General")) {
NavigationLink(destination: SettingsItem(title: "1")) {
Text("1")
}
NavigationLink(destination: SettingsItem(title: "2")) {
Text("2")
}
NavigationLink(destination: SettingsItem(title: "3")) {
Text("3")
}
NavigationLink(destination: SettingsItem(title: "4")) {
Text("4")
}
}
}
.listStyle(InsetGroupedListStyle())
.navigationTitle("Settings")
}
}
}
struct SettingsItem: View {
#State var title = "Title"
var body: some View {
NavigationView {
List {
}
}
.navigationTitle(title)
}
}
struct EmptyView: View {
var body: some View {
ZStack{
Color.green
Text("Empty View")
.padding()
}
.border(Color.black, width: 8)
}
}
Believe it or not, this has nothing to do with the TabView(). This is actually an issue in the SettingsView(). SwiftUI is defaulting to a split view. As a result, you are getting an empty view because you haven't actually navigated to any particular view, with the Settings back button showing. The fix to make this behave as you would expect is to add a .navigationViewStyle(StackNavigationViewStyle() on the NavigationView in SettingsView which forces SettingsView to display as a single view like this:
struct ContentView: View {
var body: some View {
TabView {
SettingsView()
BlankView(color: .green)
BlankView(color: .blue)
BlankView(color: .red)
}
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
}
}
struct SettingsView: View {
var body: some View {
NavigationView {
List {
Section(header: Text("General")) {
NavigationLink(destination: SettingsItem(title: "1")) {
Text("1")
}
NavigationLink(destination: SettingsItem(title: "2")) {
Text("2")
}
NavigationLink(destination: SettingsItem(title: "3")) {
Text("3")
}
NavigationLink(destination: SettingsItem(title: "4")) {
Text("4")
}
}
}
.listStyle(InsetGroupedListStyle())
.navigationTitle("Settings")
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct SettingsItem: View {
#State var title = "Title"
var body: some View {
NavigationView {
List {
}
}
.navigationTitle(title)
}
}
struct BlankView: View {
let color: Color
var body: some View {
ZStack{
color
Text("Blank View")
.padding()
}
.border(Color.black, width: 8)
}
}
Also, I edited your code a bit for clarity. EmptyView() is already designated by the system, so I changed it to BlankView() I am not sure why the compiler doesn't have a problem with you naming a struct EmptyView(), but you really shouldn't use that name. I also had the BlankView() show different colors so it was clear you were navigating to separate views.

Sticky footer in List SwiftUI

I am trying to implement a sticky footer in a List View in SwiftUI
It doesn't seem to operate the same as the header per say. This is an example of a sticky header implementation
List {
ForEach(0..<10) { index in
Section(header: Text("Hello")) {
ForEach(0..<2) { index2 in
VStack {
Rectangle().frame(height: 600).backgroundColor(Color.blue)
}
}
}.listRowInsets(EdgeInsets())
}
}
This above gives a sticky header situation. Although, once I change Section(header: ... to Section(footer:... it doesn't seem to be sticky anymore, it's simply places at the end of the row.
A more explicit reference
List {
ForEach(0..<10) { index in
Section(footer: Text("Hello")) {
ForEach(0..<2) { index2 in
VStack {
Rectangle().frame(height: 600).backgroundColor(Color.blue)
}
}
}.listRowInsets(EdgeInsets())
}
}
Does anyone have any solutions for this?
With the latest on SwiftUI (2) we now have access to a few more API's
For starters we can use a LazyVStack with a ScrollView to give us pretty good performance, we can then use the pinnedViews API to specify in an array which supplementary view we want to pin or make sticky. We can then use the Section view which wraps our content and specify either a footer or header.
** This code is working as of Xcode beta 2 **
As for using this in a List I'm not too sure, will be interesting to see the performance with List vs Lazy...
struct ContentView: View {
var body: some View {
ScrollView {
LazyVStack(spacing: 10, pinnedViews: [.sectionFooters]) {
ForEach(0..<20, id: \.self) { index in
Section(footer: FooterView(index: index)) {
ForEach(0..<6) { _ in
Rectangle().fill(Color.red).frame(height: 100).id(UUID())
}
}
}
}
}
}
}
struct FooterView: View {
let index: Int
var body: some View {
VStack {
Text("Footer \(index)").padding(5)
}.background(RoundedRectangle(cornerRadius: 4.0).foregroundColor(.green))
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
You can use overlay on the List:
struct ContentView: View {
#State private var selectedTab = 0
var body: some View {
TabView(selection: $selectedTab) {
VStack {
List {
ForEach(0..<20, id: \.self) { _ in
Section {
Text("Item 1")
Text("Item 2")
Text("Item 3")
}
}
}
.listStyle(InsetGroupedListStyle())
.overlay(
VStack {
Spacer()
Text("Updated at: 5:26 AM")
.font(.footnote)
.foregroundColor(.secondary)
.frame(maxWidth: .infinity)
}
)
}
.tabItem {
Label("First", systemImage: "alarm")
}
Text("Content 2")
.tabItem {
Label("Second", systemImage: "calendar")
}
}
}
}

.onDisappear() method isn't triggered when clicking the NavigationLink, SwiftUI

NavigationView {
List{
ForEach(self.data.firebaseObj.lists, id: \.self) { item in
NavigationLink(
destination: DetailView(
list: item,
listIndex: self.data.firebaseObj.lists.firstIndex(of: item) ?? -1
).environmentObject(self.data)
){
Text(item.name)
}
}
.onDelete(perform: delete)
}
.navigationBarTitle(Text("Le liste").font(.largeTitle), displayMode: .inline)
.navigationBarItems(
leading: SignOutButton(),
trailing: Button(action: {
self.show_modal = true
}) {Image(systemName: "plus")}.sheet(isPresented: self.$show_modal) {
AddListForm(email: self.session.session!.email!).environmentObject(self.data)
})
}.onAppear(
perform:{
self.data.createList(username: self.session.session!.email!)
})
.onDisappear(
perform: {
self.data.listener.remove()
print("should be removed")
})
That's the code I have and, as written in the title, clicking on the NavigationLink doesn't trigger the .onDisappear() method. Instead, changing to another tab view works fine. Am I doing something wrong or is this just the way it is supposed to work? In the second case, is there a simple way to execute some code when clicking on a NavigationLink?
This is how it is supposed to work, because your DetailView is a subview of your MainView if you use NavigationLink. That is why your MainView isn't really disappearing programmatically speaking.
Nevertheless you can do it like this:
struct ContentView: View {
#State private var showDetailView = false
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DetailView(), isActive: $showDetailView) {
Button(action: {
print("should be removed")
self.showDetailView = true
}, label: {
Text("Listitem")
})
}
Spacer()
}.navigationBarTitle(Text("MainView"))
}
}
}
struct DetailView: View {
var body: some View {
Text("DetailView")
}
}