skip to Main Content

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


  1. Chosen as BEST ANSWER

    Adding .contentShape(Rectangle()) to CourseCard2 answered the problem.


  2. 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:

    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()
                    .fill(Color.blue.opacity(0.2))
            )
        }
        .zIndex(1) // Ensure this button is on top
    }
    .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
            )
            .zIndex(0) // Ensure these cards are below the button
            .task {
                await loadImage(for: course.image_ref)
            }
        }
    }
    

    }

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search