SwiftUI NavigationView: Keeping back button while removing whitespace - ios

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

Related

SwiftUI mysterious leading/trailing padding in List?

Inside my SwiftUI List view are scrollable posts that have a mysterious leading & trailing padding that I cannot figure out where is coming from? (See image encircled in blue)
I want the post images to extend all the way to the edges of the phone screen so there is no white-space at all.
I put a background Color of red on my PostCell, and as you can see in the image, the padding doesn't appear to be coming from the PostCell because you see the leading/trailing white padding on either side of it.
Below is my complete view hierarchy as is in my app and there is no padding, which seems to be about 30 points, anywhere in the hierarchy.
I am assuming this padding is being applied by default somewhere??
Any ideas on how to get rid of this padding?
ROOT VIEW
struct RootView: View {
var body: some View {
TabView {
// OTHER VIEW
// OTHER VIEW
// OTHER VIEW
// OTHER VIEW
ProfileView(profileVM: ProfileViewModel())
}
}
}
PARENT VIEW
struct ProfileView: View {
#StateObject var profileVM: ProfileViewModel
#State private var presentSettings: Bool = false
var body: some View {
NavigationView {
List {
ProfileContent(profileVM: profileVM)
}
.clipped()
.listStyle(PlainListStyle())
.refreshable {
Task.detached { await profileVM.loadData() }
}
.navigationViewStyle(StackNavigationViewStyle())
.navigationBarTitle(Text(""), displayMode: .inline)
.background (
NavigationLink("", destination: ListView(profileVM: profileVM), isActive: $profileVM.presentPostView).isDetailLink(false)
)
}
}
}
CHILD VIEW
struct ListView: View {
#Environment(\.presentationMode) var presentation
#ObservedObject var profileVM: ProfileViewModel
var body: some View {
ScrollViewReader { proxy in
List(profileVM.usersPosts, id: \.id) { post in
PostCell(post: post)
.background(Color.red)
}
.clipped()
.listStyle(PlainListStyle())
.navigationViewStyle(StackNavigationViewStyle())
.navigationBarTitle(Text("your posts"), displayMode: .inline)
.navigationBarBackButtonHidden(true)
.toolbar(content: {
ToolbarItem(placement: .navigationBarLeading) {
ZStack {
Button(action: {print("action")}, label: {Text("Press")})
}.padding(.leading, 10)
}
})
}
}
}
I assume you need to zero list row insets
PostCell(post: post)
.background(Color.red)
.listRowInsets(EdgeInsets()) // << 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")
}
}

NavigationTitle visual glitches - transparent and not changing state from .large to .inline on scroll

The .navigationTitle on some views seem to be having some problems. On some views (and only some of the time), the .navigationTitle will not change from .large to .inline as would be expected. Instead, the title stays in place when scrolling up, and the navigation bar is completely invisible (as outlined in the video below). This is all reproducible every time.
Video of reproducible .navigationTitle bugs
I haven't found any people on stack overflow or the Apple Developer forums who have run into this exact issue. There have some people who have produced similar results as this, but those were all fixed by removing some stylizing code to the .navigationbar, of which I am not making any modifications to it anywhere in my code.
Below are some snippets of my code:
import SwiftUI
struct WelcomeUI: View {
var body: some View {
NavigationView {
VStack {
//NavigationLink(destination: SignupUI(), label: {
//Text("Sign Up")
//}
NavigationLink(destination: LoginUI(), label: {
Text("Log In")
})
}
}
}
}
struct LoginUI: View {
var body: some View {
VStack {
NavigationLink(destination: MainUI(), label: { Text("Log In") })
//Button(action: { ... }
}
.navigationBarHidden(false)
}
}
struct MainUI: View {
#State var selectedTab: Views = .add
var body: some View {
TabView(selection: $selectedTab) {
SpendingView()
.tabItem {
Image(systemName: "bag.circle")
Text("Spending")
}.tag(Views.spending)
Text("Adding View")
.tabItem {
Image(systemName: "plus")
Text("Add")
}.tag(Views.add)
Text("Edit View")
.tabItem {
Image(systemName: "pencil")
Text("Edit")
}.tag(Views.edit)
SettingsView()
.tabItem {
Image(systemName: "gear")
Text("Settings")
}.tag(Views.settings)
}
.navigationBarTitle(Text(selectedTab.rawValue))
.navigationBarBackButtonHidden(true)
}
}
enum Views: String {
case spending = "Spending"
case add = "Add"
case edit = "Edit"
case settings = "Settings"
}
struct SettingsView: View {
var body: some View {
VStack{
ZStack {
Form {
Section(header: Text("Section Header")) {
NavigationLink(destination: WelcomeUI()) {
Text("Setting Option")
}
}
Section {
//Button("Log Out") {
//self.logout()
//}
Text("Log Out")
}
}
Button("say-high", action: {print("Hi")})
}
}
}
}
struct SpendingView: View {
var body: some View {
ScrollView{
Text("SpendingView")
NavigationLink("subSpending", destination: SubSpendingView())
}.padding()
}
}
struct SubSpendingView: View {
var body: some View {
ScrollView{
Text("SubSpendingView")
}.navigationBarTitle("SubSpending")
}
}
It almost seems like a bug in SwiftUI itself just because the fact that bringing down the control centre makes it kind of work, but with no animation (as seen in the video). Also, changing which view is selected first in #State var selectedTab: Views seems to let the view selected to work as expected, but lets the rest of the tabs mess up.
When I build and run the app on my iPad, it behaves as expected with no bugs, it's only when run on my iPhone and the iOS simulator on Mac that it does this, any way to fix this?
For this to work flawlessly the ScrollView needs to be the direct child of the NavigationView. I ran into a similar issue with wanting to dismiss the TabView when I navigating but SwiftUI won't let that happen. Each tab needs to be a NavigationView and you need to dismiss the TabView creatively if that is what you want.
TabView {
NavigationView {
ScrollView {
// your view here
}
}.tabItem {
// tab label
}
// etc
}
Essentially the navigation view needs to be a child (in the brackets) of the tab view and the scrollview needs to be the direct child of the navigation view.
Use navigationBarTitle("Title") and navigationBarBackButtonHidden(true) on the TabView's sub-view, not on itself.
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
}
.navigationBarTitle("Title")
.navigationBarBackButtonHidden(true)
}
}
}

Why background color of List is different while presenting view in SwiftUI?

I am implementing List in Presented view (AddItemView). I want background color same as List in any view.
struct HomeView: View {
#State private var showAddItemView: Bool = false
var body: some View {
NavigationView {
List(0..<9, id: \.self) { i in
Text("Row \(i)")
}
.navigationTitle("Home")
.navigationBarItems(trailing:
Button("Add") {
showAddItemView.toggle()
})
.sheet(isPresented: $showAddItemView) {
AddItemView()
}
}
}
}
struct AddItemView: View {
init(){
UITableView.appearance().backgroundColor = .clear
}
var body: some View {
NavigationView {
List(0..<9, id: \.self) { i in
Text("Row \(i)")
}.background(Color(UIColor.systemGroupedBackground))
.listStyle(InsetGroupedListStyle())
.navigationBarTitle("Add Item View", displayMode: .inline)
}
}
}
Above code is creating simple List with InsetGroupedListStyle. But background colour is different while Presenting view (AddItemView in my case).
I have already tried https://stackoverflow.com/a/58427518/7084910
How to set background color of List in presented view as in any normal list. Red/Yellow/Green can set to List, "BUT" I want same as normal list in HomeView that will work in light & dark mode.
Use this:
var body: some View {
NavigationView {
List(0..<9, id: \.self) { i in
Text("Row \(i)")
}
.colorMultiply(Color.red)
}
}
They think it is better visual representation for .sheet (probably to make it more determinable)...
SwiftUI 2.0
The .fullScreenCover gives what you want. Alternate is to present AddItemView manually using some transition.
.navigationBarItems(trailing:
Button("Add") {
showAddItemView.toggle()
})
.fullScreenCover(isPresented: $showAddItemView) {
AddItemView()
}

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