Как лучше всего использовать dismissSearch и isSearching в моем представлении поиска?

Вопрос или проблема

Я работаю над представлением поиска, где использую области поиска и предложения поиска. Для предложений поиска я хочу проверить, активно ли выполнение поиска, и закрыть его, когда пользователь нажимает на предложение, чтобы скрыть клавиатуру. Для этого я хочу использовать 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 позволяет центрально управлять состоянием поиска, делая взаимодействие с предложениями и отображение состояния более плавным и лаконичным.

Оцените материал
Добавить комментарий

Капча загружается...