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")
}
}
Related
In the following example, I'm trying to remove padding so that "A" looks like "C". I can't find any way to remove this padding for a Menu when inside a ToolbarItemGroup (tested in iOS 15 or iOS 16). There shouldn't be any space between the green and red borders in "A". This is clearly controlled by .buttonStyle(.borderless) for a Button, but the corresponding .menuStyle(.borderlessButton) does not work when in a toolbar (but does work when no in a toolbar). Any ideas?
#main
struct TestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
ScrollView{}.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
HStack {
Button {
} label: {
Image(systemName: "square.and.arrow.up")
.border(Color.red)
}
.buttonStyle(.borderless) // <--Removed in "B" below.
.border(Color.green)
Menu {
EmptyView()
} label: {
Image(systemName: "square.and.arrow.up")
.border(Color.red)
}
.menuStyle(.borderlessButton) // <--Removed in "B" below.
.border(Color.green)
}
}
}
}
}
}
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
}
}
}
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 !!
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)
}
}
}
I want to use a simple horizontal ScrollView as NavigationLink inside a List. However, tapping on the ScrollView is not registered by a NavigationLink and therefore does not navigate to the destination.
NavigationView {
List {
NavigationLink(destination: Text("Detail")) {
ScrollView(.horizontal) {
Text("Tapping here does not navigate.")
}
}
}
}
Any ideas on how can we prevent ScrollView from capturing the navigation tap gesture?
You can move NavigationLink to the background and activate it in onTapGesture:
struct ContentView: View {
#State var isLinkActive = false
var body: some View {
NavigationView {
List {
ScrollView(.horizontal) {
Text("Tapping here does not navigate.")
}
.onTapGesture {
isLinkActive = true
}
}
.background(
NavigationLink(destination: Text("Detail"), isActive: $isLinkActive) {}
)
}
}
}
The final goal is not clear, but the following alternate does also work (tested with Xcode 12 / iOS 14)
NavigationView {
List {
ScrollView(.horizontal) {
NavigationLink(destination: Text("Detail")) {
Text("Tapping here does not navigate.")
}
}
}
}