Список SwiftUI, содержащий AsyncImages, обновляется только при прокрутке вне поля зрения

У меня есть список объектов Pokemon, отображаемый в PokemonCells. Когда эти PokemonCell создаются, он проверяет, есть ли у них объект PokemonDetail. PokemonDetail — это переменная @State. Если он равен нулю, он вызывает ProgressView и вызов для получения сведений о покемонах. Он сохраняет результаты этой выборки в PokemonDetail @State var.

Как только эта переменная @State назначается, она инициирует вызов для извлечения AsyncImage, показывая заполнитель, пока он работает.

      struct PokemonCell: View {

  let pokemon: Pokemon

  //This is nil when the view is first instantiated.
  @State var pokemonDetail: Result<PokemonDetail, PokemonAPI.RequestError>?

  var body: some View {
    HStack {
      //If pokemonDetail not nill, enter switch
      if let pokemonDetail = pokemonDetail {
        //Switch on the pokemondetail/pokemonapi.requesterror object.
        switch pokemonDetail {
        //If result from API is .success, get asyncimage
        case .success(let newPoke):
          //Create the asyncImage using the URL contained in the pokemonDetail object.
          AsyncImage(
            url: URL(string: newPoke.spriteImageURL),
            transaction: .init(animation: .spring(response: 1.6))
          ) { phase in
            switch phase {
            case .empty:
              ProgressView()
                .progressViewStyle(.circular)
            case .success(let image):
              image
                .resizable()
                .aspectRatio(contentMode: .fill)
            case .failure:
              Text("Failed fetching image try again.")
                .foregroundColor(.red)
            @unknown default:
              Text("Unknown error. Please try again.")
                .foregroundColor(.red)
            }
          }
          .frame(width: 50, height: 50)

        case .failure(_):
          Text("ImgError")

        }
      }
      //If it IS null, trigger the progressview and fetch the details.
      else {
        ProgressView().onAppear(perform: fetchPokemonDetails)
      }

      VStack(alignment: .leading) {
        Text(pokemon.name).font(.title)

      }
    }
  }

  //Fetch details from API, store the Result<PokemonDetail, PokemonAPI.RequestError> object in the pokemonDetail @State var
  func fetchPokemonDetails() {
    self.pokemonDetail = nil
    PokemonAPI.shared.getPokemonDetails(name: pokemon.name) { result in self.pokemonDetail = result
    }
  }
}

Странно то, что это отлично работает для первого изображения, а для остальных изображений продолжает показывать изображение-заполнитель. Только когда изображение прокручивается за пределы экрана, оно запускает FetchPokemonDetails и правильно обновляет изображение.

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

      var body: some View {

    if let pokemonsState = pokemonsState {

      switch pokemonsState {

      case .success(let pokemons):

        NavigationView {
          List(pokemonsList) { pokemon in
            NavigationLink(destination: PokemonDetailView(pokemon: pokemon)) {
              PokemonCell(pokemon: pokemon).onAppear {
                if pokemon.name == pokemons.last?.name {
                  offset += 10

                  fetchPokemon()

                }
              }
            }

          }.navigationTitle("Pokemons")
        }

      default: Text("pholder")
      }

    } else {
      ProgressView().onAppear(perform: fetchPokemon)
    }

  }

Вот код API

           func getPokemonDetails(
        name: String, completion: @escaping (Result<PokemonDetail, RequestError>) -> Void
      ) {
          print("api called")
        let url = URL(string: "https://pokeapi.co/api/v2/pokemon/\(name)")!
        let urlRequest = URLRequest(url: url)
          print("url completed: \(urlRequest)")
        cancellable = URLSession.shared.dataTaskPublisher(for: urlRequest)
          .map({ $0.data })
          .decode(type: PokemonDetail.self, decoder: JSONDecoder())
          .receive(on: DispatchQueue.main)
          .sink(receiveCompletion: { result in
            switch result {
            case .finished:
              print("pokemondetails fetched: \(result) <-- this a result in finished case")
              break
            case .failure(let error):
              switch error {
              case let urlError as URLError:
                completion(.failure(.urlError(urlError)))
                  print("pokemondetails fetched: \(result) <-- this a result in error case")
                print("apierror")
              case let decodingError as DecodingError:
                completion(.failure(.decodingError(decodingError)))
                print("apierror")
                  print("pokemondetails fetched: \(result) <-- this a result in error case")
              default:
                completion(.failure(.genericError(error)))
                  print("pokemondetails fetched: \(result) <-- this a result in error case")
    
              }
            }
    
          }
    
          //) { (response) in print("apiclass result: \(completion(.success(response)))")}
          ) { (response) in print("apiclass result: \(completion(.success(response)))"); completion(.success(response))   }
    
      }

Я возился с этим весь вчерашний вечер. Я попытался заменить список на Scrollview + LazyVstack. Различные способы создания экземпляра AsyncImage.

Я думал, что это может быть из-за того, что API вызывает все изображения одновременно, а затем отказывается от сервера изображений. Но провалов я не вижу, и даже уменьшение количества покемонов в списке до 2-х или 3-х приводит к тому же. В самих запросах изображений тоже нет ничего плохого, потому что они нормально работают после прокрутки за пределы экрана.

Размещение оператора печати в функции FetchPokemonDetails и в API показывает, что API действительно вызывается для каждого покемона.getPokemonDetails вызывается, URL-адрес создается успешно, но затем он просто останавливается. Он не попадает ни в один из случаев .finished или .error, возвращает нулевой результат и не обращается к оператору печати, который печатает результат в конце.

      func fetchPokemonDetails() {
    self.pokemonDetail = nil
      print("fetching for \(pokemon.name)")
    PokemonAPI.shared.getPokemonDetails(name: pokemon.name) { result in pokemonDetail = result
    }
      print("fetched, now pokemonDetail = \(pokemonDetail)")
  }

В результате получается следующий вывод для всех покемонов (даже для первого успешного):

      fetching for ivysaur
api called
url completed: https://pokeapi.co/api/v2/pokemon/ivysaur
fetched, now pokemonDetail = nil
fetching for bulbasaur
api called
url completed: https://pokeapi.co/api/v2/pokemon/bulbasaur
fetched, now pokemonDetail = nil
apiclass result: ()
pokemondetails fetched: finished <-- this a result in finished case

Похоже, API getPokemonDetails просто прерывается следующим вызовом и совершенно забывает закончить то, что делает. Как это исправить?

0 ответов

Другие вопросы по тегам