added working view
This commit is contained in:
parent
a14d16b91d
commit
99b8814974
|
@ -7,6 +7,8 @@
|
||||||
objects = {
|
objects = {
|
||||||
|
|
||||||
/* Begin PBXBuildFile section */
|
/* Begin PBXBuildFile section */
|
||||||
|
B408F0D7251F6B760043E3A4 /* Tools.swift in Sources */ = {isa = PBXBuildFile; fileRef = B408F0D6251F6B760043E3A4 /* Tools.swift */; };
|
||||||
|
B408F0DD251F6D180043E3A4 /* AsyncImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = B408F0DC251F6D180043E3A4 /* AsyncImage.swift */; };
|
||||||
B4F0CC7B251BE62B00E9EA74 /* vrtnuApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4F0CC7A251BE62B00E9EA74 /* vrtnuApp.swift */; };
|
B4F0CC7B251BE62B00E9EA74 /* vrtnuApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4F0CC7A251BE62B00E9EA74 /* vrtnuApp.swift */; };
|
||||||
B4F0CC7D251BE62B00E9EA74 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4F0CC7C251BE62B00E9EA74 /* ContentView.swift */; };
|
B4F0CC7D251BE62B00E9EA74 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B4F0CC7C251BE62B00E9EA74 /* ContentView.swift */; };
|
||||||
B4F0CC7F251BE62F00E9EA74 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B4F0CC7E251BE62F00E9EA74 /* Assets.xcassets */; };
|
B4F0CC7F251BE62F00E9EA74 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B4F0CC7E251BE62F00E9EA74 /* Assets.xcassets */; };
|
||||||
|
@ -35,6 +37,8 @@
|
||||||
/* End PBXContainerItemProxy section */
|
/* End PBXContainerItemProxy section */
|
||||||
|
|
||||||
/* Begin PBXFileReference section */
|
/* Begin PBXFileReference section */
|
||||||
|
B408F0D6251F6B760043E3A4 /* Tools.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tools.swift; sourceTree = "<group>"; };
|
||||||
|
B408F0DC251F6D180043E3A4 /* AsyncImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AsyncImage.swift; sourceTree = "<group>"; };
|
||||||
B4F0CC77251BE62B00E9EA74 /* vrtnu.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = vrtnu.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
B4F0CC77251BE62B00E9EA74 /* vrtnu.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = vrtnu.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
B4F0CC7A251BE62B00E9EA74 /* vrtnuApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = vrtnuApp.swift; sourceTree = "<group>"; };
|
B4F0CC7A251BE62B00E9EA74 /* vrtnuApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = vrtnuApp.swift; sourceTree = "<group>"; };
|
||||||
B4F0CC7C251BE62B00E9EA74 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
B4F0CC7C251BE62B00E9EA74 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
|
||||||
|
@ -76,6 +80,14 @@
|
||||||
/* End PBXFrameworksBuildPhase section */
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
/* Begin PBXGroup section */
|
/* Begin PBXGroup section */
|
||||||
|
B408F0DB251F6D030043E3A4 /* AscyncImage */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
B408F0DC251F6D180043E3A4 /* AsyncImage.swift */,
|
||||||
|
);
|
||||||
|
path = AscyncImage;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
B4F0CC6E251BE62B00E9EA74 = {
|
B4F0CC6E251BE62B00E9EA74 = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
@ -99,6 +111,7 @@
|
||||||
B4F0CC79251BE62B00E9EA74 /* vrtnu */ = {
|
B4F0CC79251BE62B00E9EA74 /* vrtnu */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
B408F0DB251F6D030043E3A4 /* AscyncImage */,
|
||||||
B4F0CC7A251BE62B00E9EA74 /* vrtnuApp.swift */,
|
B4F0CC7A251BE62B00E9EA74 /* vrtnuApp.swift */,
|
||||||
B4F0CC7C251BE62B00E9EA74 /* ContentView.swift */,
|
B4F0CC7C251BE62B00E9EA74 /* ContentView.swift */,
|
||||||
B4F0CC7E251BE62F00E9EA74 /* Assets.xcassets */,
|
B4F0CC7E251BE62F00E9EA74 /* Assets.xcassets */,
|
||||||
|
@ -106,6 +119,7 @@
|
||||||
B4F0CC80251BE62F00E9EA74 /* Preview Content */,
|
B4F0CC80251BE62F00E9EA74 /* Preview Content */,
|
||||||
B4F0CCA9251BFD6F00E9EA74 /* video.swift */,
|
B4F0CCA9251BFD6F00E9EA74 /* video.swift */,
|
||||||
B4F0CCB6251D6B5400E9EA74 /* VrtNuLayout.swift */,
|
B4F0CCB6251D6B5400E9EA74 /* VrtNuLayout.swift */,
|
||||||
|
B408F0D6251F6B760043E3A4 /* Tools.swift */,
|
||||||
);
|
);
|
||||||
path = vrtnu;
|
path = vrtnu;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
|
@ -269,6 +283,8 @@
|
||||||
B4F0CCB7251D6B5400E9EA74 /* VrtNuLayout.swift in Sources */,
|
B4F0CCB7251D6B5400E9EA74 /* VrtNuLayout.swift in Sources */,
|
||||||
B4F0CC7B251BE62B00E9EA74 /* vrtnuApp.swift in Sources */,
|
B4F0CC7B251BE62B00E9EA74 /* vrtnuApp.swift in Sources */,
|
||||||
B4F0CCAA251BFD6F00E9EA74 /* video.swift in Sources */,
|
B4F0CCAA251BFD6F00E9EA74 /* video.swift in Sources */,
|
||||||
|
B408F0DD251F6D180043E3A4 /* AsyncImage.swift in Sources */,
|
||||||
|
B408F0D7251F6B760043E3A4 /* Tools.swift in Sources */,
|
||||||
);
|
);
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
//
|
||||||
|
// AsyncImage.swift
|
||||||
|
// AsyncImage
|
||||||
|
//
|
||||||
|
// Created by Vadym Bulavin on 2/13/20.
|
||||||
|
// Copyright © 2020 Vadym Bulavin. All rights reserved.
|
||||||
|
//
|
||||||
|
import SwiftUI
|
||||||
|
import UIKit
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
protocol ImageCache {
|
||||||
|
subscript(_ url: URL) -> UIImage? { get set }
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TemporaryImageCache: ImageCache {
|
||||||
|
private let cache = NSCache<NSURL, UIImage>()
|
||||||
|
|
||||||
|
subscript(_ key: URL) -> UIImage? {
|
||||||
|
get { cache.object(forKey: key as NSURL) }
|
||||||
|
set { newValue == nil ? cache.removeObject(forKey: key as NSURL) : cache.setObject(newValue!, forKey: key as NSURL) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ImageCacheKey: EnvironmentKey {
|
||||||
|
static let defaultValue: ImageCache = TemporaryImageCache()
|
||||||
|
}
|
||||||
|
|
||||||
|
extension EnvironmentValues {
|
||||||
|
var imageCache: ImageCache {
|
||||||
|
get { self[ImageCacheKey.self] }
|
||||||
|
set { self[ImageCacheKey.self] = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct AsyncImage<Placeholder: View>: View {
|
||||||
|
@StateObject private var loader: ImageLoader
|
||||||
|
private let placeholder: Placeholder
|
||||||
|
private let image: (UIImage) -> Image
|
||||||
|
|
||||||
|
init(
|
||||||
|
url: URL,
|
||||||
|
@ViewBuilder placeholder: () -> Placeholder,
|
||||||
|
@ViewBuilder image: @escaping (UIImage) -> Image = Image.init(uiImage:)
|
||||||
|
) {
|
||||||
|
self.placeholder = placeholder()
|
||||||
|
self.image = image
|
||||||
|
_loader = StateObject(wrappedValue: ImageLoader(url: url, cache: Environment(\.imageCache).wrappedValue))
|
||||||
|
}
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
content
|
||||||
|
.onAppear(perform: loader.load)
|
||||||
|
}
|
||||||
|
|
||||||
|
private var content: some View {
|
||||||
|
Group {
|
||||||
|
if loader.image != nil {
|
||||||
|
image(loader.image!)
|
||||||
|
} else {
|
||||||
|
placeholder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ImageLoader: ObservableObject {
|
||||||
|
@Published var image: UIImage?
|
||||||
|
|
||||||
|
private(set) var isLoading = false
|
||||||
|
|
||||||
|
private let url: URL
|
||||||
|
private var cache: ImageCache?
|
||||||
|
private var cancellable: AnyCancellable?
|
||||||
|
|
||||||
|
private static let imageProcessingQueue = DispatchQueue(label: "image-processing")
|
||||||
|
|
||||||
|
init(url: URL, cache: ImageCache? = nil) {
|
||||||
|
self.url = url
|
||||||
|
self.cache = cache
|
||||||
|
}
|
||||||
|
|
||||||
|
deinit {
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
func load() {
|
||||||
|
guard !isLoading else { return }
|
||||||
|
|
||||||
|
if let image = cache?[url] {
|
||||||
|
self.image = image
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
cancellable = URLSession.shared.dataTaskPublisher(for: url)
|
||||||
|
.map { UIImage(data: $0.data) }
|
||||||
|
.replaceError(with: nil)
|
||||||
|
.handleEvents(receiveSubscription: { [weak self] _ in self?.onStart() },
|
||||||
|
receiveOutput: { [weak self] in self?.cache($0) },
|
||||||
|
receiveCompletion: { [weak self] _ in self?.onFinish() },
|
||||||
|
receiveCancel: { [weak self] in self?.onFinish() })
|
||||||
|
.subscribe(on: Self.imageProcessingQueue)
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] in self?.image = $0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
func cancel() {
|
||||||
|
cancellable?.cancel()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func onStart() {
|
||||||
|
isLoading = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private func onFinish() {
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
|
|
||||||
|
private func cache(_ image: UIImage?) {
|
||||||
|
image.map { cache?[url] = $0 }
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,20 +8,29 @@
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
import AVKit
|
import AVKit
|
||||||
import AVFoundation
|
import AVFoundation
|
||||||
|
import Combine
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
struct ContentView: View {
|
struct ContentView: View {
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView{
|
NavigationView{
|
||||||
List {
|
VStack {
|
||||||
ForEach(VRTNu().shows, id: \.self){ show in
|
ForEach(VRTNu().shows, id: \.self){ show in
|
||||||
Section(header: Text(show.title)){
|
VStack{
|
||||||
|
Text(show.title)
|
||||||
HStack{
|
HStack{
|
||||||
ForEach(show.seasons, id:\.self){ season in
|
ForEach(show.seasons, id:\.self){ season in
|
||||||
HStack{
|
ForEach(season.episodes, id:\.self){ episode in
|
||||||
ForEach(season.episodes, id:\.self){ episode in
|
NavigationLink(destination: VideoView(url: episode.video.hlsUrl)){VStack{
|
||||||
Text(season.seasonName)
|
Text(season.seasonName)
|
||||||
Text(episode.name)
|
Text(episode.name)
|
||||||
}
|
AsyncImage(
|
||||||
|
url: episode.imageurl,
|
||||||
|
placeholder: { Text("Loading ...") },
|
||||||
|
image: { Image(uiImage: $0).resizable() }
|
||||||
|
)
|
||||||
|
}}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,16 +43,19 @@ struct ContentView: View {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*struct SeasonView: View{
|
/*struct SeasonView: View{
|
||||||
var body: some View{
|
var body: some View{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
URL(string: "https://remix-cf.lwc.vrtcdn.be/remix/ecd69313-4a39-4297-95b1-aede167725b7/remix.ism/.m3u8")!
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
struct VideoView: View {
|
struct VideoView: View {
|
||||||
private let player = AVPlayer(url: URL(string: "https://remix-cf.lwc.vrtcdn.be/remix/ecd69313-4a39-4297-95b1-aede167725b7/remix.ism/.m3u8")!);
|
var url: URL
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
let player = AVPlayer(url: url);
|
||||||
VideoPlayer(player: player).fixedSize()
|
VideoPlayer(player: player).fixedSize()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
//
|
||||||
|
// Tools.swift
|
||||||
|
// vrtnu
|
||||||
|
//
|
||||||
|
// Created by Jens Timmerman on 26/09/2020.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
|
|
||||||
struct Episode: Hashable{
|
struct Episode: Hashable{
|
||||||
let name: String
|
let name: String
|
||||||
let video: Video
|
let video: Video
|
||||||
|
@ -76,6 +77,9 @@ struct VRTNu {
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
//TODO: fetch shows
|
//TODO: fetch shows
|
||||||
self.shows = [Show(showUrl: URL(string: "https://www.vrt.be/vrtnu/a-z/het-peulengaleis/")!, title: "Het Peulengaleis")]
|
self.shows = [
|
||||||
|
Show(showUrl: URL(string: "https://www.vrt.be/vrtnu/a-z/het-peulengaleis/")!, title: "Het Peulengaleis"),
|
||||||
|
Show(showUrl: URL(string: "https://www.vrt.be/vrtnu/a-z/het-peulengaleis2/")!, title: "Het Peulengaleis 2"),
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue