Site icon AppTractor

Делаем поисковые подсказки в SwiftUI

Модификатор searchSuggestions(_:) в SwiftUI — это мощная функция, которая улучшает работу поиска в приложениях для iOS. В сочетании с отслеживанием недавних поисковых запросов он создает интуитивно понятный интерфейс, который помогает пользователям быстро найти то, что они ищут, и легко вернуться к ранее просмотренным элементам.

Давайте создадим приложение для каталога Apple Products, которое продемонстрирует возможности поиска в SwiftUI. Мы реализуем интеллектуальную систему поиска, которая не только фильтрует товары в режиме реального времени, но и предоставляет предложения по поиску, а также отслеживает недавно просмотренные товары.

К концу этого урока вы узнаете, как:

Прежде чем мы начнем

Чтобы следовать этому руководству, вам понадобятся:

Создание интерфейса поиска

Шаг 1: Настраиваем модель представления

Начнем с создания ViewModel, которая управляет данными о продуктах и функциями поиска. Она станет основой нашей поисковой системы:

class ProductViewModel: ObservableObject {
    // 1.
    let products = ["AirPods", "AirPods Max", "AirPods Pro", "Apple Pencil", "Apple TV", "Apple Watch", "HomePod", "HomePod mini", "iMac", "iMac Pro", "iPad", "iPad Air", "iPad mini", "iPad Pro", "iPhone", "iPhone Pro", "iPhone Pro Max", "iPod", "iPod classic", "iPod mini", "iPod nano", "iPod shuffle", "iPod touch", "Mac mini", "Mac Pro", "MacBook", "MacBook Air", "MacBook Pro", "Macintosh", "Magic Keyboard", "Magic Mouse", "Magic Trackpad", "Studio Display", "Vision Pro"]
.sorted { $0 < $1 }

    // 2.
    func filteredProducts(searchText: String) -> [String] {
        if searchText.isEmpty { return products }
        return products.filter { $0.localizedCaseInsensitiveContains(searchText) }
    }
}
  1. Создаем отсортированный массив названий товаров
  2. Метод filteredProducts(searchText:) обеспечивает фильтрацию в реальном времени на основе пользовательского ввода

Класс ProductViewModel является основой нашей поисковой функциональности. Он управляет данными о товарах и обрабатывает логику фильтрации. Мы храним продукты в отсортированном массиве для последовательного представления и реализуем простой метод фильтрации, который реагирует на ввод пользователя в режиме реального времени.

Шаг 2: Создаем товарную строку

Далее мы создадим переиспользуемое представление, который будет отображать отдельные товары с иконкой и названием:

struct ProductRow: View {
    // 1.
    let product: String

    var body: some View {
        // 2.
        HStack {
            Image(systemName: "\(product.first!.lowercased()).circle.fill")
                .symbolRenderingMode(.hierarchical)
                .imageScale(.large)
            Text(product)
        }
    }
}
  1. Объявляем название товара как свойство
  2. Создаем горизонтальный макет с динамическим SF-символом, основанным на первой букве названия продукта

Представление ProductRow — это простой компонент, который отображает каждый продукт в нашем списке. Мы используем SF Symbols для создания динамических иконок на основе первой буквы названия продукта, обеспечивая визуальную красоту с минимальным количеством кода.

Шаг 3: Создаем представление товара

В этом руководстве мы создадим простое представление подробностей о товаре с плейсхолдером. Хотя фактические данные о продукте не имеют решающего значения для демонстрации поисковых подсказок, позже нам понадобится это представление для демонстрации отслеживания последних поисковых запросов.

struct DetailView: View {
    // 1.
    let product: String
    @ObservedObject var viewModel: ProductViewModel

    var body: some View {
        // 2.
        VStack {
            Image(systemName: "macbook.and.iphone")
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: 160)
                
            Text(product)
                .font(.title)
        }
        .navigationTitle("Product Detail")
    }
}
  1. Объявляем в качестве свойств данные о товаре и ссылку на модель представления
  2. Показываем изображение и название товара друг под другом

Шаг 4: Реализуем ProductsListView с поиском

Здесь мы собираем все воедино. ProductsListView объединяет наш компонент ProductRow и встроенную в SwiftUI функцию поиска. Модификатор searchable(text:placement:prompt:) добавляет поле поиска, а searchSuggestions(_:) позволяет всплывающему окну подсказок появляться при вводе пользователем текста.

struct ProductsListView: View {
    // 1.
    @StateObject private var viewModel = ProductViewModel()
    @State private var searchText = ""

    var body: some View {
        NavigationStack {
            // 2.
            List(viewModel.filteredProducts(searchText: searchText), id: \.self) { product in
                NavigationLink(destination: DetailView(product: product, viewModel: viewModel)) {
                    ProductRow(product: product)
                }
            }
            .navigationTitle("Apple Products")
            // 3.
            .searchable(text: $searchText, prompt: "Search products...")
            // 4.
            .searchSuggestions {
                Section {
                    ForEach(viewModel.filteredProducts(searchText: searchText).prefix(5), id: \.self) { suggestion in
                        Text(suggestion)
                            .searchCompletion(suggestion)
                    }
                }
            }
        }
    }
}
  1. Настраиваем управление состоянием для функции поиска
  2. Создаем отфильтрованный список продуктов с навигацией
  3. Добавляем строку поиска с кастомной подсказкой
  4. Реализуем поисковые подсказки, которые появляются по мере ввода пользователем текста

На данном этапе приложение имеет рабочий интерфейс поиска с интегрированными в него подсказками.

В обычном приложении вы, возможно, захотите реализовать отдельные функции для фильтрации списка и поисковых подсказок, чтобы обеспечить более интеллектуальные, контекстные предложения. Однако в этом руководстве мы упростили задачу, используя одну и ту же функцию фильтрации для обеих, ограничив количество предложений пятью пунктами.

Улучшаем поиск с помощью отслеживания истории

Шаг 5: Добавляем отслеживания недавних просмотров

Давайте улучшим нашу модель представления, чтобы отслеживать недавно просмотренные товары. Сохраняя небольшой массив недавно просмотренных товаров, мы можем обеспечить быстрый доступ к ранее просмотренным товарам, когда пользователи начинают новый поиск. Метод addToRecents(_:) гарантирует, что мы не будем хранить дубликаты и сохраним только пять самых последних товаров:

class ProductViewModel: ObservableObject {

    let products = [ ... ].sorted { $0 < $1 }

    func filteredProducts(searchText: String) -> [String] { ... }

    // 1.
    @Published var recentSearches: [String] = []

    // 2.
    func getSuggestions(for searchText: String) -> [String] {
        if searchText.isEmpty {
            return recentSearches.reversed()
        }
        
        return products.filter { $0.localizedCaseInsensitiveContains(searchText) }
    }

    // 3.
    func addToRecents(_ product: String) {
        recentSearches.removeAll { $0 == product }
        recentSearches.append(product)
        recentSearches = recentSearches.suffix(5)
    }
}
  1. Добавляем опубликованное свойство для хранения последних поисковых запросов
  2. Показываем недавние поиски, если поле поиска пусто, в противном случае показываем отфильтрованные предложения
  3. Управляем недавними поисками с максимальным количеством 5 элементов

Шаг 6: Обновляем поисковые подсказки в ProductsListView

После добавления недавних поисков в нашу модель представления нам нужно обновить ProductsListView, чтобы использовать новый метод getSuggestions(for:) вместо filteredProducts(searchText:) для подсказок. Это изменение позволит нам отображать недавние поиски, когда поле поиска пусто:

struct ProductsListView: View {
    @StateObject private var viewModel = ProductViewModel()
    @State private var searchText = ""
    
    var body: some View {
        NavigationStack {
            List(viewModel.filteredProducts(searchText: searchText), id: \.self) { product in
                NavigationLink(destination: DetailView(product: product, viewModel: viewModel)) {
                    ProductRow(product: product)
                }
            }
            .navigationTitle("Apple Products")
            
            .searchable(text: $searchText, prompt: "Search products...")
            .searchSuggestions {
                Section {
                    ForEach(viewModel.getSuggestions(for: searchText), id: \.self) { suggestion in
                        Text(suggestion)
                            .searchCompletion(suggestion)
                    }
                }
            }
        }
    }
}

Ключевым изменением здесь является использование getSuggestions(for: searchText) вместо filteredProducts(searchText: searchText).prefix(5). Это позволяет нашим поисковым подсказкам показывать либо недавние поиски, либо отфильтрованные результаты в зависимости от состояния поля поиска.

Шаг 7: Завершаем работу над представлением с подробностями о товаре

Обновите DetailView, чтобы запустить отслеживание недавно просмотренных товаров. Используя модификатор SwiftUI onAppear(perform:), мы автоматически добавляем товары в список недавнего поиска, когда пользователи просматривают их детали. Это создает бесшовный опыт, когда предложения по поиску становятся более персонализированными с течением времени:

truct DetailView: View {
    let product: String
    @ObservedObject var viewModel: ProductViewModel

    var body: some View {
        VStack {
            Image(systemName: "macbook.and.iphone")
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: 160)
            Text(product)
                .font(.title)
        }
        .navigationTitle("Product Detail")

        // Add tracking when view appears
        .onAppear {
            viewModel.addToRecents(product)
        }
    }
}

В этом уроке показано, как создать интерфейс поиска в SwiftUI с помощью модификатора searchSuggestions(_:). Сочетая фильтрацию в реальном времени с отслеживанием недавних поисков, мы создали интуитивно понятный поисковый интерфейс, который адаптируется к поведению пользователя.

Источник

Exit mobile version