Вот что у меня есть на данный момент.
Код: Выделить всё
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)
}
}
Вот как эти вызовы выполняются в моей модели представления:< /p>
Код: Выделить всё
class PlaceViewModel: ObservableObject {
private var cancellables = Set()
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
func getAttractions(placeId: Int, page: Int) -> AnyPublisher
}
class PlaceService: PlaceServiceProtocol {
let apiClient = URLSessionAPIClient
()
func getPlace(source: String, sourceId: String) -> AnyPublisher {
return apiClient.request(.getPlace(source: source, sourceId: sourceId))
}
func getAttractions(placeId: Int, page: Int) -> AnyPublisher {
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(_ endpoint: EndpointType) -> AnyPublisher
}
class URLSessionAPIClient: APIClient {
func request(_ endpoint: EndpointType) -> AnyPublisher {
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(), однако при прокрутке возникала задержка.
Подробнее здесь: https://stackoverflow.com/questions/790 ... s-from-api
Мобильная версия