Last active
October 29, 2019 13:10
-
-
Save kaizeiyimi/d2254904afc25584553ea138bafc2e8d to your computer and use it in GitHub Desktop.
UIKit Dynamic UserInterfaceStyle Compatible with iOS12 & below
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
| // | |
| // UIKit+DynamicStyle.swift | |
| // | |
| // Created by kaizei on 2019/7/19. | |
| // Copyright © 2019 yimi.kaizei. All rights reserved. | |
| // | |
| import UIKit | |
| // MARK: - reflect system style | |
| public enum ManualDynamicUserInterfaceStyle: Int { | |
| case unspecified | |
| case light | |
| case dark | |
| @available(iOS 13, *) | |
| public func asUIUserInterfaceStyle() -> UIUserInterfaceStyle { | |
| switch self { | |
| case .unspecified: return .unspecified | |
| case .light: return .light | |
| case .dark: return .dark | |
| } | |
| } | |
| } | |
| @available(iOS 13.0, *) | |
| extension UIUserInterfaceStyle { | |
| public func asManualDynamicUserInterfaceStyle() -> ManualDynamicUserInterfaceStyle { | |
| switch self { | |
| case .unspecified: return .unspecified | |
| case .light: return .light | |
| case .dark: return .dark | |
| @unknown default: | |
| return .light | |
| } | |
| } | |
| } | |
| public struct ManualDynamicMeta<T> { | |
| var value: T | |
| var traitCollection: UITraitCollection | |
| public init(_ value: T, traitCollection: UITraitCollection) { | |
| self.value = value | |
| self.traitCollection = traitCollection | |
| } | |
| } | |
| // MARK: - wrap trait init for style | |
| extension UITraitCollection { | |
| public convenience init(manualDynamicUserInterfaceStyle: ManualDynamicUserInterfaceStyle) { | |
| if #available(iOS 13, *) { | |
| self.init(userInterfaceStyle: manualDynamicUserInterfaceStyle.asUIUserInterfaceStyle()) | |
| } else { | |
| self.init() | |
| } | |
| } | |
| } | |
| // MARK: - manual dynamic color | |
| extension ManualDynamicMeta where T: UIColor { | |
| public init(_ color: T, style: ManualDynamicUserInterfaceStyle = .unspecified) { | |
| self.value = color | |
| if #available(iOS 13, *) { | |
| self.traitCollection = UITraitCollection(userInterfaceStyle: style.asUIUserInterfaceStyle()) | |
| } else { | |
| self.traitCollection = UITraitCollection() | |
| } | |
| } | |
| } | |
| extension UIColor { | |
| public static func dynamic(_ defaultColor: UIColor, variants: [ManualDynamicMeta<UIColor>] = []) -> UIColor { | |
| if #available(iOS 13, *) { | |
| return UIColor { trait in | |
| variants.first{ trait.containsTraits(in: $0.traitCollection) }?.value ?? defaultColor | |
| } | |
| } else { | |
| return defaultColor | |
| } | |
| } | |
| public static func dynamic(_ defaultColor: UIColor, variants: [ManualDynamicUserInterfaceStyle: UIColor] = [:]) -> UIColor { | |
| return dynamic(defaultColor, variants: variants.map{ ManualDynamicMeta($0.value, style: $0.key) }) | |
| } | |
| public func resolved(with traitCollection: UITraitCollection? = nil) -> UIColor { | |
| if #available(iOS 13, *) { | |
| return resolvedColor(with: traitCollection ?? UITraitCollection.current) | |
| } else { | |
| return self | |
| } | |
| } | |
| } | |
| // MARK: - manual dynamic image | |
| extension ManualDynamicMeta where T: UIImage { | |
| public init(_ image: T, scale: CGFloat? = nil, idiom: UIUserInterfaceIdiom = .unspecified, style: ManualDynamicUserInterfaceStyle = .unspecified) { | |
| self.value = image | |
| if #available(iOS 13, *) { | |
| self.traitCollection = UITraitCollection(traitsFrom: [ | |
| UITraitCollection(displayScale: scale ?? image.scale), | |
| UITraitCollection(userInterfaceIdiom: idiom), | |
| UITraitCollection(userInterfaceStyle: style.asUIUserInterfaceStyle()) | |
| ]) | |
| } else { | |
| self.traitCollection = UITraitCollection(traitsFrom: [ | |
| UITraitCollection(displayScale: scale ?? image.scale), | |
| UITraitCollection(userInterfaceIdiom: idiom) | |
| ]) | |
| } | |
| } | |
| } | |
| extension UIImage { | |
| private struct Key { | |
| static var manualDynamic = "manualDynamic.image.asset.key" | |
| } | |
| fileprivate(set) var manualDynamicAsset: UIImageAsset? { | |
| get { return objc_getAssociatedObject(self, &Key.manualDynamic) as? UIImageAsset } | |
| set { objc_setAssociatedObject(self, &Key.manualDynamic, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } | |
| } | |
| /// iOS12 & below will only use default image. variants are for iOS13. | |
| public static func dynamic(_ defaultImage: UIImage, variants: [ManualDynamicMeta<UIImage>] = []) -> UIImage { | |
| if #available(iOS 13, *) { | |
| UIImageView.swizzle() | |
| let asset = UIImageAsset() | |
| let list = (variants + [ManualDynamicMeta(defaultImage)]) | |
| list.forEach{ | |
| asset.register($0.value, with: $0.traitCollection) | |
| } | |
| let image = asset.image(with: UITraitCollection.current) | |
| image.manualDynamicAsset = asset | |
| return image | |
| } else { | |
| return defaultImage | |
| } | |
| } | |
| } | |
| // MARK: - UIImageView modification for manual dynamic color | |
| extension UIImageView { | |
| private static var hasSwizzled = false | |
| fileprivate static func swizzle() { | |
| guard !hasSwizzled else { return } | |
| hasSwizzled = true | |
| method_exchangeImplementations( | |
| class_getInstanceMethod(UIImageView.self, #selector(traitCollectionDidChange(_:)))!, | |
| class_getInstanceMethod(UIImageView.self, #selector(manualDynamicImage_traitCollectionDidChange(_:)))! | |
| ) | |
| method_exchangeImplementations( | |
| class_getInstanceMethod(UIImageView.self, #selector(setter: isHighlighted))!, | |
| class_getInstanceMethod(UIImageView.self, #selector(manualDynamicImage_isHighlighted(_:)))! | |
| ) | |
| method_exchangeImplementations( | |
| class_getInstanceMethod(UIImageView.self, #selector(setter: image))!, | |
| class_getInstanceMethod(UIImageView.self, #selector(manualDynamicImage_setImage(_:)))! | |
| ) | |
| method_exchangeImplementations( | |
| class_getInstanceMethod(UIImageView.self, #selector(setter: highlightedImage))!, | |
| class_getInstanceMethod(UIImageView.self, #selector(manualDynamicImage_setHighlightedImage(_:)))! | |
| ) | |
| } | |
| @objc private func manualDynamicImage_isHighlighted(_ value: Bool) { | |
| manualDynamicImage_isHighlighted(value) | |
| updateManualDynamicImage() | |
| } | |
| @objc private func manualDynamicImage_setImage(_ image: UIImage?) { | |
| manualDynamicImage_setImage(image) | |
| updateManualDynamicImage() | |
| } | |
| @objc private func manualDynamicImage_setHighlightedImage(_ image: UIImage?) { | |
| manualDynamicImage_setHighlightedImage(image) | |
| updateManualDynamicImage() | |
| } | |
| @objc private func manualDynamicImage_traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) { | |
| manualDynamicImage_traitCollectionDidChange(previousTraitCollection) | |
| updateManualDynamicImage() | |
| } | |
| private func updateManualDynamicImage() { | |
| guard #available(iOS 13, *) else { return } | |
| if isHighlighted { | |
| if let asset = highlightedImage?.manualDynamicAsset { | |
| let image = asset.image(with: traitCollection) | |
| image.manualDynamicAsset = asset | |
| manualDynamicImage_setHighlightedImage(image) | |
| } else if highlightedImage == nil, let asset = image?.manualDynamicAsset { | |
| let image = asset.image(with: traitCollection) | |
| image.manualDynamicAsset = asset | |
| manualDynamicImage_setImage(image) | |
| } | |
| } else if let asset = image?.manualDynamicAsset { | |
| let image = asset.image(with: traitCollection) | |
| image.manualDynamicAsset = asset | |
| manualDynamicImage_setImage(image) | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment