How to pass data to a "presentation modal" view and that data can be retrieved in Detail
I need to pass the variable title to Detail ()
struct ContentView: View {
#State var showingDetail = false
let title = "My Title"
var body: some View {
Button(action: {
self.showingDetail.toggle()
}) {
Text("Show Detail")
}.sheet(isPresented: $showingDetail) {
Detail()
}
}
}
struct Detail: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
NavigationView {
ScrollView {
VStack {
Text("Details view")
Text("Details view")
}
}
.navigationBarTitle("Booking", displayMode: .inline)
.navigationBarItems(trailing:
Button(action: {
self.presentationMode.wrappedValue.dismiss()
print("close")
}) { Image(systemName: "xmark") }).accentColor(.pink)
}
}
}
Just declare it as a viariable/constant on Detail like this:
struct Detail: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
let title: String
var body: some View {
NavigationView {
ScrollView {
VStack {
Text(title)
//...end so on
and then pass it into the initialiser in ConotentView:
struct ContentView: View {
//...
}.sheet(isPresented: $showingDetail) {
Detail(title: self.title)
}
// ...
Related
I am trying to build a app. I am using presentation mode to show the view, then I switch to another but when I use the dismiss function it just takes me back to the first view.
struct ContentView: View {
#Environment(\.presentationMode) var presentationMode
#State private var isShowingBookingScreen:Bool = false
var body: some View {
Button {
isShowingBookingScreen = true
} label: {
Spacer()
Text("Book Now").foregroundColor(.white).padding().font(.headline)
Spacer()
}.sheet(isPresented: $isShowingBookingScreen) {
SecondView()
}.background(Color.accentColor).cornerRadius(10).padding()
}
}
struct SecondView: View {
var body: some View {
NavigationView{
NavigationLink(destination:ThirdView()) {
Text("Continue to Third").foregroundColor(.red).padding().cornerRadius(10).padding()
}
}
}
}
struct ThirdView: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
Button {
presentationMode.wrappedValue.dismiss()
} label: {
Text("Dismiss")
}
}
}
I have a Content file and am hiding the navigation bar because it takes up space and pushes elements down. One of the buttons in the ContentView redirects (using a navigation link) to another view. In this other view, the navigationBar is still hidden....for simplicity sake, I'll cut out some of the code from ContentView:
//this is the view that looks "fine" (i.e. the navigation bar takes up no space)
struct ContentView: View {
#State private var isPresentedSettings = false
var body: some View {
NavigationView {
ZStack {
VStack {
SettingsButton(isPresentedSettings: $isPresentedSettings)
}
}.navigationBarTitle("").navigationBarHidden(true)
}
}
}
//this is the button that pulls up the settings page view
struct SettingsButton: View {
#Binding var isPresentedSettings: Bool
var body: some View {
NavigationLink (destination: SettingsPageView(isPresentedSettings:
self.$isPresentedSettings)) {
Button(action: { self.isPresentedSettings.toggle() }, label: { Text("Button") })
}
}
}
//This is the view that should have a navigationbar but it doesn't
struct SettingsPageView: View {
#Binding var isPresentedSettings: Bool
var body: some View {
NavigationView {
VStack {
Text("This is a view")
}.navigationBarTitle("Settings", displayMode: .inline)
}
}
}
Also...there may have been typos because I just copied the code over from another computer. Sorry and thank you in advance!
Firstly, you don't need to have this isPresentedSettings variable for presenting a NavigationLink.
NavigationLink(destination: SettingsPageView()) {
Text("Button")
}
And there should be only one NavigationView in your view hierarchy.
This is how your final code can look like:
struct ContentView: View {
#State private var navBarHidden = true
var body: some View {
NavigationView {
ZStack {
VStack {
SettingsButton(navBarHidden: $navBarHidden)
}
}
.navigationBarHidden(navBarHidden)
}
}
}
struct SettingsButton: View {
#Binding var navBarHidden: Bool
var body: some View {
NavigationLink(destination: SettingsPageView(navBarHidden: $navBarHidden)) {
Text("Show View")
}
}
}
struct SettingsPageView: View {
#Binding var navBarHidden: Bool
var body: some View {
VStack {
Text("This is a view")
}
.navigationBarTitle("Settings", displayMode: .inline)
.onAppear {
self.navBarHidden = false
}
.onDisappear {
self.navBarHidden = true
}
}
}
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()
}
}
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)
}
}
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()
}
}
}