I’m attempting to have the app get the js file and load the list in order of ID’s first.
Videolist+ViewModel.swift
import Foundation import Combine import SwiftUI extension VideoList { class ViewModel: ObservableObject { @Published private(set) var videos = [VideoUseCase]() private var dataTask: Cancellable? let navigationBarTitle = "Videos" private let apiService: APIServiceProtocol private let coreDataService: CoreDataServiceProtocol init(apiService: APIServiceProtocol = Dependencies.shared.apiService, coreDataService: CoreDataServiceProtocol = Dependencies.shared.coreDataService) { self.apiService = apiService self.coreDataService = coreDataService } func fetchVideos() { dataTask = apiService.fetchVideos() .tryMap { videos -> [VideoUseCase] in let videoItems = try self.coreDataService.fetchVideoItems() for video in videos { if let videoItem = videoItems.first(where: { $0.id == video.id }) { videoItem.update(video) } else { self.coreDataService.setItem(video) } } try self.coreDataService.saveContext() return try self.coreDataService.fetchVideoItems().map { videoItem in VideoUseCase(id: videoItem.id, name: videoItem.name, thumbnailURL: videoItem.thumbnailURL, description: videoItem.itemDescription, videoURL: videoItem.videoURL) } } .replaceError(with: []) .receive(on: DispatchQueue.main) .assign(to: .videos, on: self) } } }
VideoDetails+VideoModel.swift
import AVFoundation import Combine extension VideoDetails { class ViewModel: NSObject, ObservableObject, AVAssetDownloadDelegate { @Published private(set) var downloadProgress: CGFloat = 0 @Published private(set) var isOpaqueBarButton: Bool = true @Published private(set) var isDownloading: Bool = false @Published private(set) var avAsset: AVURLAsset? = nil private var cancellable: Cancellable? let video: VideoUseCase let asset: Asset let hlsService: HLSServiceProtocol init(video: VideoUseCase, hlsService: HLSServiceProtocol = Dependencies.shared.hlsService) { self.video = video self.asset = Asset(video) self.hlsService = hlsService super.init() let notificationCenter = NotificationCenter.default notificationCenter.addObserver(self, selector: #selector(handleAssetDownloadStateChanged(_:)), name: .AssetDownloadStateChanged, object: nil) notificationCenter.addObserver(self, selector: #selector(handleAssetDownloadProgress(_:)), name: .AssetDownloadProgress, object: nil) avAsset = hlsService.localAssetForStream(withId: asset.id) cancellable = $avAsset.map({ $0 == nil }) .receive(on: DispatchQueue.main) .assign(to: .isOpaqueBarButton, on: self) } @objc private func handleAssetDownloadStateChanged(_ notification: Notification) { guard let assetStreamId = notification.userInfo?[Asset.Keys.id] as? String, let downloadStateRawValue = notification.userInfo?[Asset.Keys.downloadState] as? String, let downloadState = HLSService.DownloadState(rawValue: downloadStateRawValue), assetStreamId == asset.id else { return } isDownloading = downloadState == .inProgress guard case .completed = downloadState else { return } avAsset = hlsService.localAssetForStream(withId: asset.id) } @objc private func handleAssetDownloadProgress(_ notification: Notification) { guard let assetStreamId = notification.userInfo?[Asset.Keys.id] as? String, assetStreamId == asset.id else { return } guard let progress = notification.userInfo?[Asset.Keys.percentDownloaded] as? Double else { return } self.downloadProgress = CGFloat(progress) } func toggleVideoDownload() { if isDownloading { hlsService.cancelDownload(for: asset) downloadProgress = 0 } else { hlsService.downloadStream(for: asset) } } } }
VideoUseCase.swift
import Foundation struct VideoUseCase { let id: Int let name: String let thumbnailURL: URL let description: String let videoURL: URL } #if DEBUG extension VideoUseCase { static var previewValue: VideoUseCase { VideoUseCase( id: 29, name: "How To Hold Your iPhone When Taking Photos", thumbnailURL: URL(string: "https://i.picsum.photos/id/29/2000/2000.jpg")!, description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", videoURL: URL(string: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8")! ) } } #endif
Response.swift
import Foundation struct Response: Decodable { let videos: [Video] struct Video: Identifiable, Equatable { let id: Int let name: String let thumbnailURL: URL let description: String let videoURL: URL } } extension Response.Video: Decodable { private enum CodingKeys: String, CodingKey { case id case name case thumbnail case description case video = "video_link" } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(Int.self, forKey: .id) name = try container.decode(String.self, forKey: .name) let thumbnail = try container.decode(String.self, forKey: .thumbnail) guard let thumbnailURL = URL(string: thumbnail) else { throw DecodingError.dataCorruptedError(forKey: .thumbnail, in: container, debugDescription: ""(thumbnail)" is not a valid URL.") } self.thumbnailURL = thumbnailURL description = try container.decode(String.self, forKey: .description) let video = try container.decode(String.self, forKey: .video) guard let videoURL = URL(string: video) else { throw DecodingError.dataCorruptedError(forKey: .video, in: container, debugDescription: ""(thumbnail)" is not a valid URL.") } self.videoURL = videoURL } } #if DEBUG extension Response.Video { static var previewValue: Response.Video { Response.Video( id: 29, name: "How To Hold Your iPhone When Taking Photos", thumbnailURL: URL(string: "https://i.picsum.photos/id/29/2000/2000.jpg")!, description: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", videoURL: URL(string: "https://bitdash-a.akamaihd.net/content/sintel/hls/playlist.m3u8")! ) } } #endif
what I do not understand is why it randomizes the ID’s it continues to generate random sets of videos in different orders. I’ve attempted everything from videos.sorted to recoding from scratch.
Advertisement
Answer
adding this line to VideoList.swift fixed the sorting
var body: some View { NavigationView { List(viewModel.videos.sorted { $0.id > $1.id}, id: .id)