HStack: onTapGesture only works on elements - swiftui

I have this example:
struct ContentView: View {
var body: some View {
HStack {
Text("part1")
Spacer()
Text("part2")
Spacer()
Text("part3")
}
.onTapGesture {
print("tapped")
}
}
}
The problem is, that the .onTapGesture is only getting called, when I tap on a Text, but not if I tap on the space between the elements.
I tried to but a Rectangle().fill(Color.clear) as a background to the HStack, but it didn't work either (it just works, if I put i. e. Color.blue).

It needs to specify content shape to cover all area, because by default only opaque things handle gesture
}
.contentShape(Rectangle()) // << here !!
.onTapGesture {
print("tapped")
}

Related

SwiftUI bottom sheet like note app's format tab

Currently, I have to implement bottom sheet. And I found the very example of my need.
Is this component system component of swift or swiftui?
OR do I need to implement on my own?
PLEASE LET ME KNOW IF U HAVE SOME INFOS! XD
At first I implement with ZStack, drag gesture but the animation is not what I expected.
I need Information about whether there is component like .sheet(isPresented: Bool, content: View) of the modal like above image.
As our friend said before, it is a sheet. Inside the sheet you can either define a new view or call any of your views. Then you have to use the modifier .presentationDetents which receive a Set of PresentationDetents to say where the view has to stop when appearing on the screen. This modifier must be apply to the content of the sheet and not directly to the sheet.
struct ContentView: View {
#State var isSheetShown = false
var body: some View {
VStack {
Button("Show view"){
isSheetShown = true
}
}.sheet(isPresented: $isSheetShown, content: {
StackOfButtons()
.presentationDetents([.medium])
})
.padding()
}
}
Finally, to create that stack type of buttons you can put them all in a HStack, give them individually some padding, set a little of spacing in the HStack and the round the corners of the stack. Something like this:
struct StackOfButtons: View {
var body: some View {
HStack(spacing: 2){
Button {
print("Hola que ase")
} label: {
Image(systemName: "list.bullet")
.padding()
.background(.thinMaterial)
.foregroundColor(.black)
}
Button {
print("Hola que ase")
} label: {
Image(systemName: "list.dash")
.padding()
.background(.thinMaterial)
.foregroundColor(.black)
}
Button {
print("Hola que ase")
} label: {
Image(systemName: "list.number")
.padding()
.background(.thinMaterial)
.foregroundColor(.black)
}
}.cornerRadius(10)
}
}
Result

How to keep a view always centered independently from the size of the left view

I have a simple HStack containing a few views and I would like to keep one of the views ("Center") always centered. The size of the left view can change dynamically.
struct ExperimentView: View {
var body: some View {
HStack() {
Text("A")
.background(.red)
Spacer()
Text("Center")
.background(.yellow)
Spacer()
Button("B1") {
}
Button("B2") {
}
}
}
}
The Center view is not centered and moves to the right depending on the size of Text. I have tried something using alignmentGuide but I had no success.
A possible way is to wrap the "Center" text and the other views in a ZStack
struct ExperimentView: View {
var body: some View {
ZStack {
Text("Center")
.background(.yellow)
HStack {
Text("A")
.background(.red)
Spacer()
Button("B1") { }
Button("B2") { }
}
}
}
}
But this will not prevent the objects from overlapping if A becomes too wide.

How to detect taps on a List cell row in SwiftUI?

Given a basic List with Text, how can i make the whole "cell" from left side of the screen to right, tappable in a List, not just the "Hello world" text?
List {
VStack {
Text("Hello world")
}
.contentShape(Rectangle())
.onTapGesture {
print("Tapped cell") // This only triggers when you directly tap the Text
}
}
Add a Button and entire cell is tappable now:
VStack {
Button(action: {
print("Tapped")
}) {
Text("Hello world")
}
}
Actually, all you really need to do is make sure the entire cell is filled with content. Changing the VStack to an HStack and adding a Spacer() will give you what you want.
List {
HStack {
Text("Hello world")
Spacer()
}
.contentShape(Rectangle())
.onTapGesture {
print("Tapped cell") // This triggers when you tap anywhere in the cell
}
}

Animated transitions and contents within ScrollView in SwiftUI

I'm not quite a SwiftUI veteran but I've shipped a couple of apps of moderate complexity. Still, I can't claim that I fully understand it and I'm hoping someone with deeper knowledge could shed some light on this issue:
I have some content that I want to toggle on and off, not unlike .sheet(), but I want more control over it. Here is some "reconstructed" code but it should be able capture the essence:
struct ContentView: View {
#State private var isShown = false
var body: some View {
GeometryReader { g in
VStack {
ZStack(alignment: .top) {
// This element "holds" the size
// while the content is hidden
Color.clear
// Content to be toggled
if self.isShown {
ScrollView {
Rectangle()
.aspectRatio(1, contentMode: .fit)
.frame(width: g.size.width) // This is a "work-around"
} // ScrollView
.transition(.move(edge: .bottom))
.animation(.easeOut)
}
} // ZStack
// Button to show / hide the content
Button(action: {
self.isShown.toggle()
}) {
Text(self.isShown ? "Hide" : "Show")
}
} // VStack
} // GeometryReader
}
}
What it does is, it toggles on and off some content block (represented here by a Rectangle within a ScrollView). When that happens, the content view in transitioned by moving in from the bottom with some animation. The opposite happens when the button is tapped again.
This particular piece of code works as intended but only because of this line:
.frame(width: g.size.width) // This is a "work-around"
Which, in turn, requires an extra GeometryReader, otherwise, the width of the content is animated, producing an unwanted effect (another "fix" I've discovered is using the .fixedSize() modifier but, to produce reasonable effects, it requires content that assumes its own width like Text)
My question to the wise is: is it possible to nicely transition in content encapsulated within a ScrollView without using such "fixes"? Alternatively, is there a more elegant fix for that?
A quick addition to the question following #Asperi's answer: contents should remain animatable.
You are my only hope,
–Baglan
Here is a solution (updated body w/o GeometryReader). Tested with Xcode 11.4 / iOS 13.4
var body: some View {
VStack {
ZStack(alignment: .top) {
// This element "holds" the size
// while the content is hidden
Color.clear
// Content to be toggled
if self.isShown {
ScrollView {
Rectangle()
.aspectRatio(1, contentMode: .fit)
.animation(nil) // << here !!
} // ScrollView
.transition(.move(edge: .bottom))
.animation(.easeOut)
}
} // ZStack
// Button to show / hide the content
Button(action: {
self.isShown.toggle()
}) {
Text(self.isShown ? "Hide" : "Show")
}
} // VStack
}

SwiftUI: Alignment not applied

I have a small example that U do not understand:
Why is the alignment of my ZStack not applied to all of it's children? The TopView stays on top but I would expect, that every child would be on bottom right:
struct ContentView: View {
var body: some View {
ZStack(alignment: .bottomTrailing) {
VStack {
TopView()
Spacer()
}
Text("A new layer")
}.padding()
}
}
struct TopView: View {
var body: some View {
HStack(alignment: .top) {
VStack {
Text("SwiftUI")
Text("Layout")
}
Spacer()
Image(systemName: "star")
}
}
}
By default all the views are in the middle of area they need. Even you're using VStack it will be in the middle of the screen and be with height of two Text (as in your example).
ZStack has the same behavior - it will be as small as possible and right in the center of safe area.
Spacer just under TopView tries to take all the free space. Just as Spacer between star and texts.
So VStack in your ZStack technically is on .bottomTrailing, but takes all the free space. Just try to remove or change position of Spacer's: