Вызов метода 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
    }
}
Другие вопросы по тегам