Вызов метода Debounce из BindableObject в SwiftUI
Я новичок в Swift и тем более в SwiftUI. Я начал создавать небольшой базовый проект. Я использую Github API для получения списка репозиториев.
Поэтому я создал "панель поиска", так как SwiftUI не имеет компонента SearchBar. Я хотел бы выполнять операцию выборки каждый раз, когда изменяется содержимое моего текстового поля.
Я не хочу, чтобы метод fetch вызывался слишком часто. Я решил отрицать это. Я столкнулся с проблемой, я не могу найти / понять пример.
Я пытался реализовать решение для устранения ошибок, но оно не работает, мое приложение просто зависало.
Вот мой BindableObject
import SwiftUI
import Combine
class ReposStore: BindableObject {
private var service: GithubService
let didChange = PassthroughSubject<Void, Never>()
@Published var searchText: String = ""
var repos: [Repository] = [] {
didSet {
didChange.send()
}
}
var error: String = "" {
didSet {
didChange.send()
}
}
var test: String = "" {
didSet {
didChange.send()
}
}
private var cancellable: AnyCancellable? = nil
init(service: GithubService) {
self.service = service
cancellable = AnyCancellable($searchText
.removeDuplicates()
.debounce(for: 2, scheduler: DispatchQueue.main)
.flatMap { self.fetch(matching: $0) }
.assign(to: \.test, on: self)
)
}
func fetch(matching query: String = "") {
print("### QUERY \(query)")
self.service.getUserRepositories(matching: query) { [weak self] result in
DispatchQueue.main.async {
print("### RESULT HERE \(result)")
switch result {
case .success(let repos): self?.repos = repos
case .failure(let error): self?.error = error.localizedDescription
}
}
}
}
}
И это мой взгляд
import SwiftUI
struct RepositoryList : View {
@EnvironmentObject var repoStore: ReposStore
@State private var userName: String = ""
var body: some View {
VStack {
NavigationView {
VStack(spacing: 0) {
HStack {
Image(systemName: "magnifyingglass").background(Color.blue).padding(.leading, 10.0)
TextField($repoStore.repoUser, placeholder: Text("Search")).background(Color.red)
.padding(.vertical, 4.0)
.padding(.trailing, 10.0)
}
.border(Color.secondary, width: 1, cornerRadius: 5)
.padding()
List {
ForEach(self.repoStore.repos) { repository in
NavigationLink(
destination: RepositoryDetail(repository: repository).environmentObject(self.repoStore)
) {
RepositoryRow(repository: repository)
}
}
}.navigationBarTitle(Text("Repositories"))
}
}
}
}
Я пытался использовать таймер, расписание и действие каждые 8 секунд, но этот метод приводил к сбою приложения.
Более того, я не знаю, является ли хорошей практикой объявление функции с аннотацией "@objc"...
Может ли кто-нибудь помочь мне реализовать правильный способ отменить метод внутри BindableObject?
Заранее спасибо:)
1 ответ
Мне наконец удалось настроить дебат.
Если это может кому-то помочь, вот моя реализация:
import SwiftUI
import Combine
class Store : ObservableObject {
private var cancellable: AnyCancellable? = nil
@Published var searchText: String= ""
@Published var user: User? = nil
init() {
cancellable = AnyCancellable(
$searchText.removeDuplicates()
.debounce(for: 0.8, scheduler: DispatchQueue.main)
.sink { searchText in
self.searchUser()
})
}
func searchUser() {
var urlComponents = URLComponents(string: "https://api.github.com/users/\(searchText)")!
urlComponents.queryItems = [
URLQueryItem(name: "access_token", value: EnvironmentConfiguration.shared.github_token)
]
var request = URLRequest(url: urlComponents.url!)
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
searchCancellable = URLSession.shared.send(request: request)
.decode(type: User.self, decoder: JSONDecoder())
.map { $0 }
.replaceError(with: nil)
.receive(on: DispatchQueue.main)
.assign(to: \.user, on: self)
}
}
extension URLSession {
func send(request: URLRequest) -> AnyPublisher<Data, URLSessionError> {
dataTaskPublisher(for: request)
.mapError { URLSessionError.urlError($0) }
.flatMap { data, response -> AnyPublisher<Data, URLSessionError> in
guard let response = response as? HTTPURLResponse else {
return .fail(.invalidResponse)
}
guard 200..<300 ~= response.statusCode else {
return .fail(.serverErrorMessage(statusCode: response.statusCode,
data: data))
}
return .just(data)
}.eraseToAnyPublisher()
}
enum URLSessionError: Error {
case invalidResponse
case serverErrorMessage(statusCode: Int, data: Data)
case urlError(URLError)
}
}
extension Publisher {
static func empty() -> AnyPublisher<Output, Failure> {
return Empty()
.eraseToAnyPublisher()
}
static func just(_ output: Output) -> AnyPublisher<Output, Failure> {
return Just(output)
.catch { _ in AnyPublisher<Output, Failure>.empty() }
.eraseToAnyPublisher()
}
static func fail(_ error: Failure) -> AnyPublisher<Output, Failure> {
return Fail(error: error)
.eraseToAnyPublisher()
}
}
struct User: Hashable, Identifiable, Decodable {
var id: Int
var login: String
var avatar_url: URL
var name: String?
enum CodingKeys: String, CodingKey {
case id, login, avatar_url, name
}
}