NavigationBar won't collapse with TabView iOS 15 - swift

Not sure if this is a bug, but this is how to reproduce it in iOS 15 (didn't try lower versions)
In tab 1, scroll up (navigation bar will collapse properly)
Now, tap tab 2, but do not scroll the list
go back to tab 1. Now you should see navigation bar failed to collapse.
Source code:
struct TestNavigationView: View {
var body: some View {
NavigationView {
TabView {
LongList()
.tabItem({Text("Tab 1")})
LongList()
.tabItem({Text("Tab 2")})
}
.navigationTitle("Navigation Bar Test")
}
}
}
struct LongList: View {
var list = (1..<50).map {$0}
var body: some View {
List(list, id: \.self) { item in
NavigationLink {
Text("New view")
} label: {
Text("Item \(item)")
}
}
}
}
I have tried to twist the styles, but nothing worked. Any help would be appreciated! Thank you!

I believe that TabView and NavigationView are not good friends with each other.
If you avoid having the NavigationView around the TabView, you can achieve the same result. Move it to LongList, together with the .navigationTitle() modifier.
The following works fine:
struct TestNavigationView: View {
var body: some View {
// NavigationView {
TabView {
LongList(text: "One")
.tabItem({Text("Tab 1")})
LongList(text: "Two")
.tabItem({Text("Tab 2")})
}
// }
}
}
struct LongList: View {
var list = (1..<50).map {$0}
let text: String
var body: some View {
NavigationView {
List(list, id: \.self) { item in
NavigationLink {
Text("New view")
} label: {
Text("Item \(item) - \(text)")
}
}
.navigationTitle("Navigation Bar Test")
}
}
}

Related

Interacting with a confirmationDialog or alert is causing the parent view to pop

When you navigate and open the confirmation dialog. When you select Yes, No or Cancel the page that the app was on is dismissed and it takes you back to the form on the previous page.
We also found this happens with alerts too.
It's a simple enough app structure, top level tabs then a menu which links to sub pages.
Here is a quick demo of the bug:
We put together an example app that demonstrates this.
How can we prevent this from happening while also maintaining the app structure?
import SwiftUI
#main
struct testApp: App {
var body: some Scene {
WindowGroup {
NavigationView {
TabView() {
Form {
NavigationLink(destination: SubPage()) {
Image(systemName: "clock")
Text("Sub Page")
}
// This is where more menu options would be
}
.tag(1)
.tabItem {
Image(systemName: "square.grid.2x2")
Text("Tab 1")
}
// This is where more tab pages would be
}
}
}
}
}
struct SubPage: View {
#State private var confirmDialogVisible = false
var body: some View {
VStack {
Button{
confirmDialogVisible = true
} label: {
Text("popup")
}
}
.confirmationDialog("Confirm?", isPresented: $confirmDialogVisible) {
Button("Yes") {
print("yes")
}
Button("No", role: .destructive) {
print("no")
}
}
}
}
We are using XCode 14.1
And running on iOS 16.1
I usually use ViewModifer to keep consistency between tabs. One modifier for the root of the tab and one for the children.
///Modifier that uses `ToolbarViewModifier` and includes a `NavigationView`
struct NavigationViewModifier: ViewModifier{
func body(content: Content) -> some View {
NavigationView{
content
.modifier(ToolbarViewModifier())
}
}
}
///`toolbar` that can be used by the root view of the navigation
///and the children of the navigation
struct ToolbarViewModifier: ViewModifier{
let title: String = "Company Name"
func body(content: Content) -> some View {
content
.toolbar {
ToolbarItem(placement: .principal) {
VStack{
Image(systemName: "sparkles")
Text(title)
}
}
}
}
}
Then the Views use it something like this.
import SwiftUI
struct CustomTabView: View {
var body: some View {
TabView{
ForEach(0..<4){ n in
CustomChildView(title: n.description)
//Each tab gets a `NavigationView` and the shared toolbar
.modifier(NavigationViewModifier())
.tabItem {
Text(n.description)
}
}
}
}
}
struct CustomChildView: View {
let title: String
#State private var showConfirmation: Bool = false
var body: some View {
VStack{
Text(title)
NavigationLink {
CustomChildView(title: "\(title) :: \(UUID().uuidString)")
} label: {
Text("open child")
}
Button("show confirmation") {
showConfirmation.toggle()
}
.confirmationDialog("Confirm?", isPresented: $showConfirmation) {
Button("Yes") {
print("yes")
}
Button("No", role: .destructive) {
print("no")
}
}
}
//Each child uses the shared toolbar
.modifier(ToolbarViewModifier())
}
}
I stuck with NavigationView since that is what you have in your code but
if we take into consideration the new NavigationStack the possibilities of these two modifiers become exponentially better.
You can include custom back buttons that only appear if the path is not empty, return to the root from anywhere, etc.
Apple says that
Tab bars use bar items to navigate between mutually exclusive panes of content in the same view
Make sure the tab bar is visible when people navigate to different areas in your app
https://developer.apple.com/design/human-interface-guidelines/components/navigation-and-search/tab-bars/
Having the NavigationView or NavigationStack on top goes against these guidelines and therefore are the source of endless bugs, Especially when you take into consideration iPadOS.
Simple solution would be to use navigationDestination.
struct testApp: App {
#State var goToSubPage = false
var body: some Scene {
WindowGroup {
NavigationStack {
TabView() {
Form {
VStack {
Image(systemName: "clock")
Text("Sub Page")
}
.onTapGesture {
goToSubPage = true
}
// This is where more menu options would be
}
.navigationDestination(isPresented: $goToSubPage, destination: {
SubPage()
})
.tag(1)
.tabItem {
Image(systemName: "square.grid.2x2")
Text("Tab 1")
}
// This is where more tab pages would be
}
}
}
}
}
I tested it and it won't popped off itself anymore.

SwiftUI PageView update outside view

I have a PageStyle TabView, and want to update a Text that is outside of it.
I've used onAppear to get the page change event, and looks good at first if I scroll forward, but once I go backwards a few onAppear are missed.
Is this the correct way of doing this. Is is possible to do that?
struct PageView: View {
#State var title: String = "hello"
var body: some View {
VStack {
Text(self.title)
TabView {
ForEach(0..<100) { i in
Text("\(i)").onAppear {
self.title = ("TITLE = \(i)")
}
}
}
.tabViewStyle(PageTabViewStyle())
}
}
}
You can do this by using TabView(selection: and tag modifier on Text items, like below (tested with Xcode 12.1 / iOS 14.1)
var body: some View {
VStack {
Text(self.title)
TabView(selection: $title){ // << here !!
ForEach(0..<100) { i in
Text("\(i)")
.tag(String(i)) // << here !!
}
}
.tabViewStyle(PageTabViewStyle())
}
}

Extra elements for SwiftUI destination view

Can someone explain why there are 2 UI elements(UINavigationBarContentView, UINavigationBarLargeTitleView) between the Image(the blue rectangle) and navigation bar title(text One)?
The code I use is this:
let item: ImageNameModel
#State private var image: Image?
var body: some View {
NavigationView {
VStack {
if image != nil {
image?.resizable().scaledToFit()
} else {
Text("Image not loaded")
}
}
.padding([.horizontal, .bottom])
}
.navigationBarTitle(Text(item.name))
.onAppear(perform: loadImage)
}
It's probably because of the extra NavigationView.
You don't need to have a NavigationView wrapping your destination.
For example, with this sample code here:
struct RootView: View {
var body: some View {
NavigationView {
NavigationLink(destination: DestinationView()) {
Text("Click here")
}.navigationBarTitle(Text("Title"))
}
}
}
struct DestinationView: View {
var body: some View {
NavigationView {
Text("Destination")
.navigationBarTitle(Text("Destination"))
}
}
}
I get a result like this:
But If I remove the NavigationView from DestinationView, I get the result that is probably what you expect:

Two UINavigationControllers after using NavigationLink in sheet

I have a modal sheet that is presented from my home view as such:
Button(action: {
...
}) {
...
}
.sheet(isPresented: ...) {
MySheetView()
}
In MySheetView, there is a NavigationView and a NavigationLink to push another view onto its view stack (while I'm on MySheetView screen and use the view inspector, there's only one UINavigationController associated with it which is what I expect).
However, as soon as I get to my next view that is presented from MySheetView using the NavigationLink, and I use the view hierarchy debugger, there are TWO UINavigationControllers on-top of each other. Note, this view does NOT have a NavigationView inside it, only MySheetView does.
Does anyone know what's going on here? I have a feeling this is causing some navigation bugs im experiencing. This can be easily reproduced in an example app with the same structure.
Ex:
// These are 3 separate SwiftUI files
struct ContentView: View {
#State var isPresented = false
var body: some View {
NavigationView {
Button(action: { self.isPresented = true }) {
Text("Press me")
}
.sheet(isPresented: $isPresented) {
ModalView()
}
}
}
}
struct ModalView: View {
var body: some View {
NavigationView {
NavigationLink(destination: FinalView()) {
Text("Go to final")
}
}
}
}
struct FinalView: View {
var body: some View {
Text("Hello, World!")
}
}
I don't observe the behaviour you described. Used Xcode 11.2. Probably you need to provide your code to find the reason.
Here is an example of using navigation views in main screen and sheet. (Note: removing navigation view in main screen does not affect one in sheet).
import SwiftUI
struct TestNavigationInSheet: View {
#State private var hasSheet = false
var body: some View {
NavigationView {
Button(action: {self.hasSheet = true }) {
Text("Show it")
}
.navigationBarTitle("Main")
.sheet(isPresented: $hasSheet) { self.sheetContent }
}
}
private var sheetContent: some View {
NavigationView {
VStack {
Text("Properties")
.navigationBarTitle("Sheet")
NavigationLink(destination: properties) {
Text("Go to Inspector")
}
}
}
}
private var properties: some View {
VStack {
Text("Inspector")
}
}
}
struct TestNavigationInSheet_Previews: PreviewProvider {
static var previews: some View {
TestNavigationInSheet()
}
}

Q: SwiftUI - navigationBarTitle transparent error

UPDATE: Resolved - This topic is just a beta bug.
I'm having a problem with the navigationBarTitle in SwiftUI.
I'm using a NavigationLink to go to a second view with a list. This view has a navigationBarTitle like the first view. But in the second view I can push the list behind the navigationBarTitle.
When I'm going from a third view back to the second the navigationBarTitle works as it should be.
Does anyone else having this problem? I'm using Xcode Version 11.0 GM (11A419c)
Here is the code:
struct ContentView: View {
private var line : [String] = ["Line 1", "Line 2", "Line 3"]
var body: some View {
NavigationView {
List {
ForEach(line, id: \.self) { item in
NavigationLink(destination: DetailView()) {
TestCell(lineName: item)
}
}
}
.navigationBarTitle("Main View")
}
}
}
struct TestCell: View {
let lineName: String
var body: some View {
Text(lineName)
}
}
struct DetailView: View {
var body: some View {
List() {
NavigationLink(destination: DetailDetailView()) {
Text("To the next view")
.foregroundColor(Color.red)
}
}
.navigationBarTitle("Detail View")
}
}
struct DetailDetailView: View {
var body: some View {
Text("Hello World!")
.navigationBarTitle("Detail Detail View")
}
}