Having a weird issue where my keyboard is dismissing automatically my .sheet. I'll attach a gif below. When I click on any textfield, the sheet will dismiss. In the gif as well I showed the functionality without the keyboard to show the view does not dismiss when not toggled.
No idea where to begin with this. If someone can point me in the right direction that'll be great, as it'll be tough to create a good minimal reproducible example with the code I have. As a note, nowhere in my code am I toggling the view to disappear.
Calling my sheet below
TabView {
ForEach(Array(allRecipes.chunked(into: 6)), id: \.self) { recipesChunk in
LazyVGrid(columns: columns) {
ForEach(recipesChunk, id: \.id) { recipe in
self.item(image: recipe.recipeImage, title: recipe.recipeTitle, ingredients: recipe.ingredientItem, directions: recipe.directions, recipeID: recipe.id, recipeCaloriesMacro: recipe.recipeCaloriesMacro, recipeFatMacro: recipe.recipeFatMacro, recipeCarbMacro: recipe.recipeCarbMacro, recipeProteinMacro: recipe.recipeProteinMacro, prepTime: recipe.recipePrepTime)
.onTapGesture {
selectedRecipe = recipe
}
}
}.frame(maxHeight: .infinity, alignment: .top)
.fullScreenCover(item: $selectedRecipe){
RecipeControllerModal(name: $0.recipeTitle, prepTime: $0.recipePrepTime, image: $0.recipeImage, ingredients: $0.ingredientItem, directions: $0.directions, recipeID: $0.id, recipeCaloriesMacro: $0.recipeCaloriesMacro, recipeFatMacro: $0.recipeFatMacro, recipeCarbMacro: $0.recipeCarbMacro, recipeProteinMacro: $0.recipeProteinMacro)
}
}
}
Textfield
TextField("ex. I am a textfield", text: $userDirection)
.font(.body)
.padding(.leading, 45)
.padding(.top, 15)
Related
I've encountered a problem while trying to learn SwiftUI. Is there any way, and if there is- how to add background, which sticks to the top and would ignore the top safe area, to the "Body weight" Section?
My code:
LazyVStack(alignment: .leading, spacing:20, pinnedViews: .sectionHeaders) {
Section(header:
//Body weight view
Text("Body weight")
.font(.system(size: 28))
.fontWeight(.medium)
){
ForEach(0..<20) { index in
Button(action: {
}, label: {
WorkoutListItem(title:"title", progress: 0.7)
})
}
}
}
What I have:
Desired result:
Since I'm really fresh, when it comes to SwiftUI and whole Apple development, I've only tried adding rectangle as a background, which didn't help.
I am writing an iOS app using SwiftUI. As part of the data presented, I need to have a password field with a show/hide button that toggles between hidden/visible. To do this, I'm using a combination of SwiftUI's SecureField and TextField controls. Both are in the disabled state to prevent users from editing, as I want this field to be read-only. This setup is working perfectly. (See screenshot below)
Now I would like to add functionality that would allow the user to copy the password to the clipboard just by tapping on it. I've added an onTapGesture block to my SecureField, however it is never called. I believe this is due to the fact that my SecureField/TextField is marked as disabled. A snippet of my code follows:
ZStack(alignment: .trailing) {
if isSecured {
VStack {
SecureField("", text: Binding.constant(text))
.disabled(true)
.onTapGesture {
print("USER TAPPED!")
}
}
} else {
TextField("", text: Binding.constant(text))
.disabled(true)
}
Image(systemName: self.isSecured ? "eye" : "eye.slash")
.accentColor(.gray)
.foregroundColor(.gray)
.frame(width: 20, height: 20, alignment: .center)
.modifier(TouchDownUpEventModifier(changeState: { (buttonState) in
withAnimation {
self.isSecured.toggle()
}
}))
}
Is there a way to accomplish this? I want my SecureField/TextField to be read-only but still detect when a user taps the control?
I was able to solve this by adding the following modifiers on my ZStack:
.contentShape(Rectangle())
.onTapGesture {
...
}
Once I did this, my ZStack began intercepting the touch events and I could perform the desired action.
I have in my app a layout showing a list of rectangular cards - each one should be tappable (once) to reveal a set of action buttons and more information, etc.
I have implemented this using .onTapGesture() and I have also put .contentShape(Rectangle() to enforce the tappable area. However, while my implementation works fine for touchscreen interface, when I'm using it with the iPadOS mouse support, and on Catalyst for that matter, I see some very unexpected behaviour.
I've made a minimal reproducible example below that you can copy to recreate the problem.
Where the problems are when using mouse/trackpad input:
Not every click of the mouse is recorded as a tap gesture. This is happening mostly arbitrary except from in a few cases:
It seems to click either only in very specific areas, or when clicking multiple times in the same spot.
It seems that in a lot of cases only every other click is recorded. So I double click to get only one tap gesture.
It isn't evident in this example code, but in my main app the tappable areas are seemingly arbitrary - you can usually click near text or in alignment with it to record a tap gesture, but not always.
If you are running the example code you should be able to see the problem by repeatedly moving the mouse and attempting one click. It doesn't work unless you click multiple times in the same spot.
What does work as expected:
All input using touch instead of mouse; regardless of where you tap it records a tap gesture.
Mouse input when running as a native Mac target. The issues mentioned above are only for mouse/trackpad when running the example under iPadOS and Mac Catalyst.
Code I used to recreate this problem (has a counter to count every time a tap gesture is recorded):
struct WidgetCompactTaskItemView: View {
let title: String
let description: String
var body: some View {
HStack {
Rectangle()
.fill(Color.purple)
.frame(maxWidth: 14, maxHeight: .infinity)
VStack(alignment: .leading) {
Text(title).font(.system(size: 14, weight: .bold, design: .rounded))
Text(description).font(.system(.footnote, design: .rounded))
.frame(maxHeight: .infinity)
.fixedSize(horizontal: false, vertical: true)
.lineLimit(1)
.padding(.vertical, 0.1)
Spacer()
}
.padding(.horizontal, 6)
.padding(.top, 12)
}
.frame(maxWidth: .infinity, maxHeight: 100, alignment: .leading)
.background(Color.black)
.cornerRadius(16)
.overlay(
RoundedRectangle(cornerRadius: 16)
.stroke(Color.green, lineWidth: 0.5)
)
}
}
struct ContentView: View {
#State var tapCounter = 0
var body: some View {
VStack {
Text("Button tapped \(tapCounter) times.")
WidgetCompactTaskItemView(title: "Example", description: "Description")
.contentShape(Rectangle())
.onTapGesture(count: 1) {
tapCounter += 1
}
Spacer()
}
}
}
I have tried several things including moving modifiers around, setting eoFill to true on the contentShape modifier (which didn't fix the problem but simply made different unexpected behaviour).
Any help to find a solution that works as expected and works consistently whether mouse or touch would be much appreciated. I am not sure if I am doing something wrong or if there is a bug here, so please try and recreate this example yourself using the code to see if you can reproduce the problem.
So I realised that there was a much better solution that could bypass all the oddities that .onTapGesture had for me with mouse input. It was to encapsulate the whole view in a Button instead.
I made this into a modifier similar to onTapGesture so that it's much more practical.
import Foundation
import SwiftUI
public struct UltraPlainButtonStyle: ButtonStyle {
public func makeBody(configuration: Self.Configuration) -> some View {
configuration.label
}
}
struct Tappable: ViewModifier {
let action: () -> ()
func body(content: Content) -> some View {
Button(action: self.action) {
content
}
.buttonStyle(UltraPlainButtonStyle())
}
}
extension View {
func tappable(do action: #escaping () -> ()) -> some View {
self.modifier(Tappable(action: action))
}
}
Going through this:
I first have a button style which simply returns the label as is. This is necessary because the default PlainButtonStyle() still has a visible effect when clicked.
I then create a modifier that encapsulates the content given in a Button with this button style, then add that as an extension to View.
Usage example
WidgetCompactTaskItemView(title: "Example", description: "Description")
.tappable {
tapCounter += 1
}
This has solved all problems I've been having with clickable area using a mouse.
I have got a modal sheet, here is the code:
SettingsDashboardView:
#State private var notificationsSettingsSheet = false
var body: some View {
Button(action: {
self.notificationsSettingsSheet.toggle()
}) {
VStack(alignment: .leading) {
HStack(alignment: .top, spacing: 4) {
Label("Set Daily Reminders", systemImage: "alarm").foregroundColor(Color("TextColor"))
.font(.system(.headline, design: .rounded))
Spacer()
}
}
}
.sheet(isPresented: $notificationsSettingsSheet) {
NotificationSettingsModal()
}
}
NotificationSettingsModal:
var body: some View {
ZStack(alignment: .bottom) {
ScrollView {
VStack(alignment: .leading, spacing: 0) {
Text("Daily Reminders")
.font(.system(.title, design: .rounded))
.fontWeight(.bold)
.padding(.top, headingTopPadding)
.padding(.horizontal, headingHorizontalPadding).foregroundColor(Color("TextColor"))
Spacer().frame(height: 164)
}.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: .infinity)
Spacer().frame(height: 64)
}
}.background(Color("BackgroundColor").edgesIgnoringSafeArea(.all))
}
When I launch the app and open my sheet, in about 50% of cases sheet dismisses itself after about half a second. If I open sheet after that everything works fine. What can cause this problem?
This will probably not solve the mentioned issue but can be useful for others.
In most cases, this issue happens when the view gets redrawn due to a change in some variables. Be careful that it might be the parent view that have some variables changes.
The best way to debug this kind of behaviour is to use the technique describe here, on Hacking with Swift. The idea is to identify what change caused a view to reload itself by printing print(Self._printChanges()) inside the body property. Note that by doing it, you will temporarily need to add an explicit return.
Then, observer the console and it most cases you will be able to identify the issue and refactor your code.
In my experience (does not seem to be the case here) this often happens when using #Environment(\.editMode) var editMode in both the view and parent view. For some reasons this value changes in both views when presenting a sheet, causing the view to be redrawn and the sheet closed.
I solved this problem by removing the codes below while setting to NavigationView on my homeView this week, which caused my subView's sheet automatically dismissed the first time showing.
NavigationView {...}
// .navigationViewStyle(StackNavigationViewStyle())
I am developing an iOS app with list by SwiftUI. I am implementing .onDelete to enable user to delete the rows. However, I have found that when I add a .onTapGesture to the VStack View containing the List, the onDelete function is not called when the user tapped the "Delete" button after slide the row left. However, it stills works when the user slide the row to the left side to delete this. It seems that the .onTapGesture blocks .onDelete to receive user input. How to solve this?
NavigationView {
VStack(alignment: .leading) {
List {
ForEach(things) { thing in
Text(thing)
}
.onDelete(perform: { indexSet in
things.remove(atOffsets: indexSet)
})
}
.listStyle(SidebarListStyle())
}
.onTapGesture {
}
}
Here is some code that can show my problem.
This is probably useless and very hacky but... If you must have an area that is tappable, you could place everything in a ZStack and put a random view over the List and make that tappable. Then you could set a good amount of padding to free the area where the delete button would be tapped, like this:
NavigationView {
ZStack {
List {
ForEach(things, id: \.self) { thing in
Text(thing)
}
.onDelete(perform: { indexSet in
things.remove(atOffsets: indexSet)
})
}
.zIndex(2)
.listStyle(SidebarListStyle())
Rectangle()
.zIndex(3)
.fill(Color.red)
.padding(.trailing, 80)
.allowsHitTesting(true)
.onTapGesture {
print("Blocking you")
}
}
}
I put the color in so you can see exactly where you are working, the code above would give you a look like this:
Tappable Rectangle on List
When you're happy with the area that the rectangle covers, you can just set the Opacity to a very small amount, just by replacing the color with
.opacity(0.000001)
or something. Unfortunately taps don't work with Color.clear or hidden() modifiers.