SwiftUI: Как вызвать вызов API для извлечения источника данных при изменении текста панели поиска - PullRequest
0 голосов
/ 17 апреля 2020

Я реализую строку поиска, используя UIKit, чтобы обернуть ее в MovieSearchView. Когда пользователь вводит некоторые ключевые слова, он отображает результаты поиска в следующем представлении списка.

Данные searchResults поступают из вызова API self.model.getMovieSearchResults(), но в моем коде вызов API никогда не запускается. Поэтому мне интересно, куда мне поставить проверку условия onAppear() в моем случае, чтобы инициировать вызов API, когда текст поиска изменяется в панели поиска?

/ / / Представление поиска

import SwiftUI

struct MovieSearchView: View {
    @ObservedObject var model = MovieListViewModel()
    @State private var searchText: String = ""

    var body: some View {
        NavigationView {
            VStack {
                SearchBar(text: $searchText)
                .onAppear() {
                    // If searchText has a value, then call api to fetch movies data.
                    if self.searchText.isEmpty == false {
                        self.model.getMovieSearchResults(for: self.searchText)
                    }
                }
                List {
                    ForEach(model.searchResults.filter {
                        self.searchText.isEmpty ? true : $0.title.lowercased().contains(self.searchText.lowercased())
                    }) { movie in
                        Text(movie.title)
                    }
                }
            }
        }
    }
}

struct MovieSearchView_Previews: PreviewProvider {
    static var previews: some View {
        MovieSearchView()
    }
}

/ / / Панель поиска

import SwiftUI

struct SearchBar: UIViewRepresentable {

    @Binding var text: String

    class Coordinator: NSObject, UISearchBarDelegate {

        @Binding var text: String

        init(text: Binding<String>) {
            _text = text
        }

        func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
            text = searchText
        }
    }

    func makeCoordinator() -> SearchBar.Coordinator {
        return Coordinator(text: $text)
    }

    func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar {
        let searchBar = UISearchBar(frame: .zero)
        searchBar.delegate = context.coordinator
        searchBar.searchBarStyle = .minimal
        searchBar.autocapitalizationType = .none
        return searchBar
    }

    func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<SearchBar>) {
        uiView.text = text
    }
}

Обновление:

Просто поймите, что мне больше не нужно выполнять фильтр, поскольку вызов API уже возвращает отфильтрованный результат поиска с сервера , Измените код, чтобы удалить фильтр {}. Я также добавляю жест касания к запуску навигации в подробном представлении mov ie и вызову страницы 2 при достижении нижней части.

Дополнительные обновления:

  • Очистить результаты поиска, когда текст поиска очищается пользователем.

  • Показать кнопку отмены на панели поиска, когда пользователь начинает редактирование, и
    закрыть клавиатуру при нажатии кнопки отмены.

Панель поиска

import SwiftUI

struct SearchBar: UIViewRepresentable {

    @Binding var text: String
    var onTextChanged: (String) -> Void

    class Coordinator: NSObject, UISearchBarDelegate {

        @Binding var text: String
        var onTextChanged: (String) -> Void

        init(text: Binding<String>, onTextChanged: @escaping (String) -> Void) {
            _text = text
            self.onTextChanged = onTextChanged
        }

        // Show cancel button when the user begins editing the search text
        func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) {
            searchBar.showsCancelButton = true
        }

        func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
            text = searchText
            onTextChanged(text)
        }

        func searchBarCancelButtonClicked(_ searchBar: UISearchBar) {
            text = ""
            searchBar.showsCancelButton = false
            searchBar.endEditing(true)
            // Send back empty string text to search view, trigger self.model.searchResults.removeAll()
            onTextChanged(text)
        }
    }

    func makeCoordinator() -> SearchBar.Coordinator {
        return Coordinator(text: $text, onTextChanged: onTextChanged)
    }

    func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar {
        let searchBar = UISearchBar(frame: .zero)
        searchBar.delegate = context.coordinator
        searchBar.placeholder = "Search TMDB"
        searchBar.searchBarStyle = .minimal
        searchBar.autocapitalizationType = .none
        searchBar.showsCancelButton = true
        return searchBar
    }

    func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<SearchBar>) {
        uiView.text = text
    }
}

Вид поиска

import SwiftUI

struct MovieSearchView: View {
    @ObservedObject var model = MovieListViewModel()
    @State private var searchText: String = ""
    @State private var selectedId = -1
    @State private var showSheet = false
    @State private var page = 1

    var body: some View {
        List {
            SearchBar(text: $searchText, onTextChanged: searchMovies)

            ForEach(0..<model.searchResults.count, id: \.self) { i in
                MovieListRow(movie: self.model.searchResults[i])
                    .onTapGesture {
                        self.selectedId = self.model.searchResults[i].id
                        self.showSheet.toggle()
                    }
                .onAppear() {
                    if i == self.model.searchResults.count - 1 {
                        self.page += 1
                        self.model.getMovieSearchResults(for: self.searchText, page: self.page)
                    }
                }
            }
        }
        .sheet(isPresented: $showSheet) {
            SingleMovieView(movieId: self.selectedId)
        }
    }

    func searchMovies(for searchText: String) {
        if !searchText.isEmpty {
            self.model.getMovieSearchResults(for: self.searchText, page: self.page)
        } else {
            // remove search result when a user clear keyword.
            self.model.searchResults.removeAll()
        }
    }
}

struct MovieSearchView_Previews: PreviewProvider {
    static var previews: some View {
        MovieSearchView()
    }
}

Теперь, как это выглядело в моем приложении. Осталось только одно - убрать клавиатуру при прокрутке списка вниз. enter image description here

Ответы [ 2 ]

0 голосов
/ 17 апреля 2020

Вы можете использовать закрытие. Здесь я добавил onTextChanged: (String) -> Void в панель поиска.

Просмотр фильма

import SwiftUI

struct MovieSearchView: View {
    @ObservedObject var model = MovieListViewModel()
    @State private var searchText: String = ""

    var body: some View {
        NavigationView {
            VStack {
                SearchBar(text: $searchText, onTextChanged: searchMovies)

                List {
                    ForEach(model.searchResults.filter {
                        self.searchText.isEmpty ? true : $0.title.lowercased().contains(self.searchText.lowercased())
                    }) { movie in
                        Text(movie.title)
                    }
                }
            }
        }
    }

    func searchMovies(for searchText: String) {
        if !searchText.isEmpty {
            model.getMovieSearchResults(for: searchText)
        }
    }
}

struct MovieSearchView_Previews: PreviewProvider {
    static var previews: some View {
        MovieSearchView()
    }
}

Панель поиска

import SwiftUI

struct SearchBar: UIViewRepresentable {
    @Binding var text: String
    var onTextChanged: (String) -> Void

    class Coordinator: NSObject, UISearchBarDelegate {
        var onTextChanged: (String) -> Void
        @Binding var text: String

        init(text: Binding<String>, onTextChanged: @escaping (String) -> Void) {
            _text = text
            self.onTextChanged = onTextChanged
        }

        func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
            text = searchText
            onTextChanged(text)
        }
    }

    func makeCoordinator() -> SearchBar.Coordinator {
        return Coordinator(text: $text, onTextChanged: onTextChanged)
    }

    func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar {
        let searchBar = UISearchBar(frame: .zero)
        searchBar.delegate = context.coordinator
        searchBar.searchBarStyle = .minimal
        searchBar.autocapitalizationType = .none
        return searchBar
    }

    func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<SearchBar>) {
        uiView.text = text
    }
}
0 голосов
/ 17 апреля 2020

вам нужно использовать метод searchBarDelegate, как показано ниже:

func searchBar(UISearchBar, shouldChangeTextIn: NSRange, replacementText: String) -> Bool

При каждом наборе вы получаете обратный вызов от пользователя, и внутри этой функции вы можете запустить API-вызов к серверу.

...