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)