Skip to content
Advertisement

Why does the decoder not sort by IDs

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) 
User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement