How to put background image on SwiftUI Form - swift

I have the below following code and I want to put some image to the area that highlighted in red. How can I do that in SwiftUI?
Here is highlighted red area example: https://ibb.co/6X2zvyq
struct ContentView: View {
var body: some View {
NavigationView {
Form {
Section(header: Text("Info")) {
HStack {
Text("Name")
Spacer()
Text("someName")
}
HStack {
Text("Surname")
Spacer()
Text("someSurname")
}
}
}
.navigationBarTitle("Profile")
}
}
}

Here is complete one module code. Tested with Xcode 11.7. Image named "plant" is located inside Assets.xcassets
extension UINavigationController {
override open func viewDidLoad() {
super.viewDidLoad()
let appearance = UINavigationBarAppearance()
appearance.configureWithTransparentBackground()
appearance.backgroundImage = UIImage(named: "plant")
navigationBar.standardAppearance = appearance
navigationBar.compactAppearance = appearance
navigationBar.scrollEdgeAppearance = appearance
}
}
struct ContentView: View {
var body: some View {
NavigationView {
Form {
Section(header: Text("Info")) {
HStack {
Text("Name")
Spacer()
Text("someName")
}
HStack {
Text("Surname")
Spacer()
Text("someSurname")
}
}
}
.navigationBarTitle("Profile")
}
}
}

Related

SwiftUI - Form Picker - How to prevent navigating back on selected?

I'm implementing Form and Picker with SwiftUI. There is a problem that it automatically navigates back to Form screen when I select a Picker option, how to keep it stay in selection screen?
Code:
struct ContentView: View {
#State private var selectedStrength = "Mild"
let strengths = ["Mild", "Medium", "Mature"]
var body: some View {
NavigationView {
Form {
Section {
Picker("Strength", selection: $selectedStrength) {
ForEach(strengths, id: \.self) {
Text($0)
}
}
}
}
.navigationTitle("Select your cheese")
}
}
}
Actual:
Expect: (sample from Iphone Settings)
You may have to make a custom view that mimics what the picker looks like:
struct ContentView: View {
let strengths = ["Mild", "Medium", "Mature"]
#State private var selectedStrength = "Mild"
var body: some View {
NavigationView {
Form {
Section {
NavigationLink(destination: CheesePickerView(strengths: strengths, selectedStrength: $selectedStrength)) {
HStack {
Text("Strength")
Spacer()
Text(selectedStrength)
.foregroundColor(.gray)
}
}
}
}
.navigationTitle("Select your cheese")
}
}
}
struct CheesePickerView: View {
let strengths: [String]
#Binding var selectedStrength: String
var body: some View {
Form {
Section {
ForEach(0..<strengths.count){ index in
HStack {
Button(action: {
selectedStrength = strengths[index]
}) {
HStack{
Text(strengths[index])
.foregroundColor(.black)
Spacer()
if selectedStrength == strengths[index] {
Image(systemName: "checkmark")
.foregroundColor(.blue)
}
}
}.buttonStyle(BorderlessButtonStyle())
}
}
}
}
}
}

SwiftUI List exposed white background in landscape orientation

I have a viewController that allows the user to select their choice of notification sounds. The list is presented by the SwiftUI with the name of each sound and a preview button. All is fine in portrait mode:
But in landscape the edges of the screen show white:
Here's my SwiftUI code:
import SwiftUI
struct SoundItem: Hashable {
var name: String
var pdSelection: Bool = false
var pESelection: Bool = false
var advanceAlertSelection: Bool = false
}
struct SoundListItem: View {
var item: SoundItem
var delegate: NotificationsSoundControllerViewController?
var body: some View {
HStack {
Image(item.pdSelection ? "post-dose-select" : "post-dose-unselect")
.onTapGesture {
if let d = self.delegate {
d.selectPDSound(name: self.item.name) // defined in the protocol of the preferences view controller for updating the user prefs.
}
}
Spacer()
Text(item.name.localizedCapitalized).font(Font.custom("Exo2-SemiBold", size: 20.0)).foregroundColor(item.advanceAlertSelection ? Color.green : Color.white)
.onTapGesture {
if let d = self.delegate {
d.selectAdvanceSound(name: self.item.name) // defined in the protocol of the preferences view controller for updating the user prefs.
}
}
Spacer()
Image(systemName: "play.fill")
.foregroundColor(Color.white)
.onTapGesture {
if let d = self.delegate {
d.playPreviewSound(name: self.item.name)
}
}.padding(.trailing, 40.0)
Image(item.preExpirySelection ? "pre-expiry-select" : "pre-expiry-unselect")
.onTapGesture {
if let d = self.delegate {
d.selectPESound(name: self.item.name) // defined in the protocol of the preferences view controller for updating the user prefs.
}
}
}
}
}
struct ListHeading: View {
let headingFont: Font = Font.custom("Exo2-SemiBold", size: 12.0)
var body: some View {
HStack{
Text("Post").font(headingFont).foregroundColor(Color.gray)
Spacer()
Text("Click name for advance alert").font(headingFont).foregroundColor(Color.gray)
Spacer()
Spacer()
Text("Pre").font(headingFont).foregroundColor(Color.gray)
}
}
}
struct NotificationSoundsSUIView: View {
#ObservedObject var noteController: NotificationsSoundControllerViewController
init(){
UITableView.appearance().backgroundColor = .black
noteController = NotificationsSoundControllerViewController()
}
var body: some View {
Section(header: ListHeading()) {
List {
ForEach (self.noteController.sounds, id: \.self) { sound in
SoundListItem(item: sound, delegate: self.noteController).listRowBackground(Color.black)
}.background(Color.black)
}.listRowBackground(Color.black)
}
}
}
struct NotificationSoundsSUIView_Previews: PreviewProvider {
static var previews: some View {
NotificationSoundsSUIView()
}
}
And it's presented in the viewController with this:
var listView = NotificationSoundsSUIView() // See above code
listView.noteController = self //pass over data for display by referencing this viewController
let childView = UIHostingController(rootView: listView)
childView.view.backgroundColor = UIColor.black
childView.view.frame = view.frame
view.addSubview(childView.view)
view.sendSubviewToBack(childView.view)
view.backgroundColor = UIColor.black
childView.view.translatesAutoresizingMaskIntoConstraints = false
How can I force the entire background of the screen to remain black?
Assuming your constraints in UIViewController set correctly here is to be done in SwiftUI part
Section(header: ListHeading()) {
List {
ForEach (self.noteController.sounds, id: \.self) { sound in
SoundListItem(item: sound, delegate: self.noteController).listRowBackground(Color.black)
}.background(Color.black)
}.listRowBackground(Color.black)
}.edgesIgnoringSafeArea([.leading, .trailing]) // << here !!
I stumbled on an answer that seems to solve the problem and also maintain the safe area clearence of the list. I changed the SwiftUI view's body to:
var body: some View {
HStack {
Spacer() // < Inserted a spacer
VStack { // < Using a HStack causes the list headings to appear to the left of the list itself
Section(header: ListHeading()) {
List {
ForEach (self.noteController.sounds, id: \.self) { sound in
SoundListItem(item: sound, delegate: self.noteController).listRowBackground(Color.black)
}.background(Color.black)
}.listRowBackground(Color.black)
}
}
Spacer() // < Inserted a spacer
}
}
}

SwiftUI - Hiding a ScrollView's indicators makes it stop scrolling

I'm trying to hide the indicators of a ScrollView but when I try doing so, the ScrollView just doesn't scroll anymore. I'm using macOS if that matters.
ScrollView(showsIndicators: false) {
// Everything is in here
}
On request of #SoOverIt
Demo:
Nothing special, just launched some other test example. Xcode 11.2 / macOS 10.15
var body : some View {
VStack {
ScrollView([.vertical], showsIndicators: false) {
Group {
Text("AAA")
Text("BBB")
Text("CCC")
Text("DDD")
Text("EEE")
}
Group {
Text("AAA")
Text("BBB")
Text("CCC")
Text("DDD")
Text("EEE")
}
Group {
Text("AAA")
Text("BBB")
Text("CCC")
Text("DDD")
Text("EEE")
}
Group {
Text("AAA")
Text("BBB")
Text("CCC")
Text("DDD")
Text("EEE")
}
}
.frame(height: 100)
.border(Color.blue)
}
.border(Color.red)
}
I fixed the issue.
extension View {
func hideIndicators() -> some View {
return PanelScrollView{ self }
}
}
struct PanelScrollView<Content> : View where Content : View {
let content: () -> Content
var body: some View {
PanelScrollViewControllerRepresentable(content: self.content())
}
}
struct PanelScrollViewControllerRepresentable<Content>: NSViewControllerRepresentable where Content: View{
func makeNSViewController(context: Context) -> PanelScrollViewHostingController<Content> {
return PanelScrollViewHostingController(rootView: self.content)
}
func updateNSViewController(_ nsViewController: PanelScrollViewHostingController<Content>, context: Context) {
}
typealias NSViewControllerType = PanelScrollViewHostingController<Content>
let content: Content
}
class PanelScrollViewHostingController<Content>: NSHostingController<Content> where Content : View {
var scrollView: NSScrollView?
override func viewDidAppear() {
self.scrollView = findNSScrollView(view: self.view)
self.scrollView?.scrollerStyle = .overlay
self.scrollView?.hasVerticalScroller = false
self.scrollView?.hasHorizontalScroller = false
super.viewDidAppear()
}
func findNSScrollView(view: NSView?) -> NSScrollView? {
if view?.isKind(of: NSScrollView.self) ?? false {
return (view as? NSScrollView)
}
for v in view?.subviews ?? [] {
if let vc = findNSScrollView(view: v) {
return vc
}
}
return nil
}
}
Preview:
struct MyScrollView_Previews: PreviewProvider {
static var previews: some View {
ScrollView{
VStack{
Text("hello")
Text("hello")
Text("hello")
Text("hello")
Text("hello")
}
}.hideIndicators()
}
}
So... I think that's the only way for now.
You basically just put a View over your ScrollView indicator with the same backgroundColor as your background View
Note: This obviously only works if your background is static with no content at the trailing edge.
Idea
struct ContentView: View {
#Environment(\.colorScheme) var colorScheme: ColorScheme
let yourBackgroundColorLight: Color = .white
let yourBackgroundColorDark: Color = .black
var yourBackgroundColor: Color { colorScheme == .light ? yourBackgroundColorLight : yourBackgroundColorDark }
var body: some View {
ScrollView {
VStack {
ForEach(0..<1000) { i in
Text(String(i)).frame(width: 280).foregroundColor(.green)
}
}
}
.background(yourBackgroundColor) //<-- Same
.overlay(
HStack {
Spacer()
Rectangle()
.frame(width: 10)
.foregroundColor(yourBackgroundColor) //<-- Same
}
)
}
}
Compact version
You could improve this like that, I suppose you have your color dynamically set up inside assets.
Usage:
ScrollView {
...
}
.hideIndicators(with: <#Your Color#>)
Implementation:
extension View {
func hideIndicators(with color: Color) -> some View {
return modifier(HideIndicators(color: color))
}
}
struct HideIndicators: ViewModifier {
let color: Color
func body(content: Content) -> some View {
content
.overlay(
HStack {
Spacer()
Rectangle()
.frame(width: 10)
.foregroundColor(color)
}
)
}
}

SwiftUI - Change TabBar Icon Color

I can't change the TabBar Color in SwiftUI. I try it with the TabbedView, with the Image/Text and with a Stack. Nothing works for me.
using .foregroundColor doesn't work.
TabbedView(selection: $selection){
TextView()
.tag(0)
.tabItemLabel(
VStack {
Image("Calendar")
.foregroundColor(.red)
Text("Appointments")
.foregroundColor(.red)
}
).foregroundColor(.red)
}.foregroundColor(.red)
Solution 1
Use renderingMode(.template)
struct MainView: View {
var body: some View {
TabView {
LoginView().tabItem {
VStack {
Text("Login")
Image("login").renderingMode(.template)
}
}
HomeView().tabItem {
VStack {
Text("Home")
Image("home").renderingMode(.template)
}
}
}.accentColor(.orange)
}
}
Solution 2
Make tabItem type
enum TabViewItemType: String {
case login = "login"
case home = "home"
case search = "search"
var image: Image {
switch self {
case .login: return Image("login")
case .home: return Image("home")
case .search: return Image("search")
}
}
var text: Text {
Text(self.rawValue)
}
}
struct MainView: View {
var body: some View {
TabView {
LoginView()
.tabItem { TabViewItem(type: .login) }
HomeView()
.tabItem { TabViewItem(type: .home) }
SearchView()
.tabItem { TabViewItem(type: .search) }
}.accentColor(.orange)
}
}
struct TabViewItem: View {
var type: TabViewItemType
var body: some View {
VStack {
type.image.renderingMode(.template)
type.text
}
}
}
Use accentColor:
TabView(selection: $selection) {
NavigationView {
HomeView()
}.navigationBarTitle("Home")
.tabItem {
VStack {
if selection == 0 {
Image(systemName: "house.fill")
} else {
Image(systemName: "house")
}
Text("Home")
}
}
.tag(0)
NavigationView {
SettingsView()
}.navigationBarTitle("Settings")
.tabItem {
VStack {
Image(systemName: "gear")
Text("Settings")
}
}
.tag(1)
}.accentColor(.purple)
You can use UITabBar.appearance() to do some customisation until Apple comes with a more standard way of updating SwiftUI TabView
Change TabItem (text + icon) color
init() {
UITabBar.appearance().unselectedItemTintColor = UIColor.white
}
Change TabView background color
init() {
UITabBar.appearance().backgroundColor = UIColor.red
UITabBar.appearance().backgroundImage = UIImage()
}
Overall code looks like this -
struct ContentView: View {
init() {
// UITabBar customization
}
var body: some View {
TabView(selection: $selection) {
FirstTabView()
.tabItem {
VStack {
Image(systemName: "map")
Text("Near Me")
}
}
}
}
}
Use .accentColor modifier for changing color of selected tabItem
After trying many options this worked for me..
I think you can just use the accentColor of TabView,it works for me :]
TabView {
...
}.accentColor(Color.red)
I made an extension for Image which initialises with a UIImage with a tint color:
extension Image {
init(_ named: String, tintColor: UIColor) {
let uiImage = UIImage(named: named) ?? UIImage()
let tintedImage = uiImage.withTintColor(tintColor,
renderingMode: .alwaysTemplate)
self = Image(uiImage: tintedImage)
}
}
On iOS16 .accentColor(Color) is deprecated.
You can use .tint(Color) on the TabView instead.
TabView {
Text("First Tab")
.tabItem {
Image(systemName: "1.circle")
Text("First")
}
}.tint(Color.yellow)
Currently SwiftUI does not have a direct method for that.
We've to use the UIKit method for that unless SwiftUI introduces any new solution.
try below code:
struct ContentView: View {
init() {
UITabBar.appearance().backgroundColor = UIColor.purple
}
var body: some View {
return TabbedView {
Text("This is tab 1")
.tag(0)
.tabItem {
Text("tab1")
}
Text("This is tab 2")
.tag(1)
.tabItem {
Text("tab1")
}
Text("This is tab 3")
.tag(2)
.tabItem {
Text("tab1")
}
}
}
}
In case you need to set up accent color for entire app with SwiftUI interface, you just need to define AccentColor in Assets.xcassets file like in the picture below. TabBar icons will get it without any additional code.

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")
}
}
}