SwiftUI实现Sheet顶部固定搜索栏的方案咨询
实现Sheet内固定顶部的搜索栏(仿苹果地图效果)
问题描述
我想在Sheet顶部始终固定显示搜索栏,目前使用searchable修饰符,不管设置哪种navigationBarDrawer参数,点击搜索栏时它都会移动到导航栏顶部。想要实现类似苹果地图底部面板里搜索栏的固定效果,该怎么做?
当前代码:
import SwiftUI import Foundation import Combine @main struct SampleAppApp: App { var body: some Scene { WindowGroup { NavigationStack { ContentView() } } } } struct SimpleTestData { let id = UUID() let score: Int let name: String } struct ContentView: View { var body: some View { VStack { Text("Hello World") } .sheet(isPresented: .constant(true)) { ContentNewView() } } } struct ContentNewView: View { @State private var searchText = "" let searchTextPublisher = PassthroughSubject<String, Never>() var body: some View { NavigationStack { ZStack { Color(red: 0.95, green: 0.95, blue: 0.97).ignoresSafeArea() VStack { ResultsList(searchText: searchText, searchTextPublisher: searchTextPublisher) .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(red: 0.95, green: 0.95, blue: 0.97)) } } .presentationDetents([.medium, .large]) .interactiveDismissDisabled(true) .presentationBackgroundInteraction(.enabled) .onAppear { let appearance = UITabBarAppearance() UITabBar.appearance().scrollEdgeAppearance = appearance } } } struct ResultsList: View { var searchText: String var searchTextPublisher: PassthroughSubject<String, Never> init(searchText: String, searchTextPublisher: PassthroughSubject<String, Never>) { self.searchText = searchText self.searchTextPublisher = searchTextPublisher UITableView.appearance().sectionFooterHeight = 0 } let results = [ SimpleTestData(score: 8, name: "Book1"), SimpleTestData(score: 5, name: "Book2"), SimpleTestData(score: 10, name: "Book3") ] var body: some View { ScrollViewReader { proxy in VStack(alignment: .leading, spacing: 8.0) { if !results.isEmpty { List { Section(header: Text("Results")) { ForEach(results, id: \.id) { result in HStack { Text(result.name).font(.body) Spacer() Text("\(result.score)").font(.body) } .listRowBackground(Color.gray) .contentShape(Rectangle()) } } Section(header: Text("Results")) { ForEach(results, id: \.id) { result in HStack { Text(result.name).font(.body) Spacer() Text("\(result.score)").font(.body) } .listRowBackground(Color.gray) .contentShape(Rectangle()) } } } .listStyle(.insetGrouped) .foregroundColor(.black) .padding([.leading, .trailing], 20.0) } } .padding(.horizontal, -20.0) } } }
当前效果:

解决方案
系统的searchable修饰符结合navigationBarDrawer会默认将搜索栏关联到导航栏,点击时必然跳转。要实现固定效果,需要自定义搜索栏,脱离系统导航栏的关联:
修改后的完整代码
import SwiftUI import Foundation import Combine @main struct SampleAppApp: App { var body: some Scene { WindowGroup { NavigationStack { ContentView() } } } } struct SimpleTestData { let id = UUID() let score: Int let name: String } struct ContentView: View { var body: some View { VStack { Text("Hello World") } .sheet(isPresented: .constant(true)) { ContentNewView() } } } struct ContentNewView: View { @State private var searchText = "" let searchTextPublisher = PassthroughSubject<String, Never>() var body: some View { NavigationStack { ZStack { Color(red: 0.95, green: 0.95, blue: 0.97).ignoresSafeArea() VStack(spacing: 12) { // 自定义固定顶部的搜索栏 HStack { Image(systemName: "magnifyingglass") .foregroundColor(.secondary) TextField("Search", text: $searchText) .onChange(of: searchText) { newText in searchTextPublisher.send(newText) } if !searchText.isEmpty { Button(action: { searchText = "" }) { Image(systemName: "xmark.circle.fill") .foregroundColor(.secondary) } } } .padding(.horizontal, 16) .padding(.vertical, 10) .background(Color.white) .cornerRadius(8) .padding(.horizontal, 20) .padding(.top, 16) // 列表部分,在搜索栏下方滚动 ResultsList(searchText: searchText, searchTextPublisher: searchTextPublisher) Spacer() } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color(red: 0.95, green: 0.95, blue: 0.97)) } } .presentationDetents([.medium, .large]) .interactiveDismissDisabled(true) .presentationBackgroundInteraction(.enabled) .onAppear { let appearance = UITabBarAppearance() UITabBar.appearance().scrollEdgeAppearance = appearance } } } struct ResultsList: View { var searchText: String var searchTextPublisher: PassthroughSubject<String, Never> init(searchText: String, searchTextPublisher: PassthroughSubject<String, Never>) { self.searchText = searchText self.searchTextPublisher = searchTextPublisher UITableView.appearance().sectionFooterHeight = 0 } let results = [ SimpleTestData(score: 8, name: "Book1"), SimpleTestData(score: 5, name: "Book2"), SimpleTestData(score: 10, name: "Book3") ] var body: some View { ScrollViewReader { proxy in VStack(alignment: .leading, spacing: 8.0) { if !results.isEmpty { List { Section(header: Text("Results")) { ForEach(results, id: \.id) { result in HStack { Text(result.name).font(.body) Spacer() Text("\(result.score)").font(.body) } .listRowBackground(Color.gray) .contentShape(Rectangle()) } } Section(header: Text("Results")) { ForEach(results, id: \.id) { result in HStack { Text(result.name).font(.body) Spacer() Text("\(result.score)").font(.body) } .listRowBackground(Color.gray) .contentShape(Rectangle()) } } } .listStyle(.insetGrouped) .foregroundColor(.black) .padding([.leading, .trailing], 20.0) } } .padding(.horizontal, -20.0) } } }
关键修改说明
- 移除系统
searchable修饰符:不再依赖系统导航栏的搜索栏实现,避免点击跳转的问题 - 自定义搜索栏:用
HStack+TextField实现和系统样式一致的搜索框,添加清除按钮保证交互完整性 - 布局调整:将搜索栏放在
VStack的最顶部,列表紧随其后,这样列表滚动时搜索栏始终固定在Sheet顶部 - 保留原有搜索逻辑:继续使用
searchTextPublisher发布搜索文本变化,不影响后续的防抖或搜索请求逻辑
这样修改后,搜索栏会像苹果地图底部面板的搜索栏一样,始终固定在Sheet顶部,点击输入时不会发生位置跳转。
内容的提问来源于stack exchange,提问作者tech_human
相关产品推荐
相关产品推荐

