Gesture blocking buttons in ZStack - swift

I have a gesture added to a Rectangle inside a ZStack. I have a VStack which is a view or menu of buttons. When I add the gesture to the Rectangle (because I need to detect a tap on that view behind the menu buttons), then the buttons can not receive interaction from the user on device. I have the Rectangle behind the buttons and they do show above the Rectangle. On the simulator it worked just fine but on device it does not. Any help would be greatly appreciated from the community. Thanks!
var body: some View {
ZStack {
Rectangle()
.foregroundColor(Color.black.opacity(0.01))
.gesture(DragGesture(minimumDistance: 0)
.onChanged({ (value) in
self.store.tapped = true
}))
VStack(alignment: .leading, spacing: 0) {
//menu buttons
HStack(alignment: .center, spacing: 18.5) {
someView()
.environmentObject(self.anotherStore)
.frame(width: 145, height: 22)
self.otherSubviews()
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 37, alignment: .leading)
.padding(EdgeInsets(top: 0, leading: 6, bottom: 0, trailing: 6))
//menu detail view
self.detailView()
}
.padding(EdgeInsets(top: 6, leading: 6, bottom: 6, trailing: 12))
}
}
I have tried changing the .zIndex for both to -1 for the Rectangle to something higher for the menu view. It does not change anything
I would like for the Rectangle to receive interaction (taps) from the user and also that the VStack of buttons will also receive interaction from the user.

Figured out that with a view in the background that has a gesture you need to make sure that your other views have .contentShape(Specify A Shape).
In the Apple documentation it states
/// Returns a new view that defines the content shape for
/// hit-testing `self` as `shape`.
I added .contentShape(Rectangle()) to the VStack and it allowed taps for the menu view of buttons as well as allowing taps to the Rectangle view in the background.
Updated code with answer
var body: some View {
ZStack {
Rectangle()
.foregroundColor(Color.black.opacity(0.01))
.gesture(DragGesture(minimumDistance: 0)
.onChanged({ (value) in
self.store.tapped = true
}))
VStack(alignment: .leading, spacing: 0) {
//menu buttons
HStack(alignment: .center, spacing: 18.5) {
someView()
.environmentObject(self.anotherStore)
.frame(width: 145, height: 22)
self.otherSubviews()
}
.frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: 37, alignment: .leading)
.padding(EdgeInsets(top: 0, leading: 6, bottom: 0, trailing: 6))
//menu detail view
self.detailView()
}.contentShape(Rectangle()) //Define a tappable area
.padding(EdgeInsets(top: 6, leading: 6, bottom: 6, trailing: 12))
}
}

Related

Expand HStack within List to go end to end but not top to bottom so it looks like there are wider spaces between items

I've been trying to create a list and expand the space between the rows, while keeping them the full width, but I seem to be having some challenges. I think the approach is to make the HStack within the list expand horizontally from end to end and try to narrow top and bottom. I thought a frame modifier would do this but it doesn't seem to be working.
Here is the code:
NavigationView() {
List(caseList) {caseItem in
HStack {
Image(systemName: "folder.fill.badge.person.crop")
.foregroundColor(.accentColor)
VStack(alignment: .leading, spacing: 5) {
Text(caseItem.alias)
Text("# team members")
.font(.subheadline)
.foregroundColor(.secondary)
}
}
.listRowInsets(EdgeInsets())
.background(Color.green)
.clipShape(RoundedRectangle(cornerRadius: 10))
.badge(Int.random(in: 0..<5))
.listRowSeparatorTint(Color.cyan, edges: .all)
.listRowSeparator(.visible)
.listRowBackground(Color.orange)
.frame(
maxWidth: .infinity,
minHeight: 0,
maxHeight: .infinity,
alignment: .topLeading
)
}
.listStyle(InsetGroupedListStyle())
}
.onAppear() {
loadCases()
}
.navigationTitle("Cases")
I would also like to delete the gap between the list items and cases at the top.
UPDATE
I added a 2nd image to show, in red, what I would like the green shape to cover. I guess my goal is to make them look a little more like independent items with additional information and bigger spaces between them. This should not be an exhaustive list and in most occasions people will only have one, so I am trying to make them of substance. I was toying with expanded/collapsed ones so if you only had one, it would be expanded and not look so lonely.
If I understood the goal correctly, here it is (tested with Xcode 13.4 / iOS 15.5)
HStack {
Image(systemName: "folder.fill.badge.person.crop")
.foregroundColor(.accentColor)
VStack(alignment: .leading, spacing: 5) {
Text(caseItem.alias)
Text("# team members")
.font(.subheadline)
.foregroundColor(.secondary)
}
Spacer()
}
.badge(Int.random(in: 0..<5)).padding(.horizontal)
.listRowInsets(EdgeInsets(top: 8, leading: 0, bottom: 8, trailing: 0))
.background(Color.green)
.listRowSeparatorTint(Color.cyan, edges: .all)
.listRowSeparator(.visible)
.listRowBackground(Color.orange)
If i understood correctly, you want to space out your Items inside of your list?
in this case, you can add some padding(EdgeInsets(top: 15, bottom: 15))
"change the cursive text to the values that you want"
this would look something like this:
NavigationView() {
List(caseList) {caseItem in
HStack {
Image(systemName: "folder.fill.badge.person.crop")
.foregroundColor(.accentColor)
VStack(alignment: .leading, spacing: 5) {
Text(caseItem.alias)
Text("# team members")
.font(.subheadline)
.foregroundColor(.secondary)
}
}
.listRowInsets(EdgeInsets())
.background(Color.green)
.clipShape(RoundedRectangle(cornerRadius: 10))
.badge(Int.random(in: 0..<5))
.listRowSeparatorTint(Color.cyan, edges: .all)
.listRowSeparator(.visible)
.listRowBackground(Color.orange)
.padding(EdgeInsets(top: 15, bottom: 15))
.frame(
maxWidth: .infinity,
minHeight: 0,
maxHeight: .infinity,
alignment: .topLeading
)
}
.listStyle(InsetGroupedListStyle())
}
.onAppear() {
loadCases()
}
.navigationTitle("Cases")
Hope that helped you! if you need anymore help, you can contact me over on GitHub or Discord! (Discord on my profile)

How to select a Picker with MenuPickerStyle outside of the label text

I have a simple menu picker that I have placed within a rounded rectangle border and chevron image. I'd like for users to be able to tap anywhere within this rectangle to activate the showing of the picker options but no matter what it only seems to be selectable within the actual text (see image with highlighted blue border). Is there any way to achieve this without scaling the text?
I've tried adding padding and scaling modifiers with no such luck.
var body: some View {
ZStack {
HStack {
Rectangle()
.foregroundColor(Color.black)
.frame(height: 40)
Image(uiImage: Icon.chevronDown.image())
.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 8))
}
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color.black, lineWidth: 1)
)
.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
Picker(selectedOption, selection: $selectedOption) {
ForEach(options, id: \.self) { option in
Text(option)
}
}
.pickerStyle(MenuPickerStyle())
}
}
How can I make the white area between the rounded black border and the blue text selectable to active the menu picker options the same way as tapping on the actual blue text?
Use Picker(selection:label:content:), which takes a View as the label: argument. Put everything you want as the tappable view inside the label: argument, like so:
var body: some View {
Picker(selection: $selectedOption, label:
HStack {
Rectangle()
.foregroundColor(Color(.systemBackground))
.frame(height: 40)
Image(systemName: "chevron.down")
.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 8))
}
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color.black, lineWidth: 1)
)
.overlay(
Text("Picker Option \(selectedOption)")
)
.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
) {
ForEach(options, id: \.self) { option in
Text("Picker Option \(option)")
}
}
.pickerStyle(MenuPickerStyle())
}
Update: The above code doesn’t work in Xcode 13 beta 5. Hopefully it’s a bug, but if not, here is a workaround: put the Picker in a Menu!
var body: some View {
Menu {
Picker("picker", selection: $selectedOption) {
ForEach(options, id: \.self) { option in
Text("Picker Option \(option)")
}
}
.labelsHidden()
.pickerStyle(InlinePickerStyle())
} label: {
HStack {
Rectangle()
.foregroundColor(Color(.systemBackground))
.frame(height: 40)
Image(systemName: "chevron.down")
.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 8))
}
.overlay(
RoundedRectangle(cornerRadius: 5)
.stroke(Color.black, lineWidth: 1)
)
.overlay(
Text("Picker Option \(selectedOption)")
)
.padding(EdgeInsets(top: 0, leading: 16, bottom: 0, trailing: 16))
}
}

How can I edit the shadow and style of an item that is being reordered in a list in SwiftUI?

So I am new to Swift and SwiftUI, and I'm trying to create a list that can be reordered. I could achieve this, but the thing is that the effect shown below is really annoying and I can't get rid of it.
Any ideas? Thanks!
List reordering effect shown in a gif
EDIT2: The effect I'm talking about is the shadow and black borders that appear on the selected item that is being dragged.
EDIT: This is my code now
GeometryReader { reader in
List {
ForEach(model.farmList, id: \.id) { farm in
buildRow(name: "Farm name")
.onTapGesture {
# does something
}
.padding(EdgeInsets(top: 4, leading: 16, bottom: 4, trailing: 16))
.frame(
minWidth: 0, maxWidth: .infinity,
minHeight: 76,
alignment: .leading
)
.listRowInsets(EdgeInsets())
}
.onMove(perform: move)
}
.environment(\.editMode, model.isEditable ? .constant(.active) : .constant(.inactive))
}

How to place texts under an image in SwiftUI?

I'm trying to create some tiles in my swiftUI project.
The tiles will need to look like this:
Basically, I need to place the caption on the left and the texts under the image.
I can create the image and I can overlay the texts on the image but I cannot place them under the image and I cannot place the caption to the left.
This is my current code:
Image("flower")
.resizable()
//.aspectRatio(contentMode: .fill)
.frame(width: 160, height: 200)
// Overlay is a very simple way to add content on top of your view ! 😁
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
.shadow(color: Color.black.opacity(0.3), radius: 6, x: 0, y: 3)
VStack(alignment: .trailing, spacing: 6) {
Text("sit amet")
.background(Color.gray.opacity(0.2))
.font(.caption)
}.onTapGesture {
// Handle the action when the card is touched
}
can someone please advice on this?
Put the Image and text both under VStack as follows:
VStack(alignment: .leading, spacing: 6) {
Image("flower")
.resizable()
//.aspectRatio(contentMode: .fill)
.frame(width: 160, height: 200)
// Overlay is a very simple way to add content on top of your view ! 😁
.clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
.shadow(color: Color.black.opacity(0.3), radius: 6, x: 0, y: 3)
Text("sit amet")
.background(Color.gray.opacity(0.2))
.font(.caption)
}.onTapGesture {
// Handle the action when the card is touched
}
Text("Some Info goes here")
}

Swift UI: Center Text into Circle

I try to put a circle-shaped frame around a text view, but I just can't get it properly aligned and I can't see where the problem is. As you can see in the picture below, the text is sometimes offset to left, while it should be centred. Any ideas on how to fix it?
struct ContentView: View {
let result = getDate();
var body: some View {
VStack {
ScrollView(.horizontal, showsIndicators: false) {
HStack(alignment: .top, spacing: 5) {
ForEach(result, id: \.self) {
day in
Text(day.name!)
.frame(width: 35, height: 35, alignment: .center)
.padding()
.overlay(
Circle()
.size(width: 35, height: 35)
.offset(x: 17.5,y: 17.5)
.scale(1.4)
.stroke(Color.orange, lineWidth: 4)
)
}
}
}
Spacer()
}.background(Color.white)
}
}
The circle is offset by half of the size of the frame, so its origin should be in the centre. The Text should be centered as well in .frame(width: 35, height: 35, alignment: .center).
Thanks a lot for helping! :)
.overlay already has size of container with Text an Circle centred in it, so it is just needed to operate with insets of circle not shifting it, like below
Text(day.name!)
.frame(width: 35, height: 35, alignment: .center)
.padding()
.overlay(
Circle()
.stroke(Color.orange, lineWidth: 4)
.padding(6)
)