从 API 获取项目后,为数组中的每个项目调用 API

问题描述 投票:0回答:1

我现在所拥有的是:页面加载,API 被调用并返回一个项目列表。当 API 返回时,我想为每个单独的项目调用 API 以请求附加数据。我被困在最后一部分了。

这是我到目前为止所拥有的。

struct MainView: View {
    @Environment(\.dismiss) var dismiss
    
    let source: String
    let sourceId: String
    
    @EnvironmentObject var startupViewModel: StartupViewModel
    
    @StateObject var vm = PlaceViewModel(placeService: PlaceService())
    
    var body: some View {
        NavigationView {
            ScrollView(showsIndicators: false) {
                VStack(spacing: 0) {
                    VStack(alignment: .leading, spacing: 0) {
                        ScrollView(.horizontal, showsIndicators: false) {
                            LazyHStack {
                                ForEach(Array((vm.attractions).enumerated()), id: \.element.id) { index, place in
                                    PlaceCardView(place: place)
                            }
                        }
                    }
                }
            }
            .task {
                vm.fetchPlace(source: source, sourceId: sourceId)
            }
            .onChange(of: vm.place != nil) {
                if let placeId = vm.place?.id {
                    vm.fetchAttractions(placeId: placeId, page: 1)
                }
            }
        }
        .environmentObject(vm)
    }
}

如您所见,当页面加载时,我首先调用

fetchPlace()
。然后,当
vm.place
发生变化时,我调用
vm.fetchAttractions()

以下是在我的视图模型中进行这些调用的方式:

class PlaceViewModel: ObservableObject {
    private var cancellables = Set<AnyCancellable>()
    let placeService: PlaceServiceProtocol
    
    @Published var place: Place?
    @Published var attractions: [Place] = []
    
    init(placeService: PlaceServiceProtocol) {
        self.placeService = placeService
    }
    
    func fetchPlace(source: String, sourceId: String) {
        placeService.getPlace(source: source, sourceId: sourceId)
            .receive(on: RunLoop.main)
            .sink(receiveCompletion: { data in
                print("Received:")
                print(data)
        }, receiveValue: {[weak self] data in
            self?.place = data.data?.place
        }).store(in: &cancellables)
        print(cancellables)
    }
    
    func fetchAttractions(placeId: Int, page: Int, completion: ((_ response: [Place]) -> Void)? = nil) {
        placeService.getAttractions(placeId: placeId, page: page)
            .receive(on: RunLoop.main)
            .sink(receiveCompletion: { data in
                print("Received \(data)")
        }, receiveValue: {[weak self] data in
            guard let newAttractions = data.data?.attractions else {
                return
            }
            
            if (newAttractions.isEmpty) {
                self?.hasMoreAttractions = false
            }
            
            self?.attractions.append(contentsOf: newAttractions)
            
            completion?(newAttractions)
        }).store(in: &cancellables)
        print(cancellables)
    }
}

这是我的网络层(来自本教程):

protocol PlaceServiceProtocol {
    func getPlace(source: String, sourceId: String) -> AnyPublisher<Response, Error>
    func getAttractions(placeId: Int, page: Int) -> AnyPublisher<Response, Error>
}

class PlaceService: PlaceServiceProtocol {
    let apiClient = URLSessionAPIClient<PlaceEndpoint>()
    
    func getPlace(source: String, sourceId: String) -> AnyPublisher<Response, Error> {
        return apiClient.request(.getPlace(source: source, sourceId: sourceId))
    }
    
    func getAttractions(placeId: Int, page: Int) -> AnyPublisher<Response, Error> {
        return apiClient.request(.getAttractions(placeId: placeId, page: page))
    }
}
enum PlaceEndpoint: APIEndpoint {
    case getPlace(source: String, sourceId: String)
    case getAttractions(placeId: Int, page: Int)
    
    var path: String {
        switch self {
        case .getPlace:
            return "/place/get"
        case .getAttractions:
            return "/place/attractions/get"
        }
    }
    
    var method: HTTPMethod {
        switch self {
        case .getPlace:
            return .post
        case .getAttractions:
            return .post
        }
    }
    
    var headers: [String: String]? {
        switch self {
        case .getPlace:
            return nil
        case .getAttractions:
            return nil
        }
    }
    
    var parameters: [String: Any]? {
        switch self {
        case .getPlace(let source, let sourceId):
            return ["source": source, "source_id": sourceId]
        case .getAttractions(let placeId, let page):
            return ["place_id": placeId, "page": page]
        }
    }
}
protocol APIClient {
    associatedtype EndpointType: APIEndpoint
    func request<T: Decodable>(_ endpoint: EndpointType) -> AnyPublisher<T, Error>
}

class URLSessionAPIClient<EndpointType: APIEndpoint>: APIClient {
    func request<T: Decodable>(_ endpoint: EndpointType) -> AnyPublisher<T, Error> {
        let url = URL(string: BuildConfiguration.shared.baseURL)!.appendingPathComponent(endpoint.path)
        var request = URLRequest(url: url)
        
        let keychain = KeychainSwift()
        
        request.httpMethod = endpoint.method.rawValue
        
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        request.addValue("application/json", forHTTPHeaderField: "Accept")
        
        // Authorization
        request.addValue("Bearer " + (keychain.get("token") ?? ""), forHTTPHeaderField: "Authorization")
        
        do {
            // convert parameters to Data and assign dictionary to httpBody of request
            request.httpBody = try JSONSerialization.data(withJSONObject: endpoint.parameters ?? [], options: .prettyPrinted)
        } catch let error {
            print(error.localizedDescription)
        }
        
        endpoint.headers?.forEach { request.addValue($0.value, forHTTPHeaderField: $0.key) }
        
        return URLSession.shared.dataTaskPublisher(for: request)
            .subscribe(on: DispatchQueue.global(qos: .background))
            .tryMap { data, response -> JSONDecoder.Input in
                print(response)
                guard let httpResponse = response as? HTTPURLResponse,
                      (200...299).contains(httpResponse.statusCode) else {
                    throw APIError.invalidResponse
                }
                return data
            }
            .decode(type: T.self, decoder: JSONDecoder())
            .eraseToAnyPublisher()
    }
}

调用

fetchAttractions()
后,我想再次调用 API 以获取列表中每个景点的附加数据。

最好的方法是什么?我尝试过循环浏览

fetchAttractions()
内的每个景点,但是在滚动时遇到了滞后。

ios swift xcode swiftui
1个回答
0
投票

尝试这样的事情:

.task(id: place.id) {
    details = await controller.fetchDetails(for: place)
}
© www.soinside.com 2019 - 2024. All rights reserved.