SwiftUI NavigationBar not disappearing while scrolling - ios

I want to hide my NavigationBar while scrolling, actually It must hide automatically but when I tried with multiple views It doesn't work. Also, It works when I remove custom views and capsulate List with NavigationView. But I need SearchBar and StatusView view. Is there any suggestion?
By the way, I run it on the device, I use canvas here for demonstration purposes.
Thank you.
var body: some View {
NavigationView {
VStack(spacing: 0) {
SearchBar(searchText: $viewModel.searchText)
StatusView(status: $viewModel.status)
Divider()
List(0...viewModel.characters.results.count, id: \.self) { index in
if index == self.viewModel.characters.results.count {
LastCell(vm: self.viewModel)
} else {
ZStack {
NavigationLink(destination: DetailView(detail: self.viewModel.characters.results[index])) {
EmptyView()
}.hidden()
CharacterCell(character: self.viewModel.characters.results[index])
}
}
}
.navigationBarTitle("Characters", displayMode: .large)
}
}
.onAppear {
self.viewModel.getCharacters()
}
}

Just idea, scratchy... try to put your custom views inside List as below (I know it will work, but I'm not sure if autohiding will work)
NavigationView {
List {
SearchBar(searchText: $viewModel.searchText)
StatusView(status: $viewModel.status)
Divider()
ForEach (0...viewModel.characters.results.count, id: \.self) { index in
...

Based on Asperi's solution, I wanted to have the SearchBar and StatusView always visible, i.e. it should stop scrolling after the title has disappeard. You can achieve this with a section header like shown below (just a rough sketch):
NavigationView {
List {
Section(header: {
VStack {
SearchBar...
StatusView....
}
}) {
ForEach...
}
}
}

Related

SwiftUI NavigationView: Keeping back button while removing whitespace

Screen shot of white space
I want to remove the empty space below the <Back button in the second navigation view. I know that this question has been asked several times before, but I have not been able to find a good solution that does this.
I have tried
.navigationBarTitle("")
.navigationBarHidden(true)
and
.navigationBarTitle("", displayMode: .inline)
without the desired result.
Any hints that could help me?
struct SecondNavView: View {
let item: String
var body: some View {
ZStack {
Color.red
Text(item)
}
}
}
struct FirstNavView: View {
let listItems = ["One", "Two", "Three"]
var body: some View {
NavigationView {
List(listItems, id: \.self) { item in
NavigationLink(destination: SecondNavView(item: item)) {
Text(item).font(.headline)
}
}
}
}
}
I assume it is do to place of applied modifiers.
The following works (tested with Xcode 13.4 / iOS 15.5)
struct SecondNavView: View {
let item: String
var body: some View {
ZStack {
Color.red
Text(item)
}
.navigationBarTitleDisplayMode(.inline) // << here !!
}
}
It seens like your parent View hasn't a title, to solve this you need to set .navigationTitle inside NavigationView on parent View like this:
NavigationView {
VStack {
//....
}
.navigationTitle(Text("Awesome View"))
.toolbar {
ToolbarItem(placement: .principal){
// Put any view (Text, Image, Stack...) you want here
}
}
}

Navigation bar title is stuck

If I use a toolbar for the keyboard which has a ScrollView inside it messes up the navigation bar title which will just be positioned stuck at the screen instead of moving in the navigation bar.
Does anyone have a solution for this issue?
(Xcode 13.4.1)
Minimal reproducible code:
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
#State var numbers = Array(1...100).map { String($0) }
var body: some View {
NavigationView {
List($numbers, id: \.self) { $number in
TextField("", text: $number)
}
.toolbar {
ToolbarItem(placement: .keyboard) {
ScrollView(.horizontal) {
HStack {
Text("Hello")
Text("World")
}
}
}
}
.navigationTitle("Messed up title")
}
}
}
It seems like you are trying to add 100 toolbar item elements inside your keyboard which is causing performance issue and impacting on your navigation bar which could be issue in lower Xcode version compatibility. if you want to show 100 toolbar item elements then instead of adding inside keyboard add separate View and add on top of it and then based on keyboard appear or disappear hide/show 100 elements view accordingly. So I modify your code which is adding two toolbar items elements inside your keyboard and that seems to be working fine without any navigation title stuck issue eg below:-
var body: some View {
NavigationView {
List($numbers, id: \.self) { $number in
TextField("", text: $number)
}.toolbar {
ToolbarItem(placement: .keyboard) {
HStack {
Button("Cancel") {
print("Pressed")
}
Spacer()
Button("Done") {
print("Pressed")
}
}
}
}.navigationTitle("Messed up title")
}
}
Edited Answer if you want to use ScrollView, instead of using List use ScrollView like below
Please note this changes are required only if you are using lower Xcode version prior than Xcode 14
var body: some View {
NavigationView {
ScrollView {
ForEach($numbers, id: \.self) { number in
VStack {
TextField("", text: number)
}
}
}.toolbar {
ToolbarItem(placement: .keyboard) {
ScrollView(.horizontal) {
HStack {
Text("Hello")
Text("World")
}
}
}
}.navigationTitle("Messed up title")
}
}

How to have a NavigationLink with swipeActions in a List in SwiftUI

Consider a List inside of a NavigationView like this:
struct ContentView: View {
var body: some View {
NavigationView {
List {
Text("Shovel")
Text("Bucket")
Text("Sieve")
}.navigationTitle("Sandbox")
}
}
}
I want to add .swipeActions to the trailing edge of the list entries like so:
Text("Shovel")
.swipeActions(edge: .trailing) {
Button{} label: { Image(systemName: "trash.fill") }
}
But once I embed the list entry inside of a NavigationLink, the .swipeActions don't work anymore.
NavigationLink(destination: Text("Shovel")) {
Text("Shovel")
.swipeActions(edge: .trailing) {
Button{} label: { Image(systemName: "trash.fill") }
}
}
Now onto the question:
Why is that and how can I have both of these features?
Thanks for your help.
You should add the SwipeAction to the NavigationLink like so:
NavigationLink(destination: Text("Shovel")) {
Text("Shovel")
}.swipeActions(edge: .trailing) {
Button{} label: { Image(systemName: "trash.fill") }
}
This behavior happens because the swipe action is a modifier meant for a List Row. When you had only Text, it was the row.
However, When Embedding your content in any View (VStack, HStack, NavigationLink...), that parent becomes the Row.

SwiftUI List Showing as Blank for non-empty List

I am trying to create a simple list view in SwiftUI however, it shows up as blank (pictured below) no matter how many objects are in the array. If I get rid of the list, it will show the elements of the array. I know for a fact that the array is not empty. Any recommendations on how to fix it.
This is my code"
var body: some View {
NavigationView {
ScrollView {
if(leaderboard.isEmpty) {
VStack {
Text("No Entries Yet :(")
.foregroundColor(.gray)
.font(.largeTitle)
}
} else {
ScrollView {
List {
ForEach(leaderboard.asArray(), id: \.self) { score in
Text("\(score)")
}
}
}
}
}
.navigationBarTitle("Leaderboard")
}
}
Here is what the view currently shows:
Also, bonus points if you can help me make it so the Text("No Entries Yet :( ") is centered vertically within the view.
You don't need either of those ScrollViews - If the list is empty you only have the text view. If the list isn't empty, List provides the necessary scroll view.
To vertically centre the text when the list is empty, simply add Spacer views above and below:
struct ContentView: View {
var body: some View {
let leaderboard:[Int] = [10,20,30,40,100,1999,1393,444]
NavigationView {
if(leaderboard.isEmpty) {
VStack {
Spacer()
Text("No Entries Yet :(")
.foregroundColor(.gray)
.font(.largeTitle)
Spacer()
}
} else {
List {
ForEach(leaderboard, id: \.self) { score in
Text("\(score)")
}
}
}
}
.navigationBarTitle("Leaderboard")
}
}
If your list is empty, just add another row without background. Stylish it as you want.
if rows.isEmpty {
Text("No records")
.listRowBackground(EmptyView())
}

How to replace the current view in SwiftUI?

I am developing an app with SwiftUI.
I have a NavigationView and I have buttons on the navigation bar. I want to replace the current view (which is a result of a TabView selection) with another one.
Basically, when the user clicks "Edit" button, I want to replace the view with another view to make the edition and when the user is done, the previous view is restored by clicking on a "Done" button.
I could just use a variable to dynamically choose which view is displayed on the current tab view, but I feel like this isn't the "right way to do" in SwiftUI. And this way I could not apply any transition visual effect.
Some code samples to explain what I am looking for.
private extension ContentView {
#ViewBuilder
var navigationBarLeadingItems: some View {
if tabSelection == 3 {
Button(action: {
print("Edit pressed")
// Here I want to replace the tabSelection 3 view by another view temporarly and update the navigation bar items
}) {
Text("Edit")
}
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
TabView(selection: $tabSelection) {
ContactPage()
.tabItem {
Text("1")
}
.tag(1)
Text("Chats")
.tabItem() {
Text("2")
}
.tag(2)
SettingsView()
.tabItem {
Text("3")
}
.tag(3)
}.navigationBarItems(leading: navigationBarLeadingItems)
}
}
}
Thank you
EDIT
I have a working version where I simply update a toggle variable in my button action that makes my view display one or another thing, it is working but I cannot apply any animation effect on it, and it doesn't look "right" in SwiftUI, I guess there is something better that I do not know.
If you just want to add animations you can try:
struct ContentView: View {
...
#State var showEditView = false
var body: some View {
NavigationView {
TabView(selection: $tabSelection) {
...
view3
.tabItem {
Text("3")
}
.tag(3)
}
.navigationBarItems(leading: navigationBarLeadingItems)
}
}
}
private extension ContentView {
var view3: some View {
VStack {
if showEditView {
FormView()
.background(Color.red)
.transition(.slide)
} else {
Text("View 3")
.background(Color.blue)
.transition(.slide)
}
}
}
}
struct FormView: View {
var body: some View {
Form {
Text("test")
}
}
}
A possible alternative is to use a ViewRouter: How To Navigate Between Views In SwiftUI By Using An #EnvironmentObject.

Resources