diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/BadgePreview.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/BadgePreview.swift index 15aa2b7f..3b8ba160 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/BadgePreview.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/BadgePreview.swift @@ -9,6 +9,10 @@ struct BadgePreview: View { var body: some View { VStack { + PreviewWrapper(title: "UIKit") { + UKBadge(model: self.model) + .preview + } PreviewWrapper(title: "SwiftUI") { SUBadge(model: self.model) } @@ -28,6 +32,14 @@ struct BadgePreview: View { Text("Filled").tag(BadgeVM.Style.filled) Text("Light").tag(BadgeVM.Style.light) } + Picker("Paddings", selection: self.$model.paddings) { + Text("8px; 6px") + .tag(Paddings(top: 6, leading: 8, bottom: 6, trailing: 8)) + Text("10px; 8px") + .tag(Paddings(top: 8, leading: 10, bottom: 8, trailing: 10)) + Text("12px; 10px") + .tag(Paddings(top: 10, leading: 12, bottom: 10, trailing: 12)) + } } } } diff --git a/Sources/ComponentsKit/Components/Badge/Models/BadgeVM.swift b/Sources/ComponentsKit/Components/Badge/Models/BadgeVM.swift index a8c900e2..4232dff6 100644 --- a/Sources/ComponentsKit/Components/Badge/Models/BadgeVM.swift +++ b/Sources/ComponentsKit/Components/Badge/Models/BadgeVM.swift @@ -53,3 +53,12 @@ extension BadgeVM { } } } + +// MARK: UIKit Helpers + +extension BadgeVM { + func shouldUpdateLayout(_ oldModel: Self?) -> Bool { + return self.font != oldModel?.font + || self.paddings != oldModel?.paddings + } +} diff --git a/Sources/ComponentsKit/Components/Badge/UKBadge.swift b/Sources/ComponentsKit/Components/Badge/UKBadge.swift new file mode 100644 index 00000000..9088b78e --- /dev/null +++ b/Sources/ComponentsKit/Components/Badge/UKBadge.swift @@ -0,0 +1,127 @@ +import AutoLayout +import UIKit + +/// A UIKit component that displays a badge. +open class UKBadge: UIView, UKComponent { + // MARK: - Properties + + /// A model that defines the appearance properties. + public var model: BadgeVM { + didSet { + self.update(oldValue) + } + } + + private var titleLabelConstraints: LayoutConstraints = .init() + + // MARK: - Subviews + + /// A label that displays the title from the model. + public var titleLabel = UILabel() + + // MARK: - UIView Properties + + open override var intrinsicContentSize: CGSize { + return self.sizeThatFits(UIView.layoutFittingExpandedSize) + } + + // MARK: - Initialization + + /// Initializes a new instance of `UKBadge`. + /// - Parameter model: A model that defines the appearance properties for the badge. + public init(model: BadgeVM = .init()) { + self.model = model + super.init(frame: .zero) + + self.setup() + self.style() + self.layout() + } + + public required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // MARK: - Setup + + private func setup() { + self.addSubview(self.titleLabel) + } + + // MARK: - Style + + private func style() { + Self.Style.mainView(self, model: self.model) + Self.Style.titleLabel(self.titleLabel, model: self.model) + } + + // MARK: - Layout + + private func layout() { + self.titleLabelConstraints = .merged { + self.titleLabel.top(self.model.paddings.top) + self.titleLabel.leading(self.model.paddings.leading) + self.titleLabel.bottom(self.model.paddings.bottom) + self.titleLabel.trailing(self.model.paddings.trailing) + } + + self.titleLabelConstraints.allConstraints.forEach { $0?.priority = .defaultHigh } + } + + open override func layoutSubviews() { + super.layoutSubviews() + + self.layer.cornerRadius = self.model.cornerRadius.value(for: self.bounds.height) + } + + // MARK: - Update + + public func update(_ oldModel: BadgeVM) { + guard self.model != oldModel else { return } + + self.style() + if self.model.shouldUpdateLayout(oldModel) { + self.titleLabelConstraints.leading?.constant = self.model.paddings.leading + self.titleLabelConstraints.top?.constant = self.model.paddings.top + self.titleLabelConstraints.bottom?.constant = -self.model.paddings.bottom + self.titleLabelConstraints.trailing?.constant = -self.model.paddings.trailing + + self.invalidateIntrinsicContentSize() + self.setNeedsLayout() + } + } + + // MARK: - UIView Methods + + open override func sizeThatFits(_ size: CGSize) -> CGSize { + let contentSize = self.titleLabel.sizeThatFits(size) + + let totalWidthPadding = self.model.paddings.leading + self.model.paddings.trailing + let totalHeightPadding = self.model.paddings.top + self.model.paddings.bottom + + let width = contentSize.width + totalWidthPadding + let height = contentSize.height + totalHeightPadding + + return CGSize( + width: min(width, size.width), + height: min(height, size.height) + ) + } +} + +// MARK: - Style Helpers + +extension UKBadge { + fileprivate enum Style { + static func mainView(_ view: UIView, model: BadgeVM) { + view.backgroundColor = model.backgroundColor.uiColor + view.layer.cornerRadius = model.cornerRadius.value(for: view.bounds.height) + } + static func titleLabel(_ label: UILabel, model: BadgeVM) { + label.textAlignment = .center + label.text = model.title + label.font = model.font.uiFont + label.textColor = model.foregroundColor.uiColor + } + } +}