iosswiftswiftuiswiftui-sheet

Prevent scaling of background view in SwiftUI when .sheet gets presented by @FocusState


I want to present a search sheet over a map similar to Apple Maps. However, using SwiftUI's default sheet(isPresented:onDismiss:content:) the background gets scaled which leads to unexpected jumps in the underlying map on sheet dismissal.

Apple Maps:

Apple Maps search bar

My app:

Custom search bar in front of a map, scaled

The suggested solution from this question makes it possible to prevent the scaling when manually dragging up the view:

Custom search bar in front of a map, unscaled

The scaling behavior is only present when I present the sheet via the search text's @FocusedState (run it on a simulator or on device as Xcode previews may not show the keyboard):

import SwiftUI
import MapKit

struct ContentView: View {
    @State private var showSheet = true

    var body: some View {
        Map()
        .sheet(isPresented: $showSheet) {
            MinimalSearchSheet()
        }
    }
}

struct MinimalSearchSheet: View {
    @State private var searchQuery = ""
    @State private var selectedDetent: PresentationDetent = .fraction(0.1)
    @FocusState private var isSearchFocused: Bool

    var body: some View {
        VStack {
            HStack {
                Image(systemName: "magnifyingglass")
                TextField("Search", text: $searchQuery)
                    .padding(12)
                    .background(Color.gray.opacity(0.1))
                    .cornerRadius(8)
                    .focused($isSearchFocused)
            }
            .padding()

            Spacer()
        }
        .presentationDetents([.fraction(0.1), .fraction(0.333), .fraction(0.999)], selection: $selectedDetent)
        .interactiveDismissDisabled()
        .onChange(of: isSearchFocused) {_, focused in
            if focused {
                selectedDetent = .fraction(0.999)
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

I assume that it has something to do with the keyboard pushing up the view because I can't set if focused { selectedDetent = .medium }, the sheet is always returned as .large. Adding .ignoresSafeArea(.keyboard) yielded no results.

Edit

When implementing a smaller detention to give the keyboard more room, I get the correct behavior when setting the detent, but dismissing it makes the map glitch.

enter image description here


Solution

  • To prevent scaling, you probably need to allow space for the keyboard to be shown in addition to your detent. On an iPhone 16 simulator, there is no scaling with a detent fraction of 0.5:

    VStack {
        // ...
    }
    .presentationDetents([.fraction(0.1), .fraction(0.333), .fraction(0.5)], selection: $selectedDetent)
    .interactiveDismissDisabled()
    .onChange(of: isSearchFocused) {_, focused in
        if focused {
            selectedDetent = .fraction(0.5)
        }
    }
    

    The other glitch that you described was the movement of the map as the sheet is swiped away. I found that it helps to apply .ignoresSafeArea(.keyboard) to the Map:

    Map()
        .ignoresSafeArea(.keyboard) // 👈 here
        .sheet(isPresented: $showSheet) {
            MinimalSearchSheet()
        }
    

    Animation