├── .DS_Store
├── .gitignore
├── MoviesAPP.xcodeproj
├── project.pbxproj
└── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── MoviesAPP.xcworkspace
├── contents.xcworkspacedata
└── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── MoviesAPP
├── Common
│ ├── AutoSizeTableView.swift
│ ├── Configurator
│ │ └── TableViewConfigurator.swift
│ ├── Constants
│ │ ├── Colors.swift
│ │ ├── Errors.swift
│ │ ├── Fonts.swift
│ │ └── Grid.swift
│ ├── Extensions
│ │ ├── NSAttributedStringExtensions.swift
│ │ ├── NumberExtensions.swift
│ │ ├── UINavigationController+Extensions.swift
│ │ └── UITableView+Extensions.swift
│ └── ViewCode.swift
├── Info.plist
├── Resources
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AccentColor.colorset
│ │ │ └── Contents.json
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ └── Images
│ │ │ ├── Contents.json
│ │ │ ├── avatar.imageset
│ │ │ ├── Contents.json
│ │ │ └── avatar.pdf
│ │ │ └── background.imageset
│ │ │ ├── Contents.json
│ │ │ └── background.pdf
│ ├── Base.lproj
│ │ └── LaunchScreen.storyboard
│ └── SceneDelegate.swift
├── Screens
│ ├── MovieCell.swift
│ └── MoviesViewController.swift
└── Service
│ ├── APIConstants.swift
│ ├── Models
│ ├── Episode.swift
│ ├── Image.swift
│ ├── Movie.swift
│ └── Responses.swift
│ ├── MoviesService.swift
│ └── Service.swift
├── Podfile
├── Podfile.lock
├── Pods
├── Kingfisher
│ ├── LICENSE
│ ├── README.md
│ └── Sources
│ │ ├── Cache
│ │ ├── CacheSerializer.swift
│ │ ├── DiskStorage.swift
│ │ ├── FormatIndicatedCacheSerializer.swift
│ │ ├── ImageCache.swift
│ │ ├── MemoryStorage.swift
│ │ └── Storage.swift
│ │ ├── Extensions
│ │ ├── CPListItem+Kingfisher.swift
│ │ ├── ImageView+Kingfisher.swift
│ │ ├── NSButton+Kingfisher.swift
│ │ ├── NSTextAttachment+Kingfisher.swift
│ │ ├── TVMonogramView+Kingfisher.swift
│ │ ├── UIButton+Kingfisher.swift
│ │ └── WKInterfaceImage+Kingfisher.swift
│ │ ├── General
│ │ ├── ImageSource
│ │ │ ├── AVAssetImageDataProvider.swift
│ │ │ ├── ImageDataProvider.swift
│ │ │ ├── Resource.swift
│ │ │ └── Source.swift
│ │ ├── KF.swift
│ │ ├── KFOptionsSetter.swift
│ │ ├── Kingfisher.swift
│ │ ├── KingfisherError.swift
│ │ ├── KingfisherManager.swift
│ │ └── KingfisherOptionsInfo.swift
│ │ ├── Image
│ │ ├── Filter.swift
│ │ ├── GIFAnimatedImage.swift
│ │ ├── GraphicsContext.swift
│ │ ├── Image.swift
│ │ ├── ImageDrawing.swift
│ │ ├── ImageFormat.swift
│ │ ├── ImageProcessor.swift
│ │ ├── ImageProgressive.swift
│ │ ├── ImageTransition.swift
│ │ └── Placeholder.swift
│ │ ├── Networking
│ │ ├── AuthenticationChallengeResponsable.swift
│ │ ├── ImageDataProcessor.swift
│ │ ├── ImageDownloader.swift
│ │ ├── ImageDownloaderDelegate.swift
│ │ ├── ImageModifier.swift
│ │ ├── ImagePrefetcher.swift
│ │ ├── RedirectHandler.swift
│ │ ├── RequestModifier.swift
│ │ ├── RetryStrategy.swift
│ │ ├── SessionDataTask.swift
│ │ └── SessionDelegate.swift
│ │ ├── SwiftUI
│ │ ├── ImageBinder.swift
│ │ ├── ImageContext.swift
│ │ ├── KFAnimatedImage.swift
│ │ ├── KFImage.swift
│ │ ├── KFImageOptions.swift
│ │ ├── KFImageProtocol.swift
│ │ └── KFImageRenderer.swift
│ │ ├── Utility
│ │ ├── Box.swift
│ │ ├── CallbackQueue.swift
│ │ ├── Delegate.swift
│ │ ├── ExtensionHelpers.swift
│ │ ├── Result.swift
│ │ ├── Runtime.swift
│ │ ├── SizeExtensions.swift
│ │ └── String+MD5.swift
│ │ └── Views
│ │ ├── AnimatedImageView.swift
│ │ └── Indicator.swift
├── Manifest.lock
├── Pods.xcodeproj
│ └── project.pbxproj
└── Target Support Files
│ ├── Kingfisher
│ ├── Kingfisher-Info.plist
│ ├── Kingfisher-dummy.m
│ ├── Kingfisher-prefix.pch
│ ├── Kingfisher-umbrella.h
│ ├── Kingfisher.debug.xcconfig
│ ├── Kingfisher.modulemap
│ └── Kingfisher.release.xcconfig
│ └── Pods-MoviesAPP
│ ├── Pods-MoviesAPP-Info.plist
│ ├── Pods-MoviesAPP-acknowledgements.markdown
│ ├── Pods-MoviesAPP-acknowledgements.plist
│ ├── Pods-MoviesAPP-dummy.m
│ ├── Pods-MoviesAPP-frameworks-Debug-input-files.xcfilelist
│ ├── Pods-MoviesAPP-frameworks-Debug-output-files.xcfilelist
│ ├── Pods-MoviesAPP-frameworks-Release-input-files.xcfilelist
│ ├── Pods-MoviesAPP-frameworks-Release-output-files.xcfilelist
│ ├── Pods-MoviesAPP-frameworks.sh
│ ├── Pods-MoviesAPP-umbrella.h
│ ├── Pods-MoviesAPP.debug.xcconfig
│ ├── Pods-MoviesAPP.modulemap
│ └── Pods-MoviesAPP.release.xcconfig
└── README.md
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Bullas/viewcode-live-coding-dio/357739f5886242b1d6e44095331da684e610b5ef/.DS_Store
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## User settings
6 | xcuserdata/
7 |
8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9)
9 | *.xcscmblueprint
10 | *.xccheckout
11 |
12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4)
13 | build/
14 | DerivedData/
15 | *.moved-aside
16 | *.pbxuser
17 | !default.pbxuser
18 | *.mode1v3
19 | !default.mode1v3
20 | *.mode2v3
21 | !default.mode2v3
22 | *.perspectivev3
23 | !default.perspectivev3
24 |
25 | ## Obj-C/Swift specific
26 | *.hmap
27 |
28 | ## App packaging
29 | *.ipa
30 | *.dSYM.zip
31 | *.dSYM
32 |
33 | ## Playgrounds
34 | timeline.xctimeline
35 | playground.xcworkspace
36 |
37 | # Swift Package Manager
38 | #
39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
40 | # Packages/
41 | # Package.pins
42 | # Package.resolved
43 | # *.xcodeproj
44 | #
45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata
46 | # hence it is not needed unless you have added a package configuration file to your project
47 | # .swiftpm
48 |
49 | .build/
50 |
51 | # CocoaPods
52 | #
53 | # We recommend against adding the Pods directory to your .gitignore. However
54 | # you should judge for yourself, the pros and cons are mentioned at:
55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
56 | #
57 | # Pods/
58 | #
59 | # Add this line if you want to avoid checking in source code from the Xcode workspace
60 | # *.xcworkspace
61 |
62 | # Carthage
63 | #
64 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
65 | # Carthage/Checkouts
66 |
67 | Carthage/Build/
68 |
69 | # Accio dependency management
70 | Dependencies/
71 | .accio/
72 |
73 | # fastlane
74 | #
75 | # It is recommended to not store the screenshots in the git repo.
76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed.
77 | # For more information about the recommended setup visit:
78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
79 |
80 | fastlane/report.xml
81 | fastlane/Preview.html
82 | fastlane/screenshots/**/*.png
83 | fastlane/test_output
84 |
85 | # Code Injection
86 | #
87 | # After new code Injection tools there's a generated folder /iOSInjectionProject
88 | # https://github.com/johnno1962/injectionforxcode
89 |
90 | iOSInjectionProject/
91 |
--------------------------------------------------------------------------------
/MoviesAPP.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/MoviesAPP.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/MoviesAPP.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/MoviesAPP.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/MoviesAPP/Common/AutoSizeTableView.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AutoSizeTableView.swift
3 | // MazeTv
4 | //
5 | // Created by Karolina Attekita on 12/02/22.
6 | //
7 |
8 | import UIKit
9 |
10 | final class AutoSizeTableView: UITableView {
11 |
12 | // MARK: - Functions
13 |
14 | override func reloadData() {
15 | super.reloadData()
16 | self.invalidateIntrinsicContentSize()
17 | self.layoutIfNeeded()
18 | }
19 |
20 | override var intrinsicContentSize: CGSize {
21 | return CGSize(width: contentSize.width, height: contentSize.height)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/MoviesAPP/Common/Configurator/TableViewConfigurator.swift:
--------------------------------------------------------------------------------
1 | //
2 | // TableViewConfigurator.swift
3 | // MazeTv
4 | //
5 | // Created by Karolina Attekita on 11/02/22.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 | import SwiftUI
11 |
12 | // MARK: Section
13 | struct TableViewSectionViewModel {
14 | let title: String?
15 | var rows: [TableViewCellViewModelProtocol]
16 | }
17 |
18 | // MARK: Cell View Model
19 | protocol TableViewCellViewModelProtocol {}
20 |
21 | // MARK: Cell View Model
22 | protocol TableViewCellProtocol where Self: UITableViewCell {
23 | func load(viewModel: TableViewCellViewModelProtocol)
24 | }
25 |
26 | // MARK: TableView Configurator
27 | protocol TableViewConfiguratorProtocol: NSObject {
28 | associatedtype Cell
29 | var source: [TableViewSectionViewModel]? { get }
30 | var target: UITableView? { get }
31 | var onSelected: ((IndexPath) -> Void)? { get set }
32 | func load(source: [TableViewSectionViewModel])
33 | }
34 |
35 | typealias TableViewConfiguratorType = NSObject & TableViewConfiguratorProtocol & UITableViewDataSource & UITableViewDelegate
36 |
37 | final class TableViewConfigurator: TableViewConfiguratorType {
38 |
39 | // MARK: Properties
40 |
41 | private(set) var source: [TableViewSectionViewModel]?
42 | private(set) weak var target: UITableView?
43 | var onSelected: ((IndexPath) -> Void)?
44 |
45 | // MARK: Init
46 |
47 | init(target: UITableView) {
48 | self.target = target
49 | super.init()
50 | self.target?.dataSource = self
51 | self.target?.delegate = self
52 | self.target?.registerCell(type: Cell.self)
53 | }
54 |
55 | func load(source: [TableViewSectionViewModel]) {
56 | self.source = source
57 | target?.reloadData()
58 | }
59 |
60 | // MARK: DataSource
61 |
62 | func numberOfSections(in tableView: UITableView) -> Int {
63 | source?.count ?? 0
64 | }
65 |
66 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
67 | source?[section].rows.count ?? 0
68 | }
69 |
70 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
71 | guard let cell = tableView.dequeueCell(withType: Cell.self) as? TableViewCellProtocol,
72 | let viewModel = source?[indexPath.section].rows[indexPath.row] else { return UITableViewCell() }
73 |
74 | cell.load(viewModel: viewModel)
75 | return cell
76 | }
77 |
78 | func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
79 | source?[section].title
80 | }
81 |
82 | // MARK: Delegate
83 |
84 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
85 | onSelected?(indexPath)
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/MoviesAPP/Common/Constants/Colors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Colors.swift
3 | // MazeTv
4 | //
5 | // Created by Karolina Attekita on 10/02/22.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | enum Colors: String {
12 |
13 | case secondary
14 | case primary
15 | case background
16 | case overlay
17 |
18 | var color: UIColor? {
19 | return UIColor(named: self.rawValue)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/MoviesAPP/Common/Constants/Errors.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Errors.swift
3 | // MazeTv
4 | //
5 | // Created by Karolina Attekita on 10/02/22.
6 | //
7 |
8 | import Foundation
9 |
10 | enum ServiceError: Error {
11 | case parseError
12 | case statusCode(Int)
13 | case badRequest
14 | }
15 |
--------------------------------------------------------------------------------
/MoviesAPP/Common/Constants/Fonts.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Fonts.swift
3 | // MazeTv
4 | //
5 | // Created by Karolina Attekita on 10/02/22.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | enum Fonts {
12 |
13 | case small(UIFont.Weight)
14 | case medium(UIFont.Weight)
15 | case large(UIFont.Weight)
16 |
17 | var font: UIFont {
18 | switch self {
19 | case .small(let weight):
20 | return UIFont.systemFont(ofSize: 12, weight: weight)
21 | case .medium(let weight):
22 | return UIFont.systemFont(ofSize: 18, weight: weight)
23 | case .large(let weight):
24 | return UIFont.systemFont(ofSize: 28, weight: weight)
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/MoviesAPP/Common/Constants/Grid.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Grid.swift
3 | // MazeTv
4 | //
5 | // Created by Karolina Attekita on 10/02/22.
6 | //
7 |
8 | import UIKit
9 |
10 | enum Grid {
11 | case small
12 | case medium
13 | case large
14 |
15 | var value: CGFloat {
16 | switch self {
17 | case .small:
18 | return 10
19 | case .medium:
20 | return 16
21 | case .large:
22 | return 32
23 | }
24 | }
25 |
26 | var insets: UIEdgeInsets {
27 | switch self {
28 | case .small:
29 | return UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
30 | case .medium:
31 | return UIEdgeInsets(top: 16, left: 16, bottom: 16, right: 15)
32 | case .large:
33 | return UIEdgeInsets(top: 32, left: 32, bottom: 32, right: 32)
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/MoviesAPP/Common/Extensions/NSAttributedStringExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NSAttributedStringExtensions.swift
3 | // MazeTv
4 | //
5 | // Created by Karolina Attekita on 12/02/22.
6 | //
7 |
8 | import Foundation
9 |
10 | extension NSAttributedString {
11 |
12 | static func fromHTML(html: String) -> NSAttributedString? {
13 | let style = ""
14 | let data = Data(html.appending(String(format: style)).utf8)
15 | let attributedString = try? NSAttributedString(data: data,
16 | options: [.documentType: NSAttributedString.DocumentType.html],
17 | documentAttributes: nil)
18 | return attributedString
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/MoviesAPP/Common/Extensions/NumberExtensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // NumberExtensions.swift
3 | // MazeTv
4 | //
5 | // Created by Karolina Attekita on 12/02/22.
6 | //
7 |
8 | import Foundation
9 |
10 | extension Int {
11 | func toString() -> String? {
12 | return "\(self)"
13 | }
14 | }
15 |
16 | extension Double {
17 | func toString() -> String? {
18 | return "\(self)"
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/MoviesAPP/Common/Extensions/UINavigationController+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UINavigationController+Extensions.swift
3 | // MazeTv
4 | //
5 | // Created by Karolina Attekita on 12/02/22.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 |
11 | extension UINavigationController {
12 | func setAppearance() {
13 | self.navigationBar.prefersLargeTitles = true
14 |
15 | let appearance = UINavigationBarAppearance()
16 | appearance.backgroundColor = Colors.primary.color
17 | appearance.titleTextAttributes = [.foregroundColor: Colors.background.color ?? .white]
18 | appearance.largeTitleTextAttributes = [.foregroundColor: Colors.background.color ?? .white]
19 |
20 | self.navigationBar.tintColor = .white
21 | self.navigationBar.standardAppearance = appearance
22 | self.navigationBar.compactAppearance = appearance
23 | self.navigationBar.scrollEdgeAppearance = appearance
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/MoviesAPP/Common/Extensions/UITableView+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // UITableView+Extensions.swift
3 | // MazeTv
4 | //
5 | // Created by Karolina Attekita on 11/02/22.
6 | //
7 |
8 | import UIKit
9 |
10 | public extension UITableView {
11 |
12 | func registerCell(type: UITableViewCell.Type) {
13 | register(type, forCellReuseIdentifier: type.identifier)
14 | }
15 |
16 | func dequeueCell(withType type: UITableViewCell.Type) -> T? {
17 | return dequeueReusableCell(withIdentifier: type.identifier) as? T
18 | }
19 |
20 | func dequeueCell(withType type: UITableViewCell.Type, for indexPath: IndexPath) -> T? {
21 | return dequeueReusableCell(withIdentifier: type.identifier, for: indexPath) as? T
22 | }
23 |
24 | }
25 |
26 | public extension UITableViewCell {
27 | static var identifier: String {
28 | return String(describing: self)
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/MoviesAPP/Common/ViewCode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewCode.swift
3 | // MazeTv
4 | //
5 | // Created by Karolina Attekita on 10/02/22.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol ViewCode {
11 |
12 | func buildHierarchy()
13 | func setupConstraints()
14 | func applyAdditionalChanges()
15 | }
16 |
17 | extension ViewCode {
18 |
19 | func setupView() {
20 | buildHierarchy()
21 | setupConstraints()
22 | applyAdditionalChanges()
23 | }
24 |
25 | func buildHierarchy() {}
26 |
27 | func setupConstraints() {}
28 |
29 | func applyAdditionalChanges() {}
30 | }
31 |
--------------------------------------------------------------------------------
/MoviesAPP/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | UIApplicationSceneManifest
6 |
7 | UIApplicationSupportsMultipleScenes
8 |
9 | UISceneConfigurations
10 |
11 | UIWindowSceneSessionRoleApplication
12 |
13 |
14 | UISceneConfigurationName
15 | Default Configuration
16 | UISceneDelegateClassName
17 | $(PRODUCT_MODULE_NAME).SceneDelegate
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/MoviesAPP/Resources/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // MoviesAPP
4 | //
5 | // Created by Karolina Attekita on 28/03/22.
6 | //
7 |
8 | import UIKit
9 |
10 | @main
11 | class AppDelegate: UIResponder, UIApplicationDelegate {
12 |
13 |
14 |
15 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
16 | // Override point for customization after application launch.
17 | return true
18 | }
19 |
20 | // MARK: UISceneSession Lifecycle
21 |
22 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
23 | // Called when a new scene session is being created.
24 | // Use this method to select a configuration to create the new scene with.
25 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
26 | }
27 |
28 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
29 | // Called when the user discards a scene session.
30 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
31 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
32 | }
33 |
34 |
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/MoviesAPP/Resources/Assets.xcassets/AccentColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "idiom" : "universal"
5 | }
6 | ],
7 | "info" : {
8 | "author" : "xcode",
9 | "version" : 1
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/MoviesAPP/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/MoviesAPP/Resources/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/MoviesAPP/Resources/Assets.xcassets/Images/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/MoviesAPP/Resources/Assets.xcassets/Images/avatar.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "avatar.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/MoviesAPP/Resources/Assets.xcassets/Images/avatar.imageset/avatar.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Bullas/viewcode-live-coding-dio/357739f5886242b1d6e44095331da684e610b5ef/MoviesAPP/Resources/Assets.xcassets/Images/avatar.imageset/avatar.pdf
--------------------------------------------------------------------------------
/MoviesAPP/Resources/Assets.xcassets/Images/background.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "background.pdf",
5 | "idiom" : "universal"
6 | }
7 | ],
8 | "info" : {
9 | "author" : "xcode",
10 | "version" : 1
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/MoviesAPP/Resources/Assets.xcassets/Images/background.imageset/background.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Bullas/viewcode-live-coding-dio/357739f5886242b1d6e44095331da684e610b5ef/MoviesAPP/Resources/Assets.xcassets/Images/background.imageset/background.pdf
--------------------------------------------------------------------------------
/MoviesAPP/Resources/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/MoviesAPP/Resources/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // MoviesAPP
4 | //
5 | // Created by Karolina Attekita on 28/03/22.
6 | //
7 |
8 | import UIKit
9 |
10 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
11 |
12 | var window: UIWindow?
13 |
14 |
15 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
16 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
17 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
18 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
19 | guard let windowScene = (scene as? UIWindowScene) else { return }
20 |
21 | self.window = UIWindow(windowScene: windowScene)
22 | self.window?.rootViewController = UINavigationController(rootViewController: MoviesViewController())
23 | self.window?.makeKeyAndVisible()
24 | }
25 |
26 | func sceneDidDisconnect(_ scene: UIScene) {
27 | // Called as the scene is being released by the system.
28 | // This occurs shortly after the scene enters the background, or when its session is discarded.
29 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
30 | // The scene may re-connect later, as its session was not necessarily discarded (see `application:didDiscardSceneSessions` instead).
31 | }
32 |
33 | func sceneDidBecomeActive(_ scene: UIScene) {
34 | // Called when the scene has moved from an inactive state to an active state.
35 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
36 | }
37 |
38 | func sceneWillResignActive(_ scene: UIScene) {
39 | // Called when the scene will move from an active state to an inactive state.
40 | // This may occur due to temporary interruptions (ex. an incoming phone call).
41 | }
42 |
43 | func sceneWillEnterForeground(_ scene: UIScene) {
44 | // Called as the scene transitions from the background to the foreground.
45 | // Use this method to undo the changes made on entering the background.
46 | }
47 |
48 | func sceneDidEnterBackground(_ scene: UIScene) {
49 | // Called as the scene transitions from the foreground to the background.
50 | // Use this method to save data, release shared resources, and store enough scene-specific state information
51 | // to restore the scene back to its current state.
52 | }
53 |
54 |
55 | }
56 |
57 |
--------------------------------------------------------------------------------
/MoviesAPP/Screens/MovieCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MovieCell.swift
3 | // MoviesAPP
4 | //
5 | // Created by Karolina Attekita on 28/03/22.
6 | //
7 |
8 | import Foundation
9 | import UIKit
10 | import Kingfisher
11 |
12 | final class MovieCell: UITableViewCell {
13 |
14 | private lazy var containerView: UIView = {
15 | let view = UIView()
16 | view.backgroundColor = .clear
17 | view.layer.masksToBounds = true
18 | view.layer.cornerRadius = 20
19 | view.translatesAutoresizingMaskIntoConstraints = false
20 | return view
21 | }()
22 |
23 | private lazy var blurView: UIVisualEffectView = {
24 | let blurEffect = UIBlurEffect(style: .dark)
25 | let blurView = UIVisualEffectView(effect: blurEffect)
26 | blurView.translatesAutoresizingMaskIntoConstraints = false
27 | return blurView
28 | }()
29 |
30 | private lazy var stackView: UIStackView = {
31 | let stackView = UIStackView(arrangedSubviews: [title, genres, rateStackView])
32 | stackView.axis = .vertical
33 | stackView.spacing = 8
34 | stackView.translatesAutoresizingMaskIntoConstraints = false
35 | return stackView
36 | }()
37 |
38 | private lazy var title: UILabel = {
39 | let label = UILabel()
40 | label.font = Fonts.large(.regular).font
41 | label.textColor = .white
42 | label.numberOfLines = 2
43 | label.translatesAutoresizingMaskIntoConstraints = false
44 | return label
45 | }()
46 |
47 | private lazy var genres: UILabel = {
48 | let label = UILabel()
49 | label.font = Fonts.small(.regular).font
50 | label.textColor = .lightGray
51 | label.numberOfLines = 2
52 | label.translatesAutoresizingMaskIntoConstraints = false
53 | return label
54 | }()
55 |
56 | private lazy var poster: UIImageView = {
57 | let image = UIImageView()
58 | image.translatesAutoresizingMaskIntoConstraints = false
59 | return image
60 | }()
61 |
62 | private lazy var rateStackView: UIStackView = {
63 | let stackView = UIStackView(arrangedSubviews: [star, rateLabel])
64 | stackView.axis = .horizontal
65 | stackView.spacing = 8
66 | stackView.translatesAutoresizingMaskIntoConstraints = false
67 | return stackView
68 | }()
69 |
70 | private lazy var star: UIImageView = {
71 | let image = UIImageView()
72 | image.translatesAutoresizingMaskIntoConstraints = false
73 | image.image = UIImage.init(systemName: "star")
74 | image.tintColor = .yellow
75 | return image
76 | }()
77 |
78 | private lazy var rateLabel: UILabel = {
79 | let label = UILabel()
80 | label.font = Fonts.medium(.regular).font
81 | label.textColor = .white
82 | label.numberOfLines = 2
83 | label.translatesAutoresizingMaskIntoConstraints = false
84 | return label
85 | }()
86 |
87 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
88 | super.init(style: style, reuseIdentifier: reuseIdentifier)
89 | setupView()
90 | }
91 |
92 | @available(*, unavailable)
93 | required init?(coder: NSCoder) {
94 | fatalError("init(coder:) has not been implemented")
95 | }
96 |
97 | func configure(with model: Movie) {
98 | title.text = model.name
99 | genres.text = model.genres?.joined(separator: ", ")
100 | rateLabel.text = model.rating?.average?.toString()
101 |
102 | guard let image = model.image?.medium,
103 | let imageUrl = URL(string: image) else {
104 | return
105 | }
106 |
107 | poster.kf.indicatorType = .activity
108 | poster.kf.setImage(with: imageUrl)
109 | }
110 | }
111 |
112 | extension MovieCell: ViewCode {
113 | func buildHierarchy() {
114 | contentView.addSubview(containerView)
115 | containerView.addSubview(blurView)
116 | containerView.addSubview(poster)
117 | containerView.addSubview(stackView)
118 | }
119 |
120 | func setupConstraints() {
121 | NSLayoutConstraint.activate([
122 | containerView.topAnchor.constraint(equalTo: contentView.topAnchor),
123 | containerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24),
124 | containerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24),
125 | containerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -24),
126 |
127 | blurView.topAnchor.constraint(equalTo: containerView.topAnchor),
128 | blurView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
129 | blurView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
130 | blurView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
131 |
132 | poster.topAnchor.constraint(equalTo: containerView.topAnchor),
133 | poster.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
134 | poster.widthAnchor.constraint(equalToConstant: 100),
135 | poster.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
136 |
137 | stackView.centerYAnchor.constraint(equalTo: containerView.centerYAnchor),
138 | stackView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16),
139 | stackView.leadingAnchor.constraint(equalTo: poster.trailingAnchor, constant: 16),
140 |
141 | star.widthAnchor.constraint(equalToConstant: 24),
142 | star.heightAnchor.constraint(equalToConstant: 24)
143 | ])
144 |
145 | let heightConstraint = containerView.heightAnchor.constraint(equalToConstant: 150)
146 | heightConstraint.isActive = true
147 | heightConstraint.priority = UILayoutPriority.init(999)
148 | }
149 |
150 | func applyAdditionalChanges() {
151 | contentView.backgroundColor = .clear
152 | backgroundColor = .clear
153 | selectionStyle = .none
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/MoviesAPP/Screens/MoviesViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MoviesViewController.swift
3 | // MoviesAPP
4 | //
5 | // Created by Karolina Attekita on 28/03/22.
6 | //
7 |
8 | import UIKit
9 |
10 | final class MoviesViewController: UIViewController {
11 |
12 | private lazy var backgroundImage: UIImageView = {
13 | let image = UIImageView()
14 | image.image = UIImage(named: "background")
15 | image.contentMode = .scaleToFill
16 | image.translatesAutoresizingMaskIntoConstraints = false
17 | image.alpha = 0.4
18 | return image
19 | }()
20 |
21 | private lazy var searchController: UISearchController = {
22 | let searchController = UISearchController(searchResultsController: nil)
23 | searchController.searchBar.tintColor = .white
24 | searchController.searchBar.barStyle = .black
25 | searchController.searchBar.delegate = self
26 | return searchController
27 | }()
28 |
29 | private lazy var tableView: UITableView = {
30 | let tableView = UITableView()
31 | tableView.dataSource = self
32 | tableView.delegate = self
33 | tableView.backgroundColor = .clear
34 | tableView.translatesAutoresizingMaskIntoConstraints = false
35 | tableView.estimatedRowHeight = 44
36 | tableView.rowHeight = UITableView.automaticDimension
37 | tableView.registerCell(type: MovieCell.self)
38 | return tableView
39 | }()
40 |
41 | private var movies: [Movie]? {
42 | didSet {
43 | self.tableView.reloadData()
44 | }
45 | }
46 |
47 | private let service: MoviesService = MoviesService()
48 |
49 | override func viewDidLoad() {
50 | super.viewDidLoad()
51 | setupView()
52 | }
53 |
54 | override func viewDidAppear(_ animated: Bool) {
55 | super.viewDidAppear(animated)
56 | fetchMovies()
57 | }
58 |
59 | private func fetchMovies() {
60 | service.fetchList { [weak self] result in
61 | switch result {
62 | case .success(let response):
63 | self?.movies = response
64 | case .failure:
65 | self?.movies = nil
66 | }
67 | }
68 | }
69 |
70 | private func searchhMovies(term: String) {
71 | service.fetchResults(term) { [weak self] result in
72 | switch result {
73 | case .success(let response):
74 | self?.movies = response.compactMap({ $0.show })
75 | case .failure:
76 | self?.movies = nil
77 | }
78 | }
79 | }
80 |
81 |
82 | private func setupNavigation() {
83 | title = "My movies"
84 | navigationController?.navigationBar.prefersLargeTitles = true
85 | let textAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
86 | navigationController?.navigationBar.largeTitleTextAttributes = textAttributes
87 | navigationItem.searchController = searchController
88 | }
89 | }
90 |
91 | extension MoviesViewController: ViewCode {
92 | func buildHierarchy() {
93 | view.addSubview(backgroundImage)
94 | view.addSubview(tableView)
95 | }
96 |
97 | func setupConstraints() {
98 | NSLayoutConstraint.activate([
99 | backgroundImage.topAnchor.constraint(equalTo: view.topAnchor),
100 | backgroundImage.leadingAnchor.constraint(equalTo: view.leadingAnchor),
101 | backgroundImage.trailingAnchor.constraint(equalTo: view.trailingAnchor),
102 | backgroundImage.bottomAnchor.constraint(equalTo: view.bottomAnchor),
103 |
104 | tableView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
105 | tableView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
106 | tableView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
107 | tableView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
108 | ])
109 | }
110 |
111 | func applyAdditionalChanges() {
112 | setupNavigation()
113 | }
114 | }
115 |
116 |
117 | extension MoviesViewController: UITableViewDataSource, UITableViewDelegate {
118 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
119 | return movies?.count ?? 0
120 | }
121 |
122 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
123 | guard let movieCell = tableView.dequeueCell(withType: MovieCell.self, for: indexPath) as? MovieCell else {
124 | return UITableViewCell()
125 | }
126 | if let model = movies?[indexPath.row] {
127 | movieCell.configure(with: model)
128 | }
129 | return movieCell
130 | }
131 | }
132 |
133 |
134 | // MARK: - Search Bar
135 | extension MoviesViewController: UISearchBarDelegate {
136 | func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
137 | guard !searchText.isEmpty else {
138 | fetchMovies()
139 | return
140 | }
141 | searchhMovies(term: searchText)
142 | }
143 | }
144 |
--------------------------------------------------------------------------------
/MoviesAPP/Service/APIConstants.swift:
--------------------------------------------------------------------------------
1 | //
2 | // APIConstants.swift
3 | // MazeTv
4 | //
5 | // Created by Karolina Attekita on 10/02/22.
6 | //
7 |
8 | import Foundation
9 |
10 | enum APIConstants {
11 | static let baseURL = "https://api.tvmaze.com/"
12 |
13 | enum Endpoints {
14 | case search
15 | case shows
16 | case episodes(Int)
17 |
18 | var path: String {
19 | switch self {
20 | case .search:
21 | return "search/shows"
22 | case .shows:
23 | return "shows"
24 | case .episodes(let showID):
25 | return "shows/\(showID)/episodes"
26 | }
27 | }
28 |
29 | var urlString: String {
30 | return APIConstants.baseURL + self.path
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/MoviesAPP/Service/Models/Episode.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Episode.swift
3 | // MazeTv
4 | //
5 | // Created by Karolina Attekita on 12/02/22.
6 | //
7 |
8 | import Foundation
9 |
10 | // MARK: - EpisodeElement
11 | struct Episode: Codable {
12 | let id: Int
13 | let name: String?
14 | let season, number: Int?
15 | let image: Image?
16 | let summary: String?
17 | }
18 |
19 | struct Season: Codable {
20 | let number: Int?
21 | let episodes: [Episode]?
22 | }
23 |
--------------------------------------------------------------------------------
/MoviesAPP/Service/Models/Image.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Image.swift
3 | // MazeTv
4 | //
5 | // Created by Karolina Attekita on 12/02/22.
6 | //
7 |
8 | import Foundation
9 |
10 | // MARK: - Image
11 | struct Image: Codable {
12 | let medium, original: String
13 | }
14 |
--------------------------------------------------------------------------------
/MoviesAPP/Service/Models/Movie.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Show.swift
3 | // MazeTv
4 | //
5 | // Created by Karolina Attekita on 11/02/22.
6 | //
7 |
8 | import Foundation
9 |
10 | // MARK: - Show
11 | struct Movie: Codable {
12 | let id: Int
13 | let name: String?
14 | let genres: [String]?
15 | let schedule: Schedule?
16 | let rating: Rating?
17 | let image: Image?
18 | let summary: String?
19 | }
20 |
21 | // MARK: - Rating
22 | struct Rating: Codable {
23 | let average: Double?
24 | }
25 |
26 | // MARK: - Schedule
27 | struct Schedule: Codable {
28 | let time: String
29 | let days: [String]
30 |
31 | func toString() -> String {
32 | return "\(time) (\(days.joined(separator: ", ")))"
33 | }
34 | }
35 | struct SearchShowElement: Codable {
36 | let show: Movie
37 | }
38 |
--------------------------------------------------------------------------------
/MoviesAPP/Service/Models/Responses.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Responses.swift
3 | // MazeTv
4 | //
5 | // Created by Karolina Attekita on 12/02/22.
6 | //
7 |
8 | import Foundation
9 |
10 | typealias EpisodeResponse = [Episode]
11 | typealias ShowResponse = [Movie]
12 | typealias SearchShowResponse = [SearchShowElement]
13 |
--------------------------------------------------------------------------------
/MoviesAPP/Service/MoviesService.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MoviesService.swift
3 | // MoviesAPP
4 | //
5 | // Created by Karolina Attekita on 28/03/22.
6 | //
7 |
8 | import Foundation
9 |
10 | final class MoviesService {
11 |
12 | // MARK: Properties
13 |
14 | private let service: ServiceProvider
15 |
16 | // MARK: Init's
17 |
18 | init(service: ServiceProvider = Service()) {
19 | self.service = service
20 | }
21 |
22 | func fetchResults(_ search: String, result: @escaping (Result) -> Void) {
23 | let parameters = ["q": search]
24 | service.makeRequest(endpoint: .search, parameters: parameters) { response in
25 | DispatchQueue.main.async {
26 | result(response)
27 | }
28 | }
29 | }
30 |
31 | func fetchList(result: @escaping(Result) -> Void) {
32 | service.makeRequest(endpoint: .shows, parameters: nil) { response in
33 | DispatchQueue.main.async {
34 | result(response)
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/MoviesAPP/Service/Service.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Service.swift
3 | // MazeTv
4 | //
5 | // Created by Karolina Attekita on 10/02/22.
6 | //
7 |
8 | import Foundation
9 |
10 | protocol ServiceProvider {
11 | func makeRequest(endpoint: APIConstants.Endpoints, parameters: [String: String?]?, result: @escaping(Result) -> Void)
12 | }
13 |
14 | final class Service: ServiceProvider {
15 |
16 | // MARK: Properties
17 |
18 | private let session: URLSession = {
19 | let config = URLSessionConfiguration.default
20 | config.allowsCellularAccess = false
21 | config.httpAdditionalHeaders = ["Content-Type": "aplication/json"]
22 | config.timeoutIntervalForRequest = 30.0
23 | config.httpMaximumConnectionsPerHost = 5
24 | return URLSession(configuration: config)
25 | }()
26 |
27 | func makeRequest(endpoint: APIConstants.Endpoints, parameters: [String: String?]?, result: @escaping(Result) -> Void) {
28 |
29 | guard var components = URLComponents(string: endpoint.urlString) else { return }
30 |
31 | components.queryItems = parameters?.map { (key, value) in
32 | URLQueryItem(name: key, value: value)
33 | }
34 |
35 | guard let url = components.url else { return }
36 | var request = URLRequest(url: url)
37 | request.setValue("application/json", forHTTPHeaderField: "Content-Type")
38 | request.setValue("application/json", forHTTPHeaderField: "Accept")
39 |
40 | let dataTask = session.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in
41 |
42 | guard error == nil,
43 | let response = response as? HTTPURLResponse,
44 | let data = data
45 | else {
46 | result(.failure(ServiceError.badRequest))
47 | return
48 | }
49 |
50 | do {
51 | guard response.statusCode == 200 else {
52 | result(.failure(ServiceError.statusCode(response.statusCode)))
53 | return
54 | }
55 |
56 | let codable = try JSONDecoder().decode(T.self, from: data)
57 | result(.success(codable))
58 | } catch {
59 | print("Unexpected error: \(error).")
60 | result(.failure(ServiceError.parseError))
61 | }
62 | }
63 | dataTask.resume()
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | target 'MoviesAPP' do
5 | # Comment the next line if you don't want to use dynamic frameworks
6 | use_frameworks!
7 |
8 | # Pods for MoviesAPP
9 | pod 'Kingfisher', '~> 7.0'
10 |
11 | end
12 |
--------------------------------------------------------------------------------
/Podfile.lock:
--------------------------------------------------------------------------------
1 | PODS:
2 | - Kingfisher (7.1.2)
3 |
4 | DEPENDENCIES:
5 | - Kingfisher (~> 7.0)
6 |
7 | SPEC REPOS:
8 | trunk:
9 | - Kingfisher
10 |
11 | SPEC CHECKSUMS:
12 | Kingfisher: 44ed6a8504763f27bab46163adfac83f5deb240c
13 |
14 | PODFILE CHECKSUM: 492d6753b0c9aa2c1e4e2a3ea542771bb3abfd47
15 |
16 | COCOAPODS: 1.11.2
17 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2019 Wei Wang
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/Cache/CacheSerializer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CacheSerializer.swift
3 | // Kingfisher
4 | //
5 | // Created by Wei Wang on 2016/09/02.
6 | //
7 | // Copyright (c) 2019 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 | import CoreGraphics
29 |
30 | /// An `CacheSerializer` is used to convert some data to an image object after
31 | /// retrieving it from disk storage, and vice versa, to convert an image to data object
32 | /// for storing to the disk storage.
33 | public protocol CacheSerializer {
34 |
35 | /// Gets the serialized data from a provided image
36 | /// and optional original data for caching to disk.
37 | ///
38 | /// - Parameters:
39 | /// - image: The image needed to be serialized.
40 | /// - original: The original data which is just downloaded.
41 | /// If the image is retrieved from cache instead of
42 | /// downloaded, it will be `nil`.
43 | /// - Returns: The data object for storing to disk, or `nil` when no valid
44 | /// data could be serialized.
45 | func data(with image: KFCrossPlatformImage, original: Data?) -> Data?
46 |
47 | /// Gets an image from provided serialized data.
48 | ///
49 | /// - Parameters:
50 | /// - data: The data from which an image should be deserialized.
51 | /// - options: The parsed options for deserialization.
52 | /// - Returns: An image deserialized or `nil` when no valid image
53 | /// could be deserialized.
54 | func image(with data: Data, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage?
55 | }
56 |
57 | /// Represents a basic and default `CacheSerializer` used in Kingfisher disk cache system.
58 | /// It could serialize and deserialize images in PNG, JPEG and GIF format. For
59 | /// image other than these formats, a normalized `pngRepresentation` will be used.
60 | public struct DefaultCacheSerializer: CacheSerializer {
61 |
62 | /// The default general cache serializer used across Kingfisher's cache.
63 | public static let `default` = DefaultCacheSerializer()
64 |
65 | /// The compression quality when converting image to a lossy format data. Default is 1.0.
66 | public var compressionQuality: CGFloat = 1.0
67 |
68 | /// Whether the original data should be preferred when serializing the image.
69 | /// If `true`, the input original data will be checked first and used unless the data is `nil`.
70 | /// In that case, the serialization will fall back to creating data from image.
71 | public var preferCacheOriginalData: Bool = false
72 |
73 | /// Creates a cache serializer that serialize and deserialize images in PNG, JPEG and GIF format.
74 | ///
75 | /// - Note:
76 | /// Use `DefaultCacheSerializer.default` unless you need to specify your own properties.
77 | ///
78 | public init() { }
79 |
80 | /// - Parameters:
81 | /// - image: The image needed to be serialized.
82 | /// - original: The original data which is just downloaded.
83 | /// If the image is retrieved from cache instead of
84 | /// downloaded, it will be `nil`.
85 | /// - Returns: The data object for storing to disk, or `nil` when no valid
86 | /// data could be serialized.
87 | ///
88 | /// - Note:
89 | /// Only when `original` contains valid PNG, JPEG and GIF format data, the `image` will be
90 | /// converted to the corresponding data type. Otherwise, if the `original` is provided but it is not
91 | /// If `original` is `nil`, the input `image` will be encoded as PNG data.
92 | public func data(with image: KFCrossPlatformImage, original: Data?) -> Data? {
93 | if preferCacheOriginalData {
94 | return original ??
95 | image.kf.data(
96 | format: original?.kf.imageFormat ?? .unknown,
97 | compressionQuality: compressionQuality
98 | )
99 | } else {
100 | return image.kf.data(
101 | format: original?.kf.imageFormat ?? .unknown,
102 | compressionQuality: compressionQuality
103 | )
104 | }
105 | }
106 |
107 | /// Gets an image deserialized from provided data.
108 | ///
109 | /// - Parameters:
110 | /// - data: The data from which an image should be deserialized.
111 | /// - options: Options for deserialization.
112 | /// - Returns: An image deserialized or `nil` when no valid image
113 | /// could be deserialized.
114 | public func image(with data: Data, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
115 | return KingfisherWrapper.image(data: data, options: options.imageCreatingOptions)
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/Cache/FormatIndicatedCacheSerializer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RequestModifier.swift
3 | // Kingfisher
4 | //
5 | // Created by Junyu Kuang on 5/28/17.
6 | //
7 | // Copyright (c) 2019 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 | import CoreGraphics
29 |
30 | /// `FormatIndicatedCacheSerializer` lets you indicate an image format for serialized caches.
31 | ///
32 | /// It could serialize and deserialize PNG, JPEG and GIF images. For
33 | /// image other than these formats, a normalized `pngRepresentation` will be used.
34 | ///
35 | /// Example:
36 | /// ````
37 | /// let profileImageSize = CGSize(width: 44, height: 44)
38 | ///
39 | /// // A round corner image.
40 | /// let imageProcessor = RoundCornerImageProcessor(
41 | /// cornerRadius: profileImageSize.width / 2, targetSize: profileImageSize)
42 | ///
43 | /// let optionsInfo: KingfisherOptionsInfo = [
44 | /// .cacheSerializer(FormatIndicatedCacheSerializer.png),
45 | /// .processor(imageProcessor)]
46 | ///
47 | /// A URL pointing to a JPEG image.
48 | /// let url = URL(string: "https://example.com/image.jpg")!
49 | ///
50 | /// // Image will be always cached as PNG format to preserve alpha channel for round rectangle.
51 | /// // So when you load it from cache again later, it will be still round cornered.
52 | /// // Otherwise, the corner part would be filled by white color (since JPEG does not contain an alpha channel).
53 | /// imageView.kf.setImage(with: url, options: optionsInfo)
54 | /// ````
55 | public struct FormatIndicatedCacheSerializer: CacheSerializer {
56 |
57 | /// A `FormatIndicatedCacheSerializer` which converts image from and to PNG format. If the image cannot be
58 | /// represented by PNG format, it will fallback to its real format which is determined by `original` data.
59 | public static let png = FormatIndicatedCacheSerializer(imageFormat: .PNG, jpegCompressionQuality: nil)
60 |
61 | /// A `FormatIndicatedCacheSerializer` which converts image from and to JPEG format. If the image cannot be
62 | /// represented by JPEG format, it will fallback to its real format which is determined by `original` data.
63 | /// The compression quality is 1.0 when using this serializer. If you need to set a customized compression quality,
64 | /// use `jpeg(compressionQuality:)`.
65 | public static let jpeg = FormatIndicatedCacheSerializer(imageFormat: .JPEG, jpegCompressionQuality: 1.0)
66 |
67 | /// A `FormatIndicatedCacheSerializer` which converts image from and to JPEG format with a settable compression
68 | /// quality. If the image cannot be represented by JPEG format, it will fallback to its real format which is
69 | /// determined by `original` data.
70 | /// - Parameter compressionQuality: The compression quality when converting image to JPEG data.
71 | public static func jpeg(compressionQuality: CGFloat) -> FormatIndicatedCacheSerializer {
72 | return FormatIndicatedCacheSerializer(imageFormat: .JPEG, jpegCompressionQuality: compressionQuality)
73 | }
74 |
75 | /// A `FormatIndicatedCacheSerializer` which converts image from and to GIF format. If the image cannot be
76 | /// represented by GIF format, it will fallback to its real format which is determined by `original` data.
77 | public static let gif = FormatIndicatedCacheSerializer(imageFormat: .GIF, jpegCompressionQuality: nil)
78 |
79 | /// The indicated image format.
80 | private let imageFormat: ImageFormat
81 |
82 | /// The compression quality used for loss image format (like JPEG).
83 | private let jpegCompressionQuality: CGFloat?
84 |
85 | /// Creates data which represents the given `image` under a format.
86 | public func data(with image: KFCrossPlatformImage, original: Data?) -> Data? {
87 |
88 | func imageData(withFormat imageFormat: ImageFormat) -> Data? {
89 | return autoreleasepool { () -> Data? in
90 | switch imageFormat {
91 | case .PNG: return image.kf.pngRepresentation()
92 | case .JPEG: return image.kf.jpegRepresentation(compressionQuality: jpegCompressionQuality ?? 1.0)
93 | case .GIF: return image.kf.gifRepresentation()
94 | case .unknown: return nil
95 | }
96 | }
97 | }
98 |
99 | // generate data with indicated image format
100 | if let data = imageData(withFormat: imageFormat) {
101 | return data
102 | }
103 |
104 | let originalFormat = original?.kf.imageFormat ?? .unknown
105 |
106 | // generate data with original image's format
107 | if originalFormat != imageFormat, let data = imageData(withFormat: originalFormat) {
108 | return data
109 | }
110 |
111 | return original ?? image.kf.normalized.kf.pngRepresentation()
112 | }
113 |
114 | /// Same implementation as `DefaultCacheSerializer`.
115 | public func image(with data: Data, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
116 | return KingfisherWrapper.image(data: data, options: options.imageCreatingOptions)
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/Cache/Storage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Storage.swift
3 | // Kingfisher
4 | //
5 | // Created by Wei Wang on 2018/10/15.
6 | //
7 | // Copyright (c) 2019 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 |
29 | /// Constants for some time intervals
30 | struct TimeConstants {
31 | static let secondsInOneMinute = 60
32 | static let minutesInOneHour = 60
33 | static let hoursInOneDay = 24
34 | static let secondsInOneDay = 86_400
35 | }
36 |
37 | /// Represents the expiration strategy used in storage.
38 | ///
39 | /// - never: The item never expires.
40 | /// - seconds: The item expires after a time duration of given seconds from now.
41 | /// - days: The item expires after a time duration of given days from now.
42 | /// - date: The item expires after a given date.
43 | public enum StorageExpiration {
44 | /// The item never expires.
45 | case never
46 | /// The item expires after a time duration of given seconds from now.
47 | case seconds(TimeInterval)
48 | /// The item expires after a time duration of given days from now.
49 | case days(Int)
50 | /// The item expires after a given date.
51 | case date(Date)
52 | /// Indicates the item is already expired. Use this to skip cache.
53 | case expired
54 |
55 | func estimatedExpirationSince(_ date: Date) -> Date {
56 | switch self {
57 | case .never: return .distantFuture
58 | case .seconds(let seconds):
59 | return date.addingTimeInterval(seconds)
60 | case .days(let days):
61 | let duration: TimeInterval = TimeInterval(TimeConstants.secondsInOneDay) * TimeInterval(days)
62 | return date.addingTimeInterval(duration)
63 | case .date(let ref):
64 | return ref
65 | case .expired:
66 | return .distantPast
67 | }
68 | }
69 |
70 | var estimatedExpirationSinceNow: Date {
71 | return estimatedExpirationSince(Date())
72 | }
73 |
74 | var isExpired: Bool {
75 | return timeInterval <= 0
76 | }
77 |
78 | var timeInterval: TimeInterval {
79 | switch self {
80 | case .never: return .infinity
81 | case .seconds(let seconds): return seconds
82 | case .days(let days): return TimeInterval(TimeConstants.secondsInOneDay) * TimeInterval(days)
83 | case .date(let ref): return ref.timeIntervalSinceNow
84 | case .expired: return -(.infinity)
85 | }
86 | }
87 | }
88 |
89 | /// Represents the expiration extending strategy used in storage to after access.
90 | ///
91 | /// - none: The item expires after the original time, without extending after access.
92 | /// - cacheTime: The item expiration extends by the original cache time after each access.
93 | /// - expirationTime: The item expiration extends by the provided time after each access.
94 | public enum ExpirationExtending {
95 | /// The item expires after the original time, without extending after access.
96 | case none
97 | /// The item expiration extends by the original cache time after each access.
98 | case cacheTime
99 | /// The item expiration extends by the provided time after each access.
100 | case expirationTime(_ expiration: StorageExpiration)
101 | }
102 |
103 | /// Represents types which cost in memory can be calculated.
104 | public protocol CacheCostCalculable {
105 | var cacheCost: Int { get }
106 | }
107 |
108 | /// Represents types which can be converted to and from data.
109 | public protocol DataTransformable {
110 | func toData() throws -> Data
111 | static func fromData(_ data: Data) throws -> Self
112 | static var empty: Self { get }
113 | }
114 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/General/ImageSource/AVAssetImageDataProvider.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AVAssetImageDataProvider.swift
3 | // Kingfisher
4 | //
5 | // Created by onevcat on 2020/08/09.
6 | //
7 | // Copyright (c) 2020 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | #if !os(watchOS)
28 |
29 | import Foundation
30 | import AVKit
31 |
32 | #if canImport(MobileCoreServices)
33 | import MobileCoreServices
34 | #else
35 | import CoreServices
36 | #endif
37 |
38 | /// A data provider to provide thumbnail data from a given AVKit asset.
39 | public struct AVAssetImageDataProvider: ImageDataProvider {
40 |
41 | /// The possible error might be caused by the `AVAssetImageDataProvider`.
42 | /// - userCancelled: The data provider process is cancelled.
43 | /// - invalidImage: The retrieved image is invalid.
44 | public enum AVAssetImageDataProviderError: Error {
45 | case userCancelled
46 | case invalidImage(_ image: CGImage?)
47 | }
48 |
49 | /// The asset image generator bound to `self`.
50 | public let assetImageGenerator: AVAssetImageGenerator
51 |
52 | /// The time at which the image should be generate in the asset.
53 | public let time: CMTime
54 |
55 | private var internalKey: String {
56 | return (assetImageGenerator.asset as? AVURLAsset)?.url.absoluteString ?? UUID().uuidString
57 | }
58 |
59 | /// The cache key used by `self`.
60 | public var cacheKey: String {
61 | return "\(internalKey)_\(time.seconds)"
62 | }
63 |
64 | /// Creates an asset image data provider.
65 | /// - Parameters:
66 | /// - assetImageGenerator: The asset image generator controls data providing behaviors.
67 | /// - time: At which time in the asset the image should be generated.
68 | public init(assetImageGenerator: AVAssetImageGenerator, time: CMTime) {
69 | self.assetImageGenerator = assetImageGenerator
70 | self.time = time
71 | }
72 |
73 | /// Creates an asset image data provider.
74 | /// - Parameters:
75 | /// - assetURL: The URL of asset for providing image data.
76 | /// - time: At which time in the asset the image should be generated.
77 | ///
78 | /// This method uses `assetURL` to create an `AVAssetImageGenerator` object and calls
79 | /// the `init(assetImageGenerator:time:)` initializer.
80 | ///
81 | public init(assetURL: URL, time: CMTime) {
82 | let asset = AVAsset(url: assetURL)
83 | let generator = AVAssetImageGenerator(asset: asset)
84 | self.init(assetImageGenerator: generator, time: time)
85 | }
86 |
87 | /// Creates an asset image data provider.
88 | ///
89 | /// - Parameters:
90 | /// - assetURL: The URL of asset for providing image data.
91 | /// - seconds: At which time in seconds in the asset the image should be generated.
92 | ///
93 | /// This method uses `assetURL` to create an `AVAssetImageGenerator` object, uses `seconds` to create a `CMTime`,
94 | /// and calls the `init(assetImageGenerator:time:)` initializer.
95 | ///
96 | public init(assetURL: URL, seconds: TimeInterval) {
97 | let time = CMTime(seconds: seconds, preferredTimescale: 600)
98 | self.init(assetURL: assetURL, time: time)
99 | }
100 |
101 | public func data(handler: @escaping (Result) -> Void) {
102 | assetImageGenerator.generateCGImagesAsynchronously(forTimes: [NSValue(time: time)]) {
103 | (requestedTime, image, imageTime, result, error) in
104 | if let error = error {
105 | handler(.failure(error))
106 | return
107 | }
108 |
109 | if result == .cancelled {
110 | handler(.failure(AVAssetImageDataProviderError.userCancelled))
111 | return
112 | }
113 |
114 | guard let cgImage = image, let data = cgImage.jpegData else {
115 | handler(.failure(AVAssetImageDataProviderError.invalidImage(image)))
116 | return
117 | }
118 |
119 | handler(.success(data))
120 | }
121 | }
122 | }
123 |
124 | extension CGImage {
125 | var jpegData: Data? {
126 | guard let mutableData = CFDataCreateMutable(nil, 0),
127 | let destination = CGImageDestinationCreateWithData(mutableData, kUTTypeJPEG, 1, nil)
128 | else {
129 | return nil
130 | }
131 | CGImageDestinationAddImage(destination, self, nil)
132 | guard CGImageDestinationFinalize(destination) else { return nil }
133 | return mutableData as Data
134 | }
135 | }
136 |
137 | #endif
138 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/General/ImageSource/Resource.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Resource.swift
3 | // Kingfisher
4 | //
5 | // Created by Wei Wang on 15/4/6.
6 | //
7 | // Copyright (c) 2019 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 |
29 | /// Represents an image resource at a certain url and a given cache key.
30 | /// Kingfisher will use a `Resource` to download a resource from network and cache it with the cache key when
31 | /// using `Source.network` as its image setting source.
32 | public protocol Resource {
33 |
34 | /// The key used in cache.
35 | var cacheKey: String { get }
36 |
37 | /// The target image URL.
38 | var downloadURL: URL { get }
39 | }
40 |
41 | extension Resource {
42 |
43 | /// Converts `self` to a valid `Source` based on its `downloadURL` scheme. A `.provider` with
44 | /// `LocalFileImageDataProvider` associated will be returned if the URL points to a local file. Otherwise,
45 | /// `.network` is returned.
46 | public func convertToSource(overrideCacheKey: String? = nil) -> Source {
47 | return downloadURL.isFileURL ?
48 | .provider(LocalFileImageDataProvider(fileURL: downloadURL, cacheKey: overrideCacheKey ?? downloadURL.localFileCacheKey)) :
49 | .network(ImageResource(downloadURL: downloadURL, cacheKey: overrideCacheKey ?? cacheKey))
50 | }
51 | }
52 |
53 | /// ImageResource is a simple combination of `downloadURL` and `cacheKey`.
54 | /// When passed to image view set methods, Kingfisher will try to download the target
55 | /// image from the `downloadURL`, and then store it with the `cacheKey` as the key in cache.
56 | public struct ImageResource: Resource {
57 |
58 | // MARK: - Initializers
59 |
60 | /// Creates an image resource.
61 | ///
62 | /// - Parameters:
63 | /// - downloadURL: The target image URL from where the image can be downloaded.
64 | /// - cacheKey: The cache key. If `nil`, Kingfisher will use the `absoluteString` of `downloadURL` as the key.
65 | /// Default is `nil`.
66 | public init(downloadURL: URL, cacheKey: String? = nil) {
67 | self.downloadURL = downloadURL
68 | self.cacheKey = cacheKey ?? downloadURL.absoluteString
69 | }
70 |
71 | // MARK: Protocol Conforming
72 |
73 | /// The key used in cache.
74 | public let cacheKey: String
75 |
76 | /// The target image URL.
77 | public let downloadURL: URL
78 | }
79 |
80 | /// URL conforms to `Resource` in Kingfisher.
81 | /// The `absoluteString` of this URL is used as `cacheKey`. And the URL itself will be used as `downloadURL`.
82 | /// If you need customize the url and/or cache key, use `ImageResource` instead.
83 | extension URL: Resource {
84 | public var cacheKey: String { return absoluteString }
85 | public var downloadURL: URL { return self }
86 | }
87 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/General/ImageSource/Source.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Source.swift
3 | // Kingfisher
4 | //
5 | // Created by onevcat on 2018/11/17.
6 | //
7 | // Copyright (c) 2019 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 |
29 | /// Represents an image setting source for Kingfisher methods.
30 | ///
31 | /// A `Source` value indicates the way how the target image can be retrieved and cached.
32 | ///
33 | /// - network: The target image should be got from network remotely. The associated `Resource`
34 | /// value defines detail information like image URL and cache key.
35 | /// - provider: The target image should be provided in a data format. Normally, it can be an image
36 | /// from local storage or in any other encoding format (like Base64).
37 | public enum Source {
38 |
39 | /// Represents the source task identifier when setting an image to a view with extension methods.
40 | public enum Identifier {
41 |
42 | /// The underlying value type of source identifier.
43 | public typealias Value = UInt
44 | static var current: Value = 0
45 | static func next() -> Value {
46 | current += 1
47 | return current
48 | }
49 | }
50 |
51 | // MARK: Member Cases
52 |
53 | /// The target image should be got from network remotely. The associated `Resource`
54 | /// value defines detail information like image URL and cache key.
55 | case network(Resource)
56 |
57 | /// The target image should be provided in a data format. Normally, it can be an image
58 | /// from local storage or in any other encoding format (like Base64).
59 | case provider(ImageDataProvider)
60 |
61 | // MARK: Getting Properties
62 |
63 | /// The cache key defined for this source value.
64 | public var cacheKey: String {
65 | switch self {
66 | case .network(let resource): return resource.cacheKey
67 | case .provider(let provider): return provider.cacheKey
68 | }
69 | }
70 |
71 | /// The URL defined for this source value.
72 | ///
73 | /// For a `.network` source, it is the `downloadURL` of associated `Resource` instance.
74 | /// For a `.provider` value, it is always `nil`.
75 | public var url: URL? {
76 | switch self {
77 | case .network(let resource): return resource.downloadURL
78 | case .provider(let provider): return provider.contentURL
79 | }
80 | }
81 | }
82 |
83 | extension Source: Hashable {
84 | public static func == (lhs: Source, rhs: Source) -> Bool {
85 | switch (lhs, rhs) {
86 | case (.network(let r1), .network(let r2)):
87 | return r1.cacheKey == r2.cacheKey && r1.downloadURL == r2.downloadURL
88 | case (.provider(let p1), .provider(let p2)):
89 | return p1.cacheKey == p2.cacheKey && p1.contentURL == p2.contentURL
90 | case (.provider(_), .network(_)):
91 | return false
92 | case (.network(_), .provider(_)):
93 | return false
94 | }
95 | }
96 |
97 | public func hash(into hasher: inout Hasher) {
98 | switch self {
99 | case .network(let r):
100 | hasher.combine(r.cacheKey)
101 | hasher.combine(r.downloadURL)
102 | case .provider(let p):
103 | hasher.combine(p.cacheKey)
104 | hasher.combine(p.contentURL)
105 | }
106 | }
107 | }
108 |
109 | extension Source {
110 | var asResource: Resource? {
111 | guard case .network(let resource) = self else {
112 | return nil
113 | }
114 | return resource
115 | }
116 |
117 | var asProvider: ImageDataProvider? {
118 | guard case .provider(let provider) = self else {
119 | return nil
120 | }
121 | return provider
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/General/Kingfisher.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Kingfisher.swift
3 | // Kingfisher
4 | //
5 | // Created by Wei Wang on 16/9/14.
6 | //
7 | // Copyright (c) 2019 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 | import ImageIO
29 |
30 | #if os(macOS)
31 | import AppKit
32 | public typealias KFCrossPlatformImage = NSImage
33 | public typealias KFCrossPlatformView = NSView
34 | public typealias KFCrossPlatformColor = NSColor
35 | public typealias KFCrossPlatformImageView = NSImageView
36 | public typealias KFCrossPlatformButton = NSButton
37 | #else
38 | import UIKit
39 | public typealias KFCrossPlatformImage = UIImage
40 | public typealias KFCrossPlatformColor = UIColor
41 | #if !os(watchOS)
42 | public typealias KFCrossPlatformImageView = UIImageView
43 | public typealias KFCrossPlatformView = UIView
44 | public typealias KFCrossPlatformButton = UIButton
45 | #if canImport(TVUIKit)
46 | import TVUIKit
47 | #endif
48 | #if canImport(CarPlay) && !targetEnvironment(macCatalyst)
49 | import CarPlay
50 | #endif
51 | #else
52 | import WatchKit
53 | #endif
54 | #endif
55 |
56 | /// Wrapper for Kingfisher compatible types. This type provides an extension point for
57 | /// convenience methods in Kingfisher.
58 | public struct KingfisherWrapper {
59 | public let base: Base
60 | public init(_ base: Base) {
61 | self.base = base
62 | }
63 | }
64 |
65 | /// Represents an object type that is compatible with Kingfisher. You can use `kf` property to get a
66 | /// value in the namespace of Kingfisher.
67 | public protocol KingfisherCompatible: AnyObject { }
68 |
69 | /// Represents a value type that is compatible with Kingfisher. You can use `kf` property to get a
70 | /// value in the namespace of Kingfisher.
71 | public protocol KingfisherCompatibleValue {}
72 |
73 | extension KingfisherCompatible {
74 | /// Gets a namespace holder for Kingfisher compatible types.
75 | public var kf: KingfisherWrapper {
76 | get { return KingfisherWrapper(self) }
77 | set { }
78 | }
79 | }
80 |
81 | extension KingfisherCompatibleValue {
82 | /// Gets a namespace holder for Kingfisher compatible types.
83 | public var kf: KingfisherWrapper {
84 | get { return KingfisherWrapper(self) }
85 | set { }
86 | }
87 | }
88 |
89 | extension KFCrossPlatformImage: KingfisherCompatible { }
90 | #if !os(watchOS)
91 | extension KFCrossPlatformImageView: KingfisherCompatible { }
92 | extension KFCrossPlatformButton: KingfisherCompatible { }
93 | extension NSTextAttachment: KingfisherCompatible { }
94 | #else
95 | extension WKInterfaceImage: KingfisherCompatible { }
96 | #endif
97 |
98 | #if os(tvOS) && canImport(TVUIKit)
99 | @available(tvOS 12.0, *)
100 | extension TVMonogramView: KingfisherCompatible { }
101 | #endif
102 |
103 | #if canImport(CarPlay) && !targetEnvironment(macCatalyst)
104 | @available(iOS 14.0, *)
105 | extension CPListItem: KingfisherCompatible { }
106 | #endif
107 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/Image/Filter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Filter.swift
3 | // Kingfisher
4 | //
5 | // Created by Wei Wang on 2016/08/31.
6 | //
7 | // Copyright (c) 2019 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | #if !os(watchOS)
28 |
29 | import CoreImage
30 |
31 | // Reuse the same CI Context for all CI drawing.
32 | private let ciContext = CIContext(options: nil)
33 |
34 | /// Represents the type of transformer method, which will be used in to provide a `Filter`.
35 | public typealias Transformer = (CIImage) -> CIImage?
36 |
37 | /// Represents a processor based on a `CIImage` `Filter`.
38 | /// It requires a filter to create an `ImageProcessor`.
39 | public protocol CIImageProcessor: ImageProcessor {
40 | var filter: Filter { get }
41 | }
42 |
43 | extension CIImageProcessor {
44 |
45 | /// Processes the input `ImageProcessItem` with this processor.
46 | ///
47 | /// - Parameters:
48 | /// - item: Input item which will be processed by `self`.
49 | /// - options: Options when processing the item.
50 | /// - Returns: The processed image.
51 | ///
52 | /// - Note: See documentation of `ImageProcessor` protocol for more.
53 | public func process(item: ImageProcessItem, options: KingfisherParsedOptionsInfo) -> KFCrossPlatformImage? {
54 | switch item {
55 | case .image(let image):
56 | return image.kf.apply(filter)
57 | case .data:
58 | return (DefaultImageProcessor.default |> self).process(item: item, options: options)
59 | }
60 | }
61 | }
62 |
63 | /// A wrapper struct for a `Transformer` of CIImage filters. A `Filter`
64 | /// value could be used to create a `CIImage` processor.
65 | public struct Filter {
66 |
67 | let transform: Transformer
68 |
69 | public init(transform: @escaping Transformer) {
70 | self.transform = transform
71 | }
72 |
73 | /// Tint filter which will apply a tint color to images.
74 | public static var tint: (KFCrossPlatformColor) -> Filter = {
75 | color in
76 | Filter {
77 | input in
78 |
79 | let colorFilter = CIFilter(name: "CIConstantColorGenerator")!
80 | colorFilter.setValue(CIColor(color: color), forKey: kCIInputColorKey)
81 |
82 | let filter = CIFilter(name: "CISourceOverCompositing")!
83 |
84 | let colorImage = colorFilter.outputImage
85 | filter.setValue(colorImage, forKey: kCIInputImageKey)
86 | filter.setValue(input, forKey: kCIInputBackgroundImageKey)
87 |
88 | return filter.outputImage?.cropped(to: input.extent)
89 | }
90 | }
91 |
92 | /// Represents color control elements. It is a tuple of
93 | /// `(brightness, contrast, saturation, inputEV)`
94 | public typealias ColorElement = (CGFloat, CGFloat, CGFloat, CGFloat)
95 |
96 | /// Color control filter which will apply color control change to images.
97 | public static var colorControl: (ColorElement) -> Filter = { arg -> Filter in
98 | let (brightness, contrast, saturation, inputEV) = arg
99 | return Filter { input in
100 | let paramsColor = [kCIInputBrightnessKey: brightness,
101 | kCIInputContrastKey: contrast,
102 | kCIInputSaturationKey: saturation]
103 | let blackAndWhite = input.applyingFilter("CIColorControls", parameters: paramsColor)
104 | let paramsExposure = [kCIInputEVKey: inputEV]
105 | return blackAndWhite.applyingFilter("CIExposureAdjust", parameters: paramsExposure)
106 | }
107 | }
108 | }
109 |
110 | extension KingfisherWrapper where Base: KFCrossPlatformImage {
111 |
112 | /// Applies a `Filter` containing `CIImage` transformer to `self`.
113 | ///
114 | /// - Parameter filter: The filter used to transform `self`.
115 | /// - Returns: A transformed image by input `Filter`.
116 | ///
117 | /// - Note:
118 | /// Only CG-based images are supported. If any error happens
119 | /// during transforming, `self` will be returned.
120 | public func apply(_ filter: Filter) -> KFCrossPlatformImage {
121 |
122 | guard let cgImage = cgImage else {
123 | assertionFailure("[Kingfisher] Tint image only works for CG-based image.")
124 | return base
125 | }
126 |
127 | let inputImage = CIImage(cgImage: cgImage)
128 | guard let outputImage = filter.transform(inputImage) else {
129 | return base
130 | }
131 |
132 | guard let result = ciContext.createCGImage(outputImage, from: outputImage.extent) else {
133 | assertionFailure("[Kingfisher] Can not make an tint image within context.")
134 | return base
135 | }
136 |
137 | #if os(macOS)
138 | return fixedForRetinaPixel(cgImage: result, to: size)
139 | #else
140 | return KFCrossPlatformImage(cgImage: result, scale: base.scale, orientation: base.imageOrientation)
141 | #endif
142 | }
143 |
144 | }
145 |
146 | #endif
147 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/Image/GIFAnimatedImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AnimatedImage.swift
3 | // Kingfisher
4 | //
5 | // Created by onevcat on 2018/09/26.
6 | //
7 | // Copyright (c) 2019 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 | import ImageIO
29 |
30 | /// Represents a set of image creating options used in Kingfisher.
31 | public struct ImageCreatingOptions {
32 |
33 | /// The target scale of image needs to be created.
34 | public let scale: CGFloat
35 |
36 | /// The expected animation duration if an animated image being created.
37 | public let duration: TimeInterval
38 |
39 | /// For an animated image, whether or not all frames should be loaded before displaying.
40 | public let preloadAll: Bool
41 |
42 | /// For an animated image, whether or not only the first image should be
43 | /// loaded as a static image. It is useful for preview purpose of an animated image.
44 | public let onlyFirstFrame: Bool
45 |
46 | /// Creates an `ImageCreatingOptions` object.
47 | ///
48 | /// - Parameters:
49 | /// - scale: The target scale of image needs to be created. Default is `1.0`.
50 | /// - duration: The expected animation duration if an animated image being created.
51 | /// A value less or equal to `0.0` means the animated image duration will
52 | /// be determined by the frame data. Default is `0.0`.
53 | /// - preloadAll: For an animated image, whether or not all frames should be loaded before displaying.
54 | /// Default is `false`.
55 | /// - onlyFirstFrame: For an animated image, whether or not only the first image should be
56 | /// loaded as a static image. It is useful for preview purpose of an animated image.
57 | /// Default is `false`.
58 | public init(
59 | scale: CGFloat = 1.0,
60 | duration: TimeInterval = 0.0,
61 | preloadAll: Bool = false,
62 | onlyFirstFrame: Bool = false)
63 | {
64 | self.scale = scale
65 | self.duration = duration
66 | self.preloadAll = preloadAll
67 | self.onlyFirstFrame = onlyFirstFrame
68 | }
69 | }
70 |
71 | /// Represents the decoding for a GIF image. This class extracts frames from an `imageSource`, then
72 | /// hold the images for later use.
73 | public class GIFAnimatedImage {
74 | let images: [KFCrossPlatformImage]
75 | let duration: TimeInterval
76 |
77 | init?(from imageSource: CGImageSource, for info: [String: Any], options: ImageCreatingOptions) {
78 | let frameCount = CGImageSourceGetCount(imageSource)
79 | var images = [KFCrossPlatformImage]()
80 | var gifDuration = 0.0
81 |
82 | for i in 0 ..< frameCount {
83 | guard let imageRef = CGImageSourceCreateImageAtIndex(imageSource, i, info as CFDictionary) else {
84 | return nil
85 | }
86 |
87 | if frameCount == 1 {
88 | gifDuration = .infinity
89 | } else {
90 | // Get current animated GIF frame duration
91 | gifDuration += GIFAnimatedImage.getFrameDuration(from: imageSource, at: i)
92 | }
93 | images.append(KingfisherWrapper.image(cgImage: imageRef, scale: options.scale, refImage: nil))
94 | if options.onlyFirstFrame { break }
95 | }
96 | self.images = images
97 | self.duration = gifDuration
98 | }
99 |
100 | /// Calculates frame duration for a gif frame out of the kCGImagePropertyGIFDictionary dictionary.
101 | public static func getFrameDuration(from gifInfo: [String: Any]?) -> TimeInterval {
102 | let defaultFrameDuration = 0.1
103 | guard let gifInfo = gifInfo else { return defaultFrameDuration }
104 |
105 | let unclampedDelayTime = gifInfo[kCGImagePropertyGIFUnclampedDelayTime as String] as? NSNumber
106 | let delayTime = gifInfo[kCGImagePropertyGIFDelayTime as String] as? NSNumber
107 | let duration = unclampedDelayTime ?? delayTime
108 |
109 | guard let frameDuration = duration else { return defaultFrameDuration }
110 | return frameDuration.doubleValue > 0.011 ? frameDuration.doubleValue : defaultFrameDuration
111 | }
112 |
113 | /// Calculates frame duration at a specific index for a gif from an `imageSource`.
114 | public static func getFrameDuration(from imageSource: CGImageSource, at index: Int) -> TimeInterval {
115 | guard let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, index, nil)
116 | as? [String: Any] else { return 0.0 }
117 |
118 | let gifInfo = properties[kCGImagePropertyGIFDictionary as String] as? [String: Any]
119 | return getFrameDuration(from: gifInfo)
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/Image/GraphicsContext.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GraphicsContext.swift
3 | // Kingfisher
4 | //
5 | // Created by taras on 19/04/2021.
6 | //
7 | // Copyright (c) 2021 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
28 | import AppKit
29 | #endif
30 | #if canImport(UIKit)
31 | import UIKit
32 | #endif
33 |
34 | enum GraphicsContext {
35 | static func begin(size: CGSize, scale: CGFloat) {
36 | #if os(macOS)
37 | NSGraphicsContext.saveGraphicsState()
38 | #else
39 | UIGraphicsBeginImageContextWithOptions(size, false, scale)
40 | #endif
41 | }
42 |
43 | static func current(size: CGSize, scale: CGFloat, inverting: Bool, cgImage: CGImage?) -> CGContext? {
44 | #if os(macOS)
45 | guard let rep = NSBitmapImageRep(
46 | bitmapDataPlanes: nil,
47 | pixelsWide: Int(size.width),
48 | pixelsHigh: Int(size.height),
49 | bitsPerSample: cgImage?.bitsPerComponent ?? 8,
50 | samplesPerPixel: 4,
51 | hasAlpha: true,
52 | isPlanar: false,
53 | colorSpaceName: .calibratedRGB,
54 | bytesPerRow: 0,
55 | bitsPerPixel: 0) else
56 | {
57 | assertionFailure("[Kingfisher] Image representation cannot be created.")
58 | return nil
59 | }
60 | rep.size = size
61 | guard let context = NSGraphicsContext(bitmapImageRep: rep) else {
62 | assertionFailure("[Kingfisher] Image context cannot be created.")
63 | return nil
64 | }
65 |
66 | NSGraphicsContext.current = context
67 | return context.cgContext
68 | #else
69 | guard let context = UIGraphicsGetCurrentContext() else {
70 | return nil
71 | }
72 | if inverting { // If drawing a CGImage, we need to make context flipped.
73 | context.scaleBy(x: 1.0, y: -1.0)
74 | context.translateBy(x: 0, y: -size.height)
75 | }
76 | return context
77 | #endif
78 | }
79 |
80 | static func end() {
81 | #if os(macOS)
82 | NSGraphicsContext.restoreGraphicsState()
83 | #else
84 | UIGraphicsEndImageContext()
85 | #endif
86 | }
87 | }
88 |
89 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/Image/ImageFormat.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageFormat.swift
3 | // Kingfisher
4 | //
5 | // Created by onevcat on 2018/09/28.
6 | //
7 | // Copyright (c) 2019 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 |
29 | /// Represents image format.
30 | ///
31 | /// - unknown: The format cannot be recognized or not supported yet.
32 | /// - PNG: PNG image format.
33 | /// - JPEG: JPEG image format.
34 | /// - GIF: GIF image format.
35 | public enum ImageFormat {
36 | /// The format cannot be recognized or not supported yet.
37 | case unknown
38 | /// PNG image format.
39 | case PNG
40 | /// JPEG image format.
41 | case JPEG
42 | /// GIF image format.
43 | case GIF
44 |
45 | struct HeaderData {
46 | static var PNG: [UInt8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]
47 | static var JPEG_SOI: [UInt8] = [0xFF, 0xD8]
48 | static var JPEG_IF: [UInt8] = [0xFF]
49 | static var GIF: [UInt8] = [0x47, 0x49, 0x46]
50 | }
51 |
52 | /// https://en.wikipedia.org/wiki/JPEG
53 | public enum JPEGMarker {
54 | case SOF0 //baseline
55 | case SOF2 //progressive
56 | case DHT //Huffman Table
57 | case DQT //Quantization Table
58 | case DRI //Restart Interval
59 | case SOS //Start Of Scan
60 | case RSTn(UInt8) //Restart
61 | case APPn //Application-specific
62 | case COM //Comment
63 | case EOI //End Of Image
64 |
65 | var bytes: [UInt8] {
66 | switch self {
67 | case .SOF0: return [0xFF, 0xC0]
68 | case .SOF2: return [0xFF, 0xC2]
69 | case .DHT: return [0xFF, 0xC4]
70 | case .DQT: return [0xFF, 0xDB]
71 | case .DRI: return [0xFF, 0xDD]
72 | case .SOS: return [0xFF, 0xDA]
73 | case .RSTn(let n): return [0xFF, 0xD0 + n]
74 | case .APPn: return [0xFF, 0xE0]
75 | case .COM: return [0xFF, 0xFE]
76 | case .EOI: return [0xFF, 0xD9]
77 | }
78 | }
79 | }
80 | }
81 |
82 |
83 | extension Data: KingfisherCompatibleValue {}
84 |
85 | // MARK: - Misc Helpers
86 | extension KingfisherWrapper where Base == Data {
87 | /// Gets the image format corresponding to the data.
88 | public var imageFormat: ImageFormat {
89 | guard base.count > 8 else { return .unknown }
90 |
91 | var buffer = [UInt8](repeating: 0, count: 8)
92 | base.copyBytes(to: &buffer, count: 8)
93 |
94 | if buffer == ImageFormat.HeaderData.PNG {
95 | return .PNG
96 |
97 | } else if buffer[0] == ImageFormat.HeaderData.JPEG_SOI[0],
98 | buffer[1] == ImageFormat.HeaderData.JPEG_SOI[1],
99 | buffer[2] == ImageFormat.HeaderData.JPEG_IF[0]
100 | {
101 | return .JPEG
102 |
103 | } else if buffer[0] == ImageFormat.HeaderData.GIF[0],
104 | buffer[1] == ImageFormat.HeaderData.GIF[1],
105 | buffer[2] == ImageFormat.HeaderData.GIF[2]
106 | {
107 | return .GIF
108 | }
109 |
110 | return .unknown
111 | }
112 |
113 | public func contains(jpeg marker: ImageFormat.JPEGMarker) -> Bool {
114 | guard imageFormat == .JPEG else {
115 | return false
116 | }
117 |
118 | let bytes = [UInt8](base)
119 | let markerBytes = marker.bytes
120 | for (index, item) in bytes.enumerated() where bytes.count > index + 1 {
121 | guard
122 | item == markerBytes.first,
123 | bytes[index + 1] == markerBytes[1] else {
124 | continue
125 | }
126 | return true
127 | }
128 | return false
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/Image/ImageTransition.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageTransition.swift
3 | // Kingfisher
4 | //
5 | // Created by Wei Wang on 15/9/18.
6 | //
7 | // Copyright (c) 2019 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 | #if os(iOS) || os(tvOS)
29 | import UIKit
30 |
31 | /// Transition effect which will be used when an image downloaded and set by `UIImageView`
32 | /// extension API in Kingfisher. You can assign an enum value with transition duration as
33 | /// an item in `KingfisherOptionsInfo` to enable the animation transition.
34 | ///
35 | /// Apple's UIViewAnimationOptions is used under the hood.
36 | /// For custom transition, you should specified your own transition options, animations and
37 | /// completion handler as well.
38 | ///
39 | /// - none: No animation transition.
40 | /// - fade: Fade in the loaded image in a given duration.
41 | /// - flipFromLeft: Flip from left transition.
42 | /// - flipFromRight: Flip from right transition.
43 | /// - flipFromTop: Flip from top transition.
44 | /// - flipFromBottom: Flip from bottom transition.
45 | /// - custom: Custom transition.
46 | public enum ImageTransition {
47 | /// No animation transition.
48 | case none
49 | /// Fade in the loaded image in a given duration.
50 | case fade(TimeInterval)
51 | /// Flip from left transition.
52 | case flipFromLeft(TimeInterval)
53 | /// Flip from right transition.
54 | case flipFromRight(TimeInterval)
55 | /// Flip from top transition.
56 | case flipFromTop(TimeInterval)
57 | /// Flip from bottom transition.
58 | case flipFromBottom(TimeInterval)
59 | /// Custom transition defined by a general animation block.
60 | /// - duration: The time duration of this custom transition.
61 | /// - options: `UIView.AnimationOptions` should be used in the transition.
62 | /// - animations: The animation block will be applied when setting image.
63 | /// - completion: A block called when the transition animation finishes.
64 | case custom(duration: TimeInterval,
65 | options: UIView.AnimationOptions,
66 | animations: ((UIImageView, UIImage) -> Void)?,
67 | completion: ((Bool) -> Void)?)
68 |
69 | var duration: TimeInterval {
70 | switch self {
71 | case .none: return 0
72 | case .fade(let duration): return duration
73 |
74 | case .flipFromLeft(let duration): return duration
75 | case .flipFromRight(let duration): return duration
76 | case .flipFromTop(let duration): return duration
77 | case .flipFromBottom(let duration): return duration
78 |
79 | case .custom(let duration, _, _, _): return duration
80 | }
81 | }
82 |
83 | var animationOptions: UIView.AnimationOptions {
84 | switch self {
85 | case .none: return []
86 | case .fade: return .transitionCrossDissolve
87 |
88 | case .flipFromLeft: return .transitionFlipFromLeft
89 | case .flipFromRight: return .transitionFlipFromRight
90 | case .flipFromTop: return .transitionFlipFromTop
91 | case .flipFromBottom: return .transitionFlipFromBottom
92 |
93 | case .custom(_, let options, _, _): return options
94 | }
95 | }
96 |
97 | var animations: ((UIImageView, UIImage) -> Void)? {
98 | switch self {
99 | case .custom(_, _, let animations, _): return animations
100 | default: return { $0.image = $1 }
101 | }
102 | }
103 |
104 | var completion: ((Bool) -> Void)? {
105 | switch self {
106 | case .custom(_, _, _, let completion): return completion
107 | default: return nil
108 | }
109 | }
110 | }
111 | #else
112 | // Just a placeholder for compiling on macOS.
113 | public enum ImageTransition {
114 | case none
115 | /// This is a placeholder on macOS now. It is for SwiftUI (KFImage) to identify the fade option only.
116 | case fade(TimeInterval)
117 | }
118 | #endif
119 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/Image/Placeholder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Placeholder.swift
3 | // Kingfisher
4 | //
5 | // Created by Tieme van Veen on 28/08/2017.
6 | //
7 | // Copyright (c) 2019 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | #if !os(watchOS)
28 |
29 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
30 | import AppKit
31 | #endif
32 |
33 | #if canImport(UIKit)
34 | import UIKit
35 | #endif
36 |
37 | /// Represents a placeholder type which could be set while loading as well as
38 | /// loading finished without getting an image.
39 | public protocol Placeholder {
40 |
41 | /// How the placeholder should be added to a given image view.
42 | func add(to imageView: KFCrossPlatformImageView)
43 |
44 | /// How the placeholder should be removed from a given image view.
45 | func remove(from imageView: KFCrossPlatformImageView)
46 | }
47 |
48 | /// Default implementation of an image placeholder. The image will be set or
49 | /// reset directly for `image` property of the image view.
50 | extension KFCrossPlatformImage: Placeholder {
51 | /// How the placeholder should be added to a given image view.
52 | public func add(to imageView: KFCrossPlatformImageView) { imageView.image = self }
53 |
54 | /// How the placeholder should be removed from a given image view.
55 | public func remove(from imageView: KFCrossPlatformImageView) { imageView.image = nil }
56 | }
57 |
58 | /// Default implementation of an arbitrary view as placeholder. The view will be
59 | /// added as a subview when adding and be removed from its super view when removing.
60 | ///
61 | /// To use your customize View type as placeholder, simply let it conforming to
62 | /// `Placeholder` by `extension MyView: Placeholder {}`.
63 | extension Placeholder where Self: KFCrossPlatformView {
64 |
65 | /// How the placeholder should be added to a given image view.
66 | public func add(to imageView: KFCrossPlatformImageView) {
67 | imageView.addSubview(self)
68 | translatesAutoresizingMaskIntoConstraints = false
69 |
70 | centerXAnchor.constraint(equalTo: imageView.centerXAnchor).isActive = true
71 | centerYAnchor.constraint(equalTo: imageView.centerYAnchor).isActive = true
72 | heightAnchor.constraint(equalTo: imageView.heightAnchor).isActive = true
73 | widthAnchor.constraint(equalTo: imageView.widthAnchor).isActive = true
74 | }
75 |
76 | /// How the placeholder should be removed from a given image view.
77 | public func remove(from imageView: KFCrossPlatformImageView) {
78 | removeFromSuperview()
79 | }
80 | }
81 |
82 | #endif
83 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/Networking/AuthenticationChallengeResponsable.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AuthenticationChallengeResponsable.swift
3 | // Kingfisher
4 | //
5 | // Created by Wei Wang on 2018/10/11.
6 | //
7 | // Copyright (c) 2019 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 |
29 | @available(*, deprecated, message: "Typo. Use `AuthenticationChallengeResponsible` instead", renamed: "AuthenticationChallengeResponsible")
30 | public typealias AuthenticationChallengeResponsable = AuthenticationChallengeResponsible
31 |
32 | /// Protocol indicates that an authentication challenge could be handled.
33 | public protocol AuthenticationChallengeResponsible: AnyObject {
34 |
35 | /// Called when a session level authentication challenge is received.
36 | /// This method provide a chance to handle and response to the authentication
37 | /// challenge before downloading could start.
38 | ///
39 | /// - Parameters:
40 | /// - downloader: The downloader which receives this challenge.
41 | /// - challenge: An object that contains the request for authentication.
42 | /// - completionHandler: A handler that your delegate method must call.
43 | ///
44 | /// - Note: This method is a forward from `URLSessionDelegate.urlSession(:didReceiveChallenge:completionHandler:)`.
45 | /// Please refer to the document of it in `URLSessionDelegate`.
46 | func downloader(
47 | _ downloader: ImageDownloader,
48 | didReceive challenge: URLAuthenticationChallenge,
49 | completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
50 |
51 | /// Called when a task level authentication challenge is received.
52 | /// This method provide a chance to handle and response to the authentication
53 | /// challenge before downloading could start.
54 | ///
55 | /// - Parameters:
56 | /// - downloader: The downloader which receives this challenge.
57 | /// - task: The task whose request requires authentication.
58 | /// - challenge: An object that contains the request for authentication.
59 | /// - completionHandler: A handler that your delegate method must call.
60 | func downloader(
61 | _ downloader: ImageDownloader,
62 | task: URLSessionTask,
63 | didReceive challenge: URLAuthenticationChallenge,
64 | completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
65 | }
66 |
67 | extension AuthenticationChallengeResponsible {
68 |
69 | public func downloader(
70 | _ downloader: ImageDownloader,
71 | didReceive challenge: URLAuthenticationChallenge,
72 | completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
73 | {
74 | if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
75 | if let trustedHosts = downloader.trustedHosts, trustedHosts.contains(challenge.protectionSpace.host) {
76 | let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
77 | completionHandler(.useCredential, credential)
78 | return
79 | }
80 | }
81 |
82 | completionHandler(.performDefaultHandling, nil)
83 | }
84 |
85 | public func downloader(
86 | _ downloader: ImageDownloader,
87 | task: URLSessionTask,
88 | didReceive challenge: URLAuthenticationChallenge,
89 | completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
90 | {
91 | completionHandler(.performDefaultHandling, nil)
92 | }
93 |
94 | }
95 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/Networking/ImageDataProcessor.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageDataProcessor.swift
3 | // Kingfisher
4 | //
5 | // Created by Wei Wang on 2018/10/11.
6 | //
7 | // Copyright (c) 2019 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 |
29 | private let sharedProcessingQueue: CallbackQueue =
30 | .dispatch(DispatchQueue(label: "com.onevcat.Kingfisher.ImageDownloader.Process"))
31 |
32 | // Handles image processing work on an own process queue.
33 | class ImageDataProcessor {
34 | let data: Data
35 | let callbacks: [SessionDataTask.TaskCallback]
36 | let queue: CallbackQueue
37 |
38 | // Note: We have an optimization choice there, to reduce queue dispatch by checking callback
39 | // queue settings in each option...
40 | let onImageProcessed = Delegate<(Result, SessionDataTask.TaskCallback), Void>()
41 |
42 | init(data: Data, callbacks: [SessionDataTask.TaskCallback], processingQueue: CallbackQueue?) {
43 | self.data = data
44 | self.callbacks = callbacks
45 | self.queue = processingQueue ?? sharedProcessingQueue
46 | }
47 |
48 | func process() {
49 | queue.execute(doProcess)
50 | }
51 |
52 | private func doProcess() {
53 | var processedImages = [String: KFCrossPlatformImage]()
54 | for callback in callbacks {
55 | let processor = callback.options.processor
56 | var image = processedImages[processor.identifier]
57 | if image == nil {
58 | image = processor.process(item: .data(data), options: callback.options)
59 | processedImages[processor.identifier] = image
60 | }
61 |
62 | let result: Result
63 | if let image = image {
64 | let finalImage = callback.options.backgroundDecode ? image.kf.decoded : image
65 | result = .success(finalImage)
66 | } else {
67 | let error = KingfisherError.processorError(
68 | reason: .processingFailed(processor: processor, item: .data(data)))
69 | result = .failure(error)
70 | }
71 | onImageProcessed.call((result, callback))
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/Networking/ImageModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageModifier.swift
3 | // Kingfisher
4 | //
5 | // Created by Ethan Gill on 2017/11/28.
6 | //
7 | // Copyright (c) 2019 Ethan Gill
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 |
29 | /// An `ImageModifier` can be used to change properties on an image between cache serialization and the actual use of
30 | /// the image. The `modify(_:)` method will be called after the image retrieved from its source and before it returned
31 | /// to the caller. This modified image is expected to be only used for rendering purpose, any changes applied by the
32 | /// `ImageModifier` will not be serialized or cached.
33 | public protocol ImageModifier {
34 | /// Modify an input `Image`.
35 | ///
36 | /// - parameter image: Image which will be modified by `self`
37 | ///
38 | /// - returns: The modified image.
39 | ///
40 | /// - Note: The return value will be unmodified if modifying is not possible on
41 | /// the current platform.
42 | /// - Note: Most modifiers support UIImage or NSImage, but not CGImage.
43 | func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage
44 | }
45 |
46 | /// A wrapper for creating an `ImageModifier` easier.
47 | /// This type conforms to `ImageModifier` and wraps an image modify block.
48 | /// If the `block` throws an error, the original image will be used.
49 | public struct AnyImageModifier: ImageModifier {
50 |
51 | /// A block which modifies images, or returns the original image
52 | /// if modification cannot be performed with an error.
53 | let block: (KFCrossPlatformImage) throws -> KFCrossPlatformImage
54 |
55 | /// Creates an `AnyImageModifier` with a given `modify` block.
56 | public init(modify: @escaping (KFCrossPlatformImage) throws -> KFCrossPlatformImage) {
57 | block = modify
58 | }
59 |
60 | /// Modify an input `Image`. See `ImageModifier` protocol for more.
61 | public func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage {
62 | return (try? block(image)) ?? image
63 | }
64 | }
65 |
66 | #if os(iOS) || os(tvOS) || os(watchOS)
67 | import UIKit
68 |
69 | /// Modifier for setting the rendering mode of images.
70 | public struct RenderingModeImageModifier: ImageModifier {
71 |
72 | /// The rendering mode to apply to the image.
73 | public let renderingMode: UIImage.RenderingMode
74 |
75 | /// Creates a `RenderingModeImageModifier`.
76 | ///
77 | /// - Parameter renderingMode: The rendering mode to apply to the image. Default is `.automatic`.
78 | public init(renderingMode: UIImage.RenderingMode = .automatic) {
79 | self.renderingMode = renderingMode
80 | }
81 |
82 | /// Modify an input `Image`. See `ImageModifier` protocol for more.
83 | public func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage {
84 | return image.withRenderingMode(renderingMode)
85 | }
86 | }
87 |
88 | /// Modifier for setting the `flipsForRightToLeftLayoutDirection` property of images.
89 | public struct FlipsForRightToLeftLayoutDirectionImageModifier: ImageModifier {
90 |
91 | /// Creates a `FlipsForRightToLeftLayoutDirectionImageModifier`.
92 | public init() {}
93 |
94 | /// Modify an input `Image`. See `ImageModifier` protocol for more.
95 | public func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage {
96 | return image.imageFlippedForRightToLeftLayoutDirection()
97 | }
98 | }
99 |
100 | /// Modifier for setting the `alignmentRectInsets` property of images.
101 | public struct AlignmentRectInsetsImageModifier: ImageModifier {
102 |
103 | /// The alignment insets to apply to the image
104 | public let alignmentInsets: UIEdgeInsets
105 |
106 | /// Creates an `AlignmentRectInsetsImageModifier`.
107 | public init(alignmentInsets: UIEdgeInsets) {
108 | self.alignmentInsets = alignmentInsets
109 | }
110 |
111 | /// Modify an input `Image`. See `ImageModifier` protocol for more.
112 | public func modify(_ image: KFCrossPlatformImage) -> KFCrossPlatformImage {
113 | return image.withAlignmentRectInsets(alignmentInsets)
114 | }
115 | }
116 | #endif
117 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/Networking/RedirectHandler.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RedirectHandler.swift
3 | // Kingfisher
4 | //
5 | // Created by Roman Maidanovych on 2018/12/10.
6 | //
7 | // Copyright (c) 2019 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 |
29 | /// Represents and wraps a method for modifying request during an image download request redirection.
30 | public protocol ImageDownloadRedirectHandler {
31 |
32 | /// The `ImageDownloadRedirectHandler` contained will be used to change the request before redirection.
33 | /// This is the posibility you can modify the image download request during redirection. You can modify the
34 | /// request for some customizing purpose, such as adding auth token to the header, do basic HTTP auth or
35 | /// something like url mapping.
36 | ///
37 | /// Usually, you pass an `ImageDownloadRedirectHandler` as the associated value of
38 | /// `KingfisherOptionsInfoItem.redirectHandler` and use it as the `options` parameter in related methods.
39 | ///
40 | /// If you do nothing with the input `request` and return it as is, a downloading process will redirect with it.
41 | ///
42 | /// - Parameters:
43 | /// - task: The current `SessionDataTask` which triggers this redirect.
44 | /// - response: The response received during redirection.
45 | /// - newRequest: The request for redirection which can be modified.
46 | /// - completionHandler: A closure for being called with modified request.
47 | func handleHTTPRedirection(
48 | for task: SessionDataTask,
49 | response: HTTPURLResponse,
50 | newRequest: URLRequest,
51 | completionHandler: @escaping (URLRequest?) -> Void)
52 | }
53 |
54 | /// A wrapper for creating an `ImageDownloadRedirectHandler` easier.
55 | /// This type conforms to `ImageDownloadRedirectHandler` and wraps a redirect request modify block.
56 | public struct AnyRedirectHandler: ImageDownloadRedirectHandler {
57 |
58 | let block: (SessionDataTask, HTTPURLResponse, URLRequest, @escaping (URLRequest?) -> Void) -> Void
59 |
60 | public func handleHTTPRedirection(
61 | for task: SessionDataTask,
62 | response: HTTPURLResponse,
63 | newRequest: URLRequest,
64 | completionHandler: @escaping (URLRequest?) -> Void)
65 | {
66 | block(task, response, newRequest, completionHandler)
67 | }
68 |
69 | /// Creates a value of `ImageDownloadRedirectHandler` which runs `modify` block.
70 | ///
71 | /// - Parameter modify: The request modifying block runs when a request modifying task comes.
72 | ///
73 | public init(handle: @escaping (SessionDataTask, HTTPURLResponse, URLRequest, @escaping (URLRequest?) -> Void) -> Void) {
74 | block = handle
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/Networking/RequestModifier.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RequestModifier.swift
3 | // Kingfisher
4 | //
5 | // Created by Wei Wang on 2016/09/05.
6 | //
7 | // Copyright (c) 2019 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 |
29 | /// Represents and wraps a method for modifying request before an image download request starts in an asynchronous way.
30 | public protocol AsyncImageDownloadRequestModifier {
31 |
32 | /// This method will be called just before the `request` being sent.
33 | /// This is the last chance you can modify the image download request. You can modify the request for some
34 | /// customizing purpose, such as adding auth token to the header, do basic HTTP auth or something like url mapping.
35 | /// When you have done with the modification, call the `reportModified` block with the modified request and the data
36 | /// download will happen with this request.
37 | ///
38 | /// Usually, you pass an `AsyncImageDownloadRequestModifier` as the associated value of
39 | /// `KingfisherOptionsInfoItem.requestModifier` and use it as the `options` parameter in related methods.
40 | ///
41 | /// If you do nothing with the input `request` and return it as is, a downloading process will start with it.
42 | ///
43 | /// - Parameters:
44 | /// - request: The input request contains necessary information like `url`. This request is generated
45 | /// according to your resource url as a GET request.
46 | /// - reportModified: The callback block you need to call after the asynchronous modifying done.
47 | ///
48 | func modified(for request: URLRequest, reportModified: @escaping (URLRequest?) -> Void)
49 |
50 | /// A block will be called when the download task started.
51 | ///
52 | /// If an `AsyncImageDownloadRequestModifier` and the asynchronous modification happens before the download, the
53 | /// related download method will not return a valid `DownloadTask` value. Instead, you can get one from this method.
54 | var onDownloadTaskStarted: ((DownloadTask?) -> Void)? { get }
55 | }
56 |
57 | /// Represents and wraps a method for modifying request before an image download request starts.
58 | public protocol ImageDownloadRequestModifier: AsyncImageDownloadRequestModifier {
59 |
60 | /// This method will be called just before the `request` being sent.
61 | /// This is the last chance you can modify the image download request. You can modify the request for some
62 | /// customizing purpose, such as adding auth token to the header, do basic HTTP auth or something like url mapping.
63 | ///
64 | /// Usually, you pass an `ImageDownloadRequestModifier` as the associated value of
65 | /// `KingfisherOptionsInfoItem.requestModifier` and use it as the `options` parameter in related methods.
66 | ///
67 | /// If you do nothing with the input `request` and return it as is, a downloading process will start with it.
68 | ///
69 | /// - Parameter request: The input request contains necessary information like `url`. This request is generated
70 | /// according to your resource url as a GET request.
71 | /// - Returns: A modified version of request, which you wish to use for downloading an image. If `nil` returned,
72 | /// a `KingfisherError.requestError` with `.emptyRequest` as its reason will occur.
73 | ///
74 | func modified(for request: URLRequest) -> URLRequest?
75 | }
76 |
77 | extension ImageDownloadRequestModifier {
78 | public func modified(for request: URLRequest, reportModified: @escaping (URLRequest?) -> Void) {
79 | let request = modified(for: request)
80 | reportModified(request)
81 | }
82 |
83 | /// This is `nil` for a sync `ImageDownloadRequestModifier` by default. You can get the `DownloadTask` from the
84 | /// return value of downloader method.
85 | public var onDownloadTaskStarted: ((DownloadTask?) -> Void)? { return nil }
86 | }
87 |
88 | /// A wrapper for creating an `ImageDownloadRequestModifier` easier.
89 | /// This type conforms to `ImageDownloadRequestModifier` and wraps an image modify block.
90 | public struct AnyModifier: ImageDownloadRequestModifier {
91 |
92 | let block: (URLRequest) -> URLRequest?
93 |
94 | /// For `ImageDownloadRequestModifier` conformation.
95 | public func modified(for request: URLRequest) -> URLRequest? {
96 | return block(request)
97 | }
98 |
99 | /// Creates a value of `ImageDownloadRequestModifier` which runs `modify` block.
100 | ///
101 | /// - Parameter modify: The request modifying block runs when a request modifying task comes.
102 | /// The return `URLRequest?` value of this block will be used as the image download request.
103 | /// If `nil` returned, a `KingfisherError.requestError` with `.emptyRequest` as its
104 | /// reason will occur.
105 | public init(modify: @escaping (URLRequest) -> URLRequest?) {
106 | block = modify
107 | }
108 | }
109 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/Networking/RetryStrategy.swift:
--------------------------------------------------------------------------------
1 | //
2 | // RetryStrategy.swift
3 | // Kingfisher
4 | //
5 | // Created by onevcat on 2020/05/04.
6 | //
7 | // Copyright (c) 2020 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 |
29 | /// Represents a retry context which could be used to determine the current retry status.
30 | public class RetryContext {
31 |
32 | /// The source from which the target image should be retrieved.
33 | public let source: Source
34 |
35 | /// The last error which caused current retry behavior.
36 | public let error: KingfisherError
37 |
38 | /// The retried count before current retry happens. This value is `0` if the current retry is for the first time.
39 | public var retriedCount: Int
40 |
41 | /// A user set value for passing any other information during the retry. If you choose to use `RetryDecision.retry`
42 | /// as the retry decision for `RetryStrategy.retry(context:retryHandler:)`, the associated value of
43 | /// `RetryDecision.retry` will be delivered to you in the next retry.
44 | public internal(set) var userInfo: Any? = nil
45 |
46 | init(source: Source, error: KingfisherError) {
47 | self.source = source
48 | self.error = error
49 | self.retriedCount = 0
50 | }
51 |
52 | @discardableResult
53 | func increaseRetryCount() -> RetryContext {
54 | retriedCount += 1
55 | return self
56 | }
57 | }
58 |
59 | /// Represents decision of behavior on the current retry.
60 | public enum RetryDecision {
61 | /// A retry should happen. The associated `userInfo` will be pass to the next retry in the `RetryContext` parameter.
62 | case retry(userInfo: Any?)
63 | /// There should be no more retry attempt. The image retrieving process will fail with an error.
64 | case stop
65 | }
66 |
67 | /// Defines a retry strategy can be applied to a `.retryStrategy` option.
68 | public protocol RetryStrategy {
69 |
70 | /// Kingfisher calls this method if an error happens during the image retrieving process from a `KingfisherManager`.
71 | /// You implement this method to provide necessary logic based on the `context` parameter. Then you need to call
72 | /// `retryHandler` to pass the retry decision back to Kingfisher.
73 | ///
74 | /// - Parameters:
75 | /// - context: The retry context containing information of current retry attempt.
76 | /// - retryHandler: A block you need to call with a decision of whether the retry should happen or not.
77 | func retry(context: RetryContext, retryHandler: @escaping (RetryDecision) -> Void)
78 | }
79 |
80 | /// A retry strategy that guides Kingfisher to retry when a `.responseError` happens, with a specified max retry count
81 | /// and a certain interval mechanism.
82 | public struct DelayRetryStrategy: RetryStrategy {
83 |
84 | /// Represents the interval mechanism which used in a `DelayRetryStrategy`.
85 | public enum Interval {
86 | /// The next retry attempt should happen in fixed seconds. For example, if the associated value is 3, the
87 | /// attempts happens after 3 seconds after the previous decision is made.
88 | case seconds(TimeInterval)
89 | /// The next retry attempt should happen in an accumulated duration. For example, if the associated value is 3,
90 | /// the attempts happens with interval of 3, 6, 9, 12, ... seconds.
91 | case accumulated(TimeInterval)
92 | /// Uses a block to determine the next interval. The current retry count is given as a parameter.
93 | case custom(block: (_ retriedCount: Int) -> TimeInterval)
94 |
95 | func timeInterval(for retriedCount: Int) -> TimeInterval {
96 | let retryAfter: TimeInterval
97 | switch self {
98 | case .seconds(let interval):
99 | retryAfter = interval
100 | case .accumulated(let interval):
101 | retryAfter = Double(retriedCount + 1) * interval
102 | case .custom(let block):
103 | retryAfter = block(retriedCount)
104 | }
105 | return retryAfter
106 | }
107 | }
108 |
109 | /// The max retry count defined for the retry strategy
110 | public let maxRetryCount: Int
111 |
112 | /// The retry interval mechanism defined for the retry strategy.
113 | public let retryInterval: Interval
114 |
115 | /// Creates a delay retry strategy.
116 | /// - Parameters:
117 | /// - maxRetryCount: The max retry count.
118 | /// - retryInterval: The retry interval mechanism. By default, `.seconds(3)` is used to provide a constant retry
119 | /// interval.
120 | public init(maxRetryCount: Int, retryInterval: Interval = .seconds(3)) {
121 | self.maxRetryCount = maxRetryCount
122 | self.retryInterval = retryInterval
123 | }
124 |
125 | public func retry(context: RetryContext, retryHandler: @escaping (RetryDecision) -> Void) {
126 | // Retry count exceeded.
127 | guard context.retriedCount < maxRetryCount else {
128 | retryHandler(.stop)
129 | return
130 | }
131 |
132 | // User cancel the task. No retry.
133 | guard !context.error.isTaskCancelled else {
134 | retryHandler(.stop)
135 | return
136 | }
137 |
138 | // Only retry for a response error.
139 | guard case KingfisherError.responseError = context.error else {
140 | retryHandler(.stop)
141 | return
142 | }
143 |
144 | let interval = retryInterval.timeInterval(for: context.retriedCount)
145 | if interval == 0 {
146 | retryHandler(.retry(userInfo: nil))
147 | } else {
148 | DispatchQueue.main.asyncAfter(deadline: .now() + interval) {
149 | retryHandler(.retry(userInfo: nil))
150 | }
151 | }
152 | }
153 | }
154 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/Networking/SessionDataTask.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SessionDataTask.swift
3 | // Kingfisher
4 | //
5 | // Created by Wei Wang on 2018/11/1.
6 | //
7 | // Copyright (c) 2019 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 |
29 | /// Represents a session data task in `ImageDownloader`. It consists of an underlying `URLSessionDataTask` and
30 | /// an array of `TaskCallback`. Multiple `TaskCallback`s could be added for a single downloading data task.
31 | public class SessionDataTask {
32 |
33 | /// Represents the type of token which used for cancelling a task.
34 | public typealias CancelToken = Int
35 |
36 | struct TaskCallback {
37 | let onCompleted: Delegate, Void>?
38 | let options: KingfisherParsedOptionsInfo
39 | }
40 |
41 | /// Downloaded raw data of current task.
42 | public private(set) var mutableData: Data
43 |
44 | // This is a copy of `task.originalRequest?.url`. It is for getting a race-safe behavior for a pitfall on iOS 13.
45 | // Ref: https://github.com/onevcat/Kingfisher/issues/1511
46 | public let originalURL: URL?
47 |
48 | /// The underlying download task. It is only for debugging purpose when you encountered an error. You should not
49 | /// modify the content of this task or start it yourself.
50 | public let task: URLSessionDataTask
51 | private var callbacksStore = [CancelToken: TaskCallback]()
52 |
53 | var callbacks: [SessionDataTask.TaskCallback] {
54 | lock.lock()
55 | defer { lock.unlock() }
56 | return Array(callbacksStore.values)
57 | }
58 |
59 | private var currentToken = 0
60 | private let lock = NSLock()
61 |
62 | let onTaskDone = Delegate<(Result<(Data, URLResponse?), KingfisherError>, [TaskCallback]), Void>()
63 | let onCallbackCancelled = Delegate<(CancelToken, TaskCallback), Void>()
64 |
65 | var started = false
66 | var containsCallbacks: Bool {
67 | // We should be able to use `task.state != .running` to check it.
68 | // However, in some rare cases, cancelling the task does not change
69 | // task state to `.cancelling` immediately, but still in `.running`.
70 | // So we need to check callbacks count to for sure that it is safe to remove the
71 | // task in delegate.
72 | return !callbacks.isEmpty
73 | }
74 |
75 | init(task: URLSessionDataTask) {
76 | self.task = task
77 | self.originalURL = task.originalRequest?.url
78 | mutableData = Data()
79 | }
80 |
81 | func addCallback(_ callback: TaskCallback) -> CancelToken {
82 | lock.lock()
83 | defer { lock.unlock() }
84 | callbacksStore[currentToken] = callback
85 | defer { currentToken += 1 }
86 | return currentToken
87 | }
88 |
89 | func removeCallback(_ token: CancelToken) -> TaskCallback? {
90 | lock.lock()
91 | defer { lock.unlock() }
92 | if let callback = callbacksStore[token] {
93 | callbacksStore[token] = nil
94 | return callback
95 | }
96 | return nil
97 | }
98 |
99 | func resume() {
100 | guard !started else { return }
101 | started = true
102 | task.resume()
103 | }
104 |
105 | func cancel(token: CancelToken) {
106 | guard let callback = removeCallback(token) else {
107 | return
108 | }
109 | onCallbackCancelled.call((token, callback))
110 | }
111 |
112 | func forceCancel() {
113 | for token in callbacksStore.keys {
114 | cancel(token: token)
115 | }
116 | }
117 |
118 | func didReceiveData(_ data: Data) {
119 | mutableData.append(data)
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/SwiftUI/ImageBinder.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageBinder.swift
3 | // Kingfisher
4 | //
5 | // Created by onevcat on 2019/06/27.
6 | //
7 | // Copyright (c) 2019 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | #if canImport(SwiftUI) && canImport(Combine)
28 | import SwiftUI
29 | import Combine
30 |
31 | @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
32 | extension KFImage {
33 |
34 | /// Represents a binder for `KFImage`. It takes responsibility as an `ObjectBinding` and performs
35 | /// image downloading and progress reporting based on `KingfisherManager`.
36 | class ImageBinder: ObservableObject {
37 |
38 | init() {}
39 |
40 | var downloadTask: DownloadTask?
41 | private var loading = false
42 |
43 | var loadingOrSucceeded: Bool {
44 | return loading || loadedImage != nil
45 | }
46 |
47 | // Do not use @Published due to https://github.com/onevcat/Kingfisher/issues/1717. Revert to @Published once
48 | // we can drop iOS 12.
49 | var loaded = false { willSet { objectWillChange.send() } }
50 | var loadedImage: KFCrossPlatformImage? = nil { willSet { objectWillChange.send() } }
51 | var progress: Progress = .init() { willSet { objectWillChange.send() } }
52 |
53 | func start(context: Context) {
54 | guard let source = context.source else {
55 | CallbackQueue.mainCurrentOrAsync.execute {
56 | context.onFailureDelegate.call(KingfisherError.imageSettingError(reason: .emptySource))
57 | }
58 | return
59 | }
60 |
61 | loading = true
62 |
63 | progress = .init()
64 | downloadTask = KingfisherManager.shared
65 | .retrieveImage(
66 | with: source,
67 | options: context.options,
68 | progressBlock: { size, total in
69 | self.updateProgress(downloaded: size, total: total)
70 | context.onProgressDelegate.call((size, total))
71 | },
72 | completionHandler: { [weak self] result in
73 |
74 | guard let self = self else { return }
75 |
76 | CallbackQueue.mainCurrentOrAsync.execute {
77 | self.downloadTask = nil
78 | self.loading = false
79 | }
80 |
81 | switch result {
82 | case .success(let value):
83 | CallbackQueue.mainCurrentOrAsync.execute {
84 | if let fadeDuration = context.fadeTransitionDuration(cacheType: value.cacheType) {
85 | let animation = Animation.linear(duration: fadeDuration)
86 | withAnimation(animation) { self.loaded = true }
87 | } else {
88 | self.loaded = true
89 | }
90 | self.loadedImage = value.image
91 | }
92 |
93 | CallbackQueue.mainAsync.execute {
94 | context.onSuccessDelegate.call(value)
95 | }
96 | case .failure(let error):
97 | CallbackQueue.mainCurrentOrAsync.execute {
98 | if let image = context.options.onFailureImage {
99 | self.loadedImage = image
100 | }
101 | self.loaded = true
102 | }
103 |
104 | CallbackQueue.mainAsync.execute {
105 | context.onFailureDelegate.call(error)
106 | }
107 | }
108 | })
109 | }
110 |
111 | private func updateProgress(downloaded: Int64, total: Int64) {
112 | progress.totalUnitCount = total
113 | progress.completedUnitCount = downloaded
114 | objectWillChange.send()
115 | }
116 |
117 | /// Cancels the download task if it is in progress.
118 | func cancel() {
119 | downloadTask?.cancel()
120 | downloadTask = nil
121 | loading = false
122 | }
123 | }
124 | }
125 | #endif
126 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/SwiftUI/ImageContext.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ImageContext.swift
3 | // Kingfisher
4 | //
5 | // Created by onevcat on 2021/05/08.
6 | //
7 | // Copyright (c) 2021 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | #if canImport(SwiftUI) && canImport(Combine)
28 | import SwiftUI
29 | import Combine
30 |
31 | @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
32 | extension KFImage {
33 | public class Context {
34 | let source: Source?
35 | var options = KingfisherParsedOptionsInfo(
36 | KingfisherManager.shared.defaultOptions + [.loadDiskFileSynchronously]
37 | )
38 |
39 | var configurations: [(HoldingView) -> HoldingView] = []
40 | var renderConfigurations: [(HoldingView.RenderingView) -> Void] = []
41 |
42 | var cancelOnDisappear: Bool = false
43 | var placeholder: ((Progress) -> AnyView)? = nil
44 |
45 | let onFailureDelegate = Delegate()
46 | let onSuccessDelegate = Delegate()
47 | let onProgressDelegate = Delegate<(Int64, Int64), Void>()
48 |
49 | init(source: Source?) {
50 | self.source = source
51 | }
52 |
53 | func shouldApplyFade(cacheType: CacheType) -> Bool {
54 | options.forceTransition || cacheType == .none
55 | }
56 |
57 | func fadeTransitionDuration(cacheType: CacheType) -> TimeInterval? {
58 | shouldApplyFade(cacheType: cacheType)
59 | ? options.transition.fadeDuration
60 | : nil
61 | }
62 | }
63 | }
64 |
65 | extension ImageTransition {
66 | // Only for fade effect in SwiftUI.
67 | fileprivate var fadeDuration: TimeInterval? {
68 | switch self {
69 | case .fade(let duration):
70 | return duration
71 | default:
72 | return nil
73 | }
74 | }
75 | }
76 |
77 |
78 | @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
79 | extension KFImage.Context: Hashable {
80 | public static func == (lhs: KFImage.Context, rhs: KFImage.Context) -> Bool {
81 | lhs.source == rhs.source &&
82 | lhs.options.processor.identifier == rhs.options.processor.identifier
83 | }
84 |
85 | public func hash(into hasher: inout Hasher) {
86 | hasher.combine(source)
87 | hasher.combine(options.processor.identifier)
88 | }
89 | }
90 |
91 | #if canImport(UIKit) && !os(watchOS)
92 | @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
93 | extension KFAnimatedImage {
94 | public typealias Context = KFImage.Context
95 | typealias ImageBinder = KFImage.ImageBinder
96 | }
97 | #endif
98 |
99 | #endif
100 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/SwiftUI/KFAnimatedImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KFAnimatedImage.swift
3 | // Kingfisher
4 | //
5 | // Created by wangxingbin on 2021/4/29.
6 | //
7 | // Copyright (c) 2021 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | #if canImport(SwiftUI) && canImport(Combine) && canImport(UIKit) && !os(watchOS)
28 | import SwiftUI
29 | import Combine
30 |
31 | @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
32 | public struct KFAnimatedImage: KFImageProtocol {
33 | public typealias HoldingView = KFAnimatedImageViewRepresenter
34 | public var context: Context
35 | public init(context: KFImage.Context) {
36 | self.context = context
37 | }
38 |
39 | /// Configures current rendering view with a `block`. This block will be applied when the under-hood
40 | /// `AnimatedImageView` is created in `UIViewRepresentable.makeUIView(context:)`
41 | ///
42 | /// - Parameter block: The block applies to the animated image view.
43 | /// - Returns: A `KFAnimatedImage` view that being configured by the `block`.
44 | public func configure(_ block: @escaping (HoldingView.RenderingView) -> Void) -> Self {
45 | context.renderConfigurations.append(block)
46 | return self
47 | }
48 | }
49 |
50 | /// A wrapped `UIViewRepresentable` of `AnimatedImageView`
51 | @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
52 | public struct KFAnimatedImageViewRepresenter: UIViewRepresentable, KFImageHoldingView {
53 | public typealias RenderingView = AnimatedImageView
54 | public static func created(from image: KFCrossPlatformImage?, context: KFImage.Context) -> KFAnimatedImageViewRepresenter {
55 | KFAnimatedImageViewRepresenter(image: image, context: context)
56 | }
57 |
58 | var image: KFCrossPlatformImage?
59 | let context: KFImage.Context
60 |
61 | public func makeUIView(context: Context) -> AnimatedImageView {
62 | let view = AnimatedImageView()
63 |
64 | self.context.renderConfigurations.forEach { $0(view) }
65 |
66 | view.image = image
67 |
68 | // Allow SwiftUI scale (fit/fill) working fine.
69 | view.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
70 | view.setContentCompressionResistancePriority(.defaultLow, for: .vertical)
71 | return view
72 | }
73 |
74 | public func updateUIView(_ uiView: AnimatedImageView, context: Context) {
75 | uiView.image = image
76 | }
77 |
78 | }
79 |
80 | #if DEBUG
81 | @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
82 | struct KFAnimatedImage_Previews : PreviewProvider {
83 | static var previews: some View {
84 | Group {
85 | KFAnimatedImage(source: .network(URL(string: "https://raw.githubusercontent.com/onevcat/Kingfisher-TestImages/master/DemoAppImage/GIF/1.gif")!))
86 | .onSuccess { r in
87 | print(r)
88 | }
89 | .placeholder {
90 | ProgressView()
91 | }
92 | .padding()
93 | }
94 | }
95 | }
96 | #endif
97 | #endif
98 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/SwiftUI/KFImage.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KFImage.swift
3 | // Kingfisher
4 | //
5 | // Created by onevcat on 2019/06/26.
6 | //
7 | // Copyright (c) 2019 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | #if canImport(SwiftUI) && canImport(Combine)
28 | import SwiftUI
29 | import Combine
30 |
31 | @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
32 | public struct KFImage: KFImageProtocol {
33 | public var context: Context
34 | public init(context: Context) {
35 | self.context = context
36 | }
37 | }
38 |
39 | @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
40 | extension Image: KFImageHoldingView {
41 | public typealias RenderingView = Image
42 | public static func created(from image: KFCrossPlatformImage?, context: KFImage.Context) -> Image {
43 | Image(crossPlatformImage: image)
44 | }
45 | }
46 |
47 | // MARK: - Image compatibility.
48 | @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
49 | extension KFImage {
50 |
51 | public func resizable(
52 | capInsets: EdgeInsets = EdgeInsets(),
53 | resizingMode: Image.ResizingMode = .stretch) -> KFImage
54 | {
55 | configure { $0.resizable(capInsets: capInsets, resizingMode: resizingMode) }
56 | }
57 |
58 | public func renderingMode(_ renderingMode: Image.TemplateRenderingMode?) -> KFImage {
59 | configure { $0.renderingMode(renderingMode) }
60 | }
61 |
62 | public func interpolation(_ interpolation: Image.Interpolation) -> KFImage {
63 | configure { $0.interpolation(interpolation) }
64 | }
65 |
66 | public func antialiased(_ isAntialiased: Bool) -> KFImage {
67 | configure { $0.antialiased(isAntialiased) }
68 | }
69 |
70 | /// Starts the loading process of `self` immediately.
71 | ///
72 | /// By default, a `KFImage` will not load its source until the `onAppear` is called. This is a lazily loading
73 | /// behavior and provides better performance. However, when you refresh the view, the lazy loading also causes a
74 | /// flickering since the loading does not happen immediately. Call this method if you want to start the load at once
75 | /// could help avoiding the flickering, with some performance trade-off.
76 | ///
77 | /// - Deprecated: This is not necessary anymore since `@StateObject` is used for holding the image data.
78 | /// It does nothing now and please just remove it.
79 | ///
80 | /// - Returns: The `Self` value with changes applied.
81 | @available(*, deprecated, message: "This is not necessary anymore since `@StateObject` is used. It does nothing now and please just remove it.")
82 | public func loadImmediately(_ start: Bool = true) -> KFImage {
83 | return self
84 | }
85 | }
86 |
87 | #if DEBUG
88 | @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
89 | struct KFImage_Previews : PreviewProvider {
90 | static var previews: some View {
91 | Group {
92 | KFImage.url(URL(string: "https://raw.githubusercontent.com/onevcat/Kingfisher/master/images/logo.png")!)
93 | .onSuccess { r in
94 | print(r)
95 | }
96 | .placeholder { p in
97 | ProgressView(p)
98 | }
99 | .resizable()
100 | .aspectRatio(contentMode: .fit)
101 | .padding()
102 | }
103 | }
104 | }
105 | #endif
106 | #endif
107 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/SwiftUI/KFImageOptions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KFImageOptions.swift
3 | // Kingfisher
4 | //
5 | // Created by onevcat on 2020/12/20.
6 | //
7 | // Copyright (c) 2020 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | #if canImport(SwiftUI) && canImport(Combine)
28 | import SwiftUI
29 | import Combine
30 |
31 | // MARK: - KFImage creating.
32 | @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
33 | extension KFImageProtocol {
34 |
35 | /// Creates a `KFImage` for a given `Source`.
36 | /// - Parameters:
37 | /// - source: The `Source` object defines data information from network or a data provider.
38 | /// - Returns: A `KFImage` for future configuration or embedding to a `SwiftUI.View`.
39 | public static func source(
40 | _ source: Source?
41 | ) -> Self
42 | {
43 | Self.init(source: source)
44 | }
45 |
46 | /// Creates a `KFImage` for a given `Resource`.
47 | /// - Parameters:
48 | /// - source: The `Resource` object defines data information like key or URL.
49 | /// - Returns: A `KFImage` for future configuration or embedding to a `SwiftUI.View`.
50 | public static func resource(
51 | _ resource: Resource?
52 | ) -> Self
53 | {
54 | source(resource?.convertToSource())
55 | }
56 |
57 | /// Creates a `KFImage` for a given `URL`.
58 | /// - Parameters:
59 | /// - url: The URL where the image should be downloaded.
60 | /// - cacheKey: The key used to store the downloaded image in cache.
61 | /// If `nil`, the `absoluteString` of `url` is used as the cache key.
62 | /// - Returns: A `KFImage` for future configuration or embedding to a `SwiftUI.View`.
63 | public static func url(
64 | _ url: URL?, cacheKey: String? = nil
65 | ) -> Self
66 | {
67 | source(url?.convertToSource(overrideCacheKey: cacheKey))
68 | }
69 |
70 | /// Creates a `KFImage` for a given `ImageDataProvider`.
71 | /// - Parameters:
72 | /// - provider: The `ImageDataProvider` object contains information about the data.
73 | /// - Returns: A `KFImage` for future configuration or embedding to a `SwiftUI.View`.
74 | public static func dataProvider(
75 | _ provider: ImageDataProvider?
76 | ) -> Self
77 | {
78 | source(provider?.convertToSource())
79 | }
80 |
81 | /// Creates a builder for some given raw data and a cache key.
82 | /// - Parameters:
83 | /// - data: The data object from which the image should be created.
84 | /// - cacheKey: The key used to store the downloaded image in cache.
85 | /// - Returns: A `KFImage` for future configuration or embedding to a `SwiftUI.View`.
86 | public static func data(
87 | _ data: Data?, cacheKey: String
88 | ) -> Self
89 | {
90 | if let data = data {
91 | return dataProvider(RawImageDataProvider(data: data, cacheKey: cacheKey))
92 | } else {
93 | return dataProvider(nil)
94 | }
95 | }
96 | }
97 |
98 | @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
99 | extension KFImageProtocol {
100 | /// Sets a placeholder `View` which shows when loading the image, with a progress parameter as input.
101 | /// - Parameter content: A view that describes the placeholder.
102 | /// - Returns: A `KFImage` view that contains `content` as its placeholder.
103 | public func placeholder(@ViewBuilder _ content: @escaping (Progress) -> P) -> Self {
104 | context.placeholder = { progress in
105 | return AnyView(content(progress))
106 | }
107 | return self
108 | }
109 |
110 | /// Sets a placeholder `View` which shows when loading the image.
111 | /// - Parameter content: A view that describes the placeholder.
112 | /// - Returns: A `KFImage` view that contains `content` as its placeholder.
113 | public func placeholder(@ViewBuilder _ content: @escaping () -> P) -> Self {
114 | placeholder { _ in content() }
115 | }
116 |
117 | /// Sets cancelling the download task bound to `self` when the view disappearing.
118 | /// - Parameter flag: Whether cancel the task or not.
119 | /// - Returns: A `KFImage` view that cancels downloading task when disappears.
120 | public func cancelOnDisappear(_ flag: Bool) -> Self {
121 | context.cancelOnDisappear = flag
122 | return self
123 | }
124 |
125 | /// Sets a fade transition for the image task.
126 | /// - Parameter duration: The duration of the fade transition.
127 | /// - Returns: A `KFImage` with changes applied.
128 | ///
129 | /// Kingfisher will use the fade transition to animate the image in if it is downloaded from web.
130 | /// The transition will not happen when the
131 | /// image is retrieved from either memory or disk cache by default. If you need to do the transition even when
132 | /// the image being retrieved from cache, also call `forceRefresh()` on the returned `KFImage`.
133 | public func fade(duration: TimeInterval) -> Self {
134 | context.options.transition = .fade(duration)
135 | return self
136 | }
137 | }
138 | #endif
139 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/SwiftUI/KFImageProtocol.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KFImageProtocol.swift
3 | // Kingfisher
4 | //
5 | // Created by onevcat on 2021/05/08.
6 | //
7 | // Copyright (c) 2021 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | #if canImport(SwiftUI) && canImport(Combine)
28 | import SwiftUI
29 | import Combine
30 |
31 | @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
32 | public protocol KFImageProtocol: View, KFOptionSetter {
33 | associatedtype HoldingView: KFImageHoldingView
34 | var context: KFImage.Context { get set }
35 | init(context: KFImage.Context)
36 | }
37 |
38 | @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
39 | extension KFImageProtocol {
40 | public var body: some View {
41 | ZStack {
42 | KFImageRenderer(
43 | context: context
44 | ).id(context)
45 | }
46 | }
47 |
48 | /// Creates a Kingfisher compatible image view to load image from the given `Source`.
49 | /// - Parameters:
50 | /// - source: The image `Source` defining where to load the target image.
51 | public init(source: Source?) {
52 | let context = KFImage.Context(source: source)
53 | self.init(context: context)
54 | }
55 |
56 | /// Creates a Kingfisher compatible image view to load image from the given `URL`.
57 | /// - Parameters:
58 | /// - source: The image `Source` defining where to load the target image.
59 | public init(_ url: URL?) {
60 | self.init(source: url?.convertToSource())
61 | }
62 |
63 | /// Configures current image with a `block`. This block will be lazily applied when creating the final `Image`.
64 | /// - Parameter block: The block applies to loaded image.
65 | /// - Returns: A `KFImage` view that configures internal `Image` with `block`.
66 | public func configure(_ block: @escaping (HoldingView) -> HoldingView) -> Self {
67 | context.configurations.append(block)
68 | return self
69 | }
70 | }
71 |
72 | @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
73 | public protocol KFImageHoldingView: View {
74 | associatedtype RenderingView
75 | static func created(from image: KFCrossPlatformImage?, context: KFImage.Context) -> Self
76 | }
77 |
78 | @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
79 | extension KFImageProtocol {
80 | public var options: KingfisherParsedOptionsInfo {
81 | get { context.options }
82 | nonmutating set { context.options = newValue }
83 | }
84 |
85 | public var onFailureDelegate: Delegate { context.onFailureDelegate }
86 | public var onSuccessDelegate: Delegate { context.onSuccessDelegate }
87 | public var onProgressDelegate: Delegate<(Int64, Int64), Void> { context.onProgressDelegate }
88 |
89 | public var delegateObserver: AnyObject { context }
90 | }
91 |
92 |
93 | #endif
94 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/SwiftUI/KFImageRenderer.swift:
--------------------------------------------------------------------------------
1 | //
2 | // KFImageRenderer.swift
3 | // Kingfisher
4 | //
5 | // Created by onevcat on 2021/05/08.
6 | //
7 | // Copyright (c) 2021 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | #if canImport(SwiftUI) && canImport(Combine)
28 | import SwiftUI
29 | import Combine
30 |
31 | /// A Kingfisher compatible SwiftUI `View` to load an image from a `Source`.
32 | /// Declaring a `KFImage` in a `View`'s body to trigger loading from the given `Source`.
33 | @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
34 | struct KFImageRenderer : View where HoldingView: KFImageHoldingView {
35 |
36 | @StateObject var binder: KFImage.ImageBinder = .init()
37 | let context: KFImage.Context
38 |
39 | var body: some View {
40 | ZStack {
41 | context.configurations
42 | .reduce(HoldingView.created(from: binder.loadedImage, context: context)) {
43 | current, config in config(current)
44 | }
45 | .opacity(binder.loaded ? 1.0 : 0.0)
46 | if binder.loadedImage == nil {
47 | Group {
48 | if let placeholder = context.placeholder, let view = placeholder(binder.progress) {
49 | view
50 | } else {
51 | Color.clear
52 | }
53 | }
54 | .onAppear { [weak binder = self.binder] in
55 | guard let binder = binder else {
56 | return
57 | }
58 | if !binder.loadingOrSucceeded {
59 | binder.start(context: context)
60 | }
61 | }
62 | .onDisappear { [weak binder = self.binder] in
63 | guard let binder = binder else {
64 | return
65 | }
66 | if context.cancelOnDisappear {
67 | binder.cancel()
68 | }
69 | }
70 | }
71 | }
72 | }
73 | }
74 |
75 | @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
76 | extension Image {
77 | // Creates an Image with either UIImage or NSImage.
78 | init(crossPlatformImage: KFCrossPlatformImage?) {
79 | #if canImport(UIKit)
80 | self.init(uiImage: crossPlatformImage ?? KFCrossPlatformImage())
81 | #elseif canImport(AppKit)
82 | self.init(nsImage: crossPlatformImage ?? KFCrossPlatformImage())
83 | #endif
84 | }
85 | }
86 |
87 | #if canImport(UIKit)
88 | @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
89 | extension UIImage.Orientation {
90 | func toSwiftUI() -> Image.Orientation {
91 | switch self {
92 | case .down: return .down
93 | case .up: return .up
94 | case .left: return .left
95 | case .right: return .right
96 | case .upMirrored: return .upMirrored
97 | case .downMirrored: return .downMirrored
98 | case .leftMirrored: return .leftMirrored
99 | case .rightMirrored: return .rightMirrored
100 | @unknown default: return .up
101 | }
102 | }
103 | }
104 | #endif
105 | #endif
106 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/Utility/Box.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Box.swift
3 | // Kingfisher
4 | //
5 | // Created by Wei Wang on 2018/3/17.
6 | // Copyright (c) 2019 Wei Wang
7 | //
8 | // Permission is hereby granted, free of charge, to any person obtaining a copy
9 | // of this software and associated documentation files (the "Software"), to deal
10 | // in the Software without restriction, including without limitation the rights
11 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | // copies of the Software, and to permit persons to whom the Software is
13 | // furnished to do so, subject to the following conditions:
14 | //
15 | // The above copyright notice and this permission notice shall be included in
16 | // all copies or substantial portions of the Software.
17 | //
18 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
24 | // THE SOFTWARE.
25 |
26 | import Foundation
27 |
28 | class Box {
29 | var value: T
30 |
31 | init(_ value: T) {
32 | self.value = value
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/Utility/CallbackQueue.swift:
--------------------------------------------------------------------------------
1 | //
2 | // CallbackQueue.swift
3 | // Kingfisher
4 | //
5 | // Created by onevcat on 2018/10/15.
6 | //
7 | // Copyright (c) 2019 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 |
29 | public typealias ExecutionQueue = CallbackQueue
30 |
31 | /// Represents callback queue behaviors when an calling of closure be dispatched.
32 | ///
33 | /// - asyncMain: Dispatch the calling to `DispatchQueue.main` with an `async` behavior.
34 | /// - currentMainOrAsync: Dispatch the calling to `DispatchQueue.main` with an `async` behavior if current queue is not
35 | /// `.main`. Otherwise, call the closure immediately in current main queue.
36 | /// - untouch: Do not change the calling queue for closure.
37 | /// - dispatch: Dispatches to a specified `DispatchQueue`.
38 | public enum CallbackQueue {
39 | /// Dispatch the calling to `DispatchQueue.main` with an `async` behavior.
40 | case mainAsync
41 | /// Dispatch the calling to `DispatchQueue.main` with an `async` behavior if current queue is not
42 | /// `.main`. Otherwise, call the closure immediately in current main queue.
43 | case mainCurrentOrAsync
44 | /// Do not change the calling queue for closure.
45 | case untouch
46 | /// Dispatches to a specified `DispatchQueue`.
47 | case dispatch(DispatchQueue)
48 |
49 | public func execute(_ block: @escaping () -> Void) {
50 | switch self {
51 | case .mainAsync:
52 | DispatchQueue.main.async { block() }
53 | case .mainCurrentOrAsync:
54 | DispatchQueue.main.safeAsync { block() }
55 | case .untouch:
56 | block()
57 | case .dispatch(let queue):
58 | queue.async { block() }
59 | }
60 | }
61 |
62 | var queue: DispatchQueue {
63 | switch self {
64 | case .mainAsync: return .main
65 | case .mainCurrentOrAsync: return .main
66 | case .untouch: return OperationQueue.current?.underlyingQueue ?? .main
67 | case .dispatch(let queue): return queue
68 | }
69 | }
70 | }
71 |
72 | extension DispatchQueue {
73 | // This method will dispatch the `block` to self.
74 | // If `self` is the main queue, and current thread is main thread, the block
75 | // will be invoked immediately instead of being dispatched.
76 | func safeAsync(_ block: @escaping () -> Void) {
77 | if self === DispatchQueue.main && Thread.isMainThread {
78 | block()
79 | } else {
80 | async { block() }
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/Utility/Delegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Delegate.swift
3 | // Kingfisher
4 | //
5 | // Created by onevcat on 2018/10/10.
6 | //
7 | // Copyright (c) 2019 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 | /// A class that keeps a weakly reference for `self` when implementing `onXXX` behaviors.
29 | /// Instead of remembering to keep `self` as weak in a stored closure:
30 | ///
31 | /// ```swift
32 | /// // MyClass.swift
33 | /// var onDone: (() -> Void)?
34 | /// func done() {
35 | /// onDone?()
36 | /// }
37 | ///
38 | /// // ViewController.swift
39 | /// var obj: MyClass?
40 | ///
41 | /// func doSomething() {
42 | /// obj = MyClass()
43 | /// obj!.onDone = { [weak self] in
44 | /// self?.reportDone()
45 | /// }
46 | /// }
47 | /// ```
48 | ///
49 | /// You can create a `Delegate` and observe on `self`. Now, there is no retain cycle inside:
50 | ///
51 | /// ```swift
52 | /// // MyClass.swift
53 | /// let onDone = Delegate<(), Void>()
54 | /// func done() {
55 | /// onDone.call()
56 | /// }
57 | ///
58 | /// // ViewController.swift
59 | /// var obj: MyClass?
60 | ///
61 | /// func doSomething() {
62 | /// obj = MyClass()
63 | /// obj!.onDone.delegate(on: self) { (self, _)
64 | /// // `self` here is shadowed and does not keep a strong ref.
65 | /// // So you can release both `MyClass` instance and `ViewController` instance.
66 | /// self.reportDone()
67 | /// }
68 | /// }
69 | /// ```
70 | ///
71 | public class Delegate {
72 | public init() {}
73 |
74 | private var block: ((Input) -> Output?)?
75 | public func delegate(on target: T, block: ((T, Input) -> Output)?) {
76 | self.block = { [weak target] input in
77 | guard let target = target else { return nil }
78 | return block?(target, input)
79 | }
80 | }
81 |
82 | public func call(_ input: Input) -> Output? {
83 | return block?(input)
84 | }
85 |
86 | public func callAsFunction(_ input: Input) -> Output? {
87 | return call(input)
88 | }
89 | }
90 |
91 | extension Delegate where Input == Void {
92 | public func call() -> Output? {
93 | return call(())
94 | }
95 |
96 | public func callAsFunction() -> Output? {
97 | return call()
98 | }
99 | }
100 |
101 | extension Delegate where Input == Void, Output: OptionalProtocol {
102 | public func call() -> Output {
103 | return call(())
104 | }
105 |
106 | public func callAsFunction() -> Output {
107 | return call()
108 | }
109 | }
110 |
111 | extension Delegate where Output: OptionalProtocol {
112 | public func call(_ input: Input) -> Output {
113 | if let result = block?(input) {
114 | return result
115 | } else {
116 | return Output._createNil
117 | }
118 | }
119 |
120 | public func callAsFunction(_ input: Input) -> Output {
121 | return call(input)
122 | }
123 | }
124 |
125 | public protocol OptionalProtocol {
126 | static var _createNil: Self { get }
127 | }
128 | extension Optional : OptionalProtocol {
129 | public static var _createNil: Optional {
130 | return nil
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/Utility/ExtensionHelpers.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExtensionHelpers.swift
3 | // Kingfisher
4 | //
5 | // Created by onevcat on 2018/09/28.
6 | //
7 | // Copyright (c) 2019 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 |
29 | extension CGFloat {
30 | var isEven: Bool {
31 | return truncatingRemainder(dividingBy: 2.0) == 0
32 | }
33 | }
34 |
35 | #if canImport(AppKit) && !targetEnvironment(macCatalyst)
36 | import AppKit
37 | extension NSBezierPath {
38 | convenience init(roundedRect rect: NSRect, topLeftRadius: CGFloat, topRightRadius: CGFloat,
39 | bottomLeftRadius: CGFloat, bottomRightRadius: CGFloat)
40 | {
41 | self.init()
42 |
43 | let maxCorner = min(rect.width, rect.height) / 2
44 |
45 | let radiusTopLeft = min(maxCorner, max(0, topLeftRadius))
46 | let radiusTopRight = min(maxCorner, max(0, topRightRadius))
47 | let radiusBottomLeft = min(maxCorner, max(0, bottomLeftRadius))
48 | let radiusBottomRight = min(maxCorner, max(0, bottomRightRadius))
49 |
50 | guard !rect.isEmpty else {
51 | return
52 | }
53 |
54 | let topLeft = NSPoint(x: rect.minX, y: rect.maxY)
55 | let topRight = NSPoint(x: rect.maxX, y: rect.maxY)
56 | let bottomRight = NSPoint(x: rect.maxX, y: rect.minY)
57 |
58 | move(to: NSPoint(x: rect.midX, y: rect.maxY))
59 | appendArc(from: topLeft, to: rect.origin, radius: radiusTopLeft)
60 | appendArc(from: rect.origin, to: bottomRight, radius: radiusBottomLeft)
61 | appendArc(from: bottomRight, to: topRight, radius: radiusBottomRight)
62 | appendArc(from: topRight, to: topLeft, radius: radiusTopRight)
63 | close()
64 | }
65 |
66 | convenience init(roundedRect rect: NSRect, byRoundingCorners corners: RectCorner, radius: CGFloat) {
67 | let radiusTopLeft = corners.contains(.topLeft) ? radius : 0
68 | let radiusTopRight = corners.contains(.topRight) ? radius : 0
69 | let radiusBottomLeft = corners.contains(.bottomLeft) ? radius : 0
70 | let radiusBottomRight = corners.contains(.bottomRight) ? radius : 0
71 |
72 | self.init(roundedRect: rect, topLeftRadius: radiusTopLeft, topRightRadius: radiusTopRight,
73 | bottomLeftRadius: radiusBottomLeft, bottomRightRadius: radiusBottomRight)
74 | }
75 | }
76 |
77 | extension KFCrossPlatformImage {
78 | // macOS does not support scale. This is just for code compatibility across platforms.
79 | convenience init?(data: Data, scale: CGFloat) {
80 | self.init(data: data)
81 | }
82 | }
83 | #endif
84 |
85 | #if canImport(UIKit)
86 | import UIKit
87 | extension RectCorner {
88 | var uiRectCorner: UIRectCorner {
89 |
90 | var result: UIRectCorner = []
91 |
92 | if contains(.topLeft) { result.insert(.topLeft) }
93 | if contains(.topRight) { result.insert(.topRight) }
94 | if contains(.bottomLeft) { result.insert(.bottomLeft) }
95 | if contains(.bottomRight) { result.insert(.bottomRight) }
96 |
97 | return result
98 | }
99 | }
100 | #endif
101 |
102 | extension Date {
103 | var isPast: Bool {
104 | return isPast(referenceDate: Date())
105 | }
106 |
107 | var isFuture: Bool {
108 | return !isPast
109 | }
110 |
111 | func isPast(referenceDate: Date) -> Bool {
112 | return timeIntervalSince(referenceDate) <= 0
113 | }
114 |
115 | func isFuture(referenceDate: Date) -> Bool {
116 | return !isPast(referenceDate: referenceDate)
117 | }
118 |
119 | // `Date` in memory is a wrap for `TimeInterval`. But in file attribute it can only accept `Int` number.
120 | // By default the system will `round` it. But it is not friendly for testing purpose.
121 | // So we always `ceil` the value when used for file attributes.
122 | var fileAttributeDate: Date {
123 | return Date(timeIntervalSince1970: ceil(timeIntervalSince1970))
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/Pods/Kingfisher/Sources/Utility/Result.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Result.swift
3 | // Kingfisher
4 | //
5 | // Created by onevcat on 2018/09/22.
6 | //
7 | // Copyright (c) 2019 Wei Wang
8 | //
9 | // Permission is hereby granted, free of charge, to any person obtaining a copy
10 | // of this software and associated documentation files (the "Software"), to deal
11 | // in the Software without restriction, including without limitation the rights
12 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13 | // copies of the Software, and to permit persons to whom the Software is
14 | // furnished to do so, subject to the following conditions:
15 | //
16 | // The above copyright notice and this permission notice shall be included in
17 | // all copies or substantial portions of the Software.
18 | //
19 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
25 | // THE SOFTWARE.
26 |
27 | import Foundation
28 |
29 | // These helper methods are not public since we do not want them to be exposed or cause any conflicting.
30 | // However, they are just wrapper of `ResultUtil` static methods.
31 | extension Result where Failure: Error {
32 |
33 | /// Evaluates the given transform closures to create a single output value.
34 | ///
35 | /// - Parameters:
36 | /// - onSuccess: A closure that transforms the success value.
37 | /// - onFailure: A closure that transforms the error value.
38 | /// - Returns: A single `Output` value.
39 | func match