SwiftUI mysterious leading/trailing padding in List? - ios

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

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

SwiftUI NavigationBar not extending to top of iPhone screen with ScrollView

In my SwiftUI View I have a ScrollView filled with text for attribution to show what frameworks were used in my app.
Once this text scrolls past the navigation bar it should be hidden past that point to the very top of the iPhone screen BUT in my case the text reappears because my navigation bar seems to only be a certain height (see image below) and does not extend to the top of the screen and prevent text from reappearing.
The attribution view below is inside of a AboutView which is also inside of a SettingsView that contains a NavigationView.
Any idea why this is happening? Image is attached..
TEXT RE-APPEARING PAST NAVIGATION BAR
PARENT VIEW
struct Settings: View {
#Environment(\.presentationMode) var presentation
#State private var presentAbout: Bool = false
var body: some View {
NavigationView {
VStack {
Text("About").onTapGesture { presentAbout.toggle() }
}
.navigationBarTitle("Settings", displayMode: .inline)
.navigationBarBackButtonHidden(true)
.toolbar(content: {
Button(action: {
self.presentation.wrappedValue.dismiss()
}, label: { Text("Cancel") })
})
.background ( NavigationLink("", destination: AboutMenu(), isActive: $presentAbout ))
}
}
}
SUB VIEW 1
struct AboutMenu: View {
#Environment(\.presentationMode) var presentation
#State private var presentAttribution: Bool = false
var body: some View {
VStack {
Text("Attribution").onTapGesture { presentAttribution.toggle() }
}
.navigationBarTitle("About", displayMode: .inline)
.navigationBarBackButtonHidden(true)
.toolbar(content: {
ToolbarItem(placement: .navigationBarLeading) {
Button(action: { self.presentation.wrappedValue.dismiss() }, label: {
Image(systemName: "chevron.backward")
}
})
.background ( NavigationLink("", destination: Attribution(), isActive: $presentAttribution))
}
}
SUB VIEW 2 Where problem exists.
struct Attribution: View {
#Environment(\.presentationMode) var presentation
var body: some View {
VStack {
ScrollView {
Text(attribution) // <- THIS TEXT shows behind NavigationBar past the navigation bar.
}
}
.navigationBarTitle("Attribution", displayMode: .inline)
.navigationBarBackButtonHidden(true)
.toolbar(content: {
ToolbarItem(placement: .navigationBarLeading) {
Button(action: { self.presentation.wrappedValue.dismiss() }, label: {
Image(systemName: "chevron.backward")
}
})
}
}
This can happen when ScrollView is not root view of the NavigationView, so it does not recognise scroll view presence.
It can be fixed by explicit clipping, like
VStack {
ScrollView {
Text(introText)
}
.clipped() // << here !!

Swift UI Clicking navigation bar link hides status bar on back

I wrote a simple Swift UI app that creates a NavigationLink on the navigation toolbar and creates a status bar at the bottom of the display. When clicking on the gear link on the navigation bar, it takes you to the child view, but when you return to the parent view, the status bar gets hidden. If you click on the NavigationLink in the middle of the screen and return to the parent view, the status bar gets displayed again.
This looks like a bug in Swift UI and does anyone know how to fix?
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(destination: Text("Child view")) {
Text("Hello, World!")
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing, content: {
NavigationLink(destination: Text("Settings view"),
label: { Image(systemName: "gearshape.fill")
})
})
ToolbarItem(placement: .status, content: {
Text("Checking for messages...")
})
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
The issue is that Swift UI doesn't handle NavigationLink properly inside a toolbar.
The workaround is to place a Button into a toolbar and use a hidden NavigationLink in the code.
This is a link to the answer that resolved my issue.
SwiftUI - Make toolbar's NavigationLink use detail view
Here is the code that implements my original code with the workaround.
struct ContentView: View {
#State var settingsLinkSelected = false
var body: some View {
NavigationView {
NavigationLink(destination: Text("Second view")) {
Text("Hello, World!")
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing,
content: { Button(action: { settingsLinkSelected = true },
label: { Image(systemName: "gearshape.fill") }) })
ToolbarItem(placement: .status,
content: { Text("Checking for messages...") })
}
.background(
NavigationLink(
destination: Text("Settings View"),
isActive: $settingsLinkSelected
) {
EmptyView()
}.hidden()
)
}
}
}

Extra navigation bar area shown in swiftUI when view is navigated to by navigation link

On view with navigation view
NavigationLink(destination: FruitySortedListView(shake: shake), isActive: $showingFruity) { EmptyView() }
On view shown in picture:
.navigationTitle("Navigation Title")
IMAGE: You can see that there is a lot of extra space
Consider this example this works perfectly without the extra space as in the Image.
import SwiftUI
struct ContentView: View {
#State var isActive = true
var body: some View {
NavigationView {
NavigationLink(destination: EmptyView(), isActive: $isActive) {
VStack {
Text("Hi")
Spacer()
}
.background(Color.red)
}
.navigationTitle("Navigation Title")
}
}
}
struct EmptyView: View {
var body: some View {
VStack {
Text("navigation")
Spacer()
}
.background(Color.red)
.navigationTitle("EmptyView")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

SwiftUI disappear back button with navigationLink

I have 3 views. One of these have NavigationView second have NavigationLink and last just a child with toolbar.
So my problem when I added toolbar in last view backButton elegant disappear. How can I solve this?
Screen recording of my problem
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
Text("Hello, world!")
.padding()
NavigationLink(destination: ListView()) {
Image(systemName: "trash")
.font(.largeTitle)
.foregroundColor(.red)
}
}.navigationBarHidden(true)
.navigationTitle("Image")
}
}
}
import SwiftUI
struct ListView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
VStack {
List {
NavigationLink(destination: DetailView()) {
Text("Detail")
}
}
}.navigationBarTitle(Text("Data"), displayMode: .large)
.toolbar {
Button("Save") {
presentationMode.wrappedValue.dismiss()
}
}
}
}
import SwiftUI
struct DetailView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
VStack {
Text("DetailView")
.padding()
}.navigationBarTitle(Text("Data"), displayMode: .large)
.toolbar {
Button("Save") {
presentationMode.wrappedValue.dismiss()
}
}
}
}
In the console, you'll notice this message:
2021-04-27 12:37:36.862733-0700 MyApp[12739:255441] [Assert] displayModeButtonItem is internally managed and not exposed for DoubleColumn style. Returning an empty, disconnected UIBarButtonItem to fulfill the non-null contract.
The default style for NavigationView is usually DefaultNavigationViewStyle, which is really just DoubleColumnNavigationViewStyle. Use StackNavigationViewStyle instead, and it works as expected.
Edit: You are right that StackNavigationViewStyle will break iPad split view. But thankfully, DoubleColumnNavigationViewStyle works fine in iPad and doesn't hide the back button. We can then just use a different NavigationStyle depending on the device, as shown in this answer.
struct ResponsiveNavigationStyle: ViewModifier {
#Environment(\.horizontalSizeClass) var horizontalSizeClass
#ViewBuilder
func body(content: Content) -> some View {
if horizontalSizeClass == .compact { /// iPhone
content.navigationViewStyle(StackNavigationViewStyle())
} else { /// iPad or larger iPhone in landscape
content.navigationViewStyle(DoubleColumnNavigationViewStyle())
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
Text("Hello, world!")
.padding()
NavigationLink(destination: ListView()) {
Image(systemName: "trash")
.font(.largeTitle)
.foregroundColor(.red)
}
}
.navigationBarHidden(true)
.navigationTitle("Image")
}
.modifier(ResponsiveNavigationStyle()) /// here!
}
}
Result:
iPad
iPhone
I don't know why, but it's what worked for me:
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button { } label: { } // button to the right
}
ToolbarItem(placement: .navigationBarLeading) {
Text("") // empty text in left to prevent back button to disappear
}
}
I already tried to replace the empty text with EmptyView() but the button keeps disappearing.
FYI: I have this problem only on my device with iOS 14, but in another device with iOS 15 the back button never disappears.

Resources