How to add functionality to SearchResultsButton in search bar with SwiftUI? - ios

I am trying to have a sheet pop up when the SearchResultsButton on my search bar is tapped. The below picture shows the button I want to add functionality for:
This is the code I have currently:
struct ContentView: View {
#State private var isSearching = ""
var body: some View {
NavigationView {
List {
Text("Hello")
Text("World")
}
.navigationTitle("Hello")
.searchable(text: $isSearching)
.onAppear {
UISearchBar.appearance().showsSearchResultsButton = true
}
}
}
}
How would I go about accomplishing my goal? Any help would be greatly appreciated.

Related

How to make NavigationBar and TabBar fade in and out?

I am trying to make an effect where the navigation bar and tab bar fade out while the user is scrolling, and then fade in when the user taps on the screen. This is the code I have so far:
import SwiftUI
struct QPageView: View {
#State private var searching = ""
#State private var isScrolling: Visibility = .visible
var body: some View {
List {
ForEach(0..<40) {
Text("\($0)")
}
}
.navigationTitle("Hello")
.navigationBarTitleDisplayMode(.inline)
.searchable(text: $searching, placement: .navigationBarDrawer(displayMode: .always))
.toolbar(isScrolling, for: .navigationBar)
.toolbar(isScrolling, for: .tabBar)
.simultaneousGesture(DragGesture().onChanged { _ in
withAnimation {
isScrolling = .hidden
}
})
.simultaneousGesture(TapGesture().onEnded { _ in
withAnimation {
isScrolling = .visible
}
})
}
}
struct PageView_Previews: PreviewProvider {
static var previews: some View {
PageView()
}
}
struct PageView: View {
var body: some View {
TabView {
NavigationView {
NavigationLink(destination: QPageView()) {
Text("Click Me")
}
}
.tabItem {
Label("Hey", systemImage: "book.fill")
}
}
}
}
This is the current effect I'm getting. I am getting a sliding animation, which is not the desired effect I am going for.
Anyone know how I can accomplish a fading effect? I cannot seem to figure it out using withAnimation. Any help would be appreciated.

SwiftUI: searchable weird push animation?

I have the following code:
enum ContentViewRouter {
case details
}
struct ContentView: View {
var body: some View {
NavigationStack {
ZStack {
NavigationLink(value: ContentViewRouter.details) {
Text("Push")
}
}
.navigationDestination(for: ContentViewRouter.self) { destiantion in
switch destiantion {
case .details:
DetailsView()
}
}
.navigationTitle("TEST")
}
}
}
struct DetailsView: View {
#State var query = ""
var body: some View {
Form {
Section("TEST") {
Text("DETAILS")
}
}
.searchable(text: $query)
.navigationTitle("DETAILS")
}
}
When pushing the details view it creates this animation:
I want to get rid of this weird looking animation "glitch":
How can I get the push animation to come in smooth where the search bar doesn't appear for a split second and then go away? Seems like it's a bug or I am doing it wrong?
The problem of that I think because you have .searchable which will create search bar and currently conflict with your .navigationTitle which both on the top. Maybe the search bar don't know where to put it because of the .navigationTitle
Update: After doing some research, I see that maybe the behavior maybe wrong in search bar .automatic
There are three ways to workaround
If you want to keep your search bar then make NavigationView to split define navigationTitle and search bar
struct DetailsView: View {
#State var query = ""
var body: some View {
NavigationView {
Form {
Section("TEST") {
Text("DETAILS")
}
}
.searchable(text: $query)
}
.navigationTitle("DETAILS")
}
}
Keep the search bar always appear
struct DetailsView: View {
#State var query = ""
var body: some View {
Form {
Section("TEST") {
Text("DETAILS")
}
}
.searchable(text: $query, placement: .navigationBarDrawer(displayMode: .always))
.navigationTitle("DETAILS")
}
}
If you want to completely remove search bar
struct DetailsView: View {
#State var query = ""
var body: some View {
Form {
Section("TEST") {
Text("DETAILS")
}
}
//.searchable(text: $query) // remove this
.navigationTitle("DETAILS")
}
}

SwiftUI isSearching dismiss behaviour on iOS 15

I have a SwiftUI view with a search bar on iOS 15. When the search bar is activated, a search list is presented, when no search is active, a regular content view is shown.
The problem I am facing is that when I activate a navigation link from the search list, when the navigation starts to take effect, the isSearching flag is turned to false and the regular content view is shown, even though I would want to search to stay active, just like when we would have a list/table and the user would select a row: the search stays active, and when the user navigates back, the search results are still displayed.
Is there a way in SwiftUI to control how the isSearching is changed?
I put together a small sample project that demoes the problem:
import SwiftUI
struct ContentView: View {
#ObservedObject var viewModel: ContentView.ViewModel
var body: some View {
NavigationView {
VStack {
ContentViewWrapper(viewModel: viewModel)
}
.navigationTitle("Searchable")
.navigationBarTitleDisplayMode(.inline)
.searchable(text: $viewModel.searchString, placement: .navigationBarDrawer(displayMode: .always), prompt: "Search")
.navigationViewStyle(.stack)
.edgesIgnoringSafeArea(.top)
}
}
}
// MARK: View model for the content view
extension ContentView {
class ViewModel: ObservableObject {
#Published var isShowingDestinationScreen = false
#Published var isSearching = false
#Published var searchString = ""
func buttonTapped() {
if !isShowingDestinationScreen {
isShowingDestinationScreen = true
}
}
func isSearchingHasChanged(newValue: Bool) {
if isSearching != newValue {
isSearching = newValue
}
}
}
}
// MARK: Wrapper for the content view so it can be used with the searchable API
struct ContentViewWrapper: View {
#ObservedObject var viewModel: ContentView.ViewModel
#Environment(\.isSearching) var isSearching
var body: some View {
VStack {
if viewModel.isSearching {
NavigationLink(
isActive: $viewModel.isShowingDestinationScreen,
destination: {
DestinationView()
.navigationTitle("Destination")
}, label: {
EmptyView()
}
)
SearchList() {
viewModel.buttonTapped()
}
} else {
ContentViewMenu()
}
}
.onChange(of: isSearching) { newValue in
viewModel.isSearchingHasChanged(newValue: newValue)
}
}
}
// MARK: Just three simple screens below
struct ContentViewMenu: View {
var body: some View {
Text("Content View Menu")
}
}
struct SearchList: View {
var destinationButtonTapped: () -> Void
var body: some View {
VStack {
Text("Search list")
Button("Go to destination") {
destinationButtonTapped()
}
}
}
}
struct DestinationView: View {
var body: some View {
Text("Destination")
}
}
Also here is a short video showing the behaviour: note how when the Go to destination button is tapped, the screen is updated to the content view because isSearching turns false.
Is there a way to keep isSearching true in this case?
I believe you have two options here:
Normally, when users click on a search field, we expect them to always enter something. It is not possible that someone clicks on a search field without typing anything, otherwise it's just an accident touch, so anything should not execute because of this. Your solution here is: you don't have to do anything at all. Just type anything to the search bar after you clicked on it; you can even just input a space, then your search and search result will always remain active no matter what.
If you still want your search bar to be active even though there is zero interaction or input with the search bar, you can adjust some part of your ContentViewWrapper as below(But I think it's not practical to do this because why would you want your search bar to be active without any input?):
code:
struct ContentViewWrapper: View {
#ObservedObject var viewModel: ContentView.ViewModel
#Environment(\.isSearching) var isSearching
//new code
#State var isShowing = false
var body: some View {
VStack {
//new code
if viewModel.isSearching || isShowing {
NavigationLink(
isActive: $viewModel.isShowingDestinationScreen,
destination: {
DestinationView()
.navigationTitle("Destination")
}, label: {
EmptyView()
}
)
.onAppear {
isShowing = true
}
}
//new code
if isShowing {
SearchList() {
viewModel.buttonTapped()
}
}
}
.onChange(of: isSearching) { newValue in
viewModel.isSearchingHasChanged(newValue: newValue)
}
}
}

SwiftUI Modal Inherits SearchBar during Sheet Presentation

Consider the following example with a list and a button wrapped in a HStack that opens up a sheet:
struct ContentView: View {
#State var text: String = ""
#State var showSheet = false
var body: some View {
NavigationView {
List {
HStack {
button
}
Text("Hello World")
}
.searchable(text: $text)
}
}
var button: some View {
Button("Press", action: { showSheet = true })
.sheet(isPresented: $showSheet) {
modalView
}
}
var modalView: some View {
NavigationView {
List {
Text("Test")
}
}
}
}
On press of the button, a modal is presented to the user. However, the searchable modifier gets passed to the modal, see this video.
Now if the HStack is removed, everything works fine:
List {
button
Text("Hello World")
}
In addition, everything works also fine if the modal is not a NavigationView:
var modalView: some View {
List {
Text("Test")
}
}
Does somebody know what the problem here might be or is it once again one of those weird SwiftUI bugs?
putting the sheet, outside of the button and the List, works for me. I think .sheet is not meant to be inside a List, especially where searchable is operating.
struct ContentView: View {
#State var text: String = ""
#State var showSheet = false
var body: some View {
NavigationView {
List {
HStack {
button
}
Text("Hello World")
}
.searchable(text: $text)
}
.sheet(isPresented: $showSheet) {
modalView
}
}
var button: some View {
Button("Press", action: { showSheet = true })
}
var modalView: some View {
NavigationView {
List {
Text("Test")
}
}
}
}
Another workaround is to use navigationBarHidden = true, but then you must live without the navigation bar in the sheet view.
var modalView: some View {
NavigationView {
List {
Text("Test")
}
.navigationBarHidden(true)
}
}
Btw, on iPadOS it helps to use .searchable(text: $text, placement: .sidebar)

(SwiftUI) How to present a View in front of navigationBar?

First View:
when click on "Hello, World!1" , I set a NavigationLink that will direct to Second View.
Second View:
when click on "Hello, World!2", my PopUpView will appear.
But it is going under the navigationBar area. I need the PopUpView to be in front of navigationBar and this view has to be transparent so that we are able to see Second View.
Does anyone know how to solve this problem?
Please share your solution with me. Thank you SO much!
My code and screenshots are shown below for your reference.
enter image description here
enter image description here
enter image description here
struct MainView: View {
var body: some View {
NavigationView {
ZStack {
Color.yellow.ignoresSafeArea(.all)
VStack {
NavigationLink(
destination: SecondView(),
label: {
Text("Hello, World!1")
})
}
.navigationTitle("First")
.navigationBarTitleDisplayMode(.inline)
}
}
}
}
struct SecondView: View {
#State var showPop = false
var body: some View {
ZStack {
Color.blue.ignoresSafeArea(.all)
VStack {
Text("Hello, World!2")
.onTapGesture {
showPop = true
}
}
if showPop {
PopUpView(showPop: $showPop)
}
}
.navigationTitle("Second")
.navigationBarTitleDisplayMode(.inline)
}
}
struct PopUpView: View {
#Binding var showPop : Bool
var body: some View {
ZStack {
Color.black.opacity(0.5).ignoresSafeArea(.all)
.onTapGesture {
showPop.toggle()
}
Text("Hello, Pop UP!")
}
}
}

Resources