Skip to content

Commit 35c10bc

Browse files
authored
Merge pull request #43 from wmorgue/searchable
Searchable
2 parents 82b4ecf + b55ea23 commit 35c10bc

File tree

2 files changed

+264
-0
lines changed

2 files changed

+264
-0
lines changed

ru/meta/articles.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,5 +160,17 @@
160160
],
161161
"updated_date": "17.02.2022",
162162
"added_date": "09.02.2022"
163+
},
164+
"searchable-swiftui" : {
165+
"title" : "Searchable в SwiftUI",
166+
"description" : "Поиск в SwiftUI. Работаем с модификатором Searchable.",
167+
"category" : "swiftui",
168+
"author" : "wmorgue",
169+
"editors" : ["ivanvorobei"],
170+
"keywords" : [
171+
"searchable"
172+
],
173+
"updated_date": "21.02.2022",
174+
"added_date": "21.02.2022"
163175
}
164176
}

ru/tutorials/searchable-swiftui.md

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
С появлением iOS 15 и SwiftUI 3 появилась возможность вызвать поисковый бар с помощь модификатора [.searchable()](https://developer.apple.com/documentation/swiftui/form/searchable(text:placement:)).
2+
3+
## Инициализация
4+
5+
Добавим модификатор `.searchable()` к `NavigationView()`:
6+
7+
```swift
8+
struct ContentView: View {
9+
@State private var searchQuery: String = ""
10+
11+
var body: some View {
12+
NavigationView {
13+
Text("Поиск \(searchQuery)")
14+
.navigationTitle("Searchable Sample")
15+
.navigationBarTitleDisplayMode(.inline)
16+
17+
}
18+
.searchable(text: $searchQuery)
19+
}
20+
}
21+
```
22+
23+
[Searchable init](https://cdn.ivanvorobei.by/websites/sparrowcode.io/searchable-swiftui/searchable_init.mov)
24+
25+
26+
Для изменения приглашения в поисковой строке добавим параметр `prompt`:
27+
28+
```swift
29+
.searchable(text: $searchQuery, prompt: "Нажмите для поиска…")
30+
```
31+
32+
33+
## Расположение
34+
35+
Инициализатор `searchable()` принимает `placement` в качестве одного из параметров. На выбор доступно четыре варианта: `automatic`, `navigationBarDrawer`, `sidebar` и `toolbar`. Обратите внимание, что этот параметр позволяет указать **предпочтительное** размещение. В зависимости от иерархии вью и платформы, размещение может не сработать:
36+
37+
```swift
38+
struct PrimaryView: View {
39+
var body: some View {
40+
Text("Primary View")
41+
}
42+
}
43+
44+
struct SecondaryView: View {
45+
var body: some View {
46+
Text("Secondary View")
47+
}
48+
}
49+
50+
struct ContentView: View {
51+
@State private var searchQuery: String = ""
52+
53+
var body: some View {
54+
NavigationView {
55+
PrimaryView()
56+
.navigationTitle("Primary")
57+
58+
SecondaryView()
59+
.navigationTitle("Secondary")
60+
.searchable(text: $searchQuery, placement: .navigationBarDrawer)
61+
}
62+
}
63+
}
64+
```
65+
В примере выше, мы применили модификатор к `SecondaryView()` и изменили расположение на `.navigationBarDrawer`.
66+
За это отвечает структура `SearchFieldPlacement()`. По умолчанию `placement` установлено в `.automatic`.
67+
68+
[Searchable placement](https://cdn.ivanvorobei.by/websites/sparrowcode.io/searchable-swiftui/searchable_placement.mov)
69+
70+
71+
## Поиск
72+
73+
Рассмотрим как можно выполнить сам поиск и выдачу результата.
74+
Создадим приложение, показывающее список авторов статей, в котором пользователь может искать определенного автора.
75+
76+
Подготовим структуру:
77+
78+
```swift
79+
struct Author {
80+
let name: String
81+
}
82+
83+
extension Author: Identifiable {
84+
var id: UUID { UUID() }
85+
86+
static let placeholder = [
87+
Author(name: "Ivan Vorobei"),
88+
Author(name: "Nikita Rossik"),
89+
Author(name: "Nikita Somenkov"),
90+
Author(name: "Nikolay Pelevin")
91+
]
92+
}
93+
```
94+
95+
Имеем одно проперти: `name` и массив данных: `placeholder`. Далее переходим в `ContentView()`:
96+
97+
```swift
98+
struct ContentView: View {
99+
let authors: [Author] = Author.placeholder
100+
@State private var searchQuery: String = ""
101+
102+
var body: some View {
103+
NavigationView {
104+
List(authorsResult) { author in
105+
NavigationLink(author.name, destination: Text(author.name))
106+
}
107+
.navigationTitle("Authors")
108+
.navigationBarTitleDisplayMode(.inline)
109+
}
110+
.searchable(text: $searchQuery, prompt: "Search author")
111+
}
112+
}
113+
114+
extension ContentView {
115+
var authorsResult: [Author] {
116+
guard searchQuery.isEmpty else {
117+
return authors.filter { $0.name.contains(searchQuery) }
118+
}
119+
return authors
120+
}
121+
}
122+
```
123+
124+
[Searchable Author run](https://cdn.ivanvorobei.by/websites/sparrowcode.io/searchable-swiftui/searchable_author_run.mov)
125+
126+
127+
Создаем `NavigationView` и внутри него создаем `List`, который принимает массив авторов c фильтром:
128+
129+
```swift
130+
authors.filter { $0.name.contains(searchQuery) }
131+
```
132+
По умолчанию поисковый бар появляется внутри списка и поэтому он скрыт. Необходимо потянуть список вниз, чтобы поле поиска появилось.
133+
В расширение нашей вью я вынес `authorsResult` проперти.
134+
135+
## Предполагаемые варианты (Suggestions)
136+
137+
Для более продвинутого использования, модификатор позволяет нам показывать список вариантов для наших авторов.
138+
139+
```swift
140+
.searchable(text: $searchQuery, prompt: "Search author") {
141+
Text("Vanya").searchCompletion("Ivan Vorobei")
142+
Text("Somenkov").searchCompletion("Nikita Somenkov")
143+
Text("Nicola").searchCompletion("Nikolay Pelevin")
144+
Text("?").searchCompletion("Unknown author")
145+
}
146+
```
147+
148+
[Searchable suggestions](https://cdn.ivanvorobei.by/websites/sparrowcode.io/searchable-swiftui/searchable_suggestions.mov)
149+
150+
151+
Параметр `suggestions` принимает `@ViewBuilder`, поэтому мы можем сделать кастомную View, а так же комбинировать варианты.
152+
Код текущего проекта:
153+
154+
```swift
155+
struct ContentView: View {
156+
let authors: [Author] = Author.placeholder
157+
@State private var searchQuery: String = ""
158+
159+
var body: some View {
160+
NavigationView {
161+
List(authorsResult) { author in
162+
NavigationLink(author.name, destination: Text(author.name))
163+
}
164+
.navigationTitle("Authors")
165+
.navigationBarTitleDisplayMode(.inline)
166+
}
167+
.searchable(text: $searchQuery, prompt: "Search author") {
168+
Text("Vanya")
169+
.searchCompletion(authorsResult.first!.name)
170+
searchableSuggestions
171+
}
172+
}
173+
}
174+
175+
extension ContentView {
176+
var authorsResult: [Author] {
177+
guard searchQuery.isEmpty else {
178+
return authors.filter { $0.name.contains(searchQuery) }
179+
}
180+
return authors
181+
}
182+
183+
private var searchableSuggestions: some View {
184+
ForEach(authorsResult) { suggestion in
185+
Text(suggestion.name)
186+
.searchCompletion(suggestion.name)
187+
}
188+
}
189+
}
190+
```
191+
192+
Обратите внимание, приложение упадет, если мы начнем вводить символы или цифры. Я оставил этот код умышленно, чтобы продемонстрировать комбинированные варианты поиска:
193+
194+
```swift
195+
.searchCompletion(authorsResult.first!.name)
196+
```
197+
198+
## Больше контроля
199+
200+
Если вам необходимо больше контроля, будь то отслеживание поисковых запросов, поиск в локальной базе данных и т.д., то вы можете использовать модификатор `.onSubmit(of: SubmitTriggers)`.
201+
202+
`SubmitTriggers()` — тип, который определяет различные триггеры приводящие к выполнению действия. Доступно 2 проперти: `text` и `search`.
203+
204+
```swift
205+
.onSubmit(of: .search) {
206+
print("Sending a search request: \(searchQuery)")
207+
}
208+
```
209+
210+
[Searchable onSubmit](https://cdn.ivanvorobei.by/websites/sparrowcode.io/searchable-swiftui/searсhable_onsubmit.mov)
211+
212+
Модификатор `.onSubmit()` сработает, когда будет отправлен поисковый запрос:
213+
214+
1. По нажатию предполагаемого варианта.
215+
2. По нажатию ввода (`return`).
216+
3. По нажатию ввода (`return`) на физической клавиатуре.
217+
218+
219+
## Environment
220+
221+
Доступно 2 значения: `\.isSearching` и `\.dismissSearch`.
222+
223+
`isSearching` показывает, взаимодействует ли пользователь в данный момент с полем поиска.
224+
`dismissSearch` требует от системы завершить текущее взаимодействие с полем поиска.
225+
Оба значения среды работают только в вью, где вызывается модификатор `.searchable()`:
226+
227+
228+
```swift
229+
struct ContentView: View {
230+
@StateObject var viewModel = SearchViewModel()
231+
@Environment(\.isSearching) private var isSearching
232+
@Environment(\.dismissSearch) private var dismissSearch
233+
234+
let query: String
235+
236+
var body: some View {
237+
List(viewModel.repos) { repo in
238+
RepoView(repo: repo)
239+
}.overlay {
240+
if isSearching && !query.isEmpty {
241+
VStack {
242+
Button("Dismiss search") {
243+
dismissSearch()
244+
}
245+
SearchResultView(query: query)
246+
.environmentObject(viewModel)
247+
}
248+
}
249+
}
250+
}
251+
}
252+
```

0 commit comments

Comments
 (0)