iOS 16下如何让.searchable修饰符的scope bar始终显示?
在iOS 16中让SwiftUI的Search Scope Bar始终显示的方案
问题背景
在SwiftUI列表中使用.searchable修饰符时,需要实现scope bar始终显示的效果(即使用户未触发搜索操作)。iOS 16.4新增的带activation参数的.searchScopes修饰符仅支持在搜索触发时显示scope bar,且不兼容iOS 16早期版本;而UIKit的UISearchBar提供了手动控制选项可实现该需求,但需要在SwiftUI中适配。同时希望保留搜索栏点击时的原生动画效果,因此排除直接在VStack中放置分段选择器的方案。
原始代码示例
enum SearchScope: String, CaseIterable { case inbox, message } struct ContentView: View { @State private var searchText = "" @State private var searchScope: SearchScope = .inbox var body: some View { NavigationStack { List { ForEach(1..<30) { num in Text(String(num)) } } .searchable(text: $searchText) .searchScopes($searchScope, activation: .onSearchPresentation) { ForEach(SearchScope.allCases, id: \.self) { scope in Text(scope.rawValue.capitalized) } } .navigationTitle("Search") } } }
解决方案:使用SwiftUI-Introspect库
由于原生SwiftUI在iOS 16上无直接API支持始终显示scope bar,可通过SwiftUI-Introspect库获取底层UIKit组件,手动控制UISearchBar的显示状态,同时保留原生动画效果。
步骤1:添加依赖
通过Swift Package Manager引入SwiftUI-Introspect库,确保版本支持iOS 16。
步骤2:修改视图代码
通过Introspect获取UISearchBar实例,设置showsScopeBar为true,并同步SwiftUI与UIKit的scope选中状态:
import SwiftUI import Introspect enum SearchScope: String, CaseIterable { case inbox, message } struct ContentView: View { @State private var searchText = "" @State private var searchScope: SearchScope = .inbox private var scopeTitles: [String] { SearchScope.allCases.map { $0.rawValue.capitalized } } var body: some View { NavigationStack { List { ForEach(1..<30) { num in Text(String(num)) } } .searchable(text: $searchText) .searchScopes($searchScope, activation: .onSearchPresentation) { ForEach(SearchScope.allCases, id: \.self) { scope in Text(scope.rawValue.capitalized) } } .navigationTitle("Search") // 直接获取底层UISearchBar实例 .introspectSearchBar { searchBar in // 强制始终显示scope bar searchBar.showsScopeBar = true // 同步scope标题与SwiftUI定义一致 searchBar.scopeButtonTitles = scopeTitles // 初始选中状态同步 searchBar.selectedScopeButtonIndex = SearchScope.allCases.firstIndex(of: searchScope) ?? 0 // 监听scope切换,同步到SwiftUI状态 searchBar.addTarget(self, action: #selector(scopeSelectionChanged), for: .valueChanged) } } } @objc private func scopeSelectionChanged(_ searchBar: UISearchBar) { guard let selectedIndex = searchBar.selectedScopeButtonIndex, selectedIndex < SearchScope.allCases.count else { return } searchScope = SearchScope.allCases[selectedIndex] } }
关键说明
- 动画保留:该方案直接控制原生
UISearchBar,因此完全保留搜索栏展开/收起的动画效果。 - 状态同步:通过监听
UISearchBar的valueChanged事件,将UIKit的选中状态同步到SwiftUI的@State变量中,确保双向一致性。 - 兼容性:适配iOS 16全版本,无需依赖iOS 16.4的新API。
内容的提问来源于stack exchange,提问作者code24
相关产品推荐
相关产品推荐

