Navigation bar title stays inline in iOS 15 - swiftui

I have a NavigationView that contains a ScrollView with a large title. When I navigate to a page with an inline title, then navigate back, the title stays inline rather than reverting to the large title.
This only occurs in iOS 15 - in iOS 14 the title reverts back to the large title as desired. Is there a way to achieve the desired behavior in iOS 15?
Here's an example that illustrates the behavior:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
ScrollView {
NavigationLink("Link", destination: DestinationView())
.padding()
}
.navigationBarTitle("Home", displayMode: .large)
}
}
}
struct DestinationView: View {
var body: some View {
Text("Destination")
.navigationBarTitle("test", displayMode: .inline)
}
}

Related

SwiftUI how to align the navigation bar title on center when user taps on a navigation link?

I have a TabView in a struct. I add a NavigationView and a NavigationLink. When the view shows up the navigation bar is centered. When i tap on the navigation link the text is on the leading side. How can i make the navigation bar text align to center after i tap the navigation link in iOS 16 and above? I have searched stackoverflow and nothing seems to work in iOS 16 with this specific implementation i am posting below. Any help appreciated.
import Foundation
import SwiftUI
public struct TabViewTest: View {
#State var selectedTab: Int = 0
public var body: some View {
TabView (selection: $selectedTab) {
NavigationView {
NavigationLink(destination: Text("destination"))
{
FeedView(selectedTab: $selectedTab)
.navigationBarTitle("Feed", displayMode: .inline)
.toolbar {
ToolbarItemGroup {
Image(systemName :"magnifyingglass.circle")
.onTapGesture {
selectedTab = 1
}
}
}
}
}
.tabItem {
Text("Feed")
.foregroundColor(.white)
}
.tag(0)
}
}
struct ProfileView: View {
#Binding var selectedTab: Int
var body: some View {
ZStack {
Color.white.ignoresSafeArea()
VStack {
Text("Nav link test tap anywhere")
.font(.largeTitle)
.foregroundColor(.white)
}
}
}
}

NavigationStack and TabView in Swiftui iOS 16: bug or improper usage?

[Xcode 14.1, iOS 16.1]
I have a NavigationStack with a navigationTitle and a TabView with 2 Views. Each View has a ScrollView (see image below):
NavigationStack and TabView problem image
When I tap on Tab1 (#1 in red on the image above), then swipe up, the behavior is as expected (#2), i.e. the big navigationTitle move to the center, and my view passes below and becomes blurry. Perfect.
However, when I tap ton Tab2 (#3) and then swipe up (#4), the big title stays big, and the view doesn't become blurry.
Then I tap on Tab1 again (#5) and it works as expected.
Please help!
Here is my code:
ContentView:
import SwiftUI
struct ContentView: View {
#State private var selection: Tab = .tab1
enum Tab {
case tab1
case tab2
}
#State private var mainTitle = "Tab1"
var body: some View {
NavigationStack {
TabView(selection: $selection) {
Tab1(mainTitle: $mainTitle)
.tabItem {
Label("Tab1", systemImage: "wrench.adjustable.fill")
}
.tag(Tab.tab1)
Tab2(mainTitle: $mainTitle)
.tabItem {
Label("Tab2", systemImage: "wrench.adjustable.fill")
}
.tag(Tab.tab2)
} .navigationTitle(mainTitle)
}
}
}
Tab1:
import SwiftUI
struct Tab1: View {
#Binding var mainTitle : String
var body: some View {
ScrollView {
Text("Text tab 1")
.padding(.all,100)
.background(.blue)
} .onAppear {
mainTitle = "Tab1"
}
}
}
Tab2:
import SwiftUI
struct Tab2: View {
#Binding var mainTitle : String
var body: some View {
ScrollView {
Text("Text tab 2")
.padding(.all,100)
.background(.green)
} .onAppear {
mainTitle = "Tab2"
}
}
}
I tried a hack that is supposed to fix the transparency bug for Tab bars, but it doesn't work.
.onAppear {
let tabBarAppearance = UITabBarAppearance()
tabBarAppearance.configureWithOpaqueBackground()
UITabBar.appearance().scrollEdgeAppearance = tabBarAppearance
}
TabViews are designed to sit at the top of the navigation hierarchy. They're intended to allow users to switch between independent sections of your app at any time.
You would generally put a separate navigation stack within each tab that then handles pushing and popping of views. And then, you can use the navigationTitle modifier to manage the screen's title.
So your structure (which might be split over multiple custom views) should look something like:
TabView {
NavigationStack {
ScrollView {
}
.navigationTitle("Tab 1")
}
.tabItem { Label("Tab1", ...) }
NavigationStack {
ScrollView {
}
.navigationTitle("Tab 2")
}
.tabItem { Label("Tab2", ...) }
}
This structure is by design, to align with Apple's Human Interface Guidelines. It's worth reading the HIG to get a handle on where Apple are coming from, and how working on the same principles can really help your app feel like it belongs on your users' device.

SwiftUI navigationBarTitle not resetting to .large returning from Toolbar set to .inline

There are a few posts regarding SwiftUI .inline not resetting to .largeTitle when navigation returns to the parent:
For example:
Navigation bar title stays inline in iOS 15
and
Navigationbar title is inline on pushed view, but was set to large
While earlier posts seem to suggest this has been corrected, I'm running into the same problem, even in iOS 16, but I'm not using a < Back button, instead I'm using "Cancel" (and not show, "Save") on my DestinationView. My goal is to mimic Apple's practice of showing a modal view when adding data, but a show-style push on the navigation stack when viewing and editing existing data (e.g. Contacts app, Reminders app, Calendar app). The brief code below illustrates the problem without adding extra code to handle data updating (e.g. #EnviornmentObject).
When I run this in the Live Preview in Xcode 14.0.1, scheme set to iPhone 13 Pro, no problems. Click a NavLink, return from destination, and ContentView shows .large navigationBarTitle. BUT when I run in the simulator or on a 13 Pro device, returning to Home from a NavigationLink remains .inline unless I pull down on the list. If I switch to iPhone 14 Pro, the live preview looks fine, but the simulator shows a short of abrupt switch from inline back to large, not a smooth animation. Am I doing something wrong in the setup here or is there a bug in the implementation, noting that the behavior oddly holds to .inline on return home to ContentView, if I use this in either a simulator or device for iPhone 13 Pro. Thanks for guidance & insight!
struct ContentView: View {
#State private var sheetIsPresented = false
var items = ["Item1", "Item2", "Item3"]
var body: some View {
NavigationStack {
List {
ForEach(items, id: \.self) { item in
NavigationLink(item, destination: DestinationView(item: item))
.padding()
}
}
.navigationBarTitle("Home", displayMode: .large)
.listStyle(.plain)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button {
sheetIsPresented.toggle()
} label: {
Image(systemName: "plus")
}
}
}
}
.sheet(isPresented: $sheetIsPresented) {
NavigationStack {
DestinationView(item: "New!")
}
}
}
}
struct DestinationView: View {
var item: String
#Environment(.dismiss) private var dismiss
var body: some View {
List {
Text(item)
}
.toolbar {
ToolbarItem (placement: .navigationBarLeading) {
Button("Cancel") {
dismiss()
}
}
}
.listStyle(.plain)
.navigationBarTitleDisplayMode(.inline)
.navigationBarBackButtonHidden()
}
}

Navigation + Tabview + Sheet broken in iOS 15

It looks like Navigation + TabView + Sheet is broken in iOS 15.
When I do this:
ContentView -> DetailView -> Bottom Sheet
When the bottom sheet comes up, the Detail view is automatically popped off the stack:
https://www.youtube.com/watch?v=gguLptAx0l4
I expect the Detail view to stay there even when the bottom sheet appears. Does anyone have any idea on why this happens and how to fix it?
Here is my sample code:
import Combine
import SwiftUI
import RealmSwift
struct ContentView: View {
var body: some View {
NavigationView {
TabView {
TabItemView(num: 1)
.tabItem {
Text("One")
}
TabItemView(num: 2)
.tabItem {
Text("Two")
}
}
}
}
}
struct TabItemView: View {
private let num: Int
init(num: Int) {
self.num = num
}
var body: some View {
NavigationLink(destination: DetailView(text: "Detail View \(num)")) {
Text("Go to Detail View")
}
}
}
struct DetailView: View {
#State private var showingSheet = false
private let text: String
init(text: String) {
self.text = text
}
var body: some View {
Button("Open Sheet") {
showingSheet.toggle()
}.sheet(isPresented: $showingSheet) {
Text("Sheet Text")
}
}
}
This works on iOS 14 btw
UPDATE 1:
Tried #Sebastian's suggestion of putting NavigationView inside of TabView. While this fixed the nav bug, it fundamentally changed the behavior (I don't want to show the tabs in DetailView).
Also tried his suggestion of using Introspect to set navigationController.hidesBottomBarWhenPushed = true on the NavigationLink destination, but that didn't do anything:
struct ContentView: View {
var body: some View {
TabView {
NavigationView {
TabItemView(num: 1)
}.tabItem {
Text("One")
}
NavigationView {
TabItemView(num: 2)
}.tabItem {
Text("Two")
}
}
}
}
struct TabItemView: View {
private let num: Int
init(num: Int) {
self.num = num
}
var body: some View {
NavigationLink(destination: DetailView(text: "Detail View \(num)").introspectNavigationController { navigationController in
navigationController.hidesBottomBarWhenPushed = true
}) {
Text("Go to Detail View")
}
}
}
struct DetailView: View {
#State private var showingSheet = false
private let text: String
init(text: String) {
self.text = text
}
var body: some View {
Button("Open Sheet") {
showingSheet.toggle()
}.sheet(isPresented: $showingSheet) {
Text("Sheet Text")
}
}
}
You need to flip how you nest TabView & NavigationView. Instead of nesting several TabView views inside a NavigationView, use the TabView as the parent component, with a NavigationView for each tab.
This is how the updated ContentView would look like:
struct ContentView: View {
var body: some View {
TabView {
NavigationView {
TabItemView(num: 1)
}
.tabItem {
Text("One")
}
NavigationView {
TabItemView(num: 2)
}
.tabItem {
Text("Two")
}
}
}
}
This makes sense and is more correct: The tabs should always be visible, but you want to show a different navigation stack with different content in each tab.
That it worked previously doesn't make it more correct - SwiftUI probably just changed its mind on dealing with unexpected situations. That, and the lack of error messages in these situations, is the downside of using a framework that tries to render anything you throw at it!
If the goal is specifically to hide the tabs when pushing a new view on a NavigationView (e.g., when tapping on a conversation in a messaging app), you have to use a different solution. Apple added the UIViewController.hidesBottomBarWhenPushed property to UIKit to support this specific use case.
This property is set on the UIViewController that, when presented, should not show a toolbar. In other words: Not the UINavigationController or the UITabBarController, but the child UIViewController that you push onto the UINavigationController.
This property is not supported in SwiftUI natively. You could set it using SwiftUI-Introspect, or simply write the navigation structure of your application using UIKit and write the views inside in SwiftUI, linking them using UIHostingViewController.

SwiftUI Navigation bar items disappear on iOS 14

Discovered in my app that navigation bar items in some views disappear when orientation of the device changes. This seems to occur only in a view that is opened using NavigationLink, on main view navigation bar items work as expected. It appears that something has changed between iOS 13.7 and iOS 14.2 related to this. Also, it does not seem to matter whether using leading or trailing items, both disappear.
Example snippet where this occurs:
struct ContentView: View {
var detailView: some View {
Text("This is detail view")
.navigationBarTitle("Detail view title", displayMode: .inline)
.navigationBarItems(trailing: Button(action: {}, label: {
Image(systemName: "pencil.circle.fill")
}))
}
var body: some View {
NavigationView {
NavigationLink(
destination: detailView,
label: {
Text("Open detail view")
})
.navigationBarTitle("Main view")
}.navigationViewStyle(StackNavigationViewStyle())
}
}
The issue occurs only when running on a real device. (iPhone 11 in my case) On simulator everything works as expected.
Anyone else seen similar issues? Workarounds/fixes?
.navigationBarTitle and .navigationBarItems are being deprecated. I think that the best "fix" is to switch to .toolbar
It's a weird issue but I guess there is a hack to make it work.
SwiftUI doesn't call body property when rotations happen. So you can add #State UIDeviceOrientation property to your view and update it every time orientation changes. One tricky thing is to use that property somewhere in the body of the view since SwiftUI smart enough to ignore #State that is not used in the body.
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(
destination: DetailsView(),
label: {
Text("Open detail view")
}).navigationBarTitle("Main view")
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct DetailsView: View {
#State var orientation: UIDeviceOrientation = UIDevice.current.orientation
var body: some View {
Text("This is detail view")
.navigationBarTitle("Detail view title")
.navigationBarItems(trailing: button)
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
orientation = UIDevice.current.orientation
}.background(Text("\(orientation.rawValue)"))
}
var button: some View {
Button(action: { print("123") }, label: {
Image(systemName: "pencil.circle.fill")
}).id("123")
}
}
In my experience when I change a Button in the toolbar from disabled to enabled, they disappear. But if I scroll to the bottom of the View, they re-appear. If I am already at the end of the View when the button is enabled, it acts normally, until I then scroll away from the bottom, the button again disappears.
Try scrolling to the bottom of your view, if you use landscape.