Вопрос или проблема
Я работаю над представлением поиска, где использую области поиска и предложения поиска. Для предложений поиска я хочу проверить, активно ли выполнение поиска, и закрыть его, когда пользователь нажимает на предложение, чтобы скрыть клавиатуру. Для этого я хочу использовать isSearching()
и dismissSearch()
, но эти методы можно использовать только из дочернего представления. Создание дочернего представления означает, что нужно передать текст поиска, области поиска, выбранную область поиска и предложения поиска в качестве входных данных в дочернее представление (в виде @Binding
). Это можно упростить, создав SearchModel
, который будет хранить эти значения и передавать его в дочернее представление.
Кажется, что сложно делать все эти данные доступными как в представлении поиска, так и в его дочернем представлении. Это лучший/единственный способ или есть более простой способ сделать это?
Это простое представление того, как в настоящее время выглядит представление поиска.
struct SearchView: View {
@State private var searchText = ""
@State private var searchScopes = [SearchScope]()
@State private var searchScope: SearchScope?
@State private var searchSuggestions = [SearchSuggestion]()
var body: some View {
NavigationStack {
Text("Поиск: \(searchText)")
}
.searchable(text: $searchText)
.searchScopes($searchScope, activation: .onSearchPresentation, {
ForEach(searchScopes) { scope in
Text(scope.label).tag(scope)
}
})
.searchSuggestions({
ForEach(searchSuggestions, id: \.self) { suggestion in
Button("\(suggestion.label)") {
searchScope = searchScopes.first(where: { $0.label == suggestion.label })
searchText = suggestion.value
// Я не вызываю метод runSearch(), потому что по какой-то причине триггерится onSubmit, который вызывает runSearch()...
}
}
})
.onSubmit(of: .search, runSearch)
.onChange(of: searchScope) {
Task {
await runSearchSuggestions()
}
}
.onChange(of: searchText) {
Task {
await runSearchSuggestions()
}
}
.task {
self.searchScopes = await httpClient.fetchSearchScopes()
if !searchScopes.isEmpty {
self.searchScope = searchScopes[0]
}
}
}
}
Создание дочернего представления, чтобы использовать dismissSearch()
и isSearching
, будет выглядеть примерно так.
struct SearchView: View {
@State private var searchText = ""
@State private var searchScopes = [SearchScope]()
@State private var searchScope: SearchScope?
@State private var searchSuggestions = [SearchSuggestion]()
var body: some View {
NavigationStack {
Text("Поиск: \(searchText)")
}
.searchable(text: $searchText)
.searchScopes($searchScope, activation: .onSearchPresentation, {
ForEach(searchScopes) { scope in
Text(scope.label).tag(scope)
}
})
.searchSuggestions({
ForEach(searchSuggestions, id: \.self) { suggestion in
SearchSuggestionButton(suggestion: suggestion)
}
})
.onSubmit(of: .search, runSearch)
.onChange(of: searchScope) {
Task {
await runSearchSuggestions()
}
}
.onChange(of: searchText) {
Task {
await runSearchSuggestions()
}
}
.task {
self.searchScopes = await httpClient.fetchSearchScopes()
if !searchScopes.isEmpty {
self.searchScope = searchScopes[0]
}
}
}
func runSearch() {
// выполнить поиск
searchText = ""
searchSuggestions = []
}
}
struct SearchSuggestionButton: View {
@Environment(\.dismissSearch) private var dismissSearch
var suggestion: SearchSuggestion
@Binding var searchText: String
@Binding var searchScopes: [SearchScope]
@Binding var searchScope: SearchScope?
var body: some View {
Button("\(suggestion.label)") {
searchScope = searchScopes.first(where: { $0.label == suggestion.label })
searchText = suggestion.value
// Я не вызываю метод runSearch(), потому что по какой-то причине триггерится onSubmit, который вызывает runSearch()...
runSearch()
dismissSearch()
}
}
// Дублирование метода runSearch()
func runSearch() {
// выполнить поиск
searchText = ""
searchSuggestions = []
}
}
Ответ или решение
Для оптимального использования методов dismissSearch()
и isSearching()
в представлении поиска (SearchView
) с учетом вашего сценария, действительно, создание дочернего представления может показаться громоздким и затруднительным, особенно если его необходимо снабдить множеством привязок типа @Binding
. Вместо этого, я предлагаю более элегантное решение с использованием модели для передачи необходимых данных.
Создание модели поиска
Вместо того чтобы передавать все данные через @Binding
, создайте одну структуру SearchModel
, которая будет хранить все параметры поиска. Это позволит вам передавать только одну переменную во все дочерние представления.
class SearchModel: ObservableObject {
@Published var searchText = ""
@Published var searchScopes = [SearchScope]()
@Published var searchScope: SearchScope?
@Published var searchSuggestions = [SearchSuggestion]()
func runSearch() {
// Логика выполнения поиска
searchText = ""
searchSuggestions = []
}
}
Основное представление поиска
В вашем основном представлении SearchView
можно использовать эту SearchModel
:
struct SearchView: View {
@StateObject private var searchModel = SearchModel()
var body: some View {
NavigationStack {
Text("Searching for \(searchModel.searchText)")
}
.searchable(text: $searchModel.searchText)
.searchScopes($searchModel.searchScope, activation: .onSearchPresentation, {
ForEach(searchModel.searchScopes) { scope in
Text(scope.label).tag(scope)
}
})
.searchSuggestions({
ForEach(searchModel.searchSuggestions, id: \.self) { suggestion in
SearchSuggestionButton(suggestion: suggestion, searchModel: searchModel)
}
})
.onSubmit(of: .search) {
searchModel.runSearch()
}
.onChange(of: searchModel.searchScope) {
Task {
await runSearchSuggestions()
}
}
.onChange(of: searchModel.searchText) {
Task {
await runSearchSuggestions()
}
}
.task {
searchModel.searchScopes = await httpClient.fetchSearchScopes()
if !searchModel.searchScopes.isEmpty {
searchModel.searchScope = searchModel.searchScopes[0]
}
}
}
}
Дочернее представление для кнопки предложения
Теперь создадим дочернее представление SearchSuggestionButton
, которое будет использовать SearchModel
вместо отдельного списка привязок:
struct SearchSuggestionButton: View {
@Environment(\.dismissSearch) private var dismissSearch
var suggestion: SearchSuggestion
@ObservedObject var searchModel: SearchModel
var body: some View {
Button("\(suggestion.label)") {
searchModel.searchScope = searchModel.searchScopes.first(where: { $0.label == suggestion.label })
searchModel.searchText = suggestion.value
// Выполняем поиск, затем закрываем представление поиска
searchModel.runSearch()
dismissSearch()
}
}
}
Заключение
Таким образом, благодаря созданию модели SearchModel
, вы упрощаете обмен данными между представлением поиска и дочерними элементами, избегая громоздких привязок @Binding
. Это не только делает код более читабельным, но и улучшает его поддержку и расширяемость в дальнейшем. Использование @StateObject
и @ObservedObject
позволяет центрально управлять состоянием поиска, делая взаимодействие с предложениями и отображение состояния более плавным и лаконичным.