Macos Swift UI View Where There Is a Search Field in The Title Bar

How to display a search bar with SwiftUI

Here is a pure swiftUI version, based on Antoine Weber's answer to his question above and what I found in this blog and this gist. It incorporates

  • a clear button,
  • a cancel button,
  • resigning keyboard on dragging in the list and
  • hiding the navigation view when the search text field is selected.

Resigning the keyboard on drag in the list can be realized using a method on UIApplication window following these answers. For easier handling I created an extension on UIApplication and view modifier for this extension and finally an extension to View:


// Deprecated with iOS 15
//extension UIApplication {
// func endEditing(_ force: Bool) {
// self.windows
// .filter{$0.isKeyWindow}
// .first?
// .endEditing(force)
// }
//}

// Update for iOS 15
// MARK: - UIApplication extension for resgning keyboard on pressing the cancel buttion of the search bar
extension UIApplication {
/// Resigns the keyboard.
///
/// Used for resigning the keyboard when pressing the cancel button in a searchbar based on [this](https://stackoverflow.com/a/58473985/3687284) solution.
/// - Parameter force: set true to resign the keyboard.
func endEditing(_ force: Bool) {
let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first
window?.endEditing(force)
}
}

struct ResignKeyboardOnDragGesture: ViewModifier {
var gesture = DragGesture().onChanged{_ in
UIApplication.shared.endEditing(true)
}
func body(content: Content) -> some View {
content.gesture(gesture)
}
}

extension View {
func resignKeyboardOnDragGesture() -> some View {
return modifier(ResignKeyboardOnDragGesture())
}
}

So the final modifier for resigning the keyboard is just one modifier that has to be placed on the list like this:

List {
ForEach(...) {
//...
}
}
.resignKeyboardOnDragGesture()

The complete swiftUI project code for the search bar with a sample list of names is as follows. You can paste it into ContentView.swift of a new swiftUI project and play with it.


import SwiftUI

struct ContentView: View {
let array = ["Peter", "Paul", "Mary", "Anna-Lena", "George", "John", "Greg", "Thomas", "Robert", "Bernie", "Mike", "Benno", "Hugo", "Miles", "Michael", "Mikel", "Tim", "Tom", "Lottie", "Lorrie", "Barbara"]
@State private var searchText = ""
@State private var showCancelButton: Bool = false

var body: some View {

NavigationView {
VStack {
// Search view
HStack {
HStack {
Image(systemName: "magnifyingglass")

TextField("search", text: $searchText, onEditingChanged: { isEditing in
self.showCancelButton = true
}, onCommit: {
print("onCommit")
}).foregroundColor(.primary)

Button(action: {
self.searchText = ""
}) {
Image(systemName: "xmark.circle.fill").opacity(searchText == "" ? 0 : 1)
}
}
.padding(EdgeInsets(top: 8, leading: 6, bottom: 8, trailing: 6))
.foregroundColor(.secondary)
.background(Color(.secondarySystemBackground))
.cornerRadius(10.0)

if showCancelButton {
Button("Cancel") {
UIApplication.shared.endEditing(true) // this must be placed before the other commands here
self.searchText = ""
self.showCancelButton = false
}
.foregroundColor(Color(.systemBlue))
}
}
.padding(.horizontal)
.navigationBarHidden(showCancelButton) // .animation(.default) // animation does not work properly

List {
// Filtered list of names
ForEach(array.filter{$0.hasPrefix(searchText) || searchText == ""}, id:\.self) {
searchText in Text(searchText)
}
}
.navigationBarTitle(Text("Search"))
.resignKeyboardOnDragGesture()
}
}
}
}

struct ContentView_Previews: PreviewProvider {
static var previews: some View {
Group {
ContentView()
.environment(\.colorScheme, .light)

ContentView()
.environment(\.colorScheme, .dark)
}
}
}

// Deprecated with iOS 15
//extension UIApplication {
// func endEditing(_ force: Bool) {
// self.windows
// .filter{$0.isKeyWindow}
// .first?
// .endEditing(force)
// }
//}

// Update for iOS 15
// MARK: - UIApplication extension for resgning keyboard on pressing the cancel buttion of the search bar
extension UIApplication {
/// Resigns the keyboard.
///
/// Used for resigning the keyboard when pressing the cancel button in a searchbar based on [this](https://stackoverflow.com/a/58473985/3687284) solution.
/// - Parameter force: set true to resign the keyboard.
func endEditing(_ force: Bool) {
let scenes = UIApplication.shared.connectedScenes
let windowScene = scenes.first as? UIWindowScene
let window = windowScene?.windows.first
window?.endEditing(force)
}
}

struct ResignKeyboardOnDragGesture: ViewModifier {
var gesture = DragGesture().onChanged{_ in
UIApplication.shared.endEditing(true)
}
func body(content: Content) -> some View {
content.gesture(gesture)
}
}

extension View {
func resignKeyboardOnDragGesture() -> some View {
return modifier(ResignKeyboardOnDragGesture())
}
}

The final result for the search bar, when initially displayed looks like this

Sample Image

and when the search bar is edited like this:

Sample Image

In Action:

Sample Image

SwiftUI - Search in List Header

Xcode 13 / SwiftUI 3

You can now use .searchable to make a List... searchable!

struct ContentView: View {

@State private var searchQuery: String = ""

var body: some View {
NavigationView {
List {
ForEach(Array(1...100)
.map { "\($0)" }
.filter { searchQuery.isEmpty ? true : $0.contains(searchQuery) }
,id: \.self) { item in
Text(verbatim: item)
}
}
.navigationTitle("Fancy Numbers")
.searchable(text: $searchQuery)
}
}

}

The search bar seems to appear only if the List is embedded in a NavigationView.


Xcode 12, SwiftUI 1/2

You can port UISearchBar to SwiftUI.

(More about this can be found in the excellent WWDC 2019 talk - Integrating SwiftUI)

struct SearchBar: UIViewRepresentable {

@Binding var text: String

class Coordinator: NSObject, UISearchBarDelegate {

@Binding var text: String

init(text: Binding<String>) {
_text = text
}

func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
text = searchText
}
}

func makeCoordinator() -> Coordinator {
return Coordinator(text: $text)
}

func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar {
let searchBar = UISearchBar(frame: .zero)
searchBar.delegate = context.coordinator
return searchBar
}

func updateUIView(_ uiView: UISearchBar,
context: UIViewRepresentableContext<SearchBar>) {
uiView.text = text
}
}

And use it like this:

struct ContentView: View {

@State private var searchQuery: String = ""

var body: some View {

List {
Section(header: SearchBar(text: self.$searchQuery)) {
ForEach(Array(1...100).filter {
self.searchQuery.isEmpty ?
true :
"\($0)".contains(self.searchQuery)
}, id: \.self) { item in
Text("\(item)")
}
}
}
}
}

It's the proper search bar, but it doesn't hide - I'm sure we'll be able to do it at some point via SwiftUI API.

Looks like this:

Sample Image

MacOS swiftUI Custom ui TextField field with rounded edge

Try to use rounded rectangle in background (with needed parameters, like corner radius, color, etc) instead of border, like

.background(
RoundedRectangle(cornerRadius: 8)
.stroke(your_color_here)
)

SwiftUI: How to set the title for the toolbar on macOS 11?

Here is how I solved it:

import SwiftUI

var listItems = ["Item 1", "Item 2", "Item 3", "Item 4"]
var secondItems = ["Second 1", "Second 2", "Second 3", "Second 4"]
struct ContentView: View
{

@State var select: String? = "Item 1"
var body: some View
{
VStack
{
NavigationView
{
List
{
ForEach((0..<listItems.count), id: \.self)
{index in
NavigationLink(destination: SecondView(), tag: listItems[index], selection: $select)
{
Text(listItems[index])
.frame(height: 20)
}
}
Spacer()

}
.listStyle(SidebarListStyle())
}

.toolbar
{
Text("this is not the title")
Button(action: {})
{
Label("Upload", systemImage: "square.and.arrow.up")
}
}
.navigationTitle(select!)
.navigationSubtitle("\(listItems.count) records")
.navigationViewStyle(DoubleColumnNavigationViewStyle())
}
}
}

struct SecondView: View {

var body: some View {
NavigationView {
List
{
ForEach((0..<secondItems.count), id: \.self)
{index in
NavigationLink(destination: Text(secondItems[index]))
{
Text(secondItems[index])
.frame(height: 20)
}
}
}
}
}
}

macOS window with integrated title bar and toolbar?

  1. Create a standard toolbar.
  2. When the window loads, set titleVisibility to hidden:

    // Objective-C
    window.titleVisibility = NSWindowTitleHidden;

    // Swift
    window?.titleVisibility = .hidden

How do I add a toolbar to a macOS app using SwiftUI?

As of macOS 11 you’ll likely want to use the new API as documented in WWDC Session 10104 as the new standard. Explicit code examples were provided in WWDC Session 10041 at the 12min mark.

NSWindowToolbarStyle.unified

or

NSWindowToolbarStyle.unifiedCompact

And in SwiftUI you can use the new .toolbar { } builder.

struct ContentView: View {

var body: some View {
List {
Text("Book List")
}
.toolbar {
Button(action: recordProgress) {
Label("Record Progress", systemImage: "book.circle")
}
}
}

private func recordProgress() {}
}


Related Topics



Leave a reply



Submit