I have a component called CourseCard2
with the following design:
VStack(spacing: 0) {
imageSection
detailsSection
}
.buttonStyle(PlainButtonStyle())
.background(
RoundedRectangle(cornerRadius: 16)
.fill(Color(.systemBackground))
.shadow(
color: Color.black.opacity(0.08),
radius: 12,
x: 0,
y: 4
)
)
.padding(.horizontal)
.onTapGesture {
showSheet = true
}
.sheet(isPresented: $showSheet) {
CourseDescription(
courseName: name,
courseDescription: description,
courseImage: image,
instructor: instructors,
youtubeVideos: youtubeLinks,
labels: labels,
isFree: isFree,
ispro: ispro
)
}
}
private var imageSection: some View {
image
.resizable()
.aspectRatio(contentMode: .fill)
.frame(height: 180)
.clipped()
.cornerRadius(16, corners: [.topLeft, .topRight])
}
private var detailsSection: some View {
VStack(alignment: .leading, spacing: 8) {
Text(name)
.font(.title3)
.fontWeight(.bold)
.lineLimit(2)
labelsView
instructorView
}
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
}
private var labelsView: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack(spacing: 8) {
ForEach(labels, id: .self) { label in
Text(label)
.font(.caption)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(Color.blue.opacity(0.1))
.foregroundColor(.blue)
.cornerRadius(8)
}
}
}
}
private var instructorView: some View {
HStack(spacing: 4) {
Image(systemName: "person.circle.fill")
.foregroundColor(.gray)
Text(instructors.joined(separator: ", "))
.font(.caption)
.foregroundColor(.gray)
}
}
}
extension View {
func cornerRadius(_ radius: CGFloat, corners: UIRectCorner) -> some View {
clipShape(RoundedCorner(radius: radius, corners: corners))
}
}
struct RoundedCorner: Shape {
var radius: CGFloat = .infinity
var corners: UIRectCorner = .allCorners
func path(in rect: CGRect) -> Path {
let path = UIBezierPath(
roundedRect: rect,
byRoundingCorners: corners,
cornerRadii: CGSize(width: radius, height: radius)
)
return Path(path.cgPath)
This component is used later in another page:
NavigationStack{
ScrollView {
ScrollView(.horizontal){
Button(action: {
showSheet = true
}) {
HStack {
Text("Instructors")
.fontWeight(.bold)
Image(systemName: "arrow.down")
}
.foregroundColor(.orange)
.padding(.horizontal, 16)
.padding(.vertical, 8)
.background(
Capsule() // Gives it a rounded, oval-like shape
.fill(Color.blue.opacity(0.2))
)
}
.zIndex(1000)
}
.frame(maxHeight: 50)
.padding(.bottom, 15)
.padding(.leading, 15)
VStack{
ForEach(filterText){ course in
let image = images[course.image_ref] ?? Image("noImage")
CoursesCard2(name: course.Name, image: image, youtubeLinks: course.youtube_videos, description: course.Description, instructors: course.instructors, labels: course.labels, isFree: course.isfree, ispro: ispro)
.task {
await loadImage(for: course.image_ref)
}
}
}
}
.searchable(text: $searchText, prompt: "Search for courses")
.navigationTitle("Browse Courses")
.sheet(isPresented: $showSheet){
AuthorCategorySelection()
.presentationDetents([.medium])
}
}
What happens though is that it becomes impossible to tap the Instructors
button. It just always selects the first CourseCard2
.
I tried adding a .background(Color.black)
to the CourseCard2
in the ForEach
loop. As seen Here, it does not take more than the card, but it is for some reason pressed every time I try to press the Instructors button.
2
Answers
Adding
.contentShape(Rectangle())
toCourseCard2
answered the problem.This issue occurs because the CourseCard2 views in the ForEach loop are intercepting the tap gestures. SwiftUI handles hit-testing hierarchically, and in your case, the CourseCard2 view is at a higher z-index than the “Instructors” button.
Here’s the fix:
}