Creating a custom back button with NavigationView on SwiftUI - ios

I'm trying to create a custom back button on SwiftUI, but I can't
figure out how to do it.
The idea is to hide the "Back" button at the top left that provides NavigationView, and make a custom button with the same functionality.
struct AnadirDatosViewA: View {
#Environment(\.presentationMode) var presentation
var body: some View{
NavigationView(){
Color(red: 48 / 255, green: 49 / 255, blue: 54 / 255)
.edgesIgnoringSafeArea(.all)
.overlay(
VStack{
AnadirDatosExpB()
HStack{
NavigationLink(destination:NuevoExperimentoView()){
Text("Back") //HERE
NavigationLink(destination:AnadirDatosExpA()){
Text("Next")
}
}
}
}
)
}.navigationBarBackButtonHidden(true)
}
}
Right now I'm "cheating" by using the view I want go go back as destination, but it doesn't work the same...
What can I do?

You can use the presentationMode var from the environment inside a Button:
See the commented example below for a possible implementation.
struct ContentView: View{
var body: some View{
// I am navigating with a Navigationlink, so there is
// no need for it in the AnadirDatosViewA
NavigationView {
NavigationLink("show AnadirDatosViewA") {
AnadirDatosViewA()
}
}
}
}
struct AnadirDatosViewA: View {
#Environment(\.presentationMode) var presentation
var body: some View{
// if you navigated to this by a Navigationlink remove the NavigationView
Color(red: 48 / 255, green: 49 / 255, blue: 54 / 255)
.edgesIgnoringSafeArea(.all)
.overlay(
HStack{
// This Button will dismiss the View
Button("Back"){
// with help of th presentationMode from the environment
presentation.wrappedValue.dismiss()
}
// This NavigationLink can forward you to another view
NavigationLink("Next") {
TextView(text: "last")
}
}
// This will hide the back Button in this View
).navigationBarBackButtonHidden(true)
}
}
// HelperView
struct TextView: View{
var text: String
var body: some View{
Text(text)
}
}

It seems like presentationMode is going to be deprecated in the future, so instead you could also do
#Environment(\.dismiss) var dismiss
paired with
Button(action: {
dismiss()
}, label: {
Image(systemName: "chevron.left")
Text(bookClub.bookTitle)
.fontWeight(.bold)
})

Related

Keyboard in a sheet's

I have a basic window with an input field (page 1), on top of it appears a popup window (page 2), inside of which there is also an input fields and buttons, which, when clicked, will bring up a small window with an input field (page 3). If there is no "Done" on the keyboard, the interface functions normally. If you add a "Done" button, it turns out that its color changes from system color blue to gray when moving from page 2 to page 3. Experimenting and wondering why this is so, I found that the toolbar on page 1 is responsible for the color of the button on page 3... If you change the color of the button on the toolbar on page 1 - it will change on the toolbar on page 3, and page 2 will not be affected. Also, adding buttons causes error: "[LayoutConstraints] Unable to simultaneously satisfy constraints." I want to understand why setting the button on the keyboard for Page 1, I also get a button when I type on Page 3? And why is it grayed out and not working? Why if I change the color for the button on Page 1, does it also change for that gray button on Page 3?
A small representative sample:
ContentView
import SwiftUI
struct ContentView: View {
#State private var bloodClucoseLvl: String = ""
#State private var isSheetShown: Bool = false
#FocusState private var focusField: Bool
var body: some View {
NavigationView {
List {
Section("Add your current blood glucose lvl") {
TextField("5,0 mmol/l", text: $bloodClucoseLvl)
.focused($focusField)
}
Section("Add food or drink") {
Button(action:{
isSheetShown.toggle()
}, label:{
HStack{
Text("Add")
Image(systemName: "folder.badge.plus")
}
})
.sheet(isPresented: $isSheetShown) {
addFoodButton()
}
}
}
.navigationTitle("Page 1 - General")
.toolbar{
ToolbarItem(placement: .keyboard) {
HStack {
Spacer()
Button(action: {
focusField = false
}) {
Text("Done")
}
}
}
}
.ignoresSafeArea(.keyboard)
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
addFoodButton
import SwiftUI
struct addFoodButton: View {
#State private var selectedFood: String = ""
#State public var addScreen: Bool = false
var body: some View {
ZStack {
NavigationView {
List {
Section("or choose from category"){
NavigationLink(destination: Alcohol(addScreen: $addScreen)){
Text("Alcohol")
}
}
}
.listStyle(.insetGrouped)
.searchable(text: $selectedFood, prompt: "Search by word")
.navigationTitle("Page 2 - Search in DB")
}
if addScreen{
addScreenView(addScreen: $addScreen)
}
}
}
}
struct Alcohol: View {
#State private var searchInsideCategory: String = ""
#Binding var addScreen: Bool
var body: some View {
List {
Button(action: {addScreen.toggle()}){
Text("Light beer")
}
}
.navigationTitle("Page 2 - Choose beer")
.searchable(text: $searchInsideCategory, prompt: "Search inside a category")
}
}
struct addFoodButton_Previews: PreviewProvider {
static var previews: some View {
addFoodButton()
}
}
addScreenView
import SwiftUI
struct addScreenView: View {
#Binding var addScreen: Bool
#State private var gram: String = ""
var body: some View {
ZStack{
Color.black.opacity(0.2).ignoresSafeArea()
VStack(spacing:0){
Text("Page 3 - Add an item")
.bold()
.padding()
Divider()
VStack(){
TextField("gram", text: $gram)
.padding(.leading, 16)
.padding(.trailing, 16)
.keyboardType(.numberPad)
Rectangle()
.frame(height: 1)
.padding(.leading, 16)
.padding(.trailing, 16)
}.padding()
Divider()
HStack(){
Button(action: {
addScreen.toggle()
}){
Text("Cancel").frame(minWidth:0 , maxWidth: .infinity)
}
Divider()
Button(action: {
addScreen.toggle()
}){
Text("Save").frame(minWidth:0 , maxWidth: .infinity)
}
}.frame(height: 50)
}
.background(Color.white.cornerRadius(10))
.padding([.leading, .trailing], 15)
}
}
}
struct addScreenView_Previews: PreviewProvider {
static var previews: some View {
addScreenView(addScreen: .constant(true))
}
}

How to change body background color with if in SwiftUI

I'm creating a simple iOS app with SwiftUI, and I'd like to change my view's background color when switch toggle change.
My code
struct ContentView: View {
#State private var isOnLight: Bool = false
var body: some View {
VStack {
Toggle(isOn: $isOnLight) {
Text("Switch")
.font(.title)
.foregroundColor(.gray)
}
if isOnLight {
}
}.padding()
}
}
For background colors you can use the ZStack like this and with one line ifs then decide on the color
struct ContentView: View {
#State private var isOnLight: Bool = false
var body: some View {
ZStack {
isOnLight ? Color.blue : Color.red
VStack {
Toggle(isOn: $isOnLight) {
Text("Switch")
.font(.title)
.foregroundColor(.gray)
}
}
.padding()
}
}
}
To learn about how to use ternary operator in SwiftUI you can watch this video
You just need to embed your VStack inside a ZStack, where the back layer is a color that changes every time isOnLight changes.
Like this:
struct Example: View {
#State private var isOnLight: Bool = false
#State private var color: Color = .white
var body: some View {
ZStack {
color
.ignoresSafeArea()
VStack {
Toggle(isOn: $isOnLight) {
Text("Switch")
.font(.title)
.foregroundColor(.gray)
}
}
.padding()
}
.onChange(of: isOnLight) { value in
if value {
color = .yellow
} else {
color = .white
}
}
}
}

SwiftUI disappear back button with navigationLink

I have 3 views. One of these have NavigationView second have NavigationLink and last just a child with toolbar.
So my problem when I added toolbar in last view backButton elegant disappear. How can I solve this?
Screen recording of my problem
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
Text("Hello, world!")
.padding()
NavigationLink(destination: ListView()) {
Image(systemName: "trash")
.font(.largeTitle)
.foregroundColor(.red)
}
}.navigationBarHidden(true)
.navigationTitle("Image")
}
}
}
import SwiftUI
struct ListView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
VStack {
List {
NavigationLink(destination: DetailView()) {
Text("Detail")
}
}
}.navigationBarTitle(Text("Data"), displayMode: .large)
.toolbar {
Button("Save") {
presentationMode.wrappedValue.dismiss()
}
}
}
}
import SwiftUI
struct DetailView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
VStack {
Text("DetailView")
.padding()
}.navigationBarTitle(Text("Data"), displayMode: .large)
.toolbar {
Button("Save") {
presentationMode.wrappedValue.dismiss()
}
}
}
}
In the console, you'll notice this message:
2021-04-27 12:37:36.862733-0700 MyApp[12739:255441] [Assert] displayModeButtonItem is internally managed and not exposed for DoubleColumn style. Returning an empty, disconnected UIBarButtonItem to fulfill the non-null contract.
The default style for NavigationView is usually DefaultNavigationViewStyle, which is really just DoubleColumnNavigationViewStyle. Use StackNavigationViewStyle instead, and it works as expected.
Edit: You are right that StackNavigationViewStyle will break iPad split view. But thankfully, DoubleColumnNavigationViewStyle works fine in iPad and doesn't hide the back button. We can then just use a different NavigationStyle depending on the device, as shown in this answer.
struct ResponsiveNavigationStyle: ViewModifier {
#Environment(\.horizontalSizeClass) var horizontalSizeClass
#ViewBuilder
func body(content: Content) -> some View {
if horizontalSizeClass == .compact { /// iPhone
content.navigationViewStyle(StackNavigationViewStyle())
} else { /// iPad or larger iPhone in landscape
content.navigationViewStyle(DoubleColumnNavigationViewStyle())
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
VStack {
Text("Hello, world!")
.padding()
NavigationLink(destination: ListView()) {
Image(systemName: "trash")
.font(.largeTitle)
.foregroundColor(.red)
}
}
.navigationBarHidden(true)
.navigationTitle("Image")
}
.modifier(ResponsiveNavigationStyle()) /// here!
}
}
Result:
iPad
iPhone
I don't know why, but it's what worked for me:
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button { } label: { } // button to the right
}
ToolbarItem(placement: .navigationBarLeading) {
Text("") // empty text in left to prevent back button to disappear
}
}
I already tried to replace the empty text with EmptyView() but the button keeps disappearing.
FYI: I have this problem only on my device with iOS 14, but in another device with iOS 15 the back button never disappears.

Buttons in NavigationViewItems do not work with incomplete swipe

I created a NavigationView which I put a NavigationLink on to another View. The first page with a hidden title. After going to page 2 through Link, everything works at first. I have a title there and 2 navigationBarItems buttons. But if you pull from left to right back to the first screen, but then return (that is, do not go, but only pull a little), then all the buttons stop working. What is the problem?
struct View1: View {
var body: some View {
NavigationView {
ZStack {
Color.red
NavigationLink("View 2", destination: View2())
}.navigationbarhidden(true)
}
}
}
struct View2: View {
#State private var texti: String = ""
#State var themeColor = Color.green
var body: some View {
VStack {
TextEditor(text: $texti).padding().onReceive(texti.publisher.collect()) {
self.texti = String($0.prefix(2000))}
}.navigationBarTitle("Title").navigationBarItems(trailing: HStack { Button(action:{}) {
Text("Fund").foregroundColor(Color(#colorLiteral(red: 0.3647058824, green: 0.6901960784, blue: 0.4588235294, alpha: 1)))
}; Button(action: {}) {
Text("Gust").foregroundColor(Color(#colorLiteral(red: 0.3647058824, green: 0.6901960784, blue: 0.4588235294, alpha: 1)))
}})
}

SwiftUI toggle active / inactive line color on custom TextField

I'm trying to create a TextField with a line below it, for that I used a Divide as suggested in this answer and I added a .onTapGesture() to it to change the Divider color, however that's a component that's embedded in another view.
In my ContentView I have a Button and my UnderscoredTextField and they are contained inside a HStack and it's contained inside a Background component (taken from this answer) to be able to dismiss the keyboard programmatically, but I'd like to be able to change the #State var isActive variable inside the UnderscoredTextField when that happens, but as I'm new to SwiftUI I'm not quite sure how to achieve this.
import SwiftUI
struct ContentView: View {
var body: some View {
Background {
UnderscoredTextField(phoneNumber: "")
}.onTapGesture {
self.hideKeyboard()
//How to tell my UnderscoreTextField's isActive variable to change
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
import SwiftUI
struct UnderscoredTextField: View {
#State var phoneNumber: String
#State var isActive: Bool = false
var body: some View {
VStack {
TextField("12345678", text: $phoneNumber)
.keyboardType(.phonePad)
.onTapGesture {
self.isActive = true
}
Divider()
.padding(.horizontal)
.frame(height: 1)
.background(isActive ? Color.red : Color.gray)
}
}
}
struct UnderscoredTextField_Previews: PreviewProvider {
static var previews: some View {
UnderscoredTextField(phoneNumber: "12345678")
}
}
This is what it looks like when I hide the keyboard, but I'd like to switch it back to gray
If I understood correctly it is enough to use onEditingChanged, like below
var body: some View {
VStack {
TextField("12345678", text: $phoneNumber, onEditingChanged: {
self.isActive = $0 // << here !!
}).keyboardType(.phonePad)
Divider()
.padding(.horizontal)
.frame(height: 1)
.background(isActive ? Color.red : Color.gray)
}
}

Resources