Skip to content

Instantly share code, notes, and snippets.

@SpectralDragon
Created October 31, 2019 11:33
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SpectralDragon/4ddd2a01d8027a2ff831af8859861764 to your computer and use it in GitHub Desktop.
Save SpectralDragon/4ddd2a01d8027a2ff831af8859861764 to your computer and use it in GitHub Desktop.
A class for dynamically update image using current trait collections. It's doesn't work for navigation bar and tab bar, because image doesn't update when trait collection did change.
public extension UIImage {
/// Creates a dynamic image that supports displaying a different image asset when dark mode is active.
static func dynamic(
light makeLight: @autoclosure () -> UIImage,
dark makeDark: @autoclosure () -> UIImage
) -> UIImage {
if #available(iOS 13, *) {
return UIDynamicProviderImage(light: makeLight(), dark: makeDark())
} else {
return makeLight()
}
}
@available(iOS 13.0, *)
var isDynamic: Bool {
return self is UIDynamicProviderImage
}
@available(iOS 13.0, *)
func resolvedImage(with traitCollection: UITraitCollection) -> UIImage {
if let dynamicProvider = self as? UIDynamicProviderImage {
return dynamicProvider._resolvedImage(with: traitCollection)
} else {
return self
}
}
}
@available(iOS 13.0, *)
class UIDynamicProviderImage: UIImage {
private let _imageAsset: UIImageAsset
private var _lastResolvedImage: UIImage
private var imageForCurrentTraitCollection: UIImage {
if _lastResolvedImage.traitCollection.hasDifferentColorAppearance(comparedTo: .current) {
let newResolvedColor = self._resolvedImage(with: .current)
_lastResolvedImage = newResolvedColor
return newResolvedColor
} else {
return _lastResolvedImage
}
}
init(light makeLight: @autoclosure () -> UIImage, dark makeDark: @autoclosure () -> UIImage) {
let imageAsset = UIImageAsset()
imageAsset.register(makeLight(), with: UITraitCollection(userInterfaceStyle: .light))
imageAsset.register(makeDark(), with: UITraitCollection(userInterfaceStyle: .dark))
self._imageAsset = imageAsset
self._lastResolvedImage = imageAsset.image(with: .current)
super.init()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func _resolvedImage(with traitCollection: UITraitCollection) -> UIImage {
return self._imageAsset.image(with: traitCollection)
}
// MARK: - Overriding
override var imageAsset: UIImageAsset? {
return _imageAsset
}
override var size: CGSize {
return self.imageForCurrentTraitCollection.size
}
override var traitCollection: UITraitCollection {
return self.imageForCurrentTraitCollection.traitCollection
}
override var imageOrientation: UIImage.Orientation {
return self.imageForCurrentTraitCollection.imageOrientation
}
override var resizingMode: UIImage.ResizingMode {
return self.imageForCurrentTraitCollection.resizingMode
}
override var configuration: UIImage.Configuration? {
return self.imageForCurrentTraitCollection.configuration
}
override var scale: CGFloat {
return imageForCurrentTraitCollection.scale
}
override var cgImage: CGImage? {
return self.imageForCurrentTraitCollection.cgImage
}
override var ciImage: CIImage? {
return self.imageForCurrentTraitCollection.ciImage
}
override func draw(in rect: CGRect, blendMode: CGBlendMode, alpha: CGFloat) {
self.imageForCurrentTraitCollection.draw(in: rect, blendMode: blendMode, alpha: alpha)
}
override func draw(at point: CGPoint) {
self.imageForCurrentTraitCollection.draw(at: point)
}
override func draw(in rect: CGRect) {
self.imageForCurrentTraitCollection.draw(in: rect)
}
override func draw(at point: CGPoint, blendMode: CGBlendMode, alpha: CGFloat) {
self.imageForCurrentTraitCollection.draw(at: point, blendMode: blendMode, alpha: alpha)
}
override func drawAsPattern(in rect: CGRect) {
self.imageForCurrentTraitCollection.drawAsPattern(in: rect)
}
override func withConfiguration(_ configuration: UIImage.Configuration) -> UIImage {
return self.imageForCurrentTraitCollection.withConfiguration(configuration)
}
override func resizableImage(withCapInsets capInsets: UIEdgeInsets) -> UIImage {
return self.imageForCurrentTraitCollection.resizableImage(withCapInsets: capInsets)
}
override func resizableImage(withCapInsets capInsets: UIEdgeInsets, resizingMode: UIImage.ResizingMode) -> UIImage {
return self.imageForCurrentTraitCollection.resizableImage(withCapInsets: capInsets, resizingMode: resizingMode)
}
override var isSymbolImage: Bool {
return self.imageForCurrentTraitCollection.isSymbolImage
}
override var images: [UIImage]? {
return self.imageForCurrentTraitCollection.images
}
override var duration: TimeInterval {
return self.imageForCurrentTraitCollection.duration
}
override var capInsets: UIEdgeInsets {
return self.imageForCurrentTraitCollection.capInsets
}
override func withAlignmentRectInsets(_ alignmentInsets: UIEdgeInsets) -> UIImage {
return self.imageForCurrentTraitCollection.withAlignmentRectInsets(alignmentInsets)
}
override var alignmentRectInsets: UIEdgeInsets {
return self.imageForCurrentTraitCollection.alignmentRectInsets
}
override func withRenderingMode(_ renderingMode: UIImage.RenderingMode) -> UIImage {
return self.imageForCurrentTraitCollection.withRenderingMode(renderingMode)
}
override var renderingMode: UIImage.RenderingMode {
return self.imageForCurrentTraitCollection.renderingMode
}
override var imageRendererFormat: UIGraphicsImageRendererFormat {
return self.imageForCurrentTraitCollection.imageRendererFormat
}
override func imageFlippedForRightToLeftLayoutDirection() -> UIImage {
return self.imageForCurrentTraitCollection.imageFlippedForRightToLeftLayoutDirection()
}
override var flipsForRightToLeftLayoutDirection: Bool {
return self.imageForCurrentTraitCollection.flipsForRightToLeftLayoutDirection
}
override func withHorizontallyFlippedOrientation() -> UIImage {
return self.imageForCurrentTraitCollection.withHorizontallyFlippedOrientation()
}
override var __baselineOffsetFromBottom: CGFloat {
return self.imageForCurrentTraitCollection.baselineOffsetFromBottom ?? 0
}
override var __hasBaseline: Bool {
return self.imageForCurrentTraitCollection.baselineOffsetFromBottom != nil
}
override func withBaselineOffset(fromBottom baselineOffset: CGFloat) -> UIImage {
return self.imageForCurrentTraitCollection.withBaselineOffset(fromBottom: baselineOffset)
}
override func imageWithoutBaseline() -> UIImage {
return self.imageForCurrentTraitCollection.imageWithoutBaseline()
}
override var symbolConfiguration: UIImage.SymbolConfiguration? {
return self.imageForCurrentTraitCollection.symbolConfiguration
}
override func applyingSymbolConfiguration(_ configuration: UIImage.SymbolConfiguration) -> UIImage? {
return self.imageForCurrentTraitCollection.applyingSymbolConfiguration(configuration)
}
override func withTintColor(_ color: UIColor) -> UIImage {
return self.imageForCurrentTraitCollection.withTintColor(color)
}
override func withTintColor(_ color: UIColor, renderingMode: UIImage.RenderingMode) -> UIImage {
return self.imageForCurrentTraitCollection.withTintColor(color, renderingMode: renderingMode)
}
}
// A little hack for subclassing UIImage
extension UIImage {
private convenience init!(failableImageLiteral name: String) {
self.init(named: name)
}
convenience init(imageLiteralResourceName name: String) {
self.init(failableImageLiteral: name)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment