How to change TintColor of NavigationBar only one screen - swift

I want to change the TintColor of the NavigationBar.
In the following implementation, I want to change only "DetailView", but the color of the screen of "EditView" also changes.
How can I change only one screen?
struct TopView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DetailView()) {
Text("Detail")
}
}
.navigationBarTitle("Top")
}
}
}
struct DetailView: View {
init(title: String) {
UINavigationBar.appearance().tintColor = UIColor(named: "White")
}
var body: some View {
VStack {
NavigationLink(destination: EditView()) {
Text("Edit")
}
}
.navigationBarTitle("Detail", displayMode: .inline)
}
}
struct EditView: View {
#Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
var body: some View {
VStack {
Text("Title")
}
.navigationBarTitle("Edit", displayMode: .inline)
.navigationBarItems(
trailing:
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Save")
}
)
}
}

If I'm understanding the question aright then this might be a solution. It works by figuring out where we are in the navigation stack and then changing the accent color depending on the position. This changes the color of the navigation bar buttons. If you are relying on the accent color for other things, you'll need to override that, and I've illustrated that on the EditView. (I've left some logging so you can see how it works.)
enum ViewShowing : String {
case top
case detail
case edit
}
class ViewShowingModel : ObservableObject {
#Published var showing = ViewShowing.top
}
struct ContentView: View {
#ObservedObject var viewModel = ViewShowingModel()
var body: some View {
return NavigationView {
VStack {
NavigationLink(destination: DetailView(title: "Detail", viewModel: viewModel)) {
Text("Go to: Detail")
}
Text("viewShowing: \(viewModel.showing.rawValue)")
}.onAppear(perform: {
print("top appear")
self.viewModel.showing = .top
})
.navigationBarTitle("Top").accentColorForView(viewModel.showing)
}.accentColorForView(viewModel.showing)
}
}
struct DetailView: View {
var title : String
#ObservedObject var viewModel : ViewShowingModel
var body: some View {
return VStack {
NavigationLink(destination: EditView(viewModel: viewModel)) {
Text("Go to Edit: \(title)")
}
Text("viewShowing: \(viewModel.showing.rawValue)")
.navigationBarTitle("\(title)", displayMode: .inline)
}
.onAppear(perform: {
print("detail appear")
self.viewModel.showing = .detail })
}
}
struct EditView: View {
#ObservedObject var viewModel : ViewShowingModel
#Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
var body: some View {
VStack {
Text("Editing View")
Text("viewShowing: \(viewModel.showing.rawValue)")
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("A Dismiss Button (no color override)")
}.padding()
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Another Dismiss Button (color override)").foregroundColor(Color(UIColor.systemBlue))
}.padding()
}
.navigationBarTitle("Edit", displayMode: .inline)
.navigationBarItems(
trailing:
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Save")
}
)
.onAppear(perform: {
print("edit appear")
self.viewModel.showing = .edit
})
}
}
extension View {
func accentColorForView(_ viewShowing : ViewShowing) -> some View {
switch viewShowing {
case .detail :
return accentColor(Color.red)
case .top :
return accentColor(Color.purple)
case .edit :
return accentColor(Color.green)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}

Related

SwiftUI Hidden ".navigationBarHidden" appearance bug

I have 3 windows that are connected by a NavigationLink and the NavigationBar is hidden, but I need the ability to swipe to go back, for this I use this code:
import SwiftUI
extension UINavigationController: UIGestureRecognizerDelegate {
override open func viewDidLoad() {
super.viewDidLoad()
interactivePopGestureRecognizer?.delegate = self
}
public func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
return viewControllers.count > 1
}
}
#main
struct testSheetApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
NavigationView{
ZStack{
VStack{
NavigationLink(destination: {
ContentView2()
}, label: {
Text("new")
})
}
} .navigationBarHidden(true)
}
}
}
struct ContentView2: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
ZStack{
VStack{
Button(action: {
presentationMode.wrappedValue.dismiss()
}, label: {
Text("back")
})
NavigationLink(destination: {
ContentView3()
}, label: {
Text("next")
})
}
} .navigationBarHidden(true)
}
}
struct ContentView3: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
VStack{
Button(action: {
presentationMode.wrappedValue.dismiss()
}, label: {
Text("back")
})
}
.navigationBarHidden(true)
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
Everything works fine with the second form, you can return with a swipe, but on the 3rd form, when you cancel the swipe, when the screen remains on the same form, a NavigationBar appears
Bug NavigationBar
I found a solution to this problem, you need to add to the NavigationLink, ".isDetailLink (false)"
code snippet:
NavigationLink(destination: {
ContentView3()
}, label: {
Text("next")
}).isDetailLink(false)

SwiftUI: How can I move texts or something else with VStack, HStack?

I'm new in SwiftUI. I would like on the top left in the corner a Button next to the Picker. But when I place the Button, the Picker moves to the right and the Button is to nearly on the edge. How can I place the Button flush over the Headline and the Picker perfectly in the middle from iPhone Nodge?
Before:
After:
import SwiftUI
import Combine
struct ContentView: View {
#State var Selection = UserDefaults.standard.integer(forKey: "Picker")
#State var Detail = false
var body: some View {
VStack {
HStack {
Button(action: {
self.Detail.toggle()
}) {
Text("click")
}.sheet(isPresented: $Detail) {
SettingView(showSheetView: self.$Detail, selection: $Selection)
}
Picker("", selection: $Selection) {
Text("Selection1").tag(0)
Text("Selection2").tag(1)
}
.pickerStyle(SegmentedPickerStyle()).padding(.horizontal, 89)
.onReceive(Just(Selection)) {
UserDefaults.standard.set($0, forKey: "Picker")
}
}
PageOne()
}
}
}
struct PageOne: View {
var body: some View {
NavigationView {
VStack {
Text("some Text")
}.navigationTitle("Headline")
}
}
}
struct SettingView: View {
#Binding var showSheetView: Bool
#Binding var selection: Int
var body: some View {
NavigationView {
Text("Test")
.navigationBarTitle(Text("Select something"))
.navigationBarItems(trailing: Button(action: {
self.showSheetView = false
}) {
Text("Ok")
.bold()
})
}
}
}
you can use a ZStack to show your Button and Picker without pushing them.
something like this:
var body: some View {
VStack {
ZStack {
HStack {
Button(action: {
self.Detail.toggle()
}) {
Text("click")
}.sheet(isPresented: $Detail) {
SettingView(showSheetView: self.$Detail, selection: $Selection)
}
Spacer()
}
HStack {
Spacer()
Picker("", selection: $Selection) {
Text("Selection1").tag(0)
Text("Selection2").tag(1)
}
.pickerStyle(SegmentedPickerStyle()).padding(.horizontal, 89)
.onReceive(Just(Selection)) {
UserDefaults.standard.set($0, forKey: "Picker")
}
Spacer()
}
}
PageOne()
}
}

If you cancel the process of returning to the previous screen by swiping, the navigationBar remains without disappearing

When swiping from the View with navigationBarItems, canceling the swipe and returning to the previous screen, the navigationBar on the previous screen remained without disappearing.
Is this a bug?
Or is my implementation wrong?
You can check the phenomenon here.
struct TopView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DetailView()) {
Text("Detail")
}
}
.navigationBarTitle("Top")
}
}
}
struct DetailView: View {
var body: some View {
VStack {
NavigationLink(destination: EditView()) {
Text("Edit")
}
}
.navigationBarTitle("Detail", displayMode: .inline)
}
}
struct EditView: View {
#Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
var body: some View {
VStack {
Text("Title")
}
.navigationBarTitle("Edit", displayMode: .inline)
.navigationBarItems(
trailing:
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Text("Save")
}
)
}
}
#Environment (. PresentationMode) private var presentationMode:
Binding
If this were not present, it would not occur.
Here is fix
struct DetailView: View {
var body: some View {
VStack {
NavigationLink(destination: EditView()) {
Text("Edit")
}.isDetailLink(false) // << here !!
}
.navigationBarTitle("Detail", displayMode: .inline)
}
}

Pop to root view using Tab Bar in SwiftUI

Is there any way to pop to root view by tapping the Tab Bar like most iOS apps, in SwiftUI?
Here's an example of the expected behavior.
I've tried to programmatically pop views using simultaneousGesture as follow:
import SwiftUI
struct TabbedView: View {
#State var selection = 0
#Environment(\.presentationMode) var presentationMode
var body: some View {
TabView(selection: $selection) {
RootView()
.tabItem {
Image(systemName: "house")
.simultaneousGesture(
TapGesture().onEnded {
self.presentationMode.wrappedValue.dismiss()
print("View popped")
}
)
}.tag(0)
Text("")
.tabItem {
Image(systemName: "line.horizontal.3")
}.tag(1)
}
}
}
struct RootView: View {
var body: some View {
NavigationView {
NavigationLink(destination: SecondView()) {
Text("Go to second view")
}
}
}
}
struct SecondView: View {
var body: some View {
Text("Tapping the house icon should pop back to root view")
}
}
But seems like those gestures were ignored.
Any suggestions or solutions are greatly appreciated
We can use tab bar selection binding to get the selected index. On this binding we can check if the tab is already selected then pop to root for navigation on selection.
struct ContentView: View {
#State var showingDetail = false
#State var selectedIndex:Int = 0
var selectionBinding: Binding<Int> { Binding(
get: {
self.selectedIndex
},
set: {
if $0 == self.selectedIndex && $0 == 0 && showingDetail {
print("Pop to root view for first tab!!")
showingDetail = false
}
self.selectedIndex = $0
}
)}
var body: some View {
TabView(selection:selectionBinding) {
NavigationView {
VStack {
Text("First View")
NavigationLink(destination: DetailView(), isActive: $showingDetail) {
Text("Go to detail")
}
}
}
.tabItem { Text("First") }.tag(0)
Text("Second View")
.tabItem { Text("Second") }.tag(1)
}
}
}
struct DetailView: View {
var body: some View {
Text("Detail")
}
}
I messed around with this for a while and this works great. I combined answers from all over and added some stuff of my own. I'm a beginner at Swift so feel free to make improvements.
Here's a demo.
This view has the NavigationView.
import SwiftUI
struct AuthenticatedView: View {
#StateObject var tabState = TabState()
var body: some View {
TabView(selection: $tabState.selectedTab) {
NavigationView {
NavigationLink(destination: TestView(titleNum: 0), isActive: $tabState.showTabRoots[0]) {
Text("GOTO TestView #1")
.padding()
.foregroundColor(Color.white)
.frame(height:50)
.background(Color.purple)
.cornerRadius(8)
}
.navigationTitle("")
.navigationBarTitleDisplayMode(.inline)
}
.navigationViewStyle(.stack)
.onAppear(perform: {
tabState.lastSelectedTab = TabState.Tab.first
}).tabItem {
Label("First", systemImage: "list.dash")
}.tag(TabState.Tab.first)
NavigationView {
NavigationLink(destination: TestView(titleNum: 0), isActive: $tabState.showTabRoots[1]) {
Text("GOTO TestView #2")
.padding()
.foregroundColor(Color.white)
.frame(height:50)
.background(Color.purple)
.cornerRadius(8)
}.navigationTitle("")
.navigationBarTitleDisplayMode(.inline).navigationBarTitle(Text(""), displayMode: .inline)
}
.navigationViewStyle(.stack)
.onAppear(perform: {
tabState.lastSelectedTab = TabState.Tab.second
}).tabItem {
Label("Second", systemImage: "square.and.pencil")
}.tag(TabState.Tab.second)
}
.onReceive(tabState.$selectedTab) { selection in
if selection == tabState.lastSelectedTab {
tabState.showTabRoots[selection.rawValue] = false
}
}
}
}
struct AuthenticatedView_Previews: PreviewProvider {
static var previews: some View {
AuthenticatedView()
}
}
class TabState: ObservableObject {
enum Tab: Int, CaseIterable {
case first = 0
case second = 1
}
#Published var selectedTab: Tab = .first
#Published var lastSelectedTab: Tab = .first
#Published var showTabRoots = Tab.allCases.map { _ in
false
}
}
This is my child view
import SwiftUI
struct TestView: View {
let titleNum: Int
let title: String
init(titleNum: Int) {
self.titleNum = titleNum
self.title = "TestView #\(titleNum)"
}
var body: some View {
VStack {
Text(title)
NavigationLink(destination: TestView(titleNum: titleNum + 1)) {
Text("Goto View #\(titleNum + 1)")
.padding()
.foregroundColor(Color.white)
.frame(height:50)
.background(Color.purple)
.cornerRadius(8)
}
NavigationLink(destination: TestView(titleNum: titleNum + 100)) {
Text("Goto View #\(titleNum + 100)")
.padding()
.foregroundColor(Color.white)
.frame(height:50)
.background(Color.purple)
.cornerRadius(8)
}
.navigationTitle(title)
.navigationBarTitleDisplayMode(.inline)
}
}
}
struct TestView_Previews: PreviewProvider {
static var previews: some View {
TestView(titleNum: 0)
}
}
You can achieve this by having the TabView within a NavigationView like so:
struct ContentView: View {
#State var selection = 0
var body: some View {
NavigationView {
TabView(selection: $selection) {
FirstTabView()
.tabItem {
Label("Home", systemImage: "house")
}
.tag(0)
}
}
.navigationViewStyle(StackNavigationViewStyle())
}
}
struct FirstTabView: View {
var body: some View {
NavigationLink("SecondView Link", destination: SecondView())
}
}
struct SecondView: View {
var body: some View {
Text("Second View")
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
ContentView()
}
}
}

Custom back button for NavigationView's navigation bar in SwiftUI

I want to add a custom navigation button that will look somewhat like this:
Now, I've written a custom BackButton view for this. When applying that view as leading navigation bar item, by doing:
.navigationBarItems(leading: BackButton())
...the navigation view looks like this:
I've played around with modifiers like:
.navigationBarItem(title: Text(""), titleDisplayMode: .automatic, hidesBackButton: true)
without any luck.
Question
How can I...
set a view used as custom back button in the navigation bar? OR:
programmatically pop the view back to its parent?
When going for this approach, I could hide the navigation bar altogether using .navigationBarHidden(true)
TL;DR
Use this to transition to your view:
NavigationLink(destination: SampleDetails()) {}
Add this to the view itself:
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
Then, in a button action or something, dismiss the view:
presentationMode.wrappedValue.dismiss()
Full code
From a parent, navigate using NavigationLink
NavigationLink(destination: SampleDetails()) {}
In DetailsView hide navigationBarBackButton and set custom back button to leading navigationBarItem,
struct SampleDetails: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var btnBack : some View { Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
HStack {
Image("ic_back") // set image here
.aspectRatio(contentMode: .fit)
.foregroundColor(.white)
Text("Go back")
}
}
}
var body: some View {
List {
Text("sample code")
}
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: btnBack)
}
}
SwiftUI 1.0
It looks like you can now combine the navigationBarBackButtonHidden and .navigationBarItems to get the effect you're trying to achieve.
Code
struct Navigation_CustomBackButton_Detail: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
ZStack {
Color("Theme3BackgroundColor")
VStack(spacing: 25) {
Image(systemName: "globe").font(.largeTitle)
Text("NavigationView").font(.largeTitle)
Text("Custom Back Button").foregroundColor(.gray)
HStack {
Image("NavBarBackButtonHidden")
Image(systemName: "plus")
Image("NavBarItems")
}
Text("Hide the system back button and then use the navigation bar items modifier to add your own.")
.frame(maxWidth: .infinity)
.padding()
.background(Color("Theme3ForegroundColor"))
.foregroundColor(Color("Theme3BackgroundColor"))
Spacer()
}
.font(.title)
.padding(.top, 50)
}
.navigationBarTitle(Text("Detail View"), displayMode: .inline)
.edgesIgnoringSafeArea(.bottom)
// Hide the system back button
.navigationBarBackButtonHidden(true)
// Add your custom back button here
.navigationBarItems(leading:
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
HStack {
Image(systemName: "arrow.left.circle")
Text("Go Back")
}
})
}
}
Example
Here is what it looks like (excerpt from the "SwiftUI Views" book):
Based on other answers here, this is a simplified answer for Option 2 working for me in XCode 11.0:
struct DetailView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Image(systemName: "gobackward").padding()
}
.navigationBarHidden(true)
}
}
Note: To get the NavigationBar to be hidden, I also needed to set and then hide the NavigationBar in ContentView.
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
NavigationLink(destination: DetailView()) {
Text("Link").padding()
}
} // Main VStack
.navigationBarTitle("Home")
.navigationBarHidden(true)
} //NavigationView
}
}
Here's a more condensed version using principles shown in the other comments to change only the text of the button. The chevron.left icon can also be easily replaced with another icon.
Create your own button, then assign it using .navigationBarItems(). I found the following format most nearly approximated the default back button.
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var backButton : some View {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
HStack(spacing: 0) {
Image(systemName: "chevron.left")
.font(.title2)
Text("Cancel")
}
}
}
Make sure you use .navigationBarBackButtonHidden(true) to hide the default button and replace it with your own!
List(series, id:\.self, selection: $selection) { series in
Text(series.SeriesLabel)
}
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: backButton)
iOS 15+
presentationMode.wrappedValue.dismiss() is now deprecated.
It's replaced by DismissAction
private struct SheetContents: View {
#Environment(\.dismiss) private var dismiss
var body: some View {
Button("Done") {
dismiss()
}
}
}
You can create a custom back button that will use this dismiss action
struct NavBackButton: View {
let dismiss: DismissAction
var body: some View {
Button {
dismiss()
} label: {
Image("...custom back button here")
}
}
}
then attach it to your view.
.navigationBarBackButtonHidden(true) // Hide default button
.navigationBarItems(leading: NavBackButton(dismiss: self.dismiss)) // Attach custom button
I expect you want to use custom back button in all navigable screens,
so I wrote custom wrapper based on #Ashish answer.
struct NavigationItemContainer<Content>: View where Content: View {
private let content: () -> Content
#Environment(\.presentationMode) var presentationMode
private var btnBack : some View { Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
HStack {
Image("back_icon") // set image here
.aspectRatio(contentMode: .fit)
.foregroundColor(.black)
Text("Go back")
}
}
}
var body: some View {
content()
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: btnBack)
}
init(#ViewBuilder content: #escaping () -> Content) {
self.content = content
}
}
Wrap screen content in NavigationItemContainer:
Usage:
struct CreateAccountScreenView: View {
var body: some View {
NavigationItemContainer {
VStack(spacing: 21) {
AppLogoView()
//...
}
}
}
}
Swiping is not disabled this way.
Works for me. XCode 11.3.1
Put this in your root View
init() {
UINavigationBar.appearance().isUserInteractionEnabled = false
UINavigationBar.appearance().backgroundColor = .clear
UINavigationBar.appearance().barTintColor = .clear
UINavigationBar.appearance().setBackgroundImage(UIImage(), for: .default)
UINavigationBar.appearance().shadowImage = UIImage()
UINavigationBar.appearance().tintColor = .clear
}
And this in your child View
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
Button(action: {self.presentationMode.wrappedValue.dismiss()}) {
Image(systemName: "gobackward")
}
You can use UIAppearance for this:
if let image = UIImage(named: "back-button") {
UINavigationBar.appearance().backIndicatorImage = image
UINavigationBar.appearance().backIndicatorTransitionMaskImage = image
}
This should be added early on in your app like App.init. This also preserves the native swipe back functionality.
All of the solutions I see here seem to disable swipe to go back functionality to navigate to the previous page, so sharing a solution I found that maintains that functionality. You can make an extension of your root view and override your navigation style and call the function in the view initializer.
Sample View
struct SampleRootView: View {
init() {
overrideNavigationAppearance()
}
var body: some View {
Text("Hello, World!")
}
}
Extension
extension SampleRootView {
func overrideNavigationAppearance() {
let navigationBarAppearance = UINavigationBarAppearance()
let barAppearace = UINavigationBar.appearance()
barAppearace.tintColor = *desired UIColor for icon*
barAppearace.barTintColor = *desired UIColor for icon*
navigationBarAppearance.setBackIndicatorImage(*desired UIImage for custom icon*, transitionMaskImage: *desired UIImage for custom icon*)
UINavigationBar.appearance().standardAppearance = navigationBarAppearance
UINavigationBar.appearance().compactAppearance = navigationBarAppearance
UINavigationBar.appearance().scrollEdgeAppearance = navigationBarAppearance
}
}
The only downfall to this approach is I haven't found a way to remove/change the text associated with the custom back button.
Really simple method. Only two lines code 🔥
#Environment(\.presentationMode) var presentationMode
self.presentationMode.wrappedValue.dismiss()
Example:
import SwiftUI
struct FirstView: View {
#State var showSecondView = false
var body: some View {
NavigationLink(destination: SecondView(),isActive : self.$showSecondView){
Text("Push to Second View")
}
}
}
struct SecondView : View{
#Environment(\.presentationMode) var presentationMode
var body : some View {
Button(action:{ self.presentationMode.wrappedValue.dismiss() }){
Text("Go Back")
}
}
}
This solution works for iPhone. However, for iPad it won't work because of the splitView.
import SwiftUI
struct NavigationBackButton: View {
var title: Text?
#Environment(\.presentationMode) private var presentationMode: Binding<PresentationMode>
var body: some View {
ZStack {
VStack {
ZStack {
HStack {
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Image(systemName: "chevron.left")
.font(.title)
.frame(width: 44, height: 44)
title
}
Spacer()
}
}
Spacer()
}
}
.zIndex(1)
.navigationBarTitle("")
.navigationBarHidden(true)
}
}
struct NavigationBackButton_Previews: PreviewProvider {
static var previews: some View {
NavigationBackButton()
}
}
I found this: https://ryanashcraft.me/swiftui-programmatic-navigation/
It does work, and it may lay the foundation for a state machine to control what is showing, but it is not a simple as it was before.
import Combine
import SwiftUI
struct DetailView: View {
var onDismiss: () -> Void
var body: some View {
Button(
"Here are details. Tap to go back.",
action: self.onDismiss
)
}
}
struct RootView: View {
var link: NavigationDestinationLink<DetailView>
var publisher: AnyPublisher<Void, Never>
init() {
let publisher = PassthroughSubject<Void, Never>()
self.link = NavigationDestinationLink(
DetailView(onDismiss: { publisher.send() }),
isDetail: false
)
self.publisher = publisher.eraseToAnyPublisher()
}
var body: some View {
VStack {
Button("I am root. Tap for more details.", action: {
self.link.presented?.value = true
})
}
.onReceive(publisher, perform: { _ in
self.link.presented?.value = false
})
}
}
struct ContentView: View {
var body: some View {
NavigationView {
RootView()
}
}
}
If you want to hide the button then you can replace the DetailView with this:
struct LocalDetailView: View {
var onDismiss: () -> Void
var body: some View {
Button(
"Here are details. Tap to go back.",
action: self.onDismiss
)
.navigationBarItems(leading: Text(""))
}
}
Just write this:
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
}.onAppear() {
UINavigationBar.appearance().tintColor = .clear
UINavigationBar.appearance().backIndicatorImage = UIImage(named: "back")?.withRenderingMode(.alwaysOriginal)
UINavigationBar.appearance().backIndicatorTransitionMaskImage = UIImage(named: "back")?.withRenderingMode(.alwaysOriginal)
}
}
}
On iOS 14+ it's actually very easy using presentationMode variable
In this example NewItemView will get dismissed on addItem completion:
struct NewItemView: View {
#State private var itemDescription:String = ""
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
VStack {
TextEditor(text: $itemDescription)
}.onTapGesture {
hideKeyboard()
}.toolbar {
ToolbarItem {
Button(action: addItem){
Text("Save")
}
}
}.navigationTitle("Add Question")
}
private func addItem() {
// Add save logic
// ...
// Dismiss on complete
presentationMode.wrappedValue.dismiss()
}
private func hideKeyboard() {
UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil)
}
}
struct NewItemView_Previews: PreviewProvider {
static var previews: some View {
NewItemView()
}
}
In case you need the parent (Main) view:
struct SampleMainView: View {
#Environment(\.managedObjectContext) private var viewContext
#FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \DbQuestion.timestamp, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
var body: some View {
NavigationView {
List {
ForEach(items) { item in
NavigationLink {
Text("This is item detail page")
} label: {
Text("Item at \(item.id)")
}
}
}
.toolbar {
ToolbarItem {
// Creates a button on toolbar
NavigationLink {
// New Item Page
NewItemView()
} label: {
Text("Add item")
}
}
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
}.navigationTitle("Main Screen")
}
}
}