From 02030be3c2f434e54d415567a046649b8711e9d5 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Wed, 7 May 2025 13:22:48 +0300 Subject: [PATCH 1/8] set tint color for button's image --- .../avatar_placeholder.imageset/Contents.json | 3 ++ .../Components/Avatar/Models/AvatarVM.swift | 2 +- .../Components/Button/SUButton.swift | 31 ++++++++++++++----- .../Components/Button/UKButton.swift | 1 + 4 files changed, 28 insertions(+), 9 deletions(-) diff --git a/Examples/DemosApp/DemosApp/Assets.xcassets/avatar_placeholder.imageset/Contents.json b/Examples/DemosApp/DemosApp/Assets.xcassets/avatar_placeholder.imageset/Contents.json index 68cb3fb8..1b231c57 100644 --- a/Examples/DemosApp/DemosApp/Assets.xcassets/avatar_placeholder.imageset/Contents.json +++ b/Examples/DemosApp/DemosApp/Assets.xcassets/avatar_placeholder.imageset/Contents.json @@ -17,5 +17,8 @@ "info" : { "author" : "xcode", "version" : 1 + }, + "properties" : { + "template-rendering-intent" : "template" } } diff --git a/Sources/ComponentsKit/Components/Avatar/Models/AvatarVM.swift b/Sources/ComponentsKit/Components/Avatar/Models/AvatarVM.swift index 2d7418b0..b946e1ed 100644 --- a/Sources/ComponentsKit/Components/Avatar/Models/AvatarVM.swift +++ b/Sources/ComponentsKit/Components/Avatar/Models/AvatarVM.swift @@ -95,7 +95,7 @@ extension AvatarVM { self.placeholderBackgroundColor.setFill() UIBezierPath(rect: CGRect(origin: .zero, size: size)).fill() - icon?.withTintColor(self.placeholderForegroundColor, renderingMode: .alwaysOriginal).draw(in: CGRect( + icon?.withTintColor(self.placeholderForegroundColor).draw(in: CGRect( x: (size.width - iconSize.width) / 2, y: (size.height - iconSize.height) / 2, width: iconSize.width, diff --git a/Sources/ComponentsKit/Components/Button/SUButton.swift b/Sources/ComponentsKit/Components/Button/SUButton.swift index 80cf0e89..10b39db9 100644 --- a/Sources/ComponentsKit/Components/Button/SUButton.swift +++ b/Sources/ComponentsKit/Components/Button/SUButton.swift @@ -59,19 +59,31 @@ 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) - .frame(width: self.model.imageSide, height: self.model.imageSide) + ButtonImageView( + image: uiImage, + tintColor: self.model.foregroundColor.uiColor + ) + .frame(width: self.model.imageSide, height: self.model.imageSide) case (false, let uiImage?, .leading): - ButtonImageView(image: uiImage) - .frame(width: self.model.imageSide, height: 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) where self.model.title.isEmpty: - ButtonImageView(image: uiImage) - .frame(width: self.model.imageSide, height: self.model.imageSide) + ButtonImageView( + image: uiImage, + tintColor: self.model.foregroundColor.uiColor + ) + .frame(width: self.model.imageSide, height: self.model.imageSide) case (false, let uiImage?, .trailing): Text(self.model.title) - ButtonImageView(image: uiImage) - .frame(width: self.model.imageSide, height: self.model.imageSide) + ButtonImageView( + image: uiImage, + tintColor: self.model.foregroundColor.uiColor + ) + .frame(width: self.model.imageSide, height: self.model.imageSide) case (false, _, _): Text(self.model.title) } @@ -88,16 +100,19 @@ private struct ButtonImageView: UIViewRepresentable { } let image: UIImage + let tintColor: UIColor func makeUIView(context: Context) -> UIImageView { let imageView = InternalImageView() imageView.image = self.image + imageView.tintColor = self.tintColor imageView.contentMode = .scaleAspectFit 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 e7b63552..a4da80a6 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -250,6 +250,7 @@ extension UKButton { imageView.image = model.image imageView.contentMode = .scaleAspectFit imageView.isHidden = model.isLoading || model.imageSrc.isNil + imageView.tintColor = model.foregroundColor.uiColor } } } From 198cbb8e8b373e31cfb49a2cb55451af07bf7430 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Wed, 7 May 2025 14:18:30 +0300 Subject: [PATCH 2/8] enable user interaction for images in buttons --- Sources/ComponentsKit/Components/Button/SUButton.swift | 1 + Sources/ComponentsKit/Components/Button/UKButton.swift | 1 + 2 files changed, 2 insertions(+) diff --git a/Sources/ComponentsKit/Components/Button/SUButton.swift b/Sources/ComponentsKit/Components/Button/SUButton.swift index 10b39db9..2a4fdc3c 100644 --- a/Sources/ComponentsKit/Components/Button/SUButton.swift +++ b/Sources/ComponentsKit/Components/Button/SUButton.swift @@ -107,6 +107,7 @@ private struct ButtonImageView: UIViewRepresentable { imageView.image = self.image imageView.tintColor = self.tintColor imageView.contentMode = .scaleAspectFit + imageView.isUserInteractionEnabled = true return imageView } diff --git a/Sources/ComponentsKit/Components/Button/UKButton.swift b/Sources/ComponentsKit/Components/Button/UKButton.swift index a4da80a6..9653de56 100644 --- a/Sources/ComponentsKit/Components/Button/UKButton.swift +++ b/Sources/ComponentsKit/Components/Button/UKButton.swift @@ -251,6 +251,7 @@ extension UKButton { imageView.contentMode = .scaleAspectFit imageView.isHidden = model.isLoading || model.imageSrc.isNil imageView.tintColor = model.foregroundColor.uiColor + imageView.isUserInteractionEnabled = true } } } From 7718b0ea65afb74e106b0efa1a1fc9c4732adf11 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Thu, 8 May 2025 11:31:36 +0300 Subject: [PATCH 3/8] set `contentShape` to all content in `SUButton` --- 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 2a4fdc3c..63399930 100644 --- a/Sources/ComponentsKit/Components/Button/SUButton.swift +++ b/Sources/ComponentsKit/Components/Button/SUButton.swift @@ -124,10 +124,10 @@ private struct CustomButtonStyle: SwiftUI.ButtonStyle { configuration.label .font(self.model.preferredFont.font) .lineLimit(1) - .contentShape(.rect) .padding(.horizontal, self.model.horizontalPadding) .frame(maxWidth: self.model.width) .frame(height: self.model.height) + .contentShape(.rect) .foregroundStyle(self.model.foregroundColor.color) .background(self.model.backgroundColor?.color ?? .clear) .clipShape( From 8538ef28b94ac6486ae0813fa7679d19052b1295 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Thu, 8 May 2025 13:37:24 +0300 Subject: [PATCH 4/8] change button's paddings when there is no title and loader --- .../Components/Button/Models/ButtonVM.swift | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift index bd9242fb..920cfb03 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift @@ -164,10 +164,18 @@ extension ButtonVM { case .minimal: return 0 case .light, .filled, .bordered, .plain: - return switch self.size { - case .small: 16 - case .medium: 20 - case .large: 24 + if self.title.isNotEmpty || self.isLoading { + return switch self.size { + case .small: 16 + case .medium: 20 + case .large: 24 + } + } else { + return switch self.size { + case .small: 8 + case .medium: 10 + case .large: 12 + } } } } From f0d764296158aa1312ba1b571a093b6bff3a01f3 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Fri, 9 May 2025 16:15:03 +0300 Subject: [PATCH 5/8] properly handle `dismiss` from environment in modals --- .../Modal/SwiftUI/Helpers/ModalPresentationModifier.swift | 5 ++++- .../SwiftUI/Helpers/ModalPresentationWithItemModifier.swift | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Sources/ComponentsKit/Components/Modal/SwiftUI/Helpers/ModalPresentationModifier.swift b/Sources/ComponentsKit/Components/Modal/SwiftUI/Helpers/ModalPresentationModifier.swift index 9b0eebc7..0d99bfa0 100644 --- a/Sources/ComponentsKit/Components/Modal/SwiftUI/Helpers/ModalPresentationModifier.swift +++ b/Sources/ComponentsKit/Components/Modal/SwiftUI/Helpers/ModalPresentationModifier.swift @@ -38,7 +38,10 @@ struct ModalPresentationModifier: ViewModifier { } } .fullScreenCover( - isPresented: self.$isPresented, + isPresented: .init( + get: { self.isPresented }, + set: { self.isContentVisible = $0 } + ), onDismiss: self.onDismiss, content: { self.content() diff --git a/Sources/ComponentsKit/Components/Modal/SwiftUI/Helpers/ModalPresentationWithItemModifier.swift b/Sources/ComponentsKit/Components/Modal/SwiftUI/Helpers/ModalPresentationWithItemModifier.swift index 0ad9a283..d6b6eed8 100644 --- a/Sources/ComponentsKit/Components/Modal/SwiftUI/Helpers/ModalPresentationWithItemModifier.swift +++ b/Sources/ComponentsKit/Components/Modal/SwiftUI/Helpers/ModalPresentationWithItemModifier.swift @@ -39,7 +39,10 @@ struct ModalPresentationWithItemModifier: ViewM } } .fullScreenCover( - item: self.$presentedItem, + item: .init( + get: { self.presentedItem }, + set: { self.visibleItem = $0 } + ), onDismiss: self.onDismiss, content: { item in self.content(item) From 129c382f44085562910f330973d1297781748cd8 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Tue, 20 May 2025 13:55:45 +0300 Subject: [PATCH 6/8] fix: set missing `onDismiss` params in su modals --- .../ComponentsKit/Components/Modal/SwiftUI/SUBottomModal.swift | 1 + .../ComponentsKit/Components/Modal/SwiftUI/SUCenterModal.swift | 1 + 2 files changed, 2 insertions(+) diff --git a/Sources/ComponentsKit/Components/Modal/SwiftUI/SUBottomModal.swift b/Sources/ComponentsKit/Components/Modal/SwiftUI/SUBottomModal.swift index 8323bc39..1d062a7e 100644 --- a/Sources/ComponentsKit/Components/Modal/SwiftUI/SUBottomModal.swift +++ b/Sources/ComponentsKit/Components/Modal/SwiftUI/SUBottomModal.swift @@ -309,6 +309,7 @@ extension View { return self.bottomModal( item: item, model: model, + onDismiss: onDismiss, header: { _ in EmptyView() }, body: body, footer: { _ in EmptyView() } diff --git a/Sources/ComponentsKit/Components/Modal/SwiftUI/SUCenterModal.swift b/Sources/ComponentsKit/Components/Modal/SwiftUI/SUCenterModal.swift index 610a4933..39b54503 100644 --- a/Sources/ComponentsKit/Components/Modal/SwiftUI/SUCenterModal.swift +++ b/Sources/ComponentsKit/Components/Modal/SwiftUI/SUCenterModal.swift @@ -271,6 +271,7 @@ extension View { return self.centerModal( item: item, model: model, + onDismiss: onDismiss, header: { _ in EmptyView() }, body: body, footer: { _ in EmptyView() } From 454264a6a440d93537afcb74d4db2f5c5de66651 Mon Sep 17 00:00:00 2001 From: Mikhail Date: Thu, 22 May 2025 12:51:23 +0300 Subject: [PATCH 7/8] add `ImageRenderingMode` type --- .../Shared/Types/ImageRenderingMode.swift | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 Sources/ComponentsKit/Shared/Types/ImageRenderingMode.swift diff --git a/Sources/ComponentsKit/Shared/Types/ImageRenderingMode.swift b/Sources/ComponentsKit/Shared/Types/ImageRenderingMode.swift new file mode 100644 index 00000000..4cf973bb --- /dev/null +++ b/Sources/ComponentsKit/Shared/Types/ImageRenderingMode.swift @@ -0,0 +1,40 @@ +import SwiftUI +import UIKit + +/// A type that indicates how images are rendered. +public enum ImageRenderingMode { + /// A mode that renders all non-transparent pixels as the foreground + /// color. + case template + /// A mode that renders pixels of bitmap images as-is. + /// + /// For system images created from the SF Symbol set, multicolor symbols + /// respect the current foreground and accent colors. + case original +} + +// MARK: - UIKit Helpers + +extension ImageRenderingMode { + var uiImageRenderingMode: UIImage.RenderingMode { + switch self { + case .template: + return .alwaysTemplate + case .original: + return .alwaysOriginal + } + } +} + +// MARK: - SwiftUI Helpers + +extension ImageRenderingMode { + var imageRenderingModel: Image.TemplateRenderingMode { + switch self { + case .template: + return .template + case .original: + return .original + } + } +} From e2a4c5c5857ec5cadc16f0e9425054f77ff8064e Mon Sep 17 00:00:00 2001 From: Mikhail Date: Thu, 22 May 2025 12:51:50 +0300 Subject: [PATCH 8/8] add image rendering mode to ButtonVM --- .../avatar_placeholder.imageset/Contents.json | 3 --- .../PreviewPages/ButtonPreview.swift | 5 +++++ .../Button/Models/ButtonImageSource.swift | 2 +- .../Components/Button/Models/ButtonVM.swift | 14 ++++++++------ .../Shared/Types/ImageRenderingMode.swift | 10 ++++++++++ 5 files changed, 24 insertions(+), 10 deletions(-) diff --git a/Examples/DemosApp/DemosApp/Assets.xcassets/avatar_placeholder.imageset/Contents.json b/Examples/DemosApp/DemosApp/Assets.xcassets/avatar_placeholder.imageset/Contents.json index 1b231c57..68cb3fb8 100644 --- a/Examples/DemosApp/DemosApp/Assets.xcassets/avatar_placeholder.imageset/Contents.json +++ b/Examples/DemosApp/DemosApp/Assets.xcassets/avatar_placeholder.imageset/Contents.json @@ -17,8 +17,5 @@ "info" : { "author" : "xcode", "version" : 1 - }, - "properties" : { - "template-rendering-intent" : "template" } } diff --git a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift index 19dc015b..5a9ce2bd 100644 --- a/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift +++ b/Examples/DemosApp/DemosApp/ComponentsPreview/PreviewPages/ButtonPreview.swift @@ -35,6 +35,11 @@ struct ButtonPreview: View { Text("Leading").tag(ButtonVM.ImageLocation.leading) Text("Trailing").tag(ButtonVM.ImageLocation.trailing) } + Picker("Image Rendering Mode", selection: self.$model.imageRenderingMode) { + Text("Default").tag(Optional.none) + Text("Template").tag(ImageRenderingMode.template) + Text("Original").tag(ImageRenderingMode.original) + } Picker("Image Source", selection: self.$model.imageSrc) { Text("SF Symbol").tag(ButtonVM.ImageSource.sfSymbol("camera.fill")) Text("Local").tag(ButtonVM.ImageSource.local("avatar_placeholder")) diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonImageSource.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonImageSource.swift index c9598303..f672cc0a 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonImageSource.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonImageSource.swift @@ -1,7 +1,7 @@ import Foundation -/// Defines the image source options for a button. extension ButtonVM { + /// Defines the image source options for a button. public enum ImageSource: Hashable { /// An image loaded from a system SF Symbol. /// diff --git a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift index 920cfb03..9303de5e 100644 --- a/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift +++ b/Sources/ComponentsKit/Components/Button/Models/ButtonVM.swift @@ -30,6 +30,9 @@ public struct ButtonVM: ComponentVM { /// Defaults to `.leading`. public var imageLocation: ImageLocation = .leading + /// Defines how image is rendered. + public var imageRenderingMode: ImageRenderingMode? + /// The source of the image to be displayed. public var imageSrc: ImageSource? @@ -184,15 +187,14 @@ extension ButtonVM { extension ButtonVM { var image: UIImage? { guard let imageSrc else { return nil } - switch imageSrc { + + let image = switch imageSrc { case .sfSymbol(let name): - return UIImage(systemName: name)?.withTintColor( - self.foregroundColor.uiColor, - renderingMode: .alwaysOriginal - ) + UIImage(systemName: name) case .local(let name, let bundle): - return UIImage(named: name, in: bundle, compatibleWith: nil) + UIImage(named: name, in: bundle, compatibleWith: nil) } + return image?.withRenderingMode(self.imageRenderingMode) } } diff --git a/Sources/ComponentsKit/Shared/Types/ImageRenderingMode.swift b/Sources/ComponentsKit/Shared/Types/ImageRenderingMode.swift index 4cf973bb..b4b8f662 100644 --- a/Sources/ComponentsKit/Shared/Types/ImageRenderingMode.swift +++ b/Sources/ComponentsKit/Shared/Types/ImageRenderingMode.swift @@ -26,6 +26,16 @@ extension ImageRenderingMode { } } +extension UIImage { + func withRenderingMode(_ mode: ImageRenderingMode?) -> UIImage { + if let mode { + return self.withRenderingMode(mode.uiImageRenderingMode) + } else { + return self + } + } +} + // MARK: - SwiftUI Helpers extension ImageRenderingMode {