SwiftUI - Form Picker & Button Not Co-Existing - swiftui

I have created a reusable component that has a picker, textfield and button within a form. However, with the button present, a tap on the picker field does not go to the picker. Rather it executes the button code. The TextField works fine. If I remove the button code, the proper behavior will occur with the picker. So the question is how to have both elements within this component? Please note that the preview adds the Navigation and Form which would otherwise come from the parent view.
var body: some View {
HStack {
if !showFee {
Spacer()
Button(action: {
withAnimation(.easeInOut(duration: 0.3)) {
self.showFee.toggle()
}
}) {
Image(systemName: "plus.circle")
.font(.largeTitle)
}
Spacer()
} else {
VStack(spacing:20) {
Picker(selection: $feeSelection, label: Text("Fee Type")) {
ForEach(0 ..< fees.count) {
Text(self.fees[$0])
}
}
TextField("Fee Amount: $", value: $feeAmount, formatter: NumberFormatter.currency)
.keyboardType(.decimalPad)
Divider()
Button(action: {
withAnimation(.easeInOut(duration: 0.3)) {
self.showFee.toggle()
}
}) {
Image(systemName: "trash.circle.fill")
.font(.largeTitle)
.foregroundColor(.red)
}
}
}
}
.padding()
}
}
struct FeeCell_Previews: PreviewProvider {
static var previews: some View {
NavigationView {
Form {
FeeCell()
}
}
}
}

What you can do is to apply the PlainButtonStyle to your button. This will stop the button's tap covering to the whole cell in the Form:
Button(action: {}) {
Text("Button")
}
.buttonStyle(PlainButtonStyle())

Related

Centering Item Inside Horizontal Stack with left and right views doesn't work with DatePicker

There was a similar question Center Item Inside Horizontal Stack that I followed the Asperi's basic answer that suited for me ( other answer did not worked, too).
struct HeaderTestView: View {
#State private var currentDate = Date()
var body: some View {
ZStack {
HStack {
HStack {
Button(action: {
}) {
Image(systemName: "arrowtriangle.left.fill")
}
}
Spacer()
HStack {
Button(action: {
}) {
Image(systemName: "arrowtriangle.right.fill")
}
}
}
HStack {
Text("Title")
DatePicker("Date", selection: $currentDate, displayedComponents: [.date])
}
}
}
}
My Center Item are a Text and a DatePicker, everything works as expected until the date picker is placed. The title of DatePicker is placed on the left and date is placed on the right.
Without the DatePicker
With the DatePicker
Any idea to solve this beautifully so that a Text and a DatePicker are close on the center while there are two HStacks that are adjested to on the left and right
Add .fixedSize() to the HStack containing the Text view and DatePicker:
struct ContentView: View {
#State private var currentDate = Date()
var body: some View {
ZStack {
HStack {
HStack {
Button(action: {
}) {
Image(systemName: "arrowtriangle.left.fill")
}
}
Spacer()
HStack {
Button(action: {
}) {
Image(systemName: "arrowtriangle.right.fill")
}
}
}
HStack {
Text("Title")
DatePicker("Date", selection: $currentDate, displayedComponents: [.date])
}
.fixedSize() // here
}
}
}
or add .fixedSize() to the DatePicker.

Closing A View From Another View

I am still learning SwiftUI and I have come across a small problem.
In my app, I have a main view. On the top is a search bar and at the bottom, a menu with different buttons. I want to change views when clicking those buttons. However, I only want to change the middle section.
No big deal, I will just put the middle part into a NavigationView. That works alright and I am able to change my views. My problem is that the buttons below do not have any impact on the new view.
To try to simplify: Let’s say I’m on home page. I then click the grocery list button (guess what I’m making school projects lol). My navigation link works just fine and goes to the list. So, now I’m on view 2 let’s say. When I press the home button, it doesn’t close that view and go to my main one. Here is my code setup:
import SwiftUI
struct ContentView: View {
#State private var searchText: String = ""
#State private var action: Int? = 0
var body: some View {
ZStack {
// Top Menu
VStack{
HStack {
Spacer()
TextField("Search",
text: $searchText)
.background(Color.white)
Button(action: {
self.action = 1
}, label: {
Image(systemName: "magnifyingglass.circle")
.font(.largeTitle)
})
Spacer()
}
// Body
NavigationView {
VStack {
Text("Can I See Something")
NavigationLink(destination: SearchView(), tag: 1, selection: $action) {
}
Text("Yes/No")
}
}
Spacer()
// Bottom Menu
HStack (alignment: .top) {
Spacer()
VStack {
Button(action: {
}, label: {
Image(systemName: "house.fill")
.font(.largeTitle)
})
.padding(.top)
Text("Home")
}
Divider()
.padding(.horizontal)
.frame(width: 2.5, height: 100)
VStack {
Button(action: {
}, label: {
Image(systemName: "newspaper")
.font(.largeTitle)
})
.padding(.top)
Text("Weekly\nAd")
.multilineTextAlignment(.center)
}
Divider()
.padding(.horizontal)
.frame(width: 2.5, height: 100)
VStack {
Button(action: {
}, label: {
Image(systemName: "checklist")
.font(.largeTitle)
})
.padding(.top)
Text("Grocery\nList")
.multilineTextAlignment(.center)
}
Divider()
.padding(.horizontal)
.frame(width: 2.5, height: 100)
VStack {
Button(action: {
}, label: {
Image(systemName: "person.crop.circle")
.font(.largeTitle)
})
.padding(.top)
Text("Account")
}
Spacer()
}
}
}
}
}
struct SearchView: View {
var body: some View {
ZStack {
Text("Nothing to see here!")
}
.navigationBarBackButtonHidden(true)
}
}
SearchView is a separate view (in its own file) in the app that opens up when the magnifying glass button is pressed. Currently it does not do anything. However I want to be able to press those buttons on this view above to still navigate the app.
Also, on another note, is there anyway to get rid of the back button?
In your code the buttons do not have any function.
Instead of creating a tab bar on your own, I'd rather take something like:
import SwiftUI
struct ContentView: View {
var body: some View {
TabView {
MainView()
.tabItem {
Label("Home", systemImage: "house.fill")
}
NewsView()
.tabItem {
Label("Weekly\nAd", systemImage: "newspaper")
}
OrderView()
.tabItem {
Label("Grocery\nList", systemImage: "checklist")
}
AccountView()
.tabItem {
Label("Account", systemImage: "person.crop.circle")
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
struct MainView: View {
var body: some View {
Text("Home View")
}
}
struct NewsView: View {
var body: some View {
Text("News View")
}
}
struct OrderView: View {
var body: some View {
Text("Order View")
}
}
struct AccountView: View {
var body: some View {
Text("Account View")
}
}
In that case you'll have to create a view for each tab you are configuring (see the last 4 structs).
If you want to do it with a Stack with your own created buttons, I think you should create al 4 views as well and then you either hide them or put them out of focus by using an offset. In that case the buttons should hide/show the specific views or change the offset accordingly to move the specific views into the visible area. With the offset you also can add some animation.
Regarding the search bar on top of your app, since the views are all different, I wouldn't keep the same search bar everywhere, but if you really want to have it that way, you can embed the code + your search bar into a VStack (as you did it in your example).

SwiftUI 2.0: Close button is not dismissing the view - how do I get the Close button to return to the previous view?

I have tried to use Buttons and Navigation Links from various examples when researched on this channel and on the net. The NavigationLink would be ok, except that the NavigationView is pushing everything down in my view.
I have a view that contains an image and a text like this: ( x Close) but when I use the code below, the Close button is not doing anything.
In ContentView() I have a (?) button that takes me from WalkthroughView(), then to the PageTabView, then to this view, TabDetailsView:
ContentView():
ZStack {
NavigationView {
VStack {
Text("Hello World")
.padding()
.font(.title)
.background(Color.red)
.foregroundColor(.white)
}
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
withAnimation {
showOnBoarding = true
}
} label: {
Image(systemName: "questionmark.circle.fill")
}
}
}
}
.accentColor(.red)
.disabled(showOnBoarding)
.blur(radius: showOnBoarding ? 3.0 : 0)
if showOnBoarding {
WalkthroughView(isWalkthroughViewShowing: $isWalkthroughViewShowing)
}
}
.onAppear {
if !isWalkthroughViewShowing {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
withAnimation {
showOnBoarding.toggle()
isWalkthroughViewShowing = true
}
}
}
}
WalkthroughView():
var body: some View {
ZStack {
GradientView()
VStack {
PageTabView(selection: $selection)
// shows Previous/Next buttons only
ButtonsView(selection: $selection)
}
}
.transition(.move(edge: .bottom))
}
PageTabView():
var body: some View {
TabView(selection: $selection) {
ForEach(tabs.indices, id: \.self) { index in
TabDetailsView(index: index)
}
}
.tabViewStyle(PageTabViewStyle())
}
below, is the TabDetailsView():
At the top of the view is this Close button, when pressed, should send me back to ContentView, but nothing is happening.
struct TabDetailsView: View {
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
let index: Int
then, inside the body:
VStack(alignment: .leading) {
Spacer()
VStack(alignment: .leading) {
// Button to close each walkthrough page...
Button(action: {
self.presentationMode.wrappedValue.dismiss()
}) {
Image(systemName: "xmark.circle.fill")
Text("Close")
}
.padding(.leading)
.font(.title2)
.accentColor(.orange)
Spacer()
VStack {
Spacer()
Image(tabs[index].image)
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 415)
.padding(.leading, 10)
Text(tabs[index].title)
.font(.title)
.bold()
Text(tabs[index].text)
.padding()
Spacer()
}
.foregroundColor(.white)
}
}
if showOnBoarding {
WalkthroughView(isWalkthroughViewShowing: $isWalkthroughViewShowing)
}
Inserting view like above is not a presentation in standard meaning, that's why provided code does not work.
As this view is shown via showOnBoarding it should be hidden also via showOnBoarding, thus the solution is to pass binding to this state into view where it will be toggled back.
Due to deep hierarchy the most appropriate way is to use custom environment value. For simplicity let's use ResetDefault from https://stackoverflow.com/a/61847419/12299030 (you can rename it in your code)
So required modifications:
if showOnBoarding {
WalkthroughView(isWalkthroughViewShowing: $isWalkthroughViewShowing)
.environment(\.resetDefault, $showOnBoarding)
}
and in child view
struct TabDetailsView: View {
#Environment(\.resetDefault) var showOnBoarding
// .. other code
Button(action: {
self.showOnBoarding.wrappedValue.toggle()
}) {
Image(systemName: "xmark.circle.fill")
Text("Close")
}

SwiftUI: Menu inside list cell with onTapGesture triggers gesture

I have a SwiftUI List on iOS14.3 (Xcode12.3) and inside it's cells I want to have a Menu popup up when the user is tapping on a button. Additionally these cells have an onTapGesture and the problem is that this gesture is also triggered when the menu button is pressed.
Example:
List {
HStack {
Text("Cell content")
Spacer()
// Triggers also onTapGesture of cell
Menu(content: {
Button(action: { }) {
Text("Menu Item 1")
Image(systemName: "command")
}
Button(action: { }) {
Text("Menu Item 2")
Image(systemName: "option")
}
Button(action: { }) {
Text("Menu Item 3")
Image(systemName: "shift")
}
}) {
Image(systemName: "ellipsis")
.imageScale(.large)
.padding()
}
}
.background(Color(.systemBackground))
.onTapGesture {
print("Cell tapped")
}
}
If you tap on the ellipsis image, the menu opens but also "Cell tapped" will be printed to the console. This is a problem for me, because in my real world example I am collapsing the cell within the tap gesture and of course I don't want that to happen when the user presses the menu button.
I found out, that when I long press on the button the gesture won't be triggered. But this is not acceptable UX in my opinion, took me a while to find that out for my self.
I also noticed, when I replace the Menu with a regular Button, then the cells gesture is not triggered while pressing (only with BorderlessButtonStyle or PlainButtonStyle, otherwise he is not active at all). But I don't know how to open a Menu from it's action.
List {
HStack {
Text("Cell content")
Spacer()
// Does not trigger cells onTapGesture, but how to open Menu from action?
Button(action: { print("Button tapped") }) {
Image(systemName: "ellipsis")
.imageScale(.large)
.padding()
}
.buttonStyle(BorderlessButtonStyle())
}
.background(Color(.systemBackground))
.onTapGesture {
print("Cell tapped")
}
}
Alternatively, you can override Menu onTapGesture:
struct ContentView: View {
var body: some View {
List {
HStack {
Text("Cell content")
Spacer()
Menu(content: {
Button(action: {}) {
Text("Menu Item 1")
Image(systemName: "command")
}
Button(action: {}) {
Text("Menu Item 2")
Image(systemName: "option")
}
Button(action: {}) {
Text("Menu Item 3")
Image(systemName: "shift")
}
}) {
Image(systemName: "ellipsis")
.imageScale(.large)
.padding()
}
.onTapGesture {} // override here
}
.contentShape(Rectangle())
.onTapGesture {
print("Cell tapped")
}
}
}
}
Also, there's no need to use .background(Color(.systemBackground)) to tap on spacers. This is not really flexible - what if you want change background color?
You can use contentShape instead:
.contentShape(Rectangle())
I'm not sure if this is a bug or expected behavior, but instead of fighting with it I would recommend to avoid such overlapping (because even with success today it might stop working on different systems/updates).
Here is possible solution (tested with Xcode 12.1 / iOS 14.1)
List {
HStack {
// row content area
HStack {
Text("Cell content")
Spacer()
}
.background(Color(.systemBackground)) // for tap on spacer area
.onTapGesture {
print("Cell tapped")
}
// menu area
Menu(content: {
Button(action: { }) {
Text("Menu Item 1")
Image(systemName: "command")
}
Button(action: { }) {
Text("Menu Item 2")
Image(systemName: "option")
}
Button(action: { }) {
Text("Menu Item 3")
Image(systemName: "shift")
}
}) {
Image(systemName: "ellipsis")
.imageScale(.large)
.padding()
}
}
}

SwiftUI NavigationLink not behaving correctly when buttons are close together

I'm trying to implement a programmable NavigationLink to go to another view, triggered by a simple button. But when that button is next to another button, instead of triggering the NavigationLink, it triggers the action of the button next to it.
struct NavLinkView: View {
#State var showPasswordStr = true
#State var showCheckView = false
#State var password = "Abc"
var body: some View {
Form {
VStack {
NavigationLink(destination: CheckView(), isActive: $showCheckView ) {
EmptyView()
}
Text("Password")
.font(.system(size: 12, weight: .light, design: .default))
.foregroundColor(.gray)
HStack {
if showPasswordStr {
TextField("", text: $password)
.textFieldStyle(RoundedBorderTextFieldStyle())
} else {
SecureField("", text: $password)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
Button(action: {
showPasswordStr.toggle()
} ) {
Image(systemName: showPasswordStr ? "eye.slash" : "eye" )
}
.padding(.leading)
Button(action: { showCheckView.toggle() } ) {
Image(systemName: "checkmark.circle" )
}
.padding(.leading)
}
}
}
}
}
What am I mising? If I move the NavigationLink to be after the VStack, then both buttons trigger both the action of the first button and the NavigationLink.
As your buttons are in a Form you need to change their style to PlainButtonStyle:
Button(action: {
self.showPasswordStr.toggle()
}) {
Image(systemName: showPasswordStr ? "eye.slash" : "eye")
}
.buttonStyle(PlainButtonStyle()) // <- add style
Button(action: { self.showCheckView.toggle() }) {
Image(systemName: "checkmark.circle")
}
.buttonStyle(PlainButtonStyle()) // <- add style