Skip to content

Commit

Permalink
Merge pull request #46 from wmorgue/searchable_translate
Browse files Browse the repository at this point in the history
Searchable translate
  • Loading branch information
ivanvorobei authored Feb 23, 2022
2 parents 4ccef32 + e5373dd commit 3c0faac
Show file tree
Hide file tree
Showing 3 changed files with 269 additions and 0 deletions.
1 change: 1 addition & 0 deletions .yaspellerrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
"хабр(е|)",
"screenshots",
"asynchronously",
"onSubmit",
"subtask",
"asynchrony",
"ImageDownloader",
Expand Down
12 changes: 12 additions & 0 deletions en/meta/articles.json
Original file line number Diff line number Diff line change
Expand Up @@ -152,5 +152,17 @@
],
"updated_date" : "17.02.2022",
"added_date" : "17.02.2022"
},
"searchable-swiftui" : {
"title" : "Searchable в SwiftUI",
"description" : "Search в SwiftUI. Working with Searchable.",
"category" : "swiftui",
"author" : "wmorgue",
"translator": "wmorgue",
"keywords" : [
"searchable"
],
"updated_date": "23.02.2022",
"added_date": "23.02.2022"
}
}
256 changes: 256 additions & 0 deletions en/tutorials/searchable-swiftui.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,256 @@
With iOS 15 and SwiftUI 3 the search bar is called by the [.searchable()](https://developer.apple.com/documentation/swiftui/form/searchable(text:placement:)) modifier.

## Init

Add the modifier `.searchable()` to `NavigationView()`:

```swift
struct ContentView: View {

@State private var searchQuery: String = ""

var body: some View {
NavigationView {
Text("Search \(searchQuery)")
.navigationTitle("Searchable Sample")
.navigationBarTitleDisplayMode(.inline)

}
.searchable(text: $searchQuery)
}
}
```

[Searchable init](https://cdn.ivanvorobei.by/websites/sparrowcode.io/searchable-swiftui/searchable_init.mov)

To change the placeholder, in the search field we will add `prompt`:

```swift
.searchable(text: $searchQuery, prompt: "Tap to search…")
```

## Placement

Initializer `searchable()` get `placement` parameter. There are four selections: `automatic`, `navigationBarDrawer`, `sidebar` and `toolbar`. The parameter provides the **preferred** placement - depending on the view hierarchy and platform, the placement may not work:

```swift
struct PrimaryView: View {

var body: some View {
Text("Primary View")
}
}

struct SecondaryView: View {

var body: some View {
Text("Secondary View")
}
}

struct ContentView: View {

@State private var searchQuery: String = ""

var body: some View {
NavigationView {
PrimaryView()
.navigationTitle("Primary")

SecondaryView()
.navigationTitle("Secondary")
.searchable(text: $searchQuery, placement: .navigationBarDrawer)
}
}
}
```

![Searchable Diff Placement](https://cdn.ivanvorobei.by/websites/sparrowcode.io/searchable-swiftui/searchable_diff_placement.jpg)

Apply a modifier to `SecondaryView()` and change the location to `.navigationBarDrawer`. The `SearchFieldPlacement()` structure is responsible for the position of the search field. By default `placement` is `.automatic`.

[Searchable Placement](https://cdn.ivanvorobei.by/websites/sparrowcode.io/searchable-swiftui/searchable_placement.mov)

## Search

Let's perform a search and output the result. Create an application that shows a list of authors of articles in which the user can find a particular author. Prepare the structure:

```swift
struct Author {
let name: String
}

extension Author: Identifiable {

var id: UUID { UUID() }

static let placeholder = [
Author(name: "Ivan Vorobei"),
Author(name: "Nikita Rossik"),
Author(name: "Nikita Somenkov"),
Author(name: "Nikolay Pelevin")
]
}
```

Have a single `name` property and a data `placeholder` array. Move to `ContentView()`:

```swift
struct ContentView: View {

let authors: [Author] = Author.placeholder
@State private var searchQuery: String = ""

var body: some View {
NavigationView {
List(authorsResult) { author in
NavigationLink(author.name, destination: Text(author.name))
}
.navigationTitle("Authors")
.navigationBarTitleDisplayMode(.inline)
}
.searchable(text: $searchQuery, prompt: "Search author")
}
}

extension ContentView {

var authorsResult: [Author] {
guard searchQuery.isEmpty else {
return authors.filter { $0.name.contains(searchQuery) }
}
return authors
}
}
```

[Searchable Author Run](https://cdn.ivanvorobei.by/websites/sparrowcode.io/searchable-swiftui/searchable_author_run.mov)

Create a `NavigationView` with `List` that takes an array of authors and filters it:

```swift
authors.filter { $0.name.contains(searchQuery) }
```

By default, the search bar appears inside the list, so is hidden. To search appear - scroll down the list. Put `authorsResult` into `ContentView` extension to split logic from interface.

## Suggestions

The modifier will show a list of different authors:

```swift
.searchable(text: $searchQuery, prompt: "Search author") {
Text("Vanya").searchCompletion("Ivan Vorobei")
Text("Somenkov").searchCompletion("Nikita Somenkov")
Text("Nicola").searchCompletion("Nikolay Pelevin")
Text("?").searchCompletion("Unknown author")
}
```

[Searchable suggestions](https://cdn.ivanvorobei.by/websites/sparrowcode.io/searchable-swiftui/searchable_suggestions.mov)

Search suggestions will overlay your main view:

![Searchable overlay](https://cdn.ivanvorobei.by/websites/sparrowcode.io/searchable-swiftui/searchable_overlay.jpg)

The `suggestions` parameter takes `@ViewBuilder`, so you can make a custom View and combine options for a search suggestion. The code of the current project:

```swift
struct ContentView: View {

let authors: [Author] = Author.placeholder
@State private var searchQuery: String = ""

var body: some View {
NavigationView {
List(authorsResult) { author in
NavigationLink(author.name, destination: Text(author.name))
}
.navigationTitle("Authors")
.navigationBarTitleDisplayMode(.inline)
}
.searchable(text: $searchQuery, prompt: "Search author") {
Text("Vanya")
.searchCompletion(authorsResult.first!.name)
searchableSuggestions
}
}
}

extension ContentView {

var authorsResult: [Author] {
guard searchQuery.isEmpty else {
return authors.filter { $0.name.contains(searchQuery) }
}
return authors
}

private var searchableSuggestions: some View {
ForEach(authorsResult) { suggestion in
Text(suggestion.name)
.searchCompletion(suggestion.name)
}
}
}
```

The app will crash if we enter symbols or digits. I kept this code to demonstrate the combined options of the search suggestions:

```swift
.searchCompletion(authorsResult.first!.name)
```

## Control

If you need more control - tracking searches, searching the local database, etc., use the modifier `.onSubmit(of: SubmitTriggers)`. It defines different triggers to start an action. There are 2 properties available: `text` and `search`.

```swift
.onSubmit(of: .search) {
print("Sending a search request: \(searchQuery)")
}
```

[Searchable onSubmit](https://cdn.ivanvorobei.by/websites/sparrowcode.io/searchable-swiftui/searсhable_onsubmit.mov)

Modifier `.onSubmit()` will trigger when a search query is submitted:

1. User tap on search suggestion.
2. User tap on the return key on the software keyboard.
3. User tap on the return key on the physical hardware keyboard.

## Environment

We have two environment values: `\.isSearching` and `\.dismissSearch`.

`isSearching` - value that indicated whether the user is currently interacting with the search bar that has been placed by a surrounding searchable modifier. `dismissSearch` asks the system to dismiss the current search interaction.
Both environment values work only in the views surrounded by the `.searchable()` modifier:

```swift
struct ContentView: View {

@StateObject var viewModel = SearchViewModel()
@Environment(\.isSearching) private var isSearching
@Environment(\.dismissSearch) private var dismissSearch

let query: String

var body: some View {
List(viewModel.repos) { repo in
RepoView(repo: repo)
}.overlay {
if isSearching && !query.isEmpty {
VStack {
Button("Dismiss search") {
dismissSearch()
}
SearchResultView(query: query)
.environmentObject(viewModel)
}
}
}
}
}
```

Adding search to the app is easy. But setting up the behavior is more difficult.

0 comments on commit 3c0faac

Please sign in to comment.