import Foundation import UIKit public final class Text: Component { private final class MeasureState: Equatable { let attributedText: NSAttributedString let availableSize: CGSize let size: CGSize init(attributedText: NSAttributedString, availableSize: CGSize, size: CGSize) { self.attributedText = attributedText self.availableSize = availableSize self.size = size } static func ==(lhs: MeasureState, rhs: MeasureState) -> Bool { if !lhs.attributedText.isEqual(rhs.attributedText) { return false } if lhs.availableSize != rhs.availableSize { return false } if lhs.size != rhs.size { return false } return true } } public final class View: UIView { private var measureState: MeasureState? public func update(component: Text, availableSize: CGSize) -> CGSize { let attributedText = NSAttributedString(string: component.text, attributes: [ NSAttributedString.Key.font: component.font, NSAttributedString.Key.foregroundColor: component.color ]) if let measureState = self.measureState { if measureState.attributedText.isEqual(to: attributedText) && measureState.availableSize == availableSize { return measureState.size } } var boundingRect = attributedText.boundingRect(with: availableSize, options: .usesLineFragmentOrigin, context: nil) boundingRect.size.width = ceil(boundingRect.size.width) boundingRect.size.height = ceil(boundingRect.size.height) let measureState = MeasureState(attributedText: attributedText, availableSize: availableSize, size: boundingRect.size) if #available(iOS 10.0, *) { let renderer = UIGraphicsImageRenderer(bounds: CGRect(origin: CGPoint(), size: measureState.size)) let image = renderer.image { context in UIGraphicsPushContext(context.cgContext) measureState.attributedText.draw(at: CGPoint()) UIGraphicsPopContext() } self.layer.contents = image.cgImage } else { UIGraphicsBeginImageContextWithOptions(measureState.size, false, 0.0) measureState.attributedText.draw(at: CGPoint()) self.layer.contents = UIGraphicsGetImageFromCurrentImageContext()?.cgImage UIGraphicsEndImageContext() } self.measureState = measureState return boundingRect.size } } public let text: String public let font: UIFont public let color: UIColor public init(text: String, font: UIFont, color: UIColor) { self.text = text self.font = font self.color = color } public static func ==(lhs: Text, rhs: Text) -> Bool { if lhs.text != rhs.text { return false } if !lhs.font.isEqual(rhs.font) { return false } if !lhs.color.isEqual(rhs.color) { return false } return true } public func makeView() -> View { return View() } public func update(view: View, availableSize: CGSize, state: EmptyComponentState, environment: Environment, transition: ComponentTransition) -> CGSize { return view.update(component: self, availableSize: availableSize) } }