Reputation: 73
So I have an SwiftUI view, that displays data in a List. You can search and filter through the objects. So far so easy, but this searchbar (initializised with .searchable()) is always visible. Only when you scroll down the list it disappears. How can I hide the search bar on appear and only when you pull down slightly it appears. Like in the default apple apps eg. notes. Is this possible with .searchable modifier or do i need to create a custom search bar, if so how? Here an example code:
ZStack{
NavigationView {
List {
if(filteredTodayItems.isEmpty == false){
Section(header: ListHeader(text: "Today")) {
ForEach(filteredTodayItems) { item in
objectCell(item: item, rememberSheet: $showRememberSheet, currentItem: $currentItem, showRememberSheet: $showRememberSheet, showDetailSheet: $showDetailSheet, showAddingChangeSheet: $showAddingChangeSheet)
}
}
}else if(tomorrowItems.isEmpty && laterItems.isEmpty){
Text("Click on the Plus to enter a secret!")
}
}
.searchable(text: $searchText, prompt: Text("Search"))
.navigationTitle("Secrets")
}
}
The funny thing i discovered now, is, that when i add
.searchable(text: $searchText, prompt: Text("Search"))
to the Section()
it kind of works.. The search field is only displayed when pulling down, but the content of the sections is totally "destroyed", means swipe actions multiplied etc. When i add the modifier to an empty section at the top everything works, besides an empty section is visible
For better understanding, here is the full body:
ZStack{
NavigationView {
List {
if(filteredTodayItems.isEmpty == false){
Section(header: ListHeader(text: "Today")) {
ForEach(filteredTodayItems) { item in
objectCell(item: item, rememberSheet: $showRememberSheet, currentItem: $currentItem, showRememberSheet: $showRememberSheet, showDetailSheet: $showDetailSheet, showAddingChangeSheet: $showAddingChangeSheet, isToday: true)
}
}
}else if(tomorrowItems.isEmpty && laterItems.isEmpty){
Text("Click on the Plus to enter a secret!")
}
if(filteredTomorrowItems.isEmpty == false){
Section(header: ListHeader(text: "Tomorrow")) {
ForEach(filteredTomorrowItems) { item in
objectCell(item: item, rememberSheet: $showRememberSheet, currentItem: $currentItem, showRememberSheet: $showRememberSheet, showDetailSheet: $showDetailSheet, showAddingChangeSheet: $showAddingChangeSheet, isToday: false)
}
}
}
if(filteredLaterItems.isEmpty == false){
Section(header: ListHeader(text: "Later")) {
ForEach(filteredLaterItems) { item in
objectCell(item: item, rememberSheet: $showRememberSheet, currentItem: $currentItem, showRememberSheet: $showRememberSheet, showDetailSheet: $showDetailSheet, showAddingChangeSheet: $showAddingChangeSheet, isToday: false)
}
}
}
}
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .automatic))
.partialSheet(isPresented: $showRememberSheet) {
checkSecretView(currentItem: $currentItem, showRememberSheet: $showRememberSheet, showToast: $showToast, showToast2: $showToast2, value: $value)
}
.toolbar {
ToolbarItem (placement: .navigationBarLeading) {
Button(action: {
showSettings.toggle()
}) {
Image(systemName: "gear")
}
}
ToolbarItem (placement: .navigationBarTrailing) {
Button(action: {
showAddingSheet.toggle()
}) {
Image(systemName: "plus")
}
}
}
.listStyle(InsetGroupedListStyle())
.navigationTitle("Secrets")
}
.sheet(isPresented: $showAddingSheet) {
AddingSheet(editingItem: nil)
}
.sheet(isPresented: $showDetailSheet) {
InfoSheet(currentItem: $currentItem)
}
.sheet(isPresented: $showSettings) {
Settings()
}
.sheet(isPresented: $showAddingChangeSheet) {
let idString = UserDefaults.standard.string(forKey: "ToBeEditedLiveGoalID")
let id = UUID(uuidString: idString ?? "")
let objectToEdit = items.first(where: { $0.id == id})
AddingSheet(editingItem: objectToEdit)
}
.attachPartialSheetToRoot()
.simpleToast(isPresented: $showToast, options: toastOptions){
HStack{
Image(systemName: "checkmark.circle.fill")
Text("Correctly Entered").bold()
}
.padding(.vertical,8)
.padding(.horizontal,16)
.background(.green.opacity(0.9))
.foregroundColor(.white)
.cornerRadius(10)
}
.simpleToast(isPresented: $showToast2, options: toastOptions){
HStack{
Image(systemName: "xmark.circle.fill")
Text("Wrongly Entered").bold()
}
.padding(.vertical,8)
.padding(.horizontal,16)
.background(.red.opacity(0.9))
.foregroundColor(.white)
.cornerRadius(10)
}
}
.blur(radius: isUnlocked ? 0 : 25)
.disabled(!isUnlocked)
.overlay(
lockedScreen(color: Binding.constant(.accentColor) , isUnlocked: $isUnlocked)
.opacity(isUnlocked ? 0 : 1)
)
.onAppear{
isUnlocked = !bioSaveAuthActive
if(!isUnlocked){
authenticate()
}
}
}
Upvotes: 5
Views: 2167
Reputation: 21005
I have the same issue... as i was was not able to resolve it fully yet, in this video around 14 min. i noticed https://www.youtube.com/watch?v=e0eO1di0cPY
that the search bar changes according on if you use NavigationStack
vs. NavigationSplitView
... but didn't make a difference in my case
What worked for me in ISO18 at least, was to put the searchable into the first header of the section... now the search is not initial visible, until you pull down the table
List {
ForEach(categories) { dc in
Section {
...
} header: {
if categories.first?.id == dc.id {
Spacer()
.searchable(text: $searchText, prompt: "...")
} else {
EmptyView()
}
}
}
}
Upvotes: 0
Reputation: 614
I'm sorry, I misunderstood the problem in the previous answer. If you want the same view as the Apple Note, you can do it as below.
import SwiftUI
struct SomeView: View {
var name: String
var body: some View {
Text(name)
}
}
struct SomeData: Identifiable {
var name: String
var id: String { self.name }
}
struct ContentView: View {
@State var searchQueryString = ""
var datas = (0...100).map(String.init).map(SomeData.init)
var filteredDatas: [SomeData] {
if searchQueryString.isEmpty {
return datas
} else {
return datas.filter { $0.name.localizedStandardContains(searchQueryString) }
}
}
var body: some View {
NavigationView {
List(filteredDatas) { data in
NavigationLink {
SomeView(name: data.name)
} label: {
Text(data.name)
}
}
.navigationTitle("Search Test")
}
.searchable(
text: $searchQueryString,
placement: .navigationBarDrawer, // <- here is.
prompt: "placholder..."
)
}
}
Here is a video testing the above code.
Upvotes: 1
Reputation: 73
So, this is maybe not the ideal answer, because it does not solve my problem, but it sets my search bar equal to apples search bar in for example notes. Why is it default hidden in their app but not in mine? Simple! The moment the whole screen is filled with content / enough list entries are available, the search bar disappears, if not specifically pulled down. If their are not enough entries, it will always stay visible!
Upvotes: 0
Reputation: 97
I kind of had the same trouble way back and I found out the solution was annoyingly simple. Hope that one works out for you too.
I couldn't make an accurate guess on what's wrong with your destroyed elements (or sections) with this little information. But the problem might be the search bar interferes with the layout or functionality of the sections or cells.
The .searchable()
modifier determines where the search bar appears in the view hierarchy, and placing it at different levels can affect the behavior of the search functionality. You can try adding the .searchable()
function to your NavigationView
instead of implementing it in your List
:
NavigationView {
// your code here, and your list, obviously
}
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .automatic))
By placing the .searchable()
modifier at the same level as the NavigationView
, the search bar will appear independently of the list content, which might help avoid the issues you mentioned.
SwiftUI can have complex interactions and behaviors, hope this little change can effectively solves your issue. Feel free to share more information so that we could find a solution if this one doesn't work for you. Also here's a really cool link for you to check out about the .searchable()
modifier.
Upvotes: 1
Reputation: 614
I think it will be difficult to use the existing method, and I think we need to make a custom search bar as you said. The other parts can be implemented as shown below to cause the searchBar to appear only when the scrollView is at the top.
import SwiftUI
struct ContentView: View {
@State private var verticalOffset: CGFloat = 0.0
var body: some View {
VStack {
if verticalOffset > 0 {
HStack {
Text("Your searchable View")
.padding()
Spacer()
}
.frame(width: UIScreen.main.bounds.width - 50, height: 50)
.background(.gray)
.cornerRadius(10)
} else {
Spacer()
.frame(height: 50)
}
ObservableScrollView.init(scrollOffset: $verticalOffset) { (proxy: ScrollViewProxy) in
ForEach(0..<100) { i in
Text("Item \(i)").padding().id(i)
.frame(width: UIScreen.main.bounds.width)
}
}
}
}
struct ObservableScrollView<Content>: View where Content : View {
@Namespace var scrollSpace
@Binding var scrollOffset: CGFloat
let content: (ScrollViewProxy) -> Content
init(scrollOffset: Binding<CGFloat>,
@ViewBuilder content: @escaping (ScrollViewProxy) -> Content) {
_scrollOffset = scrollOffset
self.content = content
}
var body: some View {
ScrollView(showsIndicators: false) {
ScrollViewReader { proxy in
content(proxy)
.background(GeometryReader { geo in
let offset = -geo.frame(in: .named(scrollSpace)).minY
Color.clear
.preference(key: ScrollViewOffsetPreferenceKey.self,
value: offset)
})
}
}
.coordinateSpace(name: scrollSpace)
.onPreferenceChange(ScrollViewOffsetPreferenceKey.self) { value in
scrollOffset = value
}
}
}
struct ScrollViewOffsetPreferenceKey: PreferenceKey {
static var defaultValue = CGFloat.zero
static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) {
value += nextValue()
}
}
}
Here is a video testing the above code.
Upvotes: 1
Reputation: 41
Try removing the ZStack, and moving the .searchable
modifier and the if statements around, so that your code looks something like this:
NavigationView {
Group {
if !filterTodayItems.isEmpty {
List {
Section(header: ListHeader(text: "Today")) {
ForEach(filteredTodayItems) { item in
objectCell(item: item, rememberSheet: $showRememberSheet, currentItem: $currentItem, showRememberSheet: $showRememberSheet, showDetailSheet: $showDetailSheet, showAddingChangeSheet: $showAddingChangeSheet)
}
}
.searchable(text: $searchText, prompt: Text("Search"))
}
} else {
Text("Click on the Plus to enter a secret!")
}
}
.navigationTitle("Secrets")
}
Upvotes: 1