From aedb812d2ab9c07beaa7772ea1554c2c8edafd6f Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Thu, 3 Apr 2025 14:01:42 +0300 Subject: [PATCH 01/35] SUButton new params --- .../PreviewPages/ButtonPreview.swift | 24 +++++++- .../Components/Button/Models/ButtonVM.swift | 57 +++++++++++++++++++ .../Components/Button/SUButton.swift | 55 ++++++++++++------ 3 files changed, 118 insertions(+), 18 deletions(-) diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift index df58ad2e..c2e3b6c3 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift @@ -6,7 +6,7 @@ struct ButtonPreview: View { @State private var model = ButtonVM { $0.title = "Button" } - + var body: some View { VStack { PreviewWrapper(title: "UIKit") { @@ -25,6 +25,18 @@ struct ButtonPreview: View { ButtonFontPicker(selection: self.$model.font) Toggle("Enabled", isOn: self.$model.isEnabled) Toggle("Full Width", isOn: self.$model.isFullWidth) + Picker("Image Source", selection: self.$model.imageSrc) { + Text("SF Symbol").tag(ButtonVM.ImageSource.sfSymbol("star.fill")) + Text("Local").tag(ButtonVM.ImageSource.local("avatar_placeholder")) + Text("None").tag(Optional.none) + } + if self.model.imageSrc != nil { + Picker("Image Location", selection: self.$model.imageLocation) { + Text("Leading").tag(ButtonVM.ImageLocation.leading) + Text("Trailing").tag(ButtonVM.ImageLocation.trailing) + } + } + Toggle("Loading", isOn: self.$model.isLoading) SizePicker(selection: self.$model.size) Picker("Style", selection: self.$model.style) { Text("Filled").tag(ButtonStyle.filled) @@ -34,6 +46,16 @@ struct ButtonPreview: View { Text("Bordered with medium border").tag(ButtonStyle.bordered(.medium)) Text("Bordered with large border").tag(ButtonStyle.bordered(.large)) } + .onChange(of: self.model.imageLocation) { _ in + if self.model.isLoading { + self.model.isLoading = false + } + } + .onChange(of: self.model.imageSrc) { _ in + if self.model.isLoading { + self.model.isLoading = false + } + } } } } diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift index 8212febf..52e4e7a9 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift @@ -1,3 +1,4 @@ +import SwiftUI import UIKit /// A model that defines the appearance properties for a button component. @@ -43,6 +44,29 @@ public struct ButtonVM: ComponentVM { /// Defaults to `.filled`. public var style: ButtonStyle = .filled + /// The loading VM used for the loading indicator. + /// + /// If not provided, a default loading view model is used. + public var loadingVM: LoadingVM? + + /// A Boolean value indicating whether the button is currently in a loading state. + /// + /// Defaults to `false`. + public var isLoading: Bool = false + + /// The source of the image to be displayed. + public var imageSrc: ImageSource? + + /// The position of the image relative to the button's title. + /// + /// Defaults to `.leading`. + public var imageLocation: ImageLocation = .leading + + /// The spacing between the button's title and its image or loading indicator. + /// + /// Defaults to `8.0`. + public var contentSpacing: CGFloat = 8.0 + /// Initializes a new instance of `ButtonVM` with default values. public init() {} } @@ -50,6 +74,15 @@ public struct ButtonVM: ComponentVM { // MARK: Shared Helpers extension ButtonVM { + var preferredLoadingVM: LoadingVM { + return self.loadingVM ?? .init { + $0.color = .init( + main: foregroundColor, + contrast: self.color?.main ?? .background + ) + $0.size = .small + } + } var backgroundColor: UniversalColor? { switch self.style { case .filled: @@ -121,6 +154,18 @@ extension ButtonVM { } } +extension ButtonVM { + public enum ImageSource: Hashable { + case sfSymbol(String) + case local(String, bundle: Bundle? = nil) + } + + public enum ImageLocation { + case leading + case trailing + } +} + // MARK: UIKit Helpers extension ButtonVM { @@ -155,3 +200,15 @@ extension ButtonVM { return self.isFullWidth ? 10_000 : nil } } + +extension ButtonVM { + var buttonImage: Image? { + guard let imageSrc = self.imageSrc else { return nil } + switch imageSrc { + case .sfSymbol(let name): + return Image(systemName: name) + case .local(let name, let bundle): + return Image(name, bundle: bundle) + } + } +} diff --git a/Sources/ComponentsKit/Components/Button/SUButton.swift b/Sources/ComponentsKit/Components/Button/SUButton.swift index d415a8ae..ffb30f7c 100644 --- a/Sources/ComponentsKit/Components/Button/SUButton.swift +++ b/Sources/ComponentsKit/Components/Button/SUButton.swift @@ -29,25 +29,46 @@ public struct SUButton: View { // MARK: Body public var body: some View { - Button(self.model.title, action: self.action) - .buttonStyle(CustomButtonStyle(model: self.model)) - .simultaneousGesture(DragGesture(minimumDistance: 0.0) - .onChanged { _ in - self.isPressed = true - } - .onEnded { _ in - self.isPressed = false - } - ) - .disabled(!self.model.isEnabled) - .scaleEffect( - self.isPressed ? self.model.animationScale.value : 1, - anchor: .center - ) + Button(action: self.action) { + HStack(spacing: self.model.contentSpacing) { + self.content() + } + .frame(maxWidth: self.model.width) + .frame(height: self.model.height) + } + .buttonStyle(CustomButtonStyle(model: self.model)) + .simultaneousGesture(DragGesture(minimumDistance: 0.0) + .onChanged { _ in + self.isPressed = true + } + .onEnded { _ in + self.isPressed = false + } + ) + .disabled(!self.model.isEnabled || self.model.isLoading) + .scaleEffect( + self.isPressed ? self.model.animationScale.value : 1, + anchor: .center + ) } -} -// MARK: - Helpers + @ViewBuilder + private func content() -> some View { + switch (self.model.isLoading, self.model.buttonImage, self.model.imageLocation) { + case (true, _, _): + SULoading(model: self.model.preferredLoadingVM) + Text(self.model.title) + case (false, let image?, .leading): + image + Text(self.model.title) + case (false, let image?, .trailing): + Text(self.model.title) + image + default: + Text(self.model.title) + } + } +} private struct CustomButtonStyle: SwiftUI.ButtonStyle { let model: ButtonVM From f3949d52b199f4b10b0fdfcdc9001f54de3d67fc Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Thu, 3 Apr 2025 14:03:41 +0300 Subject: [PATCH 02/35] image fix --- .../DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift index c2e3b6c3..58f83b49 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift @@ -26,7 +26,7 @@ struct ButtonPreview: View { Toggle("Enabled", isOn: self.$model.isEnabled) Toggle("Full Width", isOn: self.$model.isFullWidth) Picker("Image Source", selection: self.$model.imageSrc) { - Text("SF Symbol").tag(ButtonVM.ImageSource.sfSymbol("star.fill")) + Text("SF Symbol").tag(ButtonVM.ImageSource.sfSymbol("camera.fill")) Text("Local").tag(ButtonVM.ImageSource.local("avatar_placeholder")) Text("None").tag(Optional.none) } From 9e5d3028ec24eeaa6d0e201ace725e3f78a65c57 Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Thu, 3 Apr 2025 18:56:49 +0300 Subject: [PATCH 03/35] improve UKButton --- .../PreviewPages/ButtonPreview.swift | 4 +- .../Components/Button/Models/ButtonVM.swift | 28 ++++++-- .../Components/Button/UKButton.swift | 70 +++++++++++++++++-- 3 files changed, 91 insertions(+), 11 deletions(-) diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift index 58f83b49..f74491a6 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift @@ -23,7 +23,9 @@ struct ButtonPreview: View { Text("Custom: 20px").tag(ComponentRadius.custom(20)) } ButtonFontPicker(selection: self.$model.font) - Toggle("Enabled", isOn: self.$model.isEnabled) + if !self.model.isLoading { + Toggle("Enabled", isOn: self.$model.isEnabled) + } Toggle("Full Width", isOn: self.$model.isFullWidth) Picker("Image Source", selection: self.$model.imageSrc) { Text("SF Symbol").tag(ButtonVM.ImageSource.sfSymbol("camera.fill")) diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift index 52e4e7a9..e9693017 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift @@ -74,6 +74,10 @@ public struct ButtonVM: ComponentVM { // MARK: Shared Helpers extension ButtonVM { + private var isInteractive: Bool { + self.isEnabled && !self.isLoading + } + var preferredLoadingVM: LoadingVM { return self.loadingVM ?? .init { $0.color = .init( @@ -87,10 +91,10 @@ extension ButtonVM { switch self.style { case .filled: let color = self.color?.main ?? .content2 - return color.enabled(self.isEnabled) + return color.enabled(self.isInteractive) case .light: let color = self.color?.background ?? .content1 - return color.enabled(self.isEnabled) + return color.enabled(self.isInteractive) case .plain, .bordered: return nil } @@ -102,7 +106,7 @@ extension ButtonVM { case .plain, .light, .bordered: self.color?.main ?? .foreground } - return color.enabled(self.isEnabled) + return color.enabled(self.isInteractive) } var borderWidth: CGFloat { switch self.style { @@ -118,7 +122,7 @@ extension ButtonVM { return nil case .bordered: if let color { - return color.main.enabled(self.isEnabled) + return color.main.enabled(self.isInteractive) } else { return .divider } @@ -193,6 +197,18 @@ extension ButtonVM { } } +extension ButtonVM { + public var uiImage: UIImage? { + guard let imageSrc = self.imageSrc else { return nil } + switch imageSrc { + case .sfSymbol(let name): + return UIImage(systemName: name)?.withRenderingMode(.alwaysTemplate) + case .local(let name, let bundle): + return UIImage(named: name, in: bundle, compatibleWith: nil)?.withRenderingMode(.alwaysTemplate) + } + } +} + // MARK: SwiftUI Helpers extension ButtonVM { @@ -206,9 +222,9 @@ extension ButtonVM { guard let imageSrc = self.imageSrc else { return nil } switch imageSrc { case .sfSymbol(let name): - return Image(systemName: name) + return Image(systemName: name).renderingMode(.template) case .local(let name, let bundle): - return Image(name, bundle: bundle) + return Image(name, bundle: bundle).renderingMode(.template) } } } diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index 058c1609..007ae8c7 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -32,6 +32,15 @@ open class UKButton: UIView, UKComponent { /// A label that displays the title from the model. public var titleLabel = UILabel() + /// A loader view, created with the preferred loading VM from the model. + public let loaderView: UKLoading + + /// A stack view that arranges the loader and title label. + private let stackView = UIStackView() + + /// A image view for displaying the image from the model. + public let imageView: UIImageView = UIImageView() + // MARK: UIView Properties open override var intrinsicContentSize: CGSize { @@ -50,6 +59,7 @@ open class UKButton: UIView, UKComponent { ) { self.model = model self.action = action + self.loaderView = UKLoading(model: model.preferredLoadingVM) super.init(frame: .zero) self.setup() @@ -64,7 +74,7 @@ open class UKButton: UIView, UKComponent { // MARK: Setup private func setup() { - self.addSubview(self.titleLabel) + self.addSubview(self.stackView) if #available(iOS 17.0, *) { self.registerForTraitChanges([UITraitUserInterfaceStyle.self]) { (view: Self, _: UITraitCollection) in @@ -78,12 +88,23 @@ open class UKButton: UIView, UKComponent { private func style() { Self.Style.mainView(self, model: self.model) Self.Style.titleLabel(self.titleLabel, model: self.model) + Self.Style.configureStackView( + self.stackView, + model: self.model, + loaderView: self.loaderView, + titleLabel: self.titleLabel, + imageView: self.imageView + ) + + self.loaderView.model = self.model.preferredLoadingVM + + self.loaderView.isHidden = !self.model.isLoading } // MARK: Layout private func layout() { - self.titleLabel.center() + self.stackView.center() } open override func layoutSubviews() { @@ -99,7 +120,13 @@ open class UKButton: UIView, UKComponent { self.style() - if self.model.shouldUpdateSize(oldModel) { + self.imageView.image = self.model.uiImage + self.imageView.tintColor = self.model.foregroundColor.uiColor + + if self.model.shouldUpdateSize(oldModel) + || self.model.isLoading != oldModel.isLoading + || self.model.imageSrc != oldModel.imageSrc + || self.model.imageLocation != oldModel.imageLocation { self.invalidateIntrinsicContentSize() } } @@ -107,7 +134,7 @@ open class UKButton: UIView, UKComponent { // MARK: UIView methods open override func sizeThatFits(_ size: CGSize) -> CGSize { - let contentSize = self.titleLabel.sizeThatFits(size) + let contentSize = self.stackView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) let preferredSize = self.model.preferredSize( for: contentSize, parentWidth: self.superview?.bounds.width @@ -183,5 +210,40 @@ extension UKButton { label.font = model.preferredFont.uiFont label.textColor = model.foregroundColor.uiColor } + static func configureStackView( + _ stackView: UIStackView, + model: Model, + loaderView: UKLoading, + titleLabel: UILabel, + imageView: UIImageView + ) { + stackView.axis = .horizontal + stackView.alignment = .center + stackView.spacing = model.contentSpacing + + for subview in stackView.arrangedSubviews { + stackView.removeArrangedSubview(subview) + subview.removeFromSuperview() + } + + if model.isLoading { + stackView.addArrangedSubview(loaderView) + stackView.addArrangedSubview(titleLabel) + return + } + + if let _ = model.imageSrc { + switch model.imageLocation { + case .leading: + stackView.addArrangedSubview(imageView) + stackView.addArrangedSubview(titleLabel) + case .trailing: + stackView.addArrangedSubview(titleLabel) + stackView.addArrangedSubview(imageView) + } + } else { + stackView.addArrangedSubview(titleLabel) + } + } } } From 68848a0e394f586b3b16a6c3fe30b609211e252b Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Thu, 3 Apr 2025 19:06:46 +0300 Subject: [PATCH 04/35] swiftlint fix --- Sources/ComponentsKit/Components/Button/UKButton.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index 007ae8c7..9d5f9efd 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -232,7 +232,7 @@ extension UKButton { return } - if let _ = model.imageSrc { + if model.imageSrc != nil { switch model.imageLocation { case .leading: stackView.addArrangedSubview(imageView) From 572135fddc3cc06a11e2e6c6602902d612a8f1ca Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Sun, 6 Apr 2025 12:21:48 +0300 Subject: [PATCH 05/35] code style fix --- .../Components/Button/Models/ButtonVM.swift | 6 +++--- .../Components/Button/SUButton.swift | 8 +++---- .../Components/Button/UKButton.swift | 21 ++++++++++--------- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift index e9693017..8c4895ac 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift @@ -74,7 +74,7 @@ public struct ButtonVM: ComponentVM { // MARK: Shared Helpers extension ButtonVM { - private var isInteractive: Bool { + var isInteractive: Bool { self.isEnabled && !self.isLoading } @@ -199,7 +199,7 @@ extension ButtonVM { extension ButtonVM { public var uiImage: UIImage? { - guard let imageSrc = self.imageSrc else { return nil } + guard let imageSrc else { return nil } switch imageSrc { case .sfSymbol(let name): return UIImage(systemName: name)?.withRenderingMode(.alwaysTemplate) @@ -219,7 +219,7 @@ extension ButtonVM { extension ButtonVM { var buttonImage: Image? { - guard let imageSrc = self.imageSrc else { return nil } + guard let imageSrc else { return nil } switch imageSrc { case .sfSymbol(let name): return Image(systemName: name).renderingMode(.template) diff --git a/Sources/ComponentsKit/Components/Button/SUButton.swift b/Sources/ComponentsKit/Components/Button/SUButton.swift index ffb30f7c..59cc1d93 100644 --- a/Sources/ComponentsKit/Components/Button/SUButton.swift +++ b/Sources/ComponentsKit/Components/Button/SUButton.swift @@ -31,10 +31,8 @@ public struct SUButton: View { public var body: some View { Button(action: self.action) { HStack(spacing: self.model.contentSpacing) { - self.content() + self.content } - .frame(maxWidth: self.model.width) - .frame(height: self.model.height) } .buttonStyle(CustomButtonStyle(model: self.model)) .simultaneousGesture(DragGesture(minimumDistance: 0.0) @@ -45,7 +43,7 @@ public struct SUButton: View { self.isPressed = false } ) - .disabled(!self.model.isEnabled || self.model.isLoading) + .disabled(!self.model.isInteractive) .scaleEffect( self.isPressed ? self.model.animationScale.value : 1, anchor: .center @@ -53,7 +51,7 @@ public struct SUButton: View { } @ViewBuilder - private func content() -> some View { + private var content: some View { switch (self.model.isLoading, self.model.buttonImage, self.model.imageLocation) { case (true, _, _): SULoading(model: self.model.preferredLoadingVM) diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index 9d5f9efd..77ec6ca6 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -32,14 +32,14 @@ open class UKButton: UIView, UKComponent { /// A label that displays the title from the model. public var titleLabel = UILabel() - /// A loader view, created with the preferred loading VM from the model. - public let loaderView: UKLoading + /// A loading indicator shown when the button is in a loading state. + public let loaderView = UKLoading() - /// A stack view that arranges the loader and title label. + /// A stack view that manages the layout of the button’s internal content. private let stackView = UIStackView() - /// A image view for displaying the image from the model. - public let imageView: UIImageView = UIImageView() + /// An optional image displayed alongside the title. + public let imageView = UIImageView() // MARK: UIView Properties @@ -59,7 +59,6 @@ open class UKButton: UIView, UKComponent { ) { self.model = model self.action = action - self.loaderView = UKLoading(model: model.preferredLoadingVM) super.init(frame: .zero) self.setup() @@ -95,9 +94,9 @@ open class UKButton: UIView, UKComponent { titleLabel: self.titleLabel, imageView: self.imageView ) + Self.Style.imageView(self.imageView, model: self.model) self.loaderView.model = self.model.preferredLoadingVM - self.loaderView.isHidden = !self.model.isLoading } @@ -120,9 +119,6 @@ open class UKButton: UIView, UKComponent { self.style() - self.imageView.image = self.model.uiImage - self.imageView.tintColor = self.model.foregroundColor.uiColor - if self.model.shouldUpdateSize(oldModel) || self.model.isLoading != oldModel.isLoading || self.model.imageSrc != oldModel.imageSrc @@ -245,5 +241,10 @@ extension UKButton { stackView.addArrangedSubview(titleLabel) } } + + static func imageView(_ imageView: UIImageView, model: Model) { + imageView.image = model.uiImage + imageView.tintColor = model.foregroundColor.uiColor + } } } From 6ac6171dfa67fc18124d5d1c0948e2a8899b4ba5 Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Sun, 6 Apr 2025 12:26:53 +0300 Subject: [PATCH 06/35] shouldUpdateSize fix --- .../ComponentsKit/Components/Button/Models/ButtonVM.swift | 3 +++ Sources/ComponentsKit/Components/Button/UKButton.swift | 7 +++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift index 8c4895ac..e72998c0 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift @@ -194,6 +194,9 @@ extension ButtonVM { return self.size != oldModel?.size || self.font != oldModel?.font || self.isFullWidth != oldModel?.isFullWidth + || self.isLoading != oldModel?.isLoading + || self.imageSrc != oldModel?.imageSrc + || self.imageLocation != oldModel?.imageLocation } } diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index 77ec6ca6..0e0346c0 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -119,10 +119,9 @@ open class UKButton: UIView, UKComponent { self.style() - if self.model.shouldUpdateSize(oldModel) - || self.model.isLoading != oldModel.isLoading - || self.model.imageSrc != oldModel.imageSrc - || self.model.imageLocation != oldModel.imageLocation { + if self.model.shouldUpdateSize(oldModel) { + Self.Style.imageView(self.imageView, model: self.model) + self.loaderView.model = self.model.preferredLoadingVM self.invalidateIntrinsicContentSize() } } From ffd1d5992d6283089c65b0dde004dc55ca8aeeea Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Sun, 6 Apr 2025 19:51:12 +0300 Subject: [PATCH 07/35] extension ImageSource and ImageLocation extracted --- .../Button/Models/ButtonImageSource.swift | 26 +++++++++++++++++++ .../Components/Button/Models/ButtonVM.swift | 12 --------- 2 files changed, 26 insertions(+), 12 deletions(-) create mode 100644 Sources/ComponentsKit/Components/Button/Models/ButtonImageSource.swift diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonImageSource.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonImageSource.swift new file mode 100644 index 00000000..034bebd7 --- /dev/null +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonImageSource.swift @@ -0,0 +1,26 @@ +import Foundation + +/// Defines the image source options for a button. +extension ButtonVM { + public enum ImageSource: Hashable { + /// An image loaded from a system SF Symbol. + /// + /// - Parameter name: The name of the SF Symbol. + case sfSymbol(String) + + /// An image loaded from a local asset. + /// + /// - Parameters: + /// - name: The name of the local image asset. + /// - bundle: The bundle containing the image resource. Defaults to `nil` to use the main bundle. + case local(String, bundle: Bundle? = nil) + } + + /// Specifies the position of the image relative to the button's title. + public enum ImageLocation { + /// The image is displayed before the title. + case leading + /// The image is displayed after the title. + case trailing + } +} diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift index e72998c0..e9645e1e 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift @@ -158,18 +158,6 @@ extension ButtonVM { } } -extension ButtonVM { - public enum ImageSource: Hashable { - case sfSymbol(String) - case local(String, bundle: Bundle? = nil) - } - - public enum ImageLocation { - case leading - case trailing - } -} - // MARK: UIKit Helpers extension ButtonVM { From 3171a7914320ffc8f424a756fd48e5cf68ecaaf0 Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Sun, 6 Apr 2025 19:53:42 +0300 Subject: [PATCH 08/35] rename extension buttonImage - buttonImage renamed to image --- .../Components/Button/Models/ButtonVM.swift | 8 ++++---- .../ComponentsKit/Components/Button/SUButton.swift | 12 +++++++----- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift index e9645e1e..16163adb 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift @@ -209,13 +209,13 @@ extension ButtonVM { } extension ButtonVM { - var buttonImage: Image? { - guard let imageSrc else { return nil } + public var image: UIImage? { + guard let imageSrc = self.imageSrc else { return nil } switch imageSrc { case .sfSymbol(let name): - return Image(systemName: name).renderingMode(.template) + return UIImage(systemName: name)?.withRenderingMode(.alwaysTemplate) case .local(let name, let bundle): - return Image(name, bundle: bundle).renderingMode(.template) + return UIImage(named: name, in: bundle, compatibleWith: nil)?.withRenderingMode(.alwaysTemplate) } } } diff --git a/Sources/ComponentsKit/Components/Button/SUButton.swift b/Sources/ComponentsKit/Components/Button/SUButton.swift index 59cc1d93..6db9a813 100644 --- a/Sources/ComponentsKit/Components/Button/SUButton.swift +++ b/Sources/ComponentsKit/Components/Button/SUButton.swift @@ -52,16 +52,18 @@ public struct SUButton: View { @ViewBuilder private var content: some View { - switch (self.model.isLoading, self.model.buttonImage, self.model.imageLocation) { + switch (self.model.isLoading, self.model.image, self.model.imageLocation) { + case (true, _, _) where self.model.title.isEmpty: + SULoading(model: self.model.preferredLoadingVM) case (true, _, _): SULoading(model: self.model.preferredLoadingVM) Text(self.model.title) - case (false, let image?, .leading): - image + case (false, let uiImage?, .leading): + Image(uiImage: uiImage) Text(self.model.title) - case (false, let image?, .trailing): + case (false, let uiImage?, .trailing): Text(self.model.title) - image + Image(uiImage: uiImage) default: Text(self.model.title) } From f9bd1548bb5253914e09c62bce46b9ed2685d861 Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Sun, 6 Apr 2025 19:54:31 +0300 Subject: [PATCH 09/35] params in alphabetical order --- .../Components/Button/Models/ButtonVM.swift | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift index 16163adb..943ca64e 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift @@ -3,9 +3,6 @@ import UIKit /// A model that defines the appearance properties for a button component. public struct ButtonVM: ComponentVM { - /// The text displayed on the button. - public var title: String = "" - /// The scaling factor for the button's press animation, with a value between 0 and 1. /// /// Defaults to `.medium`. @@ -14,6 +11,11 @@ public struct ButtonVM: ComponentVM { /// The color of the button. public var color: ComponentColor? + /// The spacing between the button's title and its image or loading indicator. + /// + /// Defaults to `8.0`. + public var contentSpacing: CGFloat = 8.0 + /// The corner radius of the button. /// /// Defaults to `.medium`. @@ -24,6 +26,14 @@ public struct ButtonVM: ComponentVM { /// If not provided, the font is automatically calculated based on the button's size. public var font: UniversalFont? + /// The position of the image relative to the button's title. + /// + /// Defaults to `.leading`. + public var imageLocation: ImageLocation = .leading + + /// The source of the image to be displayed. + public var imageSrc: ImageSource? + /// A Boolean value indicating whether the button is enabled or disabled. /// /// Defaults to `true`. @@ -34,38 +44,28 @@ public struct ButtonVM: ComponentVM { /// Defaults to `false`. public var isFullWidth: Bool = false - /// The predefined size of the button. - /// - /// Defaults to `.medium`. - public var size: ComponentSize = .medium - - /// The visual style of the button. + /// A Boolean value indicating whether the button is currently in a loading state. /// - /// Defaults to `.filled`. - public var style: ButtonStyle = .filled + /// Defaults to `false`. + public var isLoading: Bool = false /// The loading VM used for the loading indicator. /// /// If not provided, a default loading view model is used. public var loadingVM: LoadingVM? - /// A Boolean value indicating whether the button is currently in a loading state. + /// The predefined size of the button. /// - /// Defaults to `false`. - public var isLoading: Bool = false - - /// The source of the image to be displayed. - public var imageSrc: ImageSource? + /// Defaults to `.medium`. + public var size: ComponentSize = .medium - /// The position of the image relative to the button's title. + /// The visual style of the button. /// - /// Defaults to `.leading`. - public var imageLocation: ImageLocation = .leading + /// Defaults to `.filled`. + public var style: ButtonStyle = .filled - /// The spacing between the button's title and its image or loading indicator. - /// - /// Defaults to `8.0`. - public var contentSpacing: CGFloat = 8.0 + /// The text displayed on the button. + public var title: String = "" /// Initializes a new instance of `ButtonVM` with default values. public init() {} From f316b5575f8ad886955208d8bc96da2d20243096 Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Sun, 6 Apr 2025 21:00:07 +0300 Subject: [PATCH 10/35] fix some logic --- .../Components/Button/UKButton.swift | 44 +++++++++---------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index 0e0346c0..d200ba6e 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -74,6 +74,13 @@ open class UKButton: UIView, UKComponent { private func setup() { self.addSubview(self.stackView) + self.stackView.axis = .horizontal + self.stackView.alignment = .center + self.stackView.spacing = self.model.contentSpacing + + self.stackView.addArrangedSubview(self.imageView) + self.stackView.addArrangedSubview(self.loaderView) + self.stackView.addArrangedSubview(self.titleLabel) if #available(iOS 17.0, *) { self.registerForTraitChanges([UITraitUserInterfaceStyle.self]) { (view: Self, _: UITraitCollection) in @@ -212,32 +219,21 @@ extension UKButton { titleLabel: UILabel, imageView: UIImageView ) { - stackView.axis = .horizontal - stackView.alignment = .center stackView.spacing = model.contentSpacing - for subview in stackView.arrangedSubviews { - stackView.removeArrangedSubview(subview) - subview.removeFromSuperview() - } - - if model.isLoading { - stackView.addArrangedSubview(loaderView) - stackView.addArrangedSubview(titleLabel) - return - } - - if model.imageSrc != nil { - switch model.imageLocation { - case .leading: - stackView.addArrangedSubview(imageView) - stackView.addArrangedSubview(titleLabel) - case .trailing: - stackView.addArrangedSubview(titleLabel) - stackView.addArrangedSubview(imageView) - } - } else { - stackView.addArrangedSubview(titleLabel) + loaderView.isHidden = !model.isLoading + titleLabel.isHidden = false + imageView.isHidden = model.isLoading || (model.imageSrc == nil) + + switch (model.isLoading, model.imageSrc, model.imageLocation) { + case (false, .some(_), .leading): + stackView.removeArrangedSubview(imageView) + stackView.insertArrangedSubview(imageView, at: 0) + case (false, .some(_), .trailing): + stackView.removeArrangedSubview(imageView) + stackView.addArrangedSubview(imageView) + default: + break } } From 8627341080fb4698596239432c9552852124a8a4 Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Sun, 6 Apr 2025 21:09:47 +0300 Subject: [PATCH 11/35] fix uikit bug - inactive while loading --- Sources/ComponentsKit/Components/Button/UKButton.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index d200ba6e..0b53a23a 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -18,7 +18,7 @@ open class UKButton: UIView, UKComponent { /// A Boolean value indicating whether the button is pressed. public private(set) var isPressed: Bool = false { didSet { - self.transform = self.isPressed && self.model.isEnabled + self.transform = self.isPressed && self.model.isInteractive ? .init( scaleX: self.model.animationScale.value, y: self.model.animationScale.value @@ -164,7 +164,7 @@ open class UKButton: UIView, UKComponent { defer { self.isPressed = false } - if self.model.isEnabled, + if self.model.isInteractive, let location = touches.first?.location(in: self), self.bounds.contains(location) { self.action() From 31cc411a664698df1e8f7e018c34652bd6faada8 Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Sun, 6 Apr 2025 21:25:13 +0300 Subject: [PATCH 12/35] local image size bug fix --- .../Components/Button/Models/ButtonVM.swift | 4 ++-- .../Components/Button/SUButton.swift | 16 ++++++++++++++-- .../Components/Button/UKButton.swift | 5 +++++ 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift index 943ca64e..45278c7f 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift @@ -195,7 +195,7 @@ extension ButtonVM { case .sfSymbol(let name): return UIImage(systemName: name)?.withRenderingMode(.alwaysTemplate) case .local(let name, let bundle): - return UIImage(named: name, in: bundle, compatibleWith: nil)?.withRenderingMode(.alwaysTemplate) + return UIImage(named: name, in: bundle, compatibleWith: nil) } } } @@ -215,7 +215,7 @@ extension ButtonVM { case .sfSymbol(let name): return UIImage(systemName: name)?.withRenderingMode(.alwaysTemplate) case .local(let name, let bundle): - return UIImage(named: name, in: bundle, compatibleWith: nil)?.withRenderingMode(.alwaysTemplate) + return UIImage(named: name, in: bundle, compatibleWith: nil) } } } diff --git a/Sources/ComponentsKit/Components/Button/SUButton.swift b/Sources/ComponentsKit/Components/Button/SUButton.swift index 6db9a813..8e77abd0 100644 --- a/Sources/ComponentsKit/Components/Button/SUButton.swift +++ b/Sources/ComponentsKit/Components/Button/SUButton.swift @@ -59,17 +59,29 @@ public struct SUButton: View { SULoading(model: self.model.preferredLoadingVM) Text(self.model.title) case (false, let uiImage?, .leading): - Image(uiImage: uiImage) + ButtonImageView(image: uiImage, size: self.model.height * 0.6) Text(self.model.title) case (false, let uiImage?, .trailing): Text(self.model.title) - Image(uiImage: uiImage) + ButtonImageView(image: uiImage, size: self.model.height * 0.6) default: Text(self.model.title) } } } +struct ButtonImageView: View { + let image: UIImage + let size: CGFloat + + var body: some View { + Image(uiImage: self.image) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: self.size, height: self.size) + } +} + private struct CustomButtonStyle: SwiftUI.ButtonStyle { let model: ButtonVM diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index 0b53a23a..8a5b5319 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -240,6 +240,11 @@ extension UKButton { static func imageView(_ imageView: UIImageView, model: Model) { imageView.image = model.uiImage imageView.tintColor = model.foregroundColor.uiColor + imageView.contentMode = .scaleAspectFit + imageView.clipsToBounds = true + + let imageSize = model.height * 0.6 + _ = imageView.size(width: imageSize, height: imageSize) } } } From f74bf7b98c008b7212e8371846785938ae75edd4 Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Sun, 6 Apr 2025 22:52:38 +0300 Subject: [PATCH 13/35] added contentSpacing into preview --- .../ComponentsPreview/PreviewPages/ButtonPreview.swift | 5 +++++ .../ComponentsKit/Components/Button/Models/ButtonVM.swift | 1 + 2 files changed, 6 insertions(+) diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift index f74491a6..7a1c56d0 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift @@ -22,6 +22,11 @@ struct ButtonPreview: View { ComponentRadiusPicker(selection: self.$model.cornerRadius) { Text("Custom: 20px").tag(ComponentRadius.custom(20)) } + Picker("Content Spacing", selection: self.$model.contentSpacing) { + Text("4").tag(CGFloat(4)) + Text("8").tag(CGFloat(8)) + Text("12").tag(CGFloat(12)) + } ButtonFontPicker(selection: self.$model.font) if !self.model.isLoading { Toggle("Enabled", isOn: self.$model.isEnabled) diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift index 45278c7f..a34ea0df 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift @@ -185,6 +185,7 @@ extension ButtonVM { || self.isLoading != oldModel?.isLoading || self.imageSrc != oldModel?.imageSrc || self.imageLocation != oldModel?.imageLocation + || self.contentSpacing != oldModel?.contentSpacing } } From f48ce2cffb933b4e9b8974a604f726717da87fe5 Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Mon, 7 Apr 2025 00:23:58 +0300 Subject: [PATCH 14/35] hide title toggle --- .../PreviewPages/ButtonPreview.swift | 8 +++++++- .../Components/Button/Models/ButtonVM.swift | 1 + .../Components/Button/SUButton.swift | 16 ++++++++++++---- .../Components/Button/UKButton.swift | 2 +- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift index 7a1c56d0..b6b3381e 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift @@ -37,7 +37,7 @@ struct ButtonPreview: View { Text("Local").tag(ButtonVM.ImageSource.local("avatar_placeholder")) Text("None").tag(Optional.none) } - if self.model.imageSrc != nil { + if self.model.imageSrc != nil && !self.model.title.isEmpty { Picker("Image Location", selection: self.$model.imageLocation) { Text("Leading").tag(ButtonVM.ImageLocation.leading) Text("Trailing").tag(ButtonVM.ImageLocation.trailing) @@ -45,6 +45,12 @@ struct ButtonPreview: View { } Toggle("Loading", isOn: self.$model.isLoading) SizePicker(selection: self.$model.size) + Toggle("Show Title", isOn: Binding( + get: { !self.model.title.isEmpty }, + set: { newValue in + self.model.title = newValue ? "Button" : "" + } + )) Picker("Style", selection: self.$model.style) { Text("Filled").tag(ButtonStyle.filled) Text("Plain").tag(ButtonStyle.plain) diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift index a34ea0df..5d3efe5e 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift @@ -186,6 +186,7 @@ extension ButtonVM { || self.imageSrc != oldModel?.imageSrc || self.imageLocation != oldModel?.imageLocation || self.contentSpacing != oldModel?.contentSpacing + || self.title != oldModel?.title } } diff --git a/Sources/ComponentsKit/Components/Button/SUButton.swift b/Sources/ComponentsKit/Components/Button/SUButton.swift index 8e77abd0..6447cb49 100644 --- a/Sources/ComponentsKit/Components/Button/SUButton.swift +++ b/Sources/ComponentsKit/Components/Button/SUButton.swift @@ -59,11 +59,19 @@ public struct SUButton: View { SULoading(model: self.model.preferredLoadingVM) Text(self.model.title) case (false, let uiImage?, .leading): - ButtonImageView(image: uiImage, size: self.model.height * 0.6) - Text(self.model.title) + if self.model.title.isEmpty { + ButtonImageView(image: uiImage, size: self.model.height * 0.6) + } else { + ButtonImageView(image: uiImage, size: self.model.height * 0.6) + Text(self.model.title) + } case (false, let uiImage?, .trailing): - Text(self.model.title) - ButtonImageView(image: uiImage, size: self.model.height * 0.6) + if self.model.title.isEmpty { + ButtonImageView(image: uiImage, size: self.model.height * 0.6) + } else { + Text(self.model.title) + ButtonImageView(image: uiImage, size: self.model.height * 0.6) + } default: Text(self.model.title) } diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index 8a5b5319..de43ba03 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -219,7 +219,7 @@ extension UKButton { titleLabel: UILabel, imageView: UIImageView ) { - stackView.spacing = model.contentSpacing + stackView.spacing = model.title.isEmpty ? 0 : model.contentSpacing loaderView.isHidden = !model.isLoading titleLabel.isHidden = false From 9923987f63c39d503493f1fc4a66e0f7a55d265f Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Mon, 7 Apr 2025 13:38:31 +0300 Subject: [PATCH 15/35] imageView contentMode --- .../Components/Button/Models/ButtonVM.swift | 11 +++++++++-- .../Components/Button/SUButton.swift | 8 ++++---- .../Components/Button/UKButton.swift | 16 +++++++++++----- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift index 5d3efe5e..2c7a8353 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift @@ -149,6 +149,13 @@ extension ButtonVM { case .large: 52 } } + var imageSide: CGFloat { + switch self.size { + case .small: 20 + case .medium: 24 + case .large: 28 + } + } var horizontalPadding: CGFloat { return switch self.size { case .small: 16 @@ -195,7 +202,7 @@ extension ButtonVM { guard let imageSrc else { return nil } switch imageSrc { case .sfSymbol(let name): - return UIImage(systemName: name)?.withRenderingMode(.alwaysTemplate) + return UIImage(systemName: name) case .local(let name, let bundle): return UIImage(named: name, in: bundle, compatibleWith: nil) } @@ -215,7 +222,7 @@ extension ButtonVM { guard let imageSrc = self.imageSrc else { return nil } switch imageSrc { case .sfSymbol(let name): - return UIImage(systemName: name)?.withRenderingMode(.alwaysTemplate) + return UIImage(systemName: name) case .local(let name, let bundle): return UIImage(named: name, in: bundle, compatibleWith: nil) } diff --git a/Sources/ComponentsKit/Components/Button/SUButton.swift b/Sources/ComponentsKit/Components/Button/SUButton.swift index 6447cb49..edb82708 100644 --- a/Sources/ComponentsKit/Components/Button/SUButton.swift +++ b/Sources/ComponentsKit/Components/Button/SUButton.swift @@ -60,17 +60,17 @@ public struct SUButton: View { Text(self.model.title) case (false, let uiImage?, .leading): if self.model.title.isEmpty { - ButtonImageView(image: uiImage, size: self.model.height * 0.6) + ButtonImageView(image: uiImage, size: self.model.imageSide) } else { - ButtonImageView(image: uiImage, size: self.model.height * 0.6) + ButtonImageView(image: uiImage, size: self.model.imageSide) Text(self.model.title) } case (false, let uiImage?, .trailing): if self.model.title.isEmpty { - ButtonImageView(image: uiImage, size: self.model.height * 0.6) + ButtonImageView(image: uiImage, size: self.model.imageSide) } else { Text(self.model.title) - ButtonImageView(image: uiImage, size: self.model.height * 0.6) + ButtonImageView(image: uiImage, size: self.model.imageSide) } default: Text(self.model.title) diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index de43ba03..08d56a25 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -111,6 +111,8 @@ open class UKButton: UIView, UKComponent { private func layout() { self.stackView.center() + + self.imageView.size(width: self.model.imageSide, height: self.model.imageSide) } open override func layoutSubviews() { @@ -130,6 +132,14 @@ open class UKButton: UIView, UKComponent { Self.Style.imageView(self.imageView, model: self.model) self.loaderView.model = self.model.preferredLoadingVM self.invalidateIntrinsicContentSize() + + for constraint in self.imageView.constraints { + if constraint.firstAttribute == .width || constraint.firstAttribute == .height { + constraint.constant = self.model.imageSide + } + } + self.setNeedsLayout() + self.layoutIfNeeded() } } @@ -240,11 +250,7 @@ extension UKButton { static func imageView(_ imageView: UIImageView, model: Model) { imageView.image = model.uiImage imageView.tintColor = model.foregroundColor.uiColor - imageView.contentMode = .scaleAspectFit - imageView.clipsToBounds = true - - let imageSize = model.height * 0.6 - _ = imageView.size(width: imageSize, height: imageSize) + imageView.contentMode = .scaleAspectFill } } } From 74e1bf1c37d6843575bc954430f02518875d8427 Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Mon, 7 Apr 2025 15:07:05 +0300 Subject: [PATCH 16/35] swiftui image size bug fix --- .../Components/Button/SUButton.swift | 40 ++++++++++++++----- .../Components/Button/UKButton.swift | 2 +- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/Sources/ComponentsKit/Components/Button/SUButton.swift b/Sources/ComponentsKit/Components/Button/SUButton.swift index edb82708..d52ca001 100644 --- a/Sources/ComponentsKit/Components/Button/SUButton.swift +++ b/Sources/ComponentsKit/Components/Button/SUButton.swift @@ -60,17 +60,21 @@ public struct SUButton: View { Text(self.model.title) case (false, let uiImage?, .leading): if self.model.title.isEmpty { - ButtonImageView(image: uiImage, size: self.model.imageSide) + ButtonImageView(image: uiImage, tintColor: self.model.foregroundColor.uiColor) + .frame(width: self.model.imageSide, height: self.model.imageSide) } else { - ButtonImageView(image: uiImage, size: self.model.imageSide) + ButtonImageView(image: uiImage, tintColor: self.model.foregroundColor.uiColor) + .frame(width: self.model.imageSide, height: self.model.imageSide) Text(self.model.title) } case (false, let uiImage?, .trailing): if self.model.title.isEmpty { - ButtonImageView(image: uiImage, size: self.model.imageSide) + ButtonImageView(image: uiImage, tintColor: self.model.foregroundColor.uiColor) + .frame(width: self.model.imageSide, height: self.model.imageSide) } else { Text(self.model.title) - ButtonImageView(image: uiImage, size: self.model.imageSide) + ButtonImageView(image: uiImage, tintColor: self.model.foregroundColor.uiColor) + .frame(width: self.model.imageSide, height: self.model.imageSide) } default: Text(self.model.title) @@ -78,15 +82,29 @@ public struct SUButton: View { } } -struct ButtonImageView: View { +struct ButtonImageView: UIViewRepresentable { + class InternalImageView: UIImageView { + override var intrinsicContentSize: CGSize { + return self.bounds.size + } + } + let image: UIImage - let size: CGFloat + let tintColor: UIColor? + + func makeUIView(context: Context) -> UIImageView { + let imageView = InternalImageView() + imageView.image = self.image + imageView.contentMode = .scaleAspectFit + imageView.tintColor = self.tintColor + return imageView + } - var body: some View { - Image(uiImage: self.image) - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: self.size, height: self.size) + func updateUIView(_ uiView: UIImageView, context: Context) { + uiView.image = self.image + uiView.tintColor = self.tintColor + uiView.setNeedsLayout() + uiView.layoutIfNeeded() } } diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index 08d56a25..0c582366 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -250,7 +250,7 @@ extension UKButton { static func imageView(_ imageView: UIImageView, model: Model) { imageView.image = model.uiImage imageView.tintColor = model.foregroundColor.uiColor - imageView.contentMode = .scaleAspectFill + imageView.contentMode = .scaleAspectFit } } } From 6a90b2ba370c09fc7fc42b8bf5a2ac97e05864d1 Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Mon, 7 Apr 2025 17:01:13 +0300 Subject: [PATCH 17/35] preview "if" deleted --- .../PreviewPages/ButtonPreview.swift | 26 +++++-------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift index b6b3381e..4bdd75d3 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift @@ -18,6 +18,7 @@ struct ButtonPreview: View { } Form { AnimationScalePicker(selection: self.$model.animationScale) + ButtonFontPicker(selection: self.$model.font) ComponentOptionalColorPicker(selection: self.$model.color) ComponentRadiusPicker(selection: self.$model.cornerRadius) { Text("Custom: 20px").tag(ComponentRadius.custom(20)) @@ -27,22 +28,17 @@ struct ButtonPreview: View { Text("8").tag(CGFloat(8)) Text("12").tag(CGFloat(12)) } - ButtonFontPicker(selection: self.$model.font) - if !self.model.isLoading { - Toggle("Enabled", isOn: self.$model.isEnabled) - } + Toggle("Enabled", isOn: self.$model.isEnabled) Toggle("Full Width", isOn: self.$model.isFullWidth) + Picker("Image Location", selection: self.$model.imageLocation) { + Text("Leading").tag(ButtonVM.ImageLocation.leading) + Text("Trailing").tag(ButtonVM.ImageLocation.trailing) + } Picker("Image Source", selection: self.$model.imageSrc) { Text("SF Symbol").tag(ButtonVM.ImageSource.sfSymbol("camera.fill")) Text("Local").tag(ButtonVM.ImageSource.local("avatar_placeholder")) Text("None").tag(Optional.none) } - if self.model.imageSrc != nil && !self.model.title.isEmpty { - Picker("Image Location", selection: self.$model.imageLocation) { - Text("Leading").tag(ButtonVM.ImageLocation.leading) - Text("Trailing").tag(ButtonVM.ImageLocation.trailing) - } - } Toggle("Loading", isOn: self.$model.isLoading) SizePicker(selection: self.$model.size) Toggle("Show Title", isOn: Binding( @@ -59,16 +55,6 @@ struct ButtonPreview: View { Text("Bordered with medium border").tag(ButtonStyle.bordered(.medium)) Text("Bordered with large border").tag(ButtonStyle.bordered(.large)) } - .onChange(of: self.model.imageLocation) { _ in - if self.model.isLoading { - self.model.isLoading = false - } - } - .onChange(of: self.model.imageSrc) { _ in - if self.model.isLoading { - self.model.isLoading = false - } - } } } } From d97a971f60af77e5e6f18ae118bf3e1a239136fc Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Mon, 7 Apr 2025 17:15:49 +0300 Subject: [PATCH 18/35] deleted unused code --- Sources/ComponentsKit/Components/Button/UKButton.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index 0c582366..d060354e 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -129,8 +129,6 @@ open class UKButton: UIView, UKComponent { self.style() if self.model.shouldUpdateSize(oldModel) { - Self.Style.imageView(self.imageView, model: self.model) - self.loaderView.model = self.model.preferredLoadingVM self.invalidateIntrinsicContentSize() for constraint in self.imageView.constraints { @@ -138,8 +136,6 @@ open class UKButton: UIView, UKComponent { constraint.constant = self.model.imageSide } } - self.setNeedsLayout() - self.layoutIfNeeded() } } From ef33dcd9387addb0a1faf1851f43a532515194a4 Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Mon, 7 Apr 2025 17:21:25 +0300 Subject: [PATCH 19/35] fix constraints - using layout constraints --- .../ComponentsKit/Components/Button/UKButton.swift | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index d060354e..0eb30de3 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -41,6 +41,10 @@ open class UKButton: UIView, UKComponent { /// An optional image displayed alongside the title. public let imageView = UIImageView() + // MARK: - Layout Constraints + + private var imageViewConstraints = LayoutConstraints() + // MARK: UIView Properties open override var intrinsicContentSize: CGSize { @@ -131,11 +135,8 @@ open class UKButton: UIView, UKComponent { if self.model.shouldUpdateSize(oldModel) { self.invalidateIntrinsicContentSize() - for constraint in self.imageView.constraints { - if constraint.firstAttribute == .width || constraint.firstAttribute == .height { - constraint.constant = self.model.imageSide - } - } + self.imageViewConstraints.width?.constant = self.model.imageSide + self.imageViewConstraints.height?.constant = self.model.imageSide } } From 29a2e9b46f840ea9add6a76015ef81aad7ce02d2 Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Mon, 7 Apr 2025 17:21:30 +0300 Subject: [PATCH 20/35] intrinsicContentSize fix --- Sources/ComponentsKit/Components/Button/SUButton.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ComponentsKit/Components/Button/SUButton.swift b/Sources/ComponentsKit/Components/Button/SUButton.swift index d52ca001..a8fdb9bb 100644 --- a/Sources/ComponentsKit/Components/Button/SUButton.swift +++ b/Sources/ComponentsKit/Components/Button/SUButton.swift @@ -85,7 +85,7 @@ public struct SUButton: View { struct ButtonImageView: UIViewRepresentable { class InternalImageView: UIImageView { override var intrinsicContentSize: CGSize { - return self.bounds.size + return .zero } } From 1af122741502a9dbe1ca5a03997b2c86c0c348f7 Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Mon, 7 Apr 2025 17:34:27 +0300 Subject: [PATCH 21/35] uikit component code restructurization --- .../Components/Button/UKButton.swift | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index 0eb30de3..2dac2f28 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -132,6 +132,21 @@ open class UKButton: UIView, UKComponent { self.style() + self.loaderView.isHidden = !self.model.isLoading + self.titleLabel.isHidden = false + self.imageView.isHidden = self.model.isLoading || (self.model.imageSrc == nil) + + switch (self.model.isLoading, self.model.imageSrc, self.model.imageLocation) { + case (false, .some(_), .leading): + self.stackView.removeArrangedSubview(self.imageView) + self.stackView.insertArrangedSubview(self.imageView, at: 0) + case (false, .some(_), .trailing): + self.stackView.removeArrangedSubview(self.imageView) + self.stackView.addArrangedSubview(self.imageView) + default: + break + } + if self.model.shouldUpdateSize(oldModel) { self.invalidateIntrinsicContentSize() @@ -227,21 +242,6 @@ extension UKButton { imageView: UIImageView ) { stackView.spacing = model.title.isEmpty ? 0 : model.contentSpacing - - loaderView.isHidden = !model.isLoading - titleLabel.isHidden = false - imageView.isHidden = model.isLoading || (model.imageSrc == nil) - - switch (model.isLoading, model.imageSrc, model.imageLocation) { - case (false, .some(_), .leading): - stackView.removeArrangedSubview(imageView) - stackView.insertArrangedSubview(imageView, at: 0) - case (false, .some(_), .trailing): - stackView.removeArrangedSubview(imageView) - stackView.addArrangedSubview(imageView) - default: - break - } } static func imageView(_ imageView: UIImageView, model: Model) { From 9448544adec135db905e2fba5294e0896dfe587e Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Mon, 7 Apr 2025 17:38:17 +0300 Subject: [PATCH 22/35] image tint color extracted into model --- .../ComponentsKit/Components/Button/Models/ButtonVM.swift | 6 ++++++ Sources/ComponentsKit/Components/Button/SUButton.swift | 8 ++++---- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift index 2c7a8353..5540200b 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift @@ -228,3 +228,9 @@ extension ButtonVM { } } } + +extension ButtonVM { + public var imageTintColor: UIColor? { + return self.foregroundColor.uiColor + } +} diff --git a/Sources/ComponentsKit/Components/Button/SUButton.swift b/Sources/ComponentsKit/Components/Button/SUButton.swift index a8fdb9bb..41442554 100644 --- a/Sources/ComponentsKit/Components/Button/SUButton.swift +++ b/Sources/ComponentsKit/Components/Button/SUButton.swift @@ -60,20 +60,20 @@ public struct SUButton: View { Text(self.model.title) case (false, let uiImage?, .leading): if self.model.title.isEmpty { - ButtonImageView(image: uiImage, tintColor: self.model.foregroundColor.uiColor) + ButtonImageView(image: uiImage, tintColor: self.model.imageTintColor) .frame(width: self.model.imageSide, height: self.model.imageSide) } else { - ButtonImageView(image: uiImage, tintColor: self.model.foregroundColor.uiColor) + ButtonImageView(image: uiImage, tintColor: self.model.imageTintColor) .frame(width: self.model.imageSide, height: self.model.imageSide) Text(self.model.title) } case (false, let uiImage?, .trailing): if self.model.title.isEmpty { - ButtonImageView(image: uiImage, tintColor: self.model.foregroundColor.uiColor) + ButtonImageView(image: uiImage, tintColor: self.model.imageTintColor) .frame(width: self.model.imageSide, height: self.model.imageSide) } else { Text(self.model.title) - ButtonImageView(image: uiImage, tintColor: self.model.foregroundColor.uiColor) + ButtonImageView(image: uiImage, tintColor: self.model.imageTintColor) .frame(width: self.model.imageSide, height: self.model.imageSide) } default: From 19a71e6f3292bf6644910a6f1590b324a7211bf3 Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Mon, 7 Apr 2025 17:39:21 +0300 Subject: [PATCH 23/35] func updateUIView fix --- Sources/ComponentsKit/Components/Button/SUButton.swift | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Sources/ComponentsKit/Components/Button/SUButton.swift b/Sources/ComponentsKit/Components/Button/SUButton.swift index 41442554..e7c8f3ae 100644 --- a/Sources/ComponentsKit/Components/Button/SUButton.swift +++ b/Sources/ComponentsKit/Components/Button/SUButton.swift @@ -100,11 +100,9 @@ struct ButtonImageView: UIViewRepresentable { return imageView } - func updateUIView(_ uiView: UIImageView, context: Context) { - uiView.image = self.image - uiView.tintColor = self.tintColor - uiView.setNeedsLayout() - uiView.layoutIfNeeded() + func updateUIView(_ imageView: UIImageView, context: Context) { + imageView.image = self.image + imageView.tintColor = self.tintColor } } From 97622af7383c1fa1792c2f1164779fa9489ac98e Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Mon, 7 Apr 2025 17:43:52 +0300 Subject: [PATCH 24/35] image extension fix - moved into shared helpers - duplicated code deleted --- .../Components/Button/Models/ButtonVM.swift | 36 +++++++------------ .../Components/Button/UKButton.swift | 2 +- 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift index 5540200b..10995335 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift @@ -165,6 +165,18 @@ extension ButtonVM { } } +extension ButtonVM { + var image: UIImage? { + guard let imageSrc else { return nil } + switch imageSrc { + case .sfSymbol(let name): + return UIImage(systemName: name) + case .local(let name, let bundle): + return UIImage(named: name, in: bundle, compatibleWith: nil) + } + } +} + // MARK: UIKit Helpers extension ButtonVM { @@ -197,18 +209,6 @@ extension ButtonVM { } } -extension ButtonVM { - public var uiImage: UIImage? { - guard let imageSrc else { return nil } - switch imageSrc { - case .sfSymbol(let name): - return UIImage(systemName: name) - case .local(let name, let bundle): - return UIImage(named: name, in: bundle, compatibleWith: nil) - } - } -} - // MARK: SwiftUI Helpers extension ButtonVM { @@ -217,18 +217,6 @@ extension ButtonVM { } } -extension ButtonVM { - public var image: UIImage? { - guard let imageSrc = self.imageSrc else { return nil } - switch imageSrc { - case .sfSymbol(let name): - return UIImage(systemName: name) - case .local(let name, let bundle): - return UIImage(named: name, in: bundle, compatibleWith: nil) - } - } -} - extension ButtonVM { public var imageTintColor: UIColor? { return self.foregroundColor.uiColor diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index 2dac2f28..bcfbbdd7 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -245,7 +245,7 @@ extension UKButton { } static func imageView(_ imageView: UIImageView, model: Model) { - imageView.image = model.uiImage + imageView.image = model.image imageView.tintColor = model.foregroundColor.uiColor imageView.contentMode = .scaleAspectFit } From 14d93ad03d8772d8790ad33517ce4a9ea1bc9e85 Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Mon, 7 Apr 2025 17:57:02 +0300 Subject: [PATCH 25/35] ImageLocation extracted --- .../Button/Models/ButtonImageLocation.swift | 11 +++++++++++ .../Components/Button/Models/ButtonImageSource.swift | 8 -------- 2 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 Sources/ComponentsKit/Components/Button/Models/ButtonImageLocation.swift diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonImageLocation.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonImageLocation.swift new file mode 100644 index 00000000..e22a109a --- /dev/null +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonImageLocation.swift @@ -0,0 +1,11 @@ +import Foundation + +/// Specifies the position of the image relative to the button's title. +extension ButtonVM { + public enum ImageLocation { + /// The image is displayed before the title. + case leading + /// The image is displayed after the title. + case trailing + } +} diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonImageSource.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonImageSource.swift index 034bebd7..c9598303 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonImageSource.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonImageSource.swift @@ -15,12 +15,4 @@ extension ButtonVM { /// - bundle: The bundle containing the image resource. Defaults to `nil` to use the main bundle. case local(String, bundle: Bundle? = nil) } - - /// Specifies the position of the image relative to the button's title. - public enum ImageLocation { - /// The image is displayed before the title. - case leading - /// The image is displayed after the title. - case trailing - } } From 8971daa32f73e9d17fb58bc71121d71d4be23960 Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Mon, 7 Apr 2025 18:22:08 +0300 Subject: [PATCH 26/35] fix spacing white titleLabel is hidden --- Sources/ComponentsKit/Components/Button/UKButton.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index bcfbbdd7..48ac38bc 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -133,8 +133,7 @@ open class UKButton: UIView, UKComponent { self.style() self.loaderView.isHidden = !self.model.isLoading - self.titleLabel.isHidden = false - self.imageView.isHidden = self.model.isLoading || (self.model.imageSrc == nil) + self.titleLabel.isHidden = self.model.title.isEmpty switch (self.model.isLoading, self.model.imageSrc, self.model.imageLocation) { case (false, .some(_), .leading): @@ -241,13 +240,14 @@ extension UKButton { titleLabel: UILabel, imageView: UIImageView ) { - stackView.spacing = model.title.isEmpty ? 0 : model.contentSpacing + stackView.spacing = model.contentSpacing } static func imageView(_ imageView: UIImageView, model: Model) { imageView.image = model.image imageView.tintColor = model.foregroundColor.uiColor imageView.contentMode = .scaleAspectFit + imageView.isHidden = model.isLoading || (model.imageSrc.isNil) } } } From 5058c92e7901bccba25dfc56a26e79a3ed23d054 Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Mon, 7 Apr 2025 18:38:29 +0300 Subject: [PATCH 27/35] deleted if/else from swiftui component --- .../Components/Button/SUButton.swift | 28 ++++++++----------- 1 file changed, 12 insertions(+), 16 deletions(-) diff --git a/Sources/ComponentsKit/Components/Button/SUButton.swift b/Sources/ComponentsKit/Components/Button/SUButton.swift index e7c8f3ae..5f5099e1 100644 --- a/Sources/ComponentsKit/Components/Button/SUButton.swift +++ b/Sources/ComponentsKit/Components/Button/SUButton.swift @@ -58,24 +58,20 @@ public struct SUButton: View { case (true, _, _): SULoading(model: self.model.preferredLoadingVM) Text(self.model.title) + case (false, let uiImage?, .leading) where self.model.title.isEmpty: + ButtonImageView(image: uiImage, tintColor: self.model.imageTintColor) + .frame(width: self.model.imageSide, height: self.model.imageSide) case (false, let uiImage?, .leading): - if self.model.title.isEmpty { - ButtonImageView(image: uiImage, tintColor: self.model.imageTintColor) - .frame(width: self.model.imageSide, height: self.model.imageSide) - } else { - ButtonImageView(image: uiImage, tintColor: self.model.imageTintColor) - .frame(width: self.model.imageSide, height: self.model.imageSide) - Text(self.model.title) - } + ButtonImageView(image: uiImage, tintColor: self.model.imageTintColor) + .frame(width: self.model.imageSide, height: self.model.imageSide) + Text(self.model.title) + case (false, let uiImage?, .trailing) where self.model.title.isEmpty: + ButtonImageView(image: uiImage, tintColor: self.model.imageTintColor) + .frame(width: self.model.imageSide, height: self.model.imageSide) case (false, let uiImage?, .trailing): - if self.model.title.isEmpty { - ButtonImageView(image: uiImage, tintColor: self.model.imageTintColor) - .frame(width: self.model.imageSide, height: self.model.imageSide) - } else { - Text(self.model.title) - ButtonImageView(image: uiImage, tintColor: self.model.imageTintColor) - .frame(width: self.model.imageSide, height: self.model.imageSide) - } + Text(self.model.title) + ButtonImageView(image: uiImage, tintColor: self.model.imageTintColor) + .frame(width: self.model.imageSide, height: self.model.imageSide) default: Text(self.model.title) } From 0b523e4a9d7cc4738a160cea5e6651f4a1ee6819 Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Mon, 7 Apr 2025 18:42:45 +0300 Subject: [PATCH 28/35] fix tint color to local image --- Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift index 10995335..0bdc2b88 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift @@ -170,9 +170,9 @@ extension ButtonVM { guard let imageSrc else { return nil } switch imageSrc { case .sfSymbol(let name): - return UIImage(systemName: name) + return UIImage(systemName: name)?.withRenderingMode(.alwaysTemplate) case .local(let name, let bundle): - return UIImage(named: name, in: bundle, compatibleWith: nil) + return UIImage(named: name, in: bundle, compatibleWith: nil)?.withRenderingMode(.alwaysTemplate) } } } From 5cfdf75b419b709dbbf158a2ccf6022552261e9b Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Tue, 8 Apr 2025 15:08:47 +0300 Subject: [PATCH 29/35] fix preview sort --- .../ComponentsPreview/PreviewPages/ButtonPreview.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift index 4bdd75d3..7f7715e6 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift @@ -18,17 +18,17 @@ struct ButtonPreview: View { } Form { AnimationScalePicker(selection: self.$model.animationScale) - ButtonFontPicker(selection: self.$model.font) ComponentOptionalColorPicker(selection: self.$model.color) - ComponentRadiusPicker(selection: self.$model.cornerRadius) { - Text("Custom: 20px").tag(ComponentRadius.custom(20)) - } Picker("Content Spacing", selection: self.$model.contentSpacing) { Text("4").tag(CGFloat(4)) Text("8").tag(CGFloat(8)) Text("12").tag(CGFloat(12)) } + ComponentRadiusPicker(selection: self.$model.cornerRadius) { + Text("Custom: 20px").tag(ComponentRadius.custom(20)) + } Toggle("Enabled", isOn: self.$model.isEnabled) + ButtonFontPicker(selection: self.$model.font) Toggle("Full Width", isOn: self.$model.isFullWidth) Picker("Image Location", selection: self.$model.imageLocation) { Text("Leading").tag(ButtonVM.ImageLocation.leading) @@ -40,13 +40,13 @@ struct ButtonPreview: View { Text("None").tag(Optional.none) } Toggle("Loading", isOn: self.$model.isLoading) - SizePicker(selection: self.$model.size) Toggle("Show Title", isOn: Binding( get: { !self.model.title.isEmpty }, set: { newValue in self.model.title = newValue ? "Button" : "" } )) + SizePicker(selection: self.$model.size) Picker("Style", selection: self.$model.style) { Text("Filled").tag(ButtonStyle.filled) Text("Plain").tag(ButtonStyle.plain) From f3a2ee288cd209d31aa660417a79e6f9afea25b1 Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Tue, 8 Apr 2025 15:28:42 +0300 Subject: [PATCH 30/35] code fix - delete unused code - code style fix - documentation fix - fix extension accessibility --- .../Components/Button/Models/ButtonVM.swift | 11 ++++++----- .../Components/Button/SUButton.swift | 17 ++++++++--------- .../Components/Button/UKButton.swift | 19 ++++++------------- 3 files changed, 20 insertions(+), 27 deletions(-) diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift index 0bdc2b88..cadb7ac1 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift @@ -77,7 +77,6 @@ extension ButtonVM { var isInteractive: Bool { self.isEnabled && !self.isLoading } - var preferredLoadingVM: LoadingVM { return self.loadingVM ?? .init { $0.color = .init( @@ -170,9 +169,12 @@ extension ButtonVM { guard let imageSrc else { return nil } switch imageSrc { case .sfSymbol(let name): - return UIImage(systemName: name)?.withRenderingMode(.alwaysTemplate) + return UIImage(systemName: name)?.withTintColor( + self.foregroundColor.uiColor, + renderingMode: .alwaysTemplate + ) case .local(let name, let bundle): - return UIImage(named: name, in: bundle, compatibleWith: nil)?.withRenderingMode(.alwaysTemplate) + return UIImage(named: name, in: bundle, compatibleWith: nil) } } } @@ -203,7 +205,6 @@ extension ButtonVM { || self.isFullWidth != oldModel?.isFullWidth || self.isLoading != oldModel?.isLoading || self.imageSrc != oldModel?.imageSrc - || self.imageLocation != oldModel?.imageLocation || self.contentSpacing != oldModel?.contentSpacing || self.title != oldModel?.title } @@ -218,7 +219,7 @@ extension ButtonVM { } extension ButtonVM { - public var imageTintColor: UIColor? { + var imageTintColor: UIColor? { return self.foregroundColor.uiColor } } diff --git a/Sources/ComponentsKit/Components/Button/SUButton.swift b/Sources/ComponentsKit/Components/Button/SUButton.swift index 5f5099e1..9dcf96d1 100644 --- a/Sources/ComponentsKit/Components/Button/SUButton.swift +++ b/Sources/ComponentsKit/Components/Button/SUButton.swift @@ -59,26 +59,28 @@ public struct SUButton: View { SULoading(model: self.model.preferredLoadingVM) Text(self.model.title) case (false, let uiImage?, .leading) where self.model.title.isEmpty: - ButtonImageView(image: uiImage, tintColor: self.model.imageTintColor) + ButtonImageView(image: uiImage) .frame(width: self.model.imageSide, height: self.model.imageSide) case (false, let uiImage?, .leading): - ButtonImageView(image: uiImage, tintColor: self.model.imageTintColor) + ButtonImageView(image: uiImage) .frame(width: self.model.imageSide, height: self.model.imageSide) Text(self.model.title) case (false, let uiImage?, .trailing) where self.model.title.isEmpty: - ButtonImageView(image: uiImage, tintColor: self.model.imageTintColor) + ButtonImageView(image: uiImage) .frame(width: self.model.imageSide, height: self.model.imageSide) case (false, let uiImage?, .trailing): Text(self.model.title) - ButtonImageView(image: uiImage, tintColor: self.model.imageTintColor) + ButtonImageView(image: uiImage) .frame(width: self.model.imageSide, height: self.model.imageSide) - default: + case (false, _, _): Text(self.model.title) } } } -struct ButtonImageView: UIViewRepresentable { +// MARK: - Helpers + +private struct ButtonImageView: UIViewRepresentable { class InternalImageView: UIImageView { override var intrinsicContentSize: CGSize { return .zero @@ -86,19 +88,16 @@ struct ButtonImageView: UIViewRepresentable { } let image: UIImage - let tintColor: UIColor? func makeUIView(context: Context) -> UIImageView { let imageView = InternalImageView() imageView.image = self.image imageView.contentMode = .scaleAspectFit - imageView.tintColor = self.tintColor return imageView } func updateUIView(_ imageView: UIImageView, context: Context) { imageView.image = self.image - imageView.tintColor = self.tintColor } } diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index 48ac38bc..d4de7d54 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -41,7 +41,7 @@ open class UKButton: UIView, UKComponent { /// An optional image displayed alongside the title. public let imageView = UIImageView() - // MARK: - Layout Constraints + // MARK: Private Properties private var imageViewConstraints = LayoutConstraints() @@ -100,15 +100,12 @@ open class UKButton: UIView, UKComponent { Self.Style.titleLabel(self.titleLabel, model: self.model) Self.Style.configureStackView( self.stackView, - model: self.model, - loaderView: self.loaderView, - titleLabel: self.titleLabel, - imageView: self.imageView + model: self.model ) Self.Style.imageView(self.imageView, model: self.model) self.loaderView.model = self.model.preferredLoadingVM - self.loaderView.isHidden = !self.model.isLoading + self.loaderView.isVisible = self.model.isLoading } // MARK: Layout @@ -132,7 +129,7 @@ open class UKButton: UIView, UKComponent { self.style() - self.loaderView.isHidden = !self.model.isLoading + self.loaderView.isVisible = self.model.isLoading self.titleLabel.isHidden = self.model.title.isEmpty switch (self.model.isLoading, self.model.imageSrc, self.model.imageLocation) { @@ -235,19 +232,15 @@ extension UKButton { } static func configureStackView( _ stackView: UIStackView, - model: Model, - loaderView: UKLoading, - titleLabel: UILabel, - imageView: UIImageView + model: Model ) { stackView.spacing = model.contentSpacing } static func imageView(_ imageView: UIImageView, model: Model) { imageView.image = model.image - imageView.tintColor = model.foregroundColor.uiColor imageView.contentMode = .scaleAspectFit - imageView.isHidden = model.isLoading || (model.imageSrc.isNil) + imageView.isHidden = model.isLoading || model.imageSrc.isNil } } } From 073f1ef3f5e9f35f788e93194a24b58414a01c73 Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Tue, 8 Apr 2025 16:38:44 +0300 Subject: [PATCH 31/35] fix image size updating --- .../Components/Button/Models/ButtonVM.swift | 25 ++++++++++----- .../Components/Button/UKButton.swift | 32 ++++++++++++------- 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift index cadb7ac1..4f243485 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift @@ -199,14 +199,23 @@ extension ButtonVM { return .init(width: width, height: self.height) } - func shouldUpdateSize(_ oldModel: Self?) -> Bool { - return self.size != oldModel?.size - || self.font != oldModel?.font - || self.isFullWidth != oldModel?.isFullWidth - || self.isLoading != oldModel?.isLoading - || self.imageSrc != oldModel?.imageSrc - || self.contentSpacing != oldModel?.contentSpacing - || self.title != oldModel?.title + func shouldUpdateImagePosition(_ oldModel: Self?) -> Bool { + guard let oldModel else { return true } + return self.imageLocation != oldModel.imageLocation + } + func shouldUpdateImageSize(_ oldModel: Self?) -> Bool { + guard let oldModel else { return true } + return self.imageSide != oldModel.imageSide + } + func shouldRecalculateSize(_ oldModel: Self?) -> Bool { + guard let oldModel else { return true } + return self.size != oldModel.size + || self.font != oldModel.font + || self.isFullWidth != oldModel.isFullWidth + || self.isLoading != oldModel.isLoading + || self.imageSrc != oldModel.imageSrc + || self.contentSpacing != oldModel.contentSpacing + || self.title != oldModel.title } } diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index d4de7d54..1568a758 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -113,7 +113,10 @@ open class UKButton: UIView, UKComponent { private func layout() { self.stackView.center() - self.imageView.size(width: self.model.imageSide, height: self.model.imageSide) + self.imageViewConstraints = self.imageView.size( + width: self.model.imageSide, + height: self.model.imageSide + ) } open override func layoutSubviews() { @@ -132,22 +135,27 @@ open class UKButton: UIView, UKComponent { self.loaderView.isVisible = self.model.isLoading self.titleLabel.isHidden = self.model.title.isEmpty - switch (self.model.isLoading, self.model.imageSrc, self.model.imageLocation) { - case (false, .some(_), .leading): - self.stackView.removeArrangedSubview(self.imageView) - self.stackView.insertArrangedSubview(self.imageView, at: 0) - case (false, .some(_), .trailing): + if self.model.shouldUpdateImagePosition(oldModel) { self.stackView.removeArrangedSubview(self.imageView) - self.stackView.addArrangedSubview(self.imageView) - default: - break + switch self.model.imageLocation { + case .leading: + self.stackView.insertArrangedSubview(self.imageView, at: 0) + case .trailing: + self.stackView.addArrangedSubview(self.imageView) + } } - if self.model.shouldUpdateSize(oldModel) { - self.invalidateIntrinsicContentSize() - + if self.model.shouldUpdateImageSize(oldModel) { self.imageViewConstraints.width?.constant = self.model.imageSide self.imageViewConstraints.height?.constant = self.model.imageSide + + UIView.performWithoutAnimation { + self.layoutIfNeeded() + } + } + + if self.model.shouldRecalculateSize(oldModel) { + self.invalidateIntrinsicContentSize() } } From 9243dcda45e7fd81ba451c77d7f8860eb8fdf634 Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Tue, 8 Apr 2025 16:40:34 +0300 Subject: [PATCH 32/35] some fix stackView --- Sources/ComponentsKit/Components/Button/UKButton.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index 1568a758..e8cbe3e3 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -78,9 +78,6 @@ open class UKButton: UIView, UKComponent { private func setup() { self.addSubview(self.stackView) - self.stackView.axis = .horizontal - self.stackView.alignment = .center - self.stackView.spacing = self.model.contentSpacing self.stackView.addArrangedSubview(self.imageView) self.stackView.addArrangedSubview(self.loaderView) @@ -243,6 +240,9 @@ extension UKButton { model: Model ) { stackView.spacing = model.contentSpacing + stackView.axis = .horizontal + stackView.alignment = .center + stackView.spacing = model.contentSpacing } static func imageView(_ imageView: UIImageView, model: Model) { From ae6e9c535ae1f9e74c891dce49fd60c30b52df47 Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Tue, 8 Apr 2025 16:48:04 +0300 Subject: [PATCH 33/35] tint color fix --- Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift index 4f243485..78b38307 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift @@ -171,7 +171,7 @@ extension ButtonVM { case .sfSymbol(let name): return UIImage(systemName: name)?.withTintColor( self.foregroundColor.uiColor, - renderingMode: .alwaysTemplate + renderingMode: .alwaysOriginal ) case .local(let name, let bundle): return UIImage(named: name, in: bundle, compatibleWith: nil) From 3ff5b27c062d68750558593dbe0de655647d3243 Mon Sep 17 00:00:00 2001 From: Vislov Ivan Date: Tue, 8 Apr 2025 17:11:29 +0300 Subject: [PATCH 34/35] fix imageView location bug - the image view added based on the imagePosition value in the model --- Sources/ComponentsKit/Components/Button/UKButton.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index e8cbe3e3..49ce1b35 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -79,7 +79,13 @@ open class UKButton: UIView, UKComponent { private func setup() { self.addSubview(self.stackView) - self.stackView.addArrangedSubview(self.imageView) + switch self.model.imageLocation { + case .leading: + self.stackView.insertArrangedSubview(self.imageView, at: 0) + case .trailing: + self.stackView.addArrangedSubview(self.imageView) + } + self.stackView.addArrangedSubview(self.loaderView) self.stackView.addArrangedSubview(self.titleLabel) From 04ea424cb3b09af50e64e70f5a31f443bbdfa8c6 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Tue, 8 Apr 2025 16:42:08 +0200 Subject: [PATCH 35/35] improvements and bug fixes --- .../Components/Button/Models/ButtonVM.swift | 7 ------ .../Components/Button/UKButton.swift | 23 ++++++++----------- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift index 78b38307..90c3bc45 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift @@ -1,4 +1,3 @@ -import SwiftUI import UIKit /// A model that defines the appearance properties for a button component. @@ -226,9 +225,3 @@ extension ButtonVM { return self.isFullWidth ? 10_000 : nil } } - -extension ButtonVM { - var imageTintColor: UIColor? { - return self.foregroundColor.uiColor - } -} diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index 49ce1b35..113d2a63 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -79,6 +79,8 @@ open class UKButton: UIView, UKComponent { private func setup() { self.addSubview(self.stackView) + self.stackView.addArrangedSubview(self.loaderView) + self.stackView.addArrangedSubview(self.titleLabel) switch self.model.imageLocation { case .leading: self.stackView.insertArrangedSubview(self.imageView, at: 0) @@ -86,9 +88,6 @@ open class UKButton: UIView, UKComponent { self.stackView.addArrangedSubview(self.imageView) } - self.stackView.addArrangedSubview(self.loaderView) - self.stackView.addArrangedSubview(self.titleLabel) - if #available(iOS 17.0, *) { self.registerForTraitChanges([UITraitUserInterfaceStyle.self]) { (view: Self, _: UITraitCollection) in view.handleTraitChanges() @@ -101,14 +100,9 @@ open class UKButton: UIView, UKComponent { private func style() { Self.Style.mainView(self, model: self.model) Self.Style.titleLabel(self.titleLabel, model: self.model) - Self.Style.configureStackView( - self.stackView, - model: self.model - ) + Self.Style.configureStackView(self.stackView, model: self.model) + Self.Style.loaderView(self.loaderView, model: self.model) Self.Style.imageView(self.imageView, model: self.model) - - self.loaderView.model = self.model.preferredLoadingVM - self.loaderView.isVisible = self.model.isLoading } // MARK: Layout @@ -135,9 +129,6 @@ open class UKButton: UIView, UKComponent { self.style() - self.loaderView.isVisible = self.model.isLoading - self.titleLabel.isHidden = self.model.title.isEmpty - if self.model.shouldUpdateImagePosition(oldModel) { self.stackView.removeArrangedSubview(self.imageView) switch self.model.imageLocation { @@ -240,6 +231,7 @@ extension UKButton { label.text = model.title label.font = model.preferredFont.uiFont label.textColor = model.foregroundColor.uiColor + label.isHidden = model.title.isEmpty } static func configureStackView( _ stackView: UIStackView, @@ -250,7 +242,10 @@ extension UKButton { stackView.alignment = .center stackView.spacing = model.contentSpacing } - + static func loaderView(_ view: UKLoading, model: Model) { + view.model = model.preferredLoadingVM + view.isVisible = model.isLoading + } static func imageView(_ imageView: UIImageView, model: Model) { imageView.image = model.image imageView.contentMode = .scaleAspectFit