You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

187 lines
8.4 KiB

2 years ago
  1. <p align="center"><img src="https://user-images.githubusercontent.com/1567433/34322222-f47252a6-e832-11e7-972c-fb48d8ec97dc.png" height="180"/>
  2. <p align="center">
  3. <img src="https://img.shields.io/cocoapods/v/RxNuke.svg?label=version">
  4. <img src="https://img.shields.io/badge/supports-Swift%20Package%20Manager%2C%20CocoaPods%2C%20Carthage-green.svg">
  5. <img src="https://img.shields.io/badge/platforms-iOS%20%7C%20macOS%20%7C%20watchOS%20%7C%20tvOS-lightgrey.svg">
  6. </p>
  7. This repository contains [RxSwift](https://github.com/ReactiveX/RxSwift) extensions for [Nuke](https://github.com/kean/Nuke) as well as examples of common [use cases](#h_use_cases) solved by Rx.
  8. # <a name="h_use_cases"></a>Use Cases
  9. - [Going From Low to High Resolution](#huc_low_to_high)
  10. - [Loading the First Available Image](#huc_loading_first_avail)
  11. - [Load Multiple Images, Display All at Once](#huc_load_multiple_display_once)
  12. - [Showing Stale Image While Validating It](#huc_showing_stale_first)
  13. - [Auto Retry](#huc_auto_retry)
  14. - [Tracking Activities](#huc_activity_indicator)
  15. - [Display Placeholder on Failure](#huc_placeholder_on_fail)
  16. - [Table or Collection View](#huc_table_collection_view)
  17. # <a name="h_getting_started"></a>Getting Started
  18. - [Installation Guide](https://github.com/kean/RxNuke/blob/master/Documentation/Guides/Installation%20Guide.md)
  19. - [Getting Started with RxSwift](https://github.com/ReactiveX/RxSwift/blob/master/Documentation/GettingStarted.md)
  20. # <a name="h_usage"></a>Usage
  21. RxNuke provides a set of reactive extensions for Nuke:
  22. ```swift
  23. extension Reactive where Base: ImagePipeline {
  24. public func loadImage(with url: URL) -> Single<ImageResponse>
  25. public func loadImage(with request: ImageRequest) -> Single<ImageResponse>
  26. }
  27. ```
  28. > A `Single` is a variation of `Observable` that, instead of emitting a series of elements, is always guaranteed to emit either a single element or an error. The common use case of `Single` is to wrap HTTP requests. See [Traits](https://github.com/ReactiveX/RxSwift/blob/master/Documentation/Traits.md#single) for more info.
  29. Here's a basic example where we load an image and display the result on success:
  30. ```swift
  31. ImagePipeline.shared.rx.loadImage(with: url)
  32. .subscribe(onSuccess: { imageView.image = $0.image })
  33. .disposed(by: disposeBag)
  34. ```
  35. ### <a name="huc_low_to_high"></a>Going From Low to High Resolution
  36. Suppose you want to show users a high-resolution, slow-to-download image. Rather than let them stare a placeholder for a while, you might want to quickly download a smaller thumbnail first.
  37. You can implement this using [`concat`](http://reactivex.io/documentation/operators/concat.html) operator which results in a **serial** execution. It would first start a thumbnail request, wait until it finishes, and only then start a request for a high-resolution image.
  38. ```swift
  39. Observable.concat(pipeline.rx.loadImage(with: lowResUrl).orEmpty,
  40. pipeline.rx.loadImage(with: highResUtl).orEmpty)
  41. .subscribe(onNext: { imageView.image = $0.image })
  42. .disposed(by: disposeBag)
  43. ```
  44. > `orEmpty` is a custom property which ignores errors and completes the sequence instead
  45. > (equivalent to `func catchErrorJustComplete()` from [RxSwiftExt](https://github.com/RxSwiftCommunity/RxSwiftExt).
  46. >
  47. > extension RxSwift.PrimitiveSequence {
  48. > public var orEmpty: Observable<Element> {
  49. > return self.asObservable().catchError { _ in .empty() }
  50. > }
  51. > }
  52. ### <a name="huc_loading_first_avail"></a>Loading the First Available Image
  53. Suppose you have multiple URLs for the same image. For instance, you might have uploaded an image taken from the camera. In such case, it would be beneficial to first try to get the local URL, and if that fails, try to get the network URL. It would be a shame to download the image that we may have already locally.
  54. This use case is very similar [Going From Low to High Resolution](#huc_low_to_high), but an addition of `.take(1)` guarantees that we stop execution as soon as we receive the first result.
  55. ```swift
  56. Observable.concat(pipeline.rx.loadImage(with: localUrl).orEmpty,
  57. pipeline.rx.loadImage(with: networkUrl).orEmpty)
  58. .take(1)
  59. .subscribe(onNext: { imageView.image = $0.image })
  60. .disposed(by: disposeBag)
  61. ```
  62. ### <a name="huc_load_multiple_display_once"></a>Load Multiple Images, Display All at Once
  63. Suppose you want to load two icons for a button, one icon for `.normal` state and one for `.selected` state. Only when both icons are loaded you can show the button to the user. This can be done using a [`combineLatest`](http://reactivex.io/documentation/operators/combinelatest.html) operator:
  64. ```swift
  65. Observable.combineLatest(pipeline.rx.loadImage(with: iconUrl).asObservable(),
  66. pipeline.rx.loadImage(with: iconSelectedUrl).asObservable())
  67. .subscribe(onNext: { icon, iconSelected in
  68. button.isHidden = false
  69. button.setImage(icon.image, for: .normal)
  70. button.setImage(iconSelected.image, for: .selected)
  71. }).disposed(by: disposeBag)
  72. ```
  73. ### <a name="huc_showing_stale_first"></a>Showing Stale Image While Validating It
  74. Suppose you want to show users a stale image stored in a disk cache (`Foundation.URLCache`) while you go to the server to validate it. This use case is actually similar to [Going From Low to High Resolution](#huc_low_to_high).
  75. ```swift
  76. let cacheRequest = URLRequest(url: imageUrl, cachePolicy: .returnCacheDataDontLoad)
  77. let networkRequest = URLRequest(url: imageUrl, cachePolicy: .useProtocolCachePolicy)
  78. Observable.concat(pipeline.rx.loadImage(with: ImageRequest(urlRequest: cacheRequest).orEmpty,
  79. pipeline.rx.loadImage(with: ImageRequest(urlRequest: networkRequest)).orEmpty)
  80. .subscribe(onNext: { imageView.image = $0.image })
  81. .disposed(by: disposeBag)
  82. ```
  83. > See [Image Caching](https://kean.github.io/post/image-caching) to learn more about HTTP cache
  84. ### <a name="huc_auto_retry"></a>Auto Retry
  85. Auto-retry with an exponential backoff of other delay options (including immediate retry when a network connection is re-established) using [smart retry](https://kean.github.io/post/smart-retry).
  86. ```swift
  87. pipeline.rx.loadImage(with: request).asObservable()
  88. .retry(3, delay: .exponential(initial: 3, multiplier: 1, maxDelay: 16))
  89. .subscribe(onNext: { imageView.image = $0.image })
  90. .disposed(by: disposeBag)
  91. ```
  92. ### <a name="huc_activity_indicator"></a>Tracking Activities
  93. Suppose you want to show an activity indicator while waiting for an image to load. Here's how you can do it using `ActivityIndicator` class provided by [`RxSwiftUtilities`](https://github.com/RxSwiftCommunity/RxSwiftUtilities):
  94. ```swift
  95. let isBusy = ActivityIndicator()
  96. pipeline.rx.loadImage(with: imageUrl)
  97. .trackActivity(isBusy)
  98. .subscribe(onNext: { imageView.image = $0.image })
  99. .disposed(by: disposeBag)
  100. isBusy.asDriver()
  101. .drive(activityIndicator.rx.isAnimating)
  102. .disposed(by: disposeBag)
  103. ```
  104. ### <a name="huc_table_collection_view"></a>In a Table or Collection View
  105. Here's how you can integrate the code provided in the previous examples into your table or collection view cells:
  106. ```swift
  107. final class ImageCell: UICollectionViewCell {
  108. private var imageView: UIImageView!
  109. private var disposeBag = DisposeBag()
  110. // <.. create an image view using your preferred way ..>
  111. func display(_ image: Single<ImageResponse>) {
  112. // Create a new dispose bag, previous dispose bag gets deallocated
  113. // and cancels all previous subscriptions.
  114. disposeBag = DisposeBag()
  115. imageView.image = nil
  116. // Load an image and display the result on success.
  117. image.subscribe(onSuccess: { [weak self] response in
  118. self?.imageView.image = response.image
  119. }).disposed(by: disposeBag)
  120. }
  121. }
  122. ```
  123. <a name="h_requirements"></a>
  124. # Requirements
  125. | RxNuke | Swift | Xcode | Platforms |
  126. |------------------|-----------------------|----------------------|----------------------------------------------------|
  127. | RxNuke 1.0 | Swift 5.1 | Xcode 11.0 | iOS 11.0 / watchOS 4.0 / macOS 10.13 / tvOS 11.0 |
  128. | RxNuke 0.8 | Swift 4.2 – 5.0 | Xcode 10.1 – 10.2 | iOS 10.0 / watchOS 3.0 / macOS 10.12 / tvOS 10.0 |
  129. | RxNuke 0.7 | Swift 4.0 – 4.2 | Xcode 9.2 – 10.1 | iOS 9.0 / watchOS 2.0 / macOS 10.10 / tvOS 9.0 |
  130. # License
  131. RxNuke is available under the MIT license. See the LICENSE file for more info.