Skip to content

Instantly share code, notes, and snippets.

@kaizeiyimi
Last active October 29, 2019 13:10
Show Gist options
  • Select an option

  • Save kaizeiyimi/d2254904afc25584553ea138bafc2e8d to your computer and use it in GitHub Desktop.

Select an option

Save kaizeiyimi/d2254904afc25584553ea138bafc2e8d to your computer and use it in GitHub Desktop.
UIKit Dynamic UserInterfaceStyle Compatible with iOS12 & below
//
// 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