I am working on a SwiftUI app. In the app I have a custom coded List that acts as a Form. The reason it is custom coded is because I am using a custom color. I have TextField rows among other rows that act as Navigation Links. My issue is that when I add a onTapGesture to dismiss the keyboard all other row functions stop working. For example the NavigationLinks.
NavigationLink Work Here
ZStack(alignment: .leading, content: {
Color.pacificBlue
.edgesIgnoringSafeArea(.all)
List {
Section(header: Text("Header") {
NavigationLink(
destination: SecondaryView(),
label: {
Text("Secondary View")
})
TextField("MyField", text: self.$myField)
}
}
}
NavigationLink Does Not Work Here
ZStack(alignment: .leading, content: {
Color.pacificBlue
.edgesIgnoringSafeArea(.all)
List {
Section(header: Text("Header") {
NavigationLink(
destination: SecondaryView(),
label: {
Text("Secondary View")
})
TextField("MyField", text: self.$myField)
}
}
}
.onTapGesture {
self.dismissKeyboard()
}
Dismiss Keyboard
extension View {
func dismissKeyboard() {
let resign = #selector(UIResponder.resignFirstResponder)
UIApplication.shared.sendAction(resign, to: nil, from: nil, for: nil)
}
}
Is there a way to work around this without having to move to a secondary view to add text. Any help would be appreciated.
Taking the answer from: SwiftUI NavigationLink in list
the following works for me for NavigationLink. If you have
a Button for example, this will not work.
struct ContentView: View {
#State var myField = ""
#State private var showIt: Int? = 0 // <-- here
var body: some View {
NavigationView {
ZStack(alignment: .leading) {
Color.blue.edgesIgnoringSafeArea(.all)
List {
Section(header: Text("Header")) {
// -- here --
NavigationLink(destination: Text("destination view"), tag: 1, selection: $showIt) {
Text("Secondary View")
}.disabled(true) // <-- here
.onTapGesture { showIt = 1 } // <-- here
TextField("MyField", text: $myField)
}
}.listStyle(.plain)
}
.onTapGesture {
self.dismissKeyboard()
}
}.navigationViewStyle(.stack)
}
}
I want to re-use the VStack code in SwiftUI:
var primary:[String:String] = ["Price":"price", "Grade":"Two", "Recovery":"Three"]
var secondary:[String:String] = ["Price":"price", "Grade":"Two", "Recovery":"Three"]
struct ContentView: View {
var body: some View {
VStack {
List{
ForEach(Array(primary), id: \.key) { key, value in
HStack {
Text(key)
.fontWeight(.light)
.padding()
Spacer()
Text(value)
.fontWeight(.light)
.padding()
}
}
}
}
The VStack works fine , but I now want to create a function or a ViewModifier or some sort in order to run the VStack twice for both arrays. I could just put the code within the loop into a ViewModeifier in this simple example, but this is not the point. I want the whole VStack to be repeatable with input variables.
Separate mentioned VStack into dedicated view and use it with input data, like below (tested with Xcode 12 / iOS 14)
struct ReuseContentView: View {
var body: some View {
VStack {
DictionaryView(data: primary)
DictionaryView(data: secondary)
}
}
}
struct DictionaryView: View {
let data: [String: String]
var body: some View {
VStack {
List{
ForEach(Array(primary), id: \.key) { key, value in
HStack {
Text(key)
.fontWeight(.light)
.padding()
Spacer()
Text(value)
.fontWeight(.light)
.padding()
}
}
}
}
}
}
Why I am putting TabView into a NavigationView is because I need to hide the bottom tab bar when user goes into 2nd level 'detail' views which have their own bottom action bar.
But doing this leads to another issue: all the 1st level 'list' views hosted by TabView no longer display their titles. Below is a sample code:
import SwiftUI
enum Gender: String {
case female, male
}
let members: [Gender: [String]] = [
Gender.female: ["Emma", "Olivia", "Ava"], Gender.male: ["Liam", "Noah", "William"]
]
struct TabItem: View {
let image: String
let label: String
var body: some View {
VStack {
Image(systemName: image).imageScale(.large)
Text(label)
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
TabView {
ListView(gender: .female).tag(0).tabItem {
TabItem(image: "person.crop.circle", label: Gender.female.rawValue)
}
ListView(gender: .male).tag(1).tabItem {
TabItem(image: "person.crop.circle.fill", label: Gender.male.rawValue)
}
}
}
}
}
struct ListView: View {
let gender: Gender
var body: some View {
let names = members[gender]!
return List {
ForEach(0..<names.count, id: \.self) { index in
NavigationLink(destination: DetailView(name: names[index])) {
Text(names[index])
}
}
}.navigationBarTitle(Text(gender.rawValue), displayMode: .inline)
}
}
struct DetailView: View {
let name: String
var body: some View {
ZStack {
VStack {
Text("profile views")
}
VStack {
Spacer()
HStack {
Spacer()
TabItem(image: "pencil.circle", label: "Edit")
Spacer()
TabItem(image: "minus.circle", label: "Delete")
Spacer()
}
}
}
.navigationBarTitle(Text(name), displayMode: .inline)
}
}
What I could do is to have a #State var title in the root view and pass the binding to all the list views, then have those list views to set their title back to root view on appear. But I just don't feel so right about it, is there any better way of doing this? Thanks for any help.
The idea is to join TabView selection with NavigationView content dynamically.
Demo:
Here is simplified code depicting approach (with using your views). The NavigationView and TabView just position independently in ZStack, but content of NavigationView depends on the selection of TabView (which content is just stub), thus they don't bother each other. Also in such case it becomes possible to hide/unhide TabView depending on some condition - in this case, for simplicity, presence of root list view.
struct TestTabsOverNavigation: View {
#State private var tabVisible = true
#State private var selectedTab: Int = 0
var body: some View {
ZStack(alignment: .bottom) {
contentView
tabBar
}
}
var contentView: some View {
NavigationView {
ListView(gender: selectedTab == 0 ? .female : .male)
.onAppear {
withAnimation {
self.tabVisible = true
}
}
.onDisappear {
withAnimation {
self.tabVisible = false
}
}
}
}
var tabBar: some View {
TabView(selection: $selectedTab) {
Rectangle().fill(Color.clear).tag(0).tabItem {
TabItem(image: "person.crop.circle", label: Gender.female.rawValue)
}
Rectangle().fill(Color.clear).tag(1).tabItem {
TabItem(image: "person.crop.circle.fill", label: Gender.male.rawValue)
}
}
.frame(height: 50) // << !! might be platform dependent
.opacity(tabVisible ? 1.0 : 0.0)
}
}
This maybe a late answer, but the TabView items need to be assigned tag number else binding selection parameter won't happen. Here is how I do the same thing on my project:
#State private var selectedTab:Int = 0
private var pageTitles = ["Home", "Customers","Sales", "More"]
var body: some View {
NavigationView{
TabView(selection: $selectedTab, content:{
HomeView()
.tabItem {
Image(systemName: "house.fill")
Text(pageTitles[0])
}.tag(0)
CustomerListView()
.tabItem {
Image(systemName: "rectangle.stack.person.crop.fill")
Text(pageTitles[1])
}.tag(1)
SaleView()
.tabItem {
Image(systemName: "tag.fill")
Text(pageTitles[2])
}.tag(2)
MoreView()
.tabItem {
Image(systemName: "ellipsis.circle.fill")
Text(pageTitles[3])
}.tag(3)
})
.navigationBarTitle(Text(pageTitles[selectedTab]),displayMode:.inline)
.font(.headline)
}
}
I am trying to get a context menu to navigate to another view using the following code
var body: some View
{
VStack
{
Text(self.event.name).font(.body)
...
Spacer()
NavigationLink(destination: EditView(event: self.event))
{
Image(systemName: "pencil")
}
}
.navigationBarTitle(Text(appName))
.contextMenu
{
NavigationLink(destination: EditView(event: self.event))
{
Image(systemName: "pencil")
}
}
}
The NavigationLink within the VStack works as expected and navigates to the edit view but I want to use a contextMenu. Although the context menu displays the image, when I tap on it it doesn't navigate to the edit view, instead it just cancels the context menu.
I am doing this within a watch app but don't think that should make a difference, is there anything special I have to do with context menu navigation?
I would use the isActive variant of NavigationLink that you can trigger by setting a state variable. Apple documents this here
This variant of NavigationLink is well fit for dynamic/programatic navigation.
Your .contextMenu sets the state variable to true and that activates the NavigationLink. Because you don't want the link to be visible, set the label view to EmptyView
Here's an example, not identical to your post but hopefully makes it clear.
struct ContentView: View {
#State private var showEditView = false
var body: some View {
NavigationView {
VStack {
Text("Long Press Me")
.contextMenu {
Button(action: {
self.showEditView = true
}, label: {
HStack {
Text("Edit")
Image(systemName: "pencil")
}
})
}
NavigationLink(destination: Text("Edit Mode View Here"), isActive: $showEditView) {
EmptyView()
}
}
.navigationBarTitle("Context Menu")
}
}
}
In Xcode 11.4 it's now possible to do this with sensible NavigationLink buttons. Yay! 🎉
.contextMenu {
NavigationLink(destination: VisitEditView(visit: visit)) {
Text("Edit visit")
Image(systemName: "square.and.pencil")
}
NavigationLink(destination: SegmentsEditView(timelineItem: visit)) {
Text("Edit individual segments")
Image(systemName: "ellipsis")
}
}
This works on Xcode 11.6
struct ContentView: View {
#State var isActiveFromContextMenu = false
var body: some View {
NavigationView{
VStack{
NavigationLink(destination : detailTwo(), isActive: $isActiveFromContextMenu ){
EmptyView()
}
List{
NavigationLink(destination: detail() ){
row(isActiveFromContextMenu: $isActiveFromContextMenu)
}
NavigationLink(destination: detail() ){
row(isActiveFromContextMenu: $isActiveFromContextMenu)
}
NavigationLink(destination: detail() ){
row(isActiveFromContextMenu: $isActiveFromContextMenu)
}
}
}
}
}
}
struct detail: View {
var body: some View{
Text("Detail view")
}
}
struct detailTwo: View {
var body: some View{
Text("DetailTwo view")
}
}
struct row: View {
#Binding var isActiveFromContextMenu : Bool
var body: some View {
HStack{
Text("item")
}.contextMenu{
Button(action: {
self.isActiveFromContextMenu = true
})
{
Text("navigate to")
}
}
}
}
I found success in masking the NavigationLink in the background and switching the context with a Button as the shortest yet simplest alternative.
struct ContentView: View {
#State private var isShowing = false
var body: some View {
NavigationView {
Text("Hello")
.background(NavigationLink("", destination: Text("World!"), isActive: $isShowing))
.contextMenu {
Button {
isShowing = true
} label: {
Label("Switch to New View", systemImage: "chevron.forward")
}
}
}
}
}
When making a List with a row that pushes to a new view, SwiftUI adds a disclosure indicator ">" automatically? How do I remove it if I don't want it?
NavigationView {
List {
NavigationButton(destination: DetailView()) {
ListItem()
}
}
.navigationBarTitle(Text("Some title"))
}
On a UITableViewCell you set Accessory to None but how do I do that in SwiftUI?
Setting the NavigationLink width and hiding it did the trick for me
List {
ForEach(pages) { page in
HStack(spacing: 0) {
Text("Something")
NavigationLink(destination: Text("Somewhere")) {
EmptyView()
}
.frame(width: 0)
.opacity(0)
}
}
}
Swift 5, Xcode 11. ZStack works perfect.
var body: some View {
NavigationView {
List {
ForEach(viewModel.currenciesViewModel) { cellViewModel in
ZStack {
cellViewModel.makeView()
NavigationLink(destination: ChooseCurrencyListView()) {
EmptyView()
}
.buttonStyle(PlainButtonStyle())
}
}
}
.navigationBarHidden(true)
.navigationBarTitle("", displayMode: .inline)
}
}
The easiest one. The content for each item in the list.
ZStack {
NavigationLink(destination: DetailView()) {
EmptyView()
}.hidden()
RowView()
}
As workaround I can suggest to add .padding modifier like this:
NavigationView {
List {
NavigationButton(destination: DetailView()) {
ListItem()
}
}
.navigationBarTitle(Text("Some title"))
}
.padding(.trailing, -32.0)
So you will get rows without visible disclosure:
You can also put it in the .background modifier:
List {
Text("Go to...")
.background(NavigationLink("", destination: Text("Detail View")))
}
If you already have the background modifier on the Text, you can wrap the Text in a HStack and apply background to the HStack.
What you can do, if you are using list, is setting the navigationlink to hidden and its frame width to zero.
HStack{
Button(action: {self.statusShow = 1}, label: {
Image(systemName: "info.circle")
})
NavigationLink(destination: StimulatorSettingView(),
tag: 1,
selection: self.$statusShow){
EmptyView()
}.hidden().frame(width: 0)
}
This worked for me.
As of beta 6, this works well:
struct SwiftUIView: View {
var body: some View {
NavigationView {
List {
HStack {
Text("My Cell Content")
NavigationLink(destination: Text("destination"), label: {
EmptyView()
})
}
}
}
}
}
You don't have to use NavigationLink to wrap your Label directly. It will work as long as the link is anywhere in your view hierarchy.
Here I've wrapped it in a button, which allows you to trigger an action prior to pushing the view. Since the NavigationLink has an EmptyView for the label the disclosure indicator is not visible. You can also style this with ButtonStyle.
struct NavigationButton<Destination: View, Label: View>: View {
var action: () -> Void = { }
var destination: () -> Destination
var label: () -> Label
#State private var isActive: Bool = false
var body: some View {
Button(action: {
self.action()
self.isActive.toggle()
}) {
self.label()
.background(NavigationLink(destination: self.destination(), isActive: self.$isActive) {
EmptyView()
})
}
}
}
And to use it:
NavigationButton(
action: { print("tapped!") },
destination: { Text("Pushed View") },
label: { Text("Tap me") }
)
NavigationLink is what we should define in a scope enclosed inside a NavigationView.
But when we use NavigationLink it is attached to the enclosing view, so to reuse the same NavigationLink with other views, we use tag which differentiates between different Destinations.
struct SwiftUIView: View {
#State private var viewState: Int? = 0
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: Text("View 1"), tag: 1, selection: $viewState) {
EmptyView()
}
NavigationLink(destination: Text("View 2"), tag: 2, selection: $viewState) {
EmptyView()
}
Text("First View")
.onTapGesture {
self.viewState = 1
}
Text("Second View")
.onTapGesture {
self.viewState = 2
}
}
}
}
}
Here we bind a Hashable property with all the NavigationLinks present in our VStack so that when a particular View is tapped we can notify which Destination should be opened by setting the value of Bindable property.
If we don't notify the correct Destination by setting the value of tag, always the View defined inside the Closure of NavigationLink will be clickable and nothing else.
Using this approach you don't need to wrap all your clickable views inside NavigationView, any action on any view can use any NavigationLink just by setting the tag.
Thanks, hope this helps.
Works well for me!
import SwiftUI
struct LandmarkList: View {
var body: some View {
NavigationView {
List(landmarkData) { landmark in
LandmarkRow(landmark: landmark)
NavigationLink(destination: LandmarkDetail(landmark: landmark)) {
EmptyView()
}
}
.navigationBarTitle(Text("Landmarks"))
}
}
}
struct LandmarkList_Previews: PreviewProvider {
static var previews: some View {
ForEach(["iPhone SE", "iPhone 11 Pro Max"], id: \.self) { deviceName in
LandmarkList()
.previewDevice(PreviewDevice(rawValue: deviceName))
.previewDisplayName(deviceName)
}
}
}
Use .frame(width: 0).opacity(0.0):
NavigationView {
List {
ForEach(options) {
option in
ZStack {
YourView(option: option)
NavigationLink(destination: ProductListView(),
label: {
EmptyView()
}).frame(width: 0).opacity(0.0)
}.listRowInsets(EdgeInsets())
}
}.navigationBarHidden(true)
}
My version of this solution is to make a view modifier. I think it's the cleanest way, as it doesn't use AnyView.
Note that this solution runs the init() for the destination when it draws the element the .navigationLink() is attached to.
Usage
Text("Link")
.navigationLink({
// put your destination here
})
How To
import SwiftUI
extension View {
func navigationLink<Destination: View>(_ destination: #escaping () -> Destination) -> some View {
modifier(NavigationLinkModifier(destination: destination))
}
}
fileprivate struct NavigationLinkModifier<Destination: View>: ViewModifier {
#ViewBuilder var destination: () -> Destination
func body(content: Content) -> some View {
content
.background(
NavigationLink(destination: self.destination) { EmptyView() }.opacity(0)
)
}
}
This helps to push and pass the model to the next navigation view controller.
struct ContentView : View {
#State var model = PostListViewModel()
var body: some View {
NavigationView {
List(model.post) { post in
ListCell(listData: post)
}.navigationBarTitle(Text("My Post"))
}
}
}
struct ListCell: View {
var listData: Post
var body: some View {
return NavigationButton(destination: DetailContentView(post: listData)) {
HStack {
ImageRow(model: listData) // Get image
VStack(alignment: .leading) {
Text(listData.login).font(.headline).lineLimit(nil)
Text(listData.url).font(.subheadline).lineLimit(nil)
}.padding(.leading, 10)
}.padding(.init(top: 5, leading: 0, bottom: 5, trailing: 0))
}
}
}
Here's a reusable "plain" navigation link view (i.e. without the chevron disclosure indicator) that can be a drop-in replacement for NavigationLink:
struct PlainNavigationLink<Label, Destination>: View where Label: View, Destination: View {
#ViewBuilder var destination: () -> Destination
#ViewBuilder var label: () -> Label
var body: some View {
label()
.background(
NavigationLink(destination: destination, label: {})
.opacity(0)
)
}
}
To use it, simply replace NavigationLink with PlainNavigationLink:
NavigationView { // or NavigationStack in iOS 16
List {
ForEach(1...30, id: \.self) { _ in
PlainNavigationLink {
Text("Hello, world!")
} label: {
Text("Hello, world!")
}
}
}
}
We can also extend it with convenience initializers for LocalizedStringKey and String, just like NavigationLink does.
just came here looking for the answer to this question, but none of the proposed solutions worked for me (can't have an empty view, because i want to put something in the list row; i'm already messing with the padding (and increasing trailing padding didn't seem to work) ... i was about to give up, and then something occurred to me: what if you crank up the z-index of the list row itself? seemed somewhat unlikely, but i gave it a try and, i'll be damned, it worked! i was so pleasantly surprised, i felt like sharing ...
e.g.:
// in body of your list row view
HStack(alignment: .top, spacing: 0.0) {
// stuff ...
}
.zIndex(9999999999)
If you need children behaviour for List and NavigationLink, without additional discloser in the same time, I want to promote this tricky solution, main point at HStack
var body: some View {
NavigationView {
List(items, children: \.items) { item in
ZStack {
NavigationLink(destination: DetailsView()) {
EmptyView()
}.hidden()
HStack {
RowView(item: item)
Spacer()
}
}
}
}
}
Once you put your button in a scrollview, the disclosure button will be hidden. Just make sure to disable your scroll indicator.
there is no documentation yet, so you can use ScrollView for now
NavigationView {
ScrollView {
ForEach(0...100){ x in
NavigationButton(destination: Text("ss")) {
HStack {
Text(String(x))
Spacer()
}
.padding()
.background(Color.white)
.shadow(radius:1,y:1)
}
}
.frame(width: UIScreen.main.bounds.width - 32)
.padding()
}
}
Removing List and just using ForEach works fine with navigation link. You just have to create your own list row. This works for me
NavigationView {
ForEach(pages) {
page in
NavigationLink(destination: DetailView()) {
ListItem()
}
}
}