skip to Main Content

I have a tab view at the bottom of main view. Tapping on the tab opens another view which would be a custom modal behind the tab view (in this sample code it’s showing up as main view). Anyways, main issue is that this view has a search bar and whenever drag gesture is added to this view, then search bar doesn’t work i.e. tapping on the search bar doesn’t bring up the keyboard.

How do I make search bar work when drag gesture is present?

I have too much code is much complex. I add pieces of code to make it a sample code. It works except in my actual app, the view attached to tab is a modal behind tab bar whereas in this sample, it’s main view. But the issue is reproducible with this sample code.

Code:

import SwiftUI
import Foundation
import Combine

struct ContentView: View {
    var body: some View {
        VStack {
            SimpleDataView()
        }
        .overlay(alignment: .bottom) {
            BottomTabView()
        }
    }
}

struct SimpleDataView: View {
    var body: some View {
        VStack {
            Text("Map will be displayed over here").background(Color.gray).frame(maxWidth: .infinity, maxHeight: .infinity)
            Spacer()
        }
        .background(Color.gray)
        .ignoresSafeArea()
    }
}

struct BottomTabView: View {
    @State private var defaultHandleColor = Color.secondary
    @State private var activeHandleColor = Color.primary
    @State private var sliderHandleHeight: CGFloat = 15.0
    @State private var radius: CGFloat = 16
    
    /// The latest recorded drag gesture value.
    @State var latestDragGesture: DragGesture.Value?
    @State private var maximumHeight: CGFloat = .infinity
    @State private var isPanelExpanding: Bool = false
    @State private var handleColor = Color.secondary

    
    @State var selection: Int = 1
    var body: some View {
        TabView(selection: $selection) {
            Group {
                ZStack {
                    SearchExploreModalView()
                }.tabItem {
                    Label("Explore", systemImage: "magnifyingglass")
                    Text("Explore")
                }
                .tag(1)
                .gesture(drag)
            }
        }
        .background(Color(red: 0.98, green: 0.98, blue: 0.98).opacity(0.94))
        .onAppear {
            let appearance = UITabBarAppearance()
            UITabBar.appearance().scrollEdgeAppearance = appearance
        }
    }
    
    var drag: some Gesture {
        DragGesture(minimumDistance: 0, coordinateSpace: .global)
            .onChanged {
                let deltaY = $0.location.y - (latestDragGesture?.location.y ?? $0.location.y)
                let proposedHeight = 250.0 + (-1 * deltaY)
                self.isPanelExpanding = proposedHeight >= 250.0 ? true : false
                handleColor = activeHandleColor
                latestDragGesture = $0
            }
            .onEnded { _ in
                // On Ended logic
            }
    }
}

struct SearchExploreModalView: View {
    @State private var searchText = ""
    let searchTextPublisher = PassthroughSubject<String, Never>()

    var body: some View {
        VStack {
            NavigationView {
                List {
                    // For loop iterating overdata
                }
                .searchable(
                    text: $searchText,
                    placement: .navigationBarDrawer(displayMode: .always),
                    prompt: "Search"
                )
                .onChange(of: searchText) { newText in
                    searchTextPublisher.send(newText) /// Publishes when a search term is changed. This is used to debounce the search.
                }
            }
            Spacer()
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(Color.gray)
    }
}

2

Answers


  1. I believe there is some conflict of priority between the different gestures on screen that is causing something to go wrong behind the scenes.

    This might be a bug, so maybe a future visitor to this question can weigh in.

    I was able to get it to work by adding an empty onTapGesture on the line prior to .gesture – see below:

     struct BottomTabView: View {
        @State private var defaultHandleColor = Color.secondary
        @State private var activeHandleColor = Color.primary
        @State private var sliderHandleHeight: CGFloat = 15.0
        @State private var radius: CGFloat = 16
        
        /// The latest recorded drag gesture value.
        @State var latestDragGesture: DragGesture.Value?
        @State private var maximumHeight: CGFloat = .infinity
        @State private var isPanelExpanding: Bool = false
        @State private var handleColor = Color.secondary
    
        
        @State var selection: Int = 1
        var body: some View {
            TabView(selection: $selection) {
                Group {
                    ZStack {
                        SearchExploreModalView()
                    }.tabItem {
                        Label("Explore", systemImage: "magnifyingglass")
                        Text("Explore")
                    }
                    .tag(1)
                    .onTapGesture {} // <-- Here!!
                    .gesture(drag)
                }
            }
            .background(Color(red: 0.98, green: 0.98, blue: 0.98).opacity(0.94))
            .onAppear {
                let appearance = UITabBarAppearance()
                UITabBar.appearance().scrollEdgeAppearance = appearance
            }
        }
        
        var drag: some Gesture {
            DragGesture(minimumDistance: 0, coordinateSpace: .global)
                .onChanged {
                    print("hihihihi")
                    let deltaY = $0.location.y - (latestDragGesture?.location.y ?? $0.location.y)
                    let proposedHeight = 250.0 + (-1 * deltaY)
                    self.isPanelExpanding = proposedHeight >= 250.0 ? true : false
                    handleColor = activeHandleColor
                    latestDragGesture = $0
                }
                .onEnded { _ in
                    // On Ended logic
                }
        }
    }
    
    Login or Signup to reply.
  2. This happens because you added the gesture to the NavigationView, which contains the search bar. When you tap on the search bar, SwiftUI prioritises your drag gesture, instead of the search bar’s built-in tap gesture.

    The behaviour of gestures in the superview overriding built-in gestures in the subviews seems to be a common problem. See also: 1, 2, 3, 4

    One way to fix this is simply to not add the gesture on the NavigationView. Add it to the List instead.

    struct SearchExploreModalView<T: Gesture>: View {
        ...
    
        let drag: T
    
        var body: some View {
            VStack {
                NavigationView {
                    List {
                        ...
                    }
                    ...
                    .gesture(drag) // <--- here!
                }
                Spacer()
            }
            ...
    
        }
    }
    

    Of course, this makes some parts of the NavigationView (e.g. toolbar buttons) not able to detect the gesture. You can try adding the same gesture to those parts of the NavigationView too.

    Alternatively, set a higher minimumDistance for the drag gesture to succeed.

    DragGesture(minimumDistance: 1, coordinateSpace: .global)
    

    This allows the search bar’s tap gesture to succeed when you are just tapping it.

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