Created
October 31, 2019 11:33
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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