From 2826693d127807ddef12cc9ec4487f0b014c6a5e Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Wed, 9 Jul 2025 13:46:16 +0900 Subject: [PATCH 01/18] =?UTF-8?q?[BOOK-114]=20feat:=20SetNeeds=20propertyW?= =?UTF-8?q?rapper=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Components/BKButton.swift | 3 -- .../Sources/Extensions/SetNeeds.swift | 54 +++++++++++++++++++ 2 files changed, 54 insertions(+), 3 deletions(-) delete mode 100644 src/Projects/BKDesign/Sources/Components/BKButton.swift create mode 100644 src/Projects/BKDesign/Sources/Extensions/SetNeeds.swift diff --git a/src/Projects/BKDesign/Sources/Components/BKButton.swift b/src/Projects/BKDesign/Sources/Components/BKButton.swift deleted file mode 100644 index 55807205..00000000 --- a/src/Projects/BKDesign/Sources/Components/BKButton.swift +++ /dev/null @@ -1,3 +0,0 @@ -// Copyright © 2025 Booket. All rights reserved - -import Foundation diff --git a/src/Projects/BKDesign/Sources/Extensions/SetNeeds.swift b/src/Projects/BKDesign/Sources/Extensions/SetNeeds.swift new file mode 100644 index 00000000..8ddf030f --- /dev/null +++ b/src/Projects/BKDesign/Sources/Extensions/SetNeeds.swift @@ -0,0 +1,54 @@ +// Copyright © 2025 Booket. All rights reserved + +import Foundation +import UIKit + +@propertyWrapper +struct SetNeeds { + enum Need { + case layout + case display + } + + private var value: Value + private let needs: Set + private weak var view: UIView? + + // 초기화 + init(wrappedValue: Value, _ needs: Need...) { + self.value = wrappedValue + self.needs = Set(needs) + self.view = nil + } + + // 실제 값에 접근할 때 사용되는 프로퍼티 + var wrappedValue: Value { + get { value } + set { + let oldValue = value + value = newValue + + // 값이 변경되었을 때만 UI 업데이트 + if oldValue != newValue { + updateView() + } + } + } + + // 뷰를 설정하는 메서드 + mutating func configure(with view: UIView) { + self.view = view + } + + private func updateView() { + guard let view = view else { return } + + // needs에 따라 적절한 메서드 호출 + if needs.contains(.layout) { + view.setNeedsLayout() // 레이아웃 재계산 필요 + } + if needs.contains(.display) { + view.setNeedsDisplay() // 다시 그리기 필요 + } + } +} From df63b162c1f5ffe35717c05172e3c76e34c1f96c Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Wed, 9 Jul 2025 14:07:04 +0900 Subject: [PATCH 02/18] [BOOK-114] feat: add BKButtonProtocol --- .../BKButton/BKButtonProtocol.swift | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/Projects/BKDesign/Sources/Components/BKButton/BKButtonProtocol.swift diff --git a/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonProtocol.swift b/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonProtocol.swift new file mode 100644 index 00000000..818c1944 --- /dev/null +++ b/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonProtocol.swift @@ -0,0 +1,24 @@ +// Copyright © 2025 Booket. All rights reserved + +import UIKit + +protocol BKButtonProtocol: AnyObject { + var isDisabled: Bool { get set } + var style: BKButtonStyle { get set } + var size: BKButtonSize { get set } + var title: String? { get set } + var leftIcon: UIImage? { get set } + var rightIcon: UIImage? { get set } +} + +struct ButtonState { + let normal: UIColor + let pressed: UIColor + let disabled: UIColor + + init(normal: UIColor, pressed: UIColor, disabled: UIColor) { + self.normal = normal + self.pressed = pressed + self.disabled = disabled + } +} From fc471b24b6ca3fa97ef15b19559141d65abdc6d6 Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Wed, 9 Jul 2025 14:07:51 +0900 Subject: [PATCH 03/18] [BOOK-114] feat: add ButtonStyle --- .../Components/BKButton/BKButtonStyle.swift | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 src/Projects/BKDesign/Sources/Components/BKButton/BKButtonStyle.swift diff --git a/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonStyle.swift b/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonStyle.swift new file mode 100644 index 00000000..35db7b25 --- /dev/null +++ b/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonStyle.swift @@ -0,0 +1,60 @@ +// Copyright © 2025 Booket. All rights reserved + +import UIKit + +enum BKButtonStyle { + case primary + case secondary + case tertiary + + var backgroundColor: ButtonState { + switch self { + case .primary: + return ButtonState( + normal: .bkBackgroundColor(.primary), + pressed: .bkBackgroundColor(.primaryPressed), + disabled: .bkBackgroundColor(.disable) + ) + + case .secondary: + return ButtonState( + normal: .bkBackgroundColor(.secondary), + pressed: .bkBackgroundColor(.secondaryPressed), + disabled: .bkBackgroundColor(.disable) + ) + + case .tertiary: + return ButtonState( + normal: .bkBackgroundColor(.tertiary), + pressed: .bkBackgroundColor(.tertiaryPressed), + disabled: .bkBackgroundColor(.disable) + ) + } + } + + var foregroundColor: ButtonState { + switch self { + case .primary: + return ButtonState( + normal: .bkContentColor(.inverse), + pressed: .bkContentColor(.inverse), + disabled: .bkContentColor(.disable) + ) + + case .secondary: + return ButtonState( + normal: .bkContentColor(.primary), + pressed: .bkContentColor(.primary), + disabled: .bkContentColor(.disable) + ) + + case .tertiary: + return ButtonState( + normal: .bkContentColor(.brand), + pressed: .bkContentColor(.brand), + disabled: .bkContentColor(.disable) + ) + } + } + +} From 024bd4af6c4ba69e203839e832bea999c5451b7c Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Wed, 9 Jul 2025 15:04:43 +0900 Subject: [PATCH 04/18] =?UTF-8?q?[BOOK-114]=20feat:=20ButtonState=20enum?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=BB=AC=EB=9F=AC=EC=85=8B?= =?UTF-8?q?=EA=B3=BC=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/BKButton/BKButtonState.swift | 19 ++++++++++ .../Components/BKButton/BKButtonStyle.swift | 36 ++++++++++++++----- 2 files changed, 47 insertions(+), 8 deletions(-) create mode 100644 src/Projects/BKDesign/Sources/Components/BKButton/BKButtonState.swift diff --git a/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonState.swift b/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonState.swift new file mode 100644 index 00000000..169534cd --- /dev/null +++ b/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonState.swift @@ -0,0 +1,19 @@ +// Copyright © 2025 Booket. All rights reserved + +import UIKit + +enum BKButtonState { + case normal + case pressed + case disabled + + init(isEnabled: Bool, isHighlighted: Bool) { + if !isEnabled { + self = .disabled + } else if isHighlighted { + self = .pressed + } else { + self = .normal + } + } +} diff --git a/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonStyle.swift b/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonStyle.swift index 35db7b25..3fbbe092 100644 --- a/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonStyle.swift +++ b/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonStyle.swift @@ -7,24 +7,24 @@ enum BKButtonStyle { case secondary case tertiary - var backgroundColor: ButtonState { + var backgroundColor: BKButtonColorSet { switch self { case .primary: - return ButtonState( + return BKButtonColorSet( normal: .bkBackgroundColor(.primary), pressed: .bkBackgroundColor(.primaryPressed), disabled: .bkBackgroundColor(.disable) ) case .secondary: - return ButtonState( + return BKButtonColorSet( normal: .bkBackgroundColor(.secondary), pressed: .bkBackgroundColor(.secondaryPressed), disabled: .bkBackgroundColor(.disable) ) case .tertiary: - return ButtonState( + return BKButtonColorSet( normal: .bkBackgroundColor(.tertiary), pressed: .bkBackgroundColor(.tertiaryPressed), disabled: .bkBackgroundColor(.disable) @@ -32,24 +32,24 @@ enum BKButtonStyle { } } - var foregroundColor: ButtonState { + var foregroundColor: BKButtonColorSet { switch self { case .primary: - return ButtonState( + return BKButtonColorSet( normal: .bkContentColor(.inverse), pressed: .bkContentColor(.inverse), disabled: .bkContentColor(.disable) ) case .secondary: - return ButtonState( + return BKButtonColorSet( normal: .bkContentColor(.primary), pressed: .bkContentColor(.primary), disabled: .bkContentColor(.disable) ) case .tertiary: - return ButtonState( + return BKButtonColorSet( normal: .bkContentColor(.brand), pressed: .bkContentColor(.brand), disabled: .bkContentColor(.disable) @@ -58,3 +58,23 @@ enum BKButtonStyle { } } + +struct BKButtonColorSet { + let normal: UIColor + let pressed: UIColor + let disabled: UIColor + + init(normal: UIColor, pressed: UIColor, disabled: UIColor) { + self.normal = normal + self.pressed = pressed + self.disabled = disabled + } + + public func color(for state: BKButtonState) -> UIColor { + switch state { + case .normal: return normal + case .pressed: return pressed + case .disabled: return disabled + } + } +} From f815beb82b8dc70a1d1c919fc8a83a5156e4da0c Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Wed, 9 Jul 2025 15:05:10 +0900 Subject: [PATCH 05/18] =?UTF-8?q?[BOOK-114]=20feat:=20ButtonSize=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/BKButton/BKButtonSize.swift | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 src/Projects/BKDesign/Sources/Components/BKButton/BKButtonSize.swift diff --git a/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonSize.swift b/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonSize.swift new file mode 100644 index 00000000..c943a735 --- /dev/null +++ b/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonSize.swift @@ -0,0 +1,75 @@ +// Copyright © 2025 Booket. All rights reserved + +import UIKit + +enum BKButtonSize { + case small + case medium + case large + case rounded + + var height: CGFloat { + switch self { + case .small, .rounded: + 40 + case .medium: + 48 + case .large: + 52 + } + } + + var horizontalPadding: CGFloat { + switch self { + case .small, .rounded: + BKSpacing.spacing2 + case .medium: + BKSpacing.spacing3 + case .large: + BKSpacing.spacing3 + } + } + + var font: UIFont { + switch self { + case .rounded, .small, .medium: + BKTextStyle + .label1(weight: .medium).uiFont ?? + .systemFont( + ofSize: BKTextStyle.label1(weight: .medium).fontAttributes.fontSize.rawValue, + weight: .medium + ) + case .large: + BKTextStyle + .body1(weight: .medium).uiFont ?? + .systemFont( + ofSize: BKTextStyle.body1(weight: .medium).fontAttributes.fontSize.rawValue, + weight: .medium + ) + } + } + + var iconSize: CGSize { + CGSize(width: 24, height: 24) + } + + var iconSpacing: CGFloat { + switch self { + case .rounded, .small, .medium: + BKSpacing.spacing1 + case .large: + BKSpacing.spacing2 + } + } + + var cornerRadius: CGFloat { + switch self { + case .small: + BKRadius.xsmall + case .medium, .large: + BKRadius.small + case .rounded: + BKRadius.full + } + } +} From 6cf615041be1c1a2c88f6bce55405cf57dc43f42 Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Wed, 9 Jul 2025 15:05:25 +0900 Subject: [PATCH 06/18] =?UTF-8?q?[BOOK-114]=20feat:=20BKButtonConfiguratio?= =?UTF-8?q?n=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BKButton/BKButtonConfiguration.swift | 55 +++++++++++++++++++ .../BKButton/BKButtonProtocol.swift | 12 ---- 2 files changed, 55 insertions(+), 12 deletions(-) create mode 100644 src/Projects/BKDesign/Sources/Components/BKButton/BKButtonConfiguration.swift diff --git a/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonConfiguration.swift b/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonConfiguration.swift new file mode 100644 index 00000000..94e678d7 --- /dev/null +++ b/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonConfiguration.swift @@ -0,0 +1,55 @@ +// Copyright © 2025 Booket. All rights reserved + +import UIKit + +struct BKButtonConfiguration { + var style: BKButtonStyle + var size: BKButtonSize + var title: String? + var leftIcon: UIImage? + var rightIcon: UIImage? + var isEnabled: Bool = true + var isFullWidth: Bool = false + + func withStyle(_ style: BKButtonStyle) -> BKButtonConfiguration { + var config = self + config.style = style + return config + } + + func withSize(_ size: BKButtonSize) -> BKButtonConfiguration { + var config = self + config.size = size + return config + } + + func withTitle(_ title: String?) -> BKButtonConfiguration { + var config = self + config.title = title + return config + } + + func withLeftIcon(_ icon: UIImage?) -> BKButtonConfiguration { + var config = self + config.leftIcon = icon + return config + } + + func withRightIcon(_ icon: UIImage?) -> BKButtonConfiguration { + var config = self + config.rightIcon = icon + return config + } + + func withEnabled(_ enabled: Bool) -> BKButtonConfiguration { + var config = self + config.isEnabled = enabled + return config + } + + func withFullWidth(_ fullWidth: Bool) -> BKButtonConfiguration { + var config = self + config.isFullWidth = fullWidth + return config + } +} diff --git a/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonProtocol.swift b/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonProtocol.swift index 818c1944..357397db 100644 --- a/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonProtocol.swift +++ b/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonProtocol.swift @@ -10,15 +10,3 @@ protocol BKButtonProtocol: AnyObject { var leftIcon: UIImage? { get set } var rightIcon: UIImage? { get set } } - -struct ButtonState { - let normal: UIColor - let pressed: UIColor - let disabled: UIColor - - init(normal: UIColor, pressed: UIColor, disabled: UIColor) { - self.normal = normal - self.pressed = pressed - self.disabled = disabled - } -} From 4b6f3334ff66056117b123dfd336c37d8955addf Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Wed, 9 Jul 2025 15:05:43 +0900 Subject: [PATCH 07/18] [BOOK-114] feat: UIImage extension for Icon resizing --- .../BKDesign/Sources/Extensions/UIImage+.swift | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/Projects/BKDesign/Sources/Extensions/UIImage+.swift diff --git a/src/Projects/BKDesign/Sources/Extensions/UIImage+.swift b/src/Projects/BKDesign/Sources/Extensions/UIImage+.swift new file mode 100644 index 00000000..70b55730 --- /dev/null +++ b/src/Projects/BKDesign/Sources/Extensions/UIImage+.swift @@ -0,0 +1,17 @@ +// Copyright © 2025 Booket. All rights reserved + +import UIKit + +extension UIImage { + /// Asset 이미지를 버튼 크기에 맞게 조정 + func resize(to targetSize: CGSize) -> UIImage? { + let renderer = UIGraphicsImageRenderer( + size: targetSize, + format: UIGraphicsImageRendererFormat.preferred() + ) + + return renderer.image { context in + self.draw(in: CGRect(origin: .zero, size: targetSize)) + } + } +} From 70f8e28b5f978eebb8cc913ead54a130a6b7ea57 Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Thu, 10 Jul 2025 00:09:13 +0900 Subject: [PATCH 08/18] =?UTF-8?q?[BOOK-114]=20feat:=20BKDesignTestApp=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Projects/BKDesign/PreviewApp/Info.plist | 68 +++++++++++++++++++ .../Resources/LaunchScreen.storyboard | 30 ++++++++ .../PreviewApp/Sources/AppDelegate.swift | 23 +++++++ .../PreviewApp/Sources/SceneDelegate.swift | 23 +++++++ src/Projects/BKDesign/Project.swift | 32 ++++++++- .../Sources/Extensions/SetNeeds.swift | 4 +- 6 files changed, 177 insertions(+), 3 deletions(-) create mode 100644 src/Projects/BKDesign/PreviewApp/Info.plist create mode 100644 src/Projects/BKDesign/PreviewApp/Resources/LaunchScreen.storyboard create mode 100644 src/Projects/BKDesign/PreviewApp/Sources/AppDelegate.swift create mode 100644 src/Projects/BKDesign/PreviewApp/Sources/SceneDelegate.swift diff --git a/src/Projects/BKDesign/PreviewApp/Info.plist b/src/Projects/BKDesign/PreviewApp/Info.plist new file mode 100644 index 00000000..912bf849 --- /dev/null +++ b/src/Projects/BKDesign/PreviewApp/Info.plist @@ -0,0 +1,68 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + + UILaunchStoryboardName + LaunchScreen + + + UIDeviceFamily + + 1 + + + + UIApplicationSceneManifest + + UIApplicationSupportsMultipleScenes + + UISceneConfigurations + + UIWindowSceneSessionRoleApplication + + + UISceneClassName + UIWindowScene + UISceneConfigurationName + Default Configuration + UISceneDelegateClassName + BKDesignPreviewApp.SceneDelegate + + + + + + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + + + + UIAppFonts + + Pretendard-Regular.otf + Pretendard-Medium.otf + Pretendard-SemiBold.otf + Pretendard-Bold.otf + + + diff --git a/src/Projects/BKDesign/PreviewApp/Resources/LaunchScreen.storyboard b/src/Projects/BKDesign/PreviewApp/Resources/LaunchScreen.storyboard new file mode 100644 index 00000000..dd79351e --- /dev/null +++ b/src/Projects/BKDesign/PreviewApp/Resources/LaunchScreen.storyboard @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Projects/BKDesign/PreviewApp/Sources/AppDelegate.swift b/src/Projects/BKDesign/PreviewApp/Sources/AppDelegate.swift new file mode 100644 index 00000000..d7723cda --- /dev/null +++ b/src/Projects/BKDesign/PreviewApp/Sources/AppDelegate.swift @@ -0,0 +1,23 @@ +// Copyright © 2025 Booket. All rights reserved + +import UIKit + +@main +class AppDelegate: UIResponder, UIApplicationDelegate { + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + return true + } + + // MARK: UISceneSession Lifecycle + + func application( + _ application: UIApplication, + configurationForConnecting connectingSceneSession: UISceneSession, + options: UIScene.ConnectionOptions + ) -> UISceneConfiguration { + return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role) + } +} diff --git a/src/Projects/BKDesign/PreviewApp/Sources/SceneDelegate.swift b/src/Projects/BKDesign/PreviewApp/Sources/SceneDelegate.swift new file mode 100644 index 00000000..d684a969 --- /dev/null +++ b/src/Projects/BKDesign/PreviewApp/Sources/SceneDelegate.swift @@ -0,0 +1,23 @@ +// Copyright © 2025 Booket. All rights reserved + +import BKDesign +import UIKit + +class SceneDelegate: UIResponder, UIWindowSceneDelegate { + var window: UIWindow? + + func scene( + _ scene: UIScene, + willConnectTo session: UISceneSession, + options connectionOptions: UIScene.ConnectionOptions + ) { + guard let windowScene = scene as? UIWindowScene else { return } + + let window = UIWindow(windowScene: windowScene) + let viewController = BKButtonGroupDemoViewController() + window.rootViewController = UINavigationController(rootViewController: viewController) + window.makeKeyAndVisible() + + self.window = window + } +} diff --git a/src/Projects/BKDesign/Project.swift b/src/Projects/BKDesign/Project.swift index 90fd3d14..9a6de982 100644 --- a/src/Projects/BKDesign/Project.swift +++ b/src/Projects/BKDesign/Project.swift @@ -14,7 +14,8 @@ let project = Project.project( swiftLintScript ], dependencies: [ - .core() + .core(), + .external(dependency: .SnapKit) ] ), Target.target( @@ -25,6 +26,35 @@ let project = Project.project( dependencies: [ .design() ] + ), + Target.target( + name: "BKDesignPreviewApp", + product: .app, + bundleId: "designpreview." + Project.bundleID, + infoPlist: .file(path: .relativeToRoot("Projects/BKDesign/PreviewApp/Info.plist")), // 복사본! + sources: ["PreviewApp/Sources/**"], + resources: [ + "PreviewApp/Resources/**", + .glob(pattern: .relativeToRoot("Projects/BKDesign/Resources/Font/**")) + ], + scripts: [ + swiftLintScript + ], + dependencies: [ + .design() // BKDesign 모듈 의존성 + ], + settings: .settings( + base: [ + "DEVELOPMENT_LANGUAGE": "ko", + "CODE_SIGN_STYLE": "Automatic" // 미리보기 앱은 자동으로 + ], + configurations: [ + .debug(name: "Debug", xcconfig: .relativeToRoot("SupportingFiles/Booket/Debug.xcconfig")), + ] + ) ) + + ] ) + diff --git a/src/Projects/BKDesign/Sources/Extensions/SetNeeds.swift b/src/Projects/BKDesign/Sources/Extensions/SetNeeds.swift index 8ddf030f..942abd2d 100644 --- a/src/Projects/BKDesign/Sources/Extensions/SetNeeds.swift +++ b/src/Projects/BKDesign/Sources/Extensions/SetNeeds.swift @@ -4,7 +4,7 @@ import Foundation import UIKit @propertyWrapper -struct SetNeeds { +public struct SetNeeds { enum Need { case layout case display @@ -22,7 +22,7 @@ struct SetNeeds { } // 실제 값에 접근할 때 사용되는 프로퍼티 - var wrappedValue: Value { + public var wrappedValue: Value { get { value } set { let oldValue = value From 90f0c65ff26212c85cfbe15eac166265af72cd22 Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Thu, 10 Jul 2025 00:09:59 +0900 Subject: [PATCH 09/18] =?UTF-8?q?[BOOK-114]=20feat:=20Button=20Foldering?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BKButton/BKButtonConfiguration.swift | 55 --------- .../BKButton/BKButtonProtocol.swift | 12 -- .../Components/BKButton/BKButtonState.swift | 19 --- .../Button/BKButtonConfiguration.swift | 110 ++++++++++++++++++ .../Components/Button/BKButtonProtocol.swift | 34 ++++++ .../{BKButton => Button}/BKButtonSize.swift | 46 +++++++- .../Components/Button/BKButtonState.swift | 33 ++++++ .../{BKButton => Button}/BKButtonStyle.swift | 41 +++++-- 8 files changed, 248 insertions(+), 102 deletions(-) delete mode 100644 src/Projects/BKDesign/Sources/Components/BKButton/BKButtonConfiguration.swift delete mode 100644 src/Projects/BKDesign/Sources/Components/BKButton/BKButtonProtocol.swift delete mode 100644 src/Projects/BKDesign/Sources/Components/BKButton/BKButtonState.swift create mode 100644 src/Projects/BKDesign/Sources/Components/Button/BKButtonConfiguration.swift create mode 100644 src/Projects/BKDesign/Sources/Components/Button/BKButtonProtocol.swift rename src/Projects/BKDesign/Sources/Components/{BKButton => Button}/BKButtonSize.swift (57%) create mode 100644 src/Projects/BKDesign/Sources/Components/Button/BKButtonState.swift rename src/Projects/BKDesign/Sources/Components/{BKButton => Button}/BKButtonStyle.swift (61%) diff --git a/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonConfiguration.swift b/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonConfiguration.swift deleted file mode 100644 index 94e678d7..00000000 --- a/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonConfiguration.swift +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright © 2025 Booket. All rights reserved - -import UIKit - -struct BKButtonConfiguration { - var style: BKButtonStyle - var size: BKButtonSize - var title: String? - var leftIcon: UIImage? - var rightIcon: UIImage? - var isEnabled: Bool = true - var isFullWidth: Bool = false - - func withStyle(_ style: BKButtonStyle) -> BKButtonConfiguration { - var config = self - config.style = style - return config - } - - func withSize(_ size: BKButtonSize) -> BKButtonConfiguration { - var config = self - config.size = size - return config - } - - func withTitle(_ title: String?) -> BKButtonConfiguration { - var config = self - config.title = title - return config - } - - func withLeftIcon(_ icon: UIImage?) -> BKButtonConfiguration { - var config = self - config.leftIcon = icon - return config - } - - func withRightIcon(_ icon: UIImage?) -> BKButtonConfiguration { - var config = self - config.rightIcon = icon - return config - } - - func withEnabled(_ enabled: Bool) -> BKButtonConfiguration { - var config = self - config.isEnabled = enabled - return config - } - - func withFullWidth(_ fullWidth: Bool) -> BKButtonConfiguration { - var config = self - config.isFullWidth = fullWidth - return config - } -} diff --git a/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonProtocol.swift b/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonProtocol.swift deleted file mode 100644 index 357397db..00000000 --- a/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonProtocol.swift +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright © 2025 Booket. All rights reserved - -import UIKit - -protocol BKButtonProtocol: AnyObject { - var isDisabled: Bool { get set } - var style: BKButtonStyle { get set } - var size: BKButtonSize { get set } - var title: String? { get set } - var leftIcon: UIImage? { get set } - var rightIcon: UIImage? { get set } -} diff --git a/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonState.swift b/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonState.swift deleted file mode 100644 index 169534cd..00000000 --- a/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonState.swift +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright © 2025 Booket. All rights reserved - -import UIKit - -enum BKButtonState { - case normal - case pressed - case disabled - - init(isEnabled: Bool, isHighlighted: Bool) { - if !isEnabled { - self = .disabled - } else if isHighlighted { - self = .pressed - } else { - self = .normal - } - } -} diff --git a/src/Projects/BKDesign/Sources/Components/Button/BKButtonConfiguration.swift b/src/Projects/BKDesign/Sources/Components/Button/BKButtonConfiguration.swift new file mode 100644 index 00000000..0782b259 --- /dev/null +++ b/src/Projects/BKDesign/Sources/Components/Button/BKButtonConfiguration.swift @@ -0,0 +1,110 @@ +// Copyright © 2025 Booket. All rights reserved + +import UIKit + +/// `BKButton`에 적용할 외부 구성 정보를 담는 구조체입니다. +/// +/// 버튼 스타일, 크기, 타이틀, 아이콘, 사용 가능 상태 등을 통합 관리하며 +/// 설정을 체이닝 방식으로 손쉽게 조합할 수 있습니다. +public struct BKButtonConfiguration { + + /// 버튼 스타일 (기본값: `.primary`) + var style: BKButtonStyle + + /// 버튼 사이즈 (기본값: `.medium`) + var size: BKButtonSize + + /// 버튼 타이틀 텍스트 + var title: String? + + /// 왼쪽에 표시할 아이콘 + var leftIcon: UIImage? + + /// 오른쪽에 표시할 아이콘 + var rightIcon: UIImage? + + /// 버튼 활성화 상태 (기본값: `true`) + var isEnabled: Bool + + /// 가로로 꽉 차는지 여부 (기본값: `false`) + var isFullWidth: Bool + + /// 초기화 메서드 + /// + /// - Parameters: + /// - style: 버튼 스타일 + /// - size: 버튼 크기 + /// - title: 버튼 텍스트 + /// - leftIcon: 좌측 아이콘 이미지 + /// - rightIcon: 우측 아이콘 이미지 + /// - isEnabled: 사용 가능 여부 + /// - isFullWidth: 가로 꽉 채우기 여부 + public init( + style: BKButtonStyle = .primary, + size: BKButtonSize = .medium, + title: String? = nil, + leftIcon: UIImage? = nil, + rightIcon: UIImage? = nil, + isEnabled: Bool = true, + isFullWidth: Bool = false + ) { + self.style = style + self.size = size + self.title = title + self.leftIcon = leftIcon + self.rightIcon = rightIcon + self.isEnabled = isEnabled + self.isFullWidth = isFullWidth + } + + // MARK: - Immutable Modifier Helpers + + /// 스타일을 변경한 새로운 설정 반환 + public func withStyle(_ style: BKButtonStyle) -> BKButtonConfiguration { + var config = self + config.style = style + return config + } + + /// 사이즈를 변경한 새로운 설정 반환 + public func withSize(_ size: BKButtonSize) -> BKButtonConfiguration { + var config = self + config.size = size + return config + } + + /// 타이틀을 변경한 새로운 설정 반환 + public func withTitle(_ title: String?) -> BKButtonConfiguration { + var config = self + config.title = title + return config + } + + /// 왼쪽 아이콘을 변경한 새로운 설정 반환 + public func withLeftIcon(_ icon: UIImage?) -> BKButtonConfiguration { + var config = self + config.leftIcon = icon + return config + } + + /// 오른쪽 아이콘을 변경한 새로운 설정 반환 + public func withRightIcon(_ icon: UIImage?) -> BKButtonConfiguration { + var config = self + config.rightIcon = icon + return config + } + + /// 사용 가능 여부를 변경한 새로운 설정 반환 + public func withEnabled(_ enabled: Bool) -> BKButtonConfiguration { + var config = self + config.isEnabled = enabled + return config + } + + /// 가로 채우기 여부를 변경한 새로운 설정 반환 + public func withFullWidth(_ fullWidth: Bool) -> BKButtonConfiguration { + var config = self + config.isFullWidth = fullWidth + return config + } +} diff --git a/src/Projects/BKDesign/Sources/Components/Button/BKButtonProtocol.swift b/src/Projects/BKDesign/Sources/Components/Button/BKButtonProtocol.swift new file mode 100644 index 00000000..5bc3fff5 --- /dev/null +++ b/src/Projects/BKDesign/Sources/Components/Button/BKButtonProtocol.swift @@ -0,0 +1,34 @@ +// Copyright © 2025 Booket. All rights reserved + +import UIKit + +/// BKButton 스타일 버튼을 구성하기 위한 공통 인터페이스입니다. +/// +/// 이 프로토콜은 버튼의 상태, 스타일, 크기, 텍스트 및 아이콘 속성을 정의하여 +/// 일관된 UI 컴포넌트를 구현할 수 있도록 도와줍니다. +protocol BKButtonProtocol: AnyObject { + + /// 버튼의 활성화 여부를 나타냅니다. + /// + /// `true`인 경우 버튼은 비활성화 상태이며, 사용자와의 상호작용이 차단됩니다. + var isDisabled: Bool { get set } + + /// 버튼의 스타일을 지정합니다. + /// + /// 스타일에 따라 배경색, 텍스트 색상 등이 달라집니다. + var style: BKButtonStyle { get set } + + /// 버튼의 크기를 지정합니다. + /// + /// 크기에 따라 높이, 폰트, 패딩 값 등이 조정됩니다. + var size: BKButtonSize { get set } + + /// 버튼에 표시될 텍스트입니다. + var title: String? { get set } + + /// 버튼의 왼쪽에 표시될 아이콘 이미지입니다. + var leftIcon: UIImage? { get set } + + /// 버튼의 오른쪽에 표시될 아이콘 이미지입니다. + var rightIcon: UIImage? { get set } +} diff --git a/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonSize.swift b/src/Projects/BKDesign/Sources/Components/Button/BKButtonSize.swift similarity index 57% rename from src/Projects/BKDesign/Sources/Components/BKButton/BKButtonSize.swift rename to src/Projects/BKDesign/Sources/Components/Button/BKButtonSize.swift index c943a735..7fc47d93 100644 --- a/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonSize.swift +++ b/src/Projects/BKDesign/Sources/Components/Button/BKButtonSize.swift @@ -2,12 +2,23 @@ import UIKit -enum BKButtonSize { +/// `BKButton`의 크기 타입을 정의하는 열거형입니다. +/// +/// 버튼의 높이, 패딩, 폰트, 아이콘 크기, 모서리 반경 등 레이아웃 관련 속성에 영향을 줍니다. +public enum BKButtonSize { + /// 작은 버튼 case small + + /// 중간 크기 버튼 case medium + + /// 대형 버튼 case large + + /// 둥근 버튼 case rounded + /// 버튼 높이 값 var height: CGFloat { switch self { case .small, .rounded: @@ -19,17 +30,19 @@ enum BKButtonSize { } } + /// 버튼 좌우 패딩 값 var horizontalPadding: CGFloat { switch self { case .small, .rounded: - BKSpacing.spacing2 - case .medium: BKSpacing.spacing3 + case .medium: + BKSpacing.spacing4 case .large: - BKSpacing.spacing3 + BKSpacing.spacing5 } } + /// 버튼 텍스트에 사용할 폰트 var font: UIFont { switch self { case .rounded, .small, .medium: @@ -49,10 +62,17 @@ enum BKButtonSize { } } + /// 아이콘의 크기 var iconSize: CGSize { - CGSize(width: 24, height: 24) + switch self { + case .large: + CGSize(width: 24, height: 24) + default: + CGSize(width: 22, height: 22) + } } + /// 아이콘과 텍스트 사이의 간격 var iconSpacing: CGFloat { switch self { case .rounded, .small, .medium: @@ -62,6 +82,7 @@ enum BKButtonSize { } } + /// 기본 모서리 반경 var cornerRadius: CGFloat { switch self { case .small: @@ -72,4 +93,19 @@ enum BKButtonSize { BKRadius.full } } + + /// 버튼의 넓이를 기준으로 계산된 둥근 corner radius 반환 + /// + /// - Parameter width: 버튼의 실제 넓이 + /// - Returns: radius 값 + public func cornerRadius(for width: CGFloat) -> CGFloat { + switch self { + case .small: + return BKRadius.xsmall + case .medium, .large: + return BKRadius.small + case .rounded: + return width / 2 + } + } } diff --git a/src/Projects/BKDesign/Sources/Components/Button/BKButtonState.swift b/src/Projects/BKDesign/Sources/Components/Button/BKButtonState.swift new file mode 100644 index 00000000..41f0be73 --- /dev/null +++ b/src/Projects/BKDesign/Sources/Components/Button/BKButtonState.swift @@ -0,0 +1,33 @@ +// Copyright © 2025 Booket. All rights reserved + +import UIKit + +/// `BKButton`의 시각적 상태를 나타내는 열거형입니다. +/// +/// 버튼의 활성화 여부 및 강조 상태에 따라 결정되며, 스타일 렌더링에 사용됩니다. +public enum BKButtonState { + /// 기본(normal) 상태 + case normal + + /// 터치 강조(pressed) 상태 + case pressed + + /// 비활성화(disabled) 상태 + case disabled + + /// 버튼의 `isEnabled`와 `isHighlighted` 속성을 바탕으로 + /// 적절한 상태를 초기화합니다. + /// + /// - Parameters: + /// - isEnabled: 버튼이 활성화되어 있는지 여부 + /// - isHighlighted: 버튼이 강조 상태인지 여부 + public init(isEnabled: Bool, isHighlighted: Bool) { + if !isEnabled { + self = .disabled + } else if isHighlighted { + self = .pressed + } else { + self = .normal + } + } +} diff --git a/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonStyle.swift b/src/Projects/BKDesign/Sources/Components/Button/BKButtonStyle.swift similarity index 61% rename from src/Projects/BKDesign/Sources/Components/BKButton/BKButtonStyle.swift rename to src/Projects/BKDesign/Sources/Components/Button/BKButtonStyle.swift index 3fbbe092..0a95f626 100644 --- a/src/Projects/BKDesign/Sources/Components/BKButton/BKButtonStyle.swift +++ b/src/Projects/BKDesign/Sources/Components/Button/BKButtonStyle.swift @@ -2,12 +2,24 @@ import UIKit -enum BKButtonStyle { +/// 버튼의 시각적 스타일을 정의하는 열거형입니다. +/// +/// 각 스타일은 고유한 배경색 및 텍스트 색상을 가지며, +/// 버튼의 목적과 사용 맥락에 따라 적절한 시각적 표현을 제공합니다. +public enum BKButtonStyle { + /// 강한 강조의 기본 버튼 스타일 case primary + + /// 중간 강조의 보조 버튼 스타일 case secondary + + /// 가장 낮은 강조의 텍스트 중심 스타일 case tertiary - var backgroundColor: BKButtonColorSet { + /// 스타일에 따른 배경색 세트입니다. + /// + /// 버튼 상태별 색상(normal, pressed, disabled)을 포함합니다. + var backgroundColors: BKButtonColorSet { switch self { case .primary: return BKButtonColorSet( @@ -32,7 +44,10 @@ enum BKButtonStyle { } } - var foregroundColor: BKButtonColorSet { + /// 스타일에 따른 텍스트 색상 세트입니다. + /// + /// 버튼 상태별 색상(normal, pressed, disabled)을 포함합니다. + var foregroundColors: BKButtonColorSet { switch self { case .primary: return BKButtonColorSet( @@ -59,17 +74,21 @@ enum BKButtonStyle { } -struct BKButtonColorSet { +/// 버튼의 상태별 색상 세트를 정의하는 구조체입니다. +public struct BKButtonColorSet { + /// 기본(normal) 상태에서의 색상 let normal: UIColor + + /// 눌림(pressed) 상태에서의 색상 let pressed: UIColor + + /// 비활성화(disabled) 상태에서의 색상 let disabled: UIColor - - init(normal: UIColor, pressed: UIColor, disabled: UIColor) { - self.normal = normal - self.pressed = pressed - self.disabled = disabled - } - + + /// 버튼의 상태에 맞는 색상을 반환합니다. + /// + /// - Parameter state: 버튼의 현재 상태 + /// - Returns: 해당 상태에 맞는 색상 public func color(for state: BKButtonState) -> UIColor { switch state { case .normal: return normal From 0296e98517594c5d88dd65fff201821b87b8e0fb Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Thu, 10 Jul 2025 00:10:14 +0900 Subject: [PATCH 10/18] =?UTF-8?q?[BOOK-114]=20feat:=20BKButton=20class=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Components/Button/BKButton.swift | 375 ++++++++++++++++++ 1 file changed, 375 insertions(+) create mode 100644 src/Projects/BKDesign/Sources/Components/Button/BKButton.swift diff --git a/src/Projects/BKDesign/Sources/Components/Button/BKButton.swift b/src/Projects/BKDesign/Sources/Components/Button/BKButton.swift new file mode 100644 index 00000000..83a20663 --- /dev/null +++ b/src/Projects/BKDesign/Sources/Components/Button/BKButton.swift @@ -0,0 +1,375 @@ +// Copyright © 2025 Booket. All rights reserved + +import SnapKit +import UIKit + +public class BKButton: UIButton, BKButtonProtocol { + + @SetNeeds(.layout, .display) + public var style: BKButtonStyle = .primary { + didSet { + updateButtonStyle() + } + } + + @SetNeeds(.layout) + public var size: BKButtonSize = .medium { + didSet { + updateButtonSize() + } + } + + @SetNeeds(wrappedValue: nil, .layout) + public var title: String? { + didSet { + setTitle("", for: .normal) // UIButton 기본 타이틀 제거 + customTitleLabel.text = title + updateLayout() + } + } + + @SetNeeds(wrappedValue: nil, .layout) + public var leftIcon: UIImage? { + didSet { + updateLeftIcon() + updateLayout() + } + } + + @SetNeeds(wrappedValue: nil, .layout) + public var rightIcon: UIImage? { + didSet { + updateRightIcon() + updateLayout() + } + } + + @SetNeeds(.layout, .display) + public var isDisabled: Bool = false { + didSet { + isEnabled = !isDisabled + updateButtonState() + } + } + + public var isFullWidth: Bool = false { + didSet { + invalidateIntrinsicContentSize() + } + } + + // MARK: - Override UIButton Properties + public override var isHighlighted: Bool { + didSet { + updateButtonState() + animatePressedState() + } + } + + public override var isEnabled: Bool { + didSet { + updateButtonState() + } + } + + // MARK: - Private Properties + // 커스텀 레이아웃을 위한 뷰 + private let customContainerView = UIView() + private let leftIconView = UIImageView() + private let customTitleLabel = UILabel() + private let rightIconView = UIImageView() + private let stackView = UIStackView() + + // Animation properties + private let pressedScale: CGFloat = 0.96 + private let animationDuration: TimeInterval = 0.15 + private let animationSpringDamping: CGFloat = 0.7 + private let animationSpringVelocity: CGFloat = 0.5 + + private var currentState: BKButtonState { + return BKButtonState(isEnabled: isEnabled, isHighlighted: isHighlighted) + } + + // MARK: - Initialization + public init(style: BKButtonStyle = .primary, size: BKButtonSize = .medium) { + super.init(frame: .zero) + self.style = style + self.size = size + setupButton() + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + setupButton() + } + + private func setupButton() { + _style.configure(with: self) + _size.configure(with: self) + _title.configure(with: self) + _leftIcon.configure(with: self) + _rightIcon.configure(with: self) + _isDisabled.configure(with: self) + + // UIButton의 기본 요소들을 숨김 + setTitle("", for: .normal) + setImage(nil, for: .normal) + + // iOS 15.0+ Configuration 비활성화 + if #available(iOS 15.0, *) { + configuration = nil + } + + setupCustomViews() + updateButtonStyle() + updateButtonSize() + updateLayout() + } + + private func setupCustomViews() { + addSubview(customContainerView) + customContainerView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + customContainerView.isUserInteractionEnabled = false + + customContainerView.addSubview(stackView) + stackView.axis = .horizontal + stackView.alignment = .center + stackView.distribution = .fill + stackView.isUserInteractionEnabled = false + + stackView.addArrangedSubview(leftIconView) + stackView.addArrangedSubview(customTitleLabel) + stackView.addArrangedSubview(rightIconView) + + stackView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + setupIconViews() + setupTitleLabel() + updatePadding() + } + + private func setupIconViews() { + [leftIconView, rightIconView].forEach { iconView in + iconView.contentMode = .scaleAspectFit + iconView.isHidden = true + iconView.isUserInteractionEnabled = false + iconView.setContentHuggingPriority(.required, for: .horizontal) + iconView.setContentCompressionResistancePriority(.required, for: .horizontal) + } + + leftIconView.snp.makeConstraints { make in + make.width.height.equalTo(size.iconSize.width) + } + + rightIconView.snp.makeConstraints { make in + make.width.height.equalTo(size.iconSize.width) + } + } + + private func setupTitleLabel() { + customTitleLabel.textAlignment = .center + customTitleLabel.numberOfLines = 1 + customTitleLabel.isUserInteractionEnabled = false + customTitleLabel.setContentHuggingPriority(.defaultHigh, for: .horizontal) + customTitleLabel.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) + } + + // MARK: - Update Methods + private func updateButtonState() { + updateColors() + } + + private func updateButtonStyle() { + updateColors() + } + + private func updateButtonSize() { + customTitleLabel.font = size.font + updateCornerRadius() + updateIconSizes() + updatePadding() + invalidateIntrinsicContentSize() + } + + private func updateColors() { + let backgroundColors = style.backgroundColors + let foregroundColors = style.foregroundColors + + backgroundColor = backgroundColors.color(for: currentState) + let foregroundColor = foregroundColors.color(for: currentState) + + customTitleLabel.textColor = foregroundColor + leftIconView.tintColor = foregroundColor + rightIconView.tintColor = foregroundColor + } + + private func updateCornerRadius() { + if size != .rounded { + layer.cornerRadius = size.cornerRadius + } + layer.masksToBounds = true + } + + private func updateIconSizes() { + let iconSize = size.iconSize.width + leftIconView.snp.updateConstraints { make in + make.width.height.equalTo(iconSize) + } + rightIconView.snp.updateConstraints { make in + make.width.height.equalTo(iconSize) + } + } + + private func updatePadding() { + stackView.isLayoutMarginsRelativeArrangement = true + stackView.layoutMargins = UIEdgeInsets( + top: 0, + left: size.horizontalPadding, + bottom: 0, + right: size.horizontalPadding + ) + } + + private func updateLayout() { + stackView.spacing = 0 + stackView.setCustomSpacing(0, after: leftIconView) + stackView.setCustomSpacing(0, after: customTitleLabel) + + if leftIcon != nil, let title = title, !title.isEmpty { + stackView.setCustomSpacing(size.iconSpacing, after: leftIconView) + } + + if rightIcon != nil, let title = title, !title.isEmpty { + stackView.setCustomSpacing(size.iconSpacing, after: customTitleLabel) + } + } + + private func updateLeftIcon() { + if let icon = leftIcon { + let resizedIcon = icon.resize(to: size.iconSize) + leftIconView.image = resizedIcon?.withRenderingMode(.alwaysTemplate) + leftIconView.isHidden = false + } else { + leftIconView.image = nil + leftIconView.isHidden = true + } + } + + private func updateRightIcon() { + if let icon = rightIcon { + let resizedIcon = icon.resize(to: size.iconSize) + rightIconView.image = resizedIcon?.withRenderingMode(.alwaysTemplate) + rightIconView.isHidden = false + } else { + rightIconView.image = nil + rightIconView.isHidden = true + } + } + + // MARK: - Animation + private func animatePressedState() { + if isHighlighted { + UIView.animate( + withDuration: animationDuration, + delay: 0, + usingSpringWithDamping: animationSpringDamping, + initialSpringVelocity: animationSpringVelocity, + options: [.allowUserInteraction, .beginFromCurrentState], + animations: { + self.transform = CGAffineTransform(scaleX: self.pressedScale, y: self.pressedScale) + } + ) + } else { + UIView.animate( + withDuration: animationDuration, + delay: 0, + usingSpringWithDamping: animationSpringDamping, + initialSpringVelocity: animationSpringVelocity, + options: [.allowUserInteraction, .beginFromCurrentState], + animations: { + self.transform = .identity + } + ) + } + } + + // MARK: - Layout + override public func layoutSubviews() { + super.layoutSubviews() + + // 'Rounded'일 때만 동적으로 반지름 설정 + if size == .rounded { + let height = bounds.height + let width = bounds.width + + let minimumRadius = height / 2 + let dynamicRadius = width / 2 + + layer.cornerRadius = min(minimumRadius, dynamicRadius) + } + } + + public override var intrinsicContentSize: CGSize { + if isFullWidth { + return CGSize(width: UIView.noIntrinsicMetric, height: size.height) + } + + let stackSize = stackView.systemLayoutSizeFitting( + UIView.layoutFittingCompressedSize + ) + let totalPadding = size.horizontalPadding * 2 + + return CGSize( + width: stackSize.width + totalPadding, + height: size.height + ) + } +} + +// MARK: - Factory Methods +extension BKButton { + public static func primary( + title: String, + size: BKButtonSize = .medium + ) -> BKButton { + let button = BKButton(style: .primary, size: size) + button.title = title + return button + } + + public static func secondary( + title: String, + size: BKButtonSize = .medium + ) -> BKButton { + let button = BKButton(style: .secondary, size: size) + button.title = title + return button + } + + public static func tertiary( + title: String, + size: BKButtonSize = .medium + ) -> BKButton { + let button = BKButton(style: .tertiary, size: size) + button.title = title + return button + } +} + +// MARK: - Configuration Support +extension BKButton { + public func configure(with configuration: BKButtonConfiguration) { + style = configuration.style + size = configuration.size + title = configuration.title + leftIcon = configuration.leftIcon + rightIcon = configuration.rightIcon + isDisabled = !configuration.isEnabled + isFullWidth = configuration.isFullWidth + } +} From 6840b3b2fbf7abdc84968883f89606faef197d69 Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Thu, 10 Jul 2025 00:10:35 +0900 Subject: [PATCH 11/18] =?UTF-8?q?[BOOK-114]=20test:=20=EA=B8=B0=EB=B3=B8?= =?UTF-8?q?=20=EB=B2=84=ED=8A=BC=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B7=B0?= =?UTF-8?q?=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../View/BKButtonTestViewController.swift | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 src/Projects/BKDesign/PreviewApp/Sources/View/BKButtonTestViewController.swift diff --git a/src/Projects/BKDesign/PreviewApp/Sources/View/BKButtonTestViewController.swift b/src/Projects/BKDesign/PreviewApp/Sources/View/BKButtonTestViewController.swift new file mode 100644 index 00000000..c8318f7b --- /dev/null +++ b/src/Projects/BKDesign/PreviewApp/Sources/View/BKButtonTestViewController.swift @@ -0,0 +1,133 @@ +// Copyright © 2025 Booket. All rights reserved + +import BKDesign +import SnapKit +import UIKit + +public final class BKButtonTestViewController: UIViewController { + + // MARK: - UI Components + private let scrollView = UIScrollView() + private let containerView = UIStackView() + + // MARK: - Lifecycle + + public override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .white + setupScrollView() + setupIndependentButtons() +// setupStackView() + } + + public override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) +// setupAllTestButtons() + } + + // MARK: - Setup Scroll & Stack + + private func setupScrollView() { + view.addSubview(scrollView) + scrollView.snp.makeConstraints { $0.edges.equalToSuperview() } + + scrollView.addSubview(containerView) + containerView.snp.makeConstraints { + $0.edges.equalToSuperview().inset(20) + $0.width.equalToSuperview().inset(20) + } + } + + private func setupStackView() { + containerView.axis = .vertical + containerView.spacing = 16 + containerView.alignment = .fill + containerView.distribution = .equalSpacing + } + + // MARK: - Setup Buttons by Size + + private func setupAllTestButtons() { + setupButtons(for: .large) + setupButtons(for: .medium) + setupButtons(for: .small) + setupButtons(for: .rounded) + } + + private func setupButtons(for size: BKButtonSize) { + addButton("Primary", style: .primary, size: size) + addButton("Secondary", style: .secondary, size: size) + addButton("Tertiary", style: .tertiary, size: size) + + addIconButton("Apple 로그인", style: .primary, size: size, left: .appleLogo) + addIconButton("카카오 로그인", style: .primary, size: size, right: .kakaoLogo) + addIconButton("양쪽 아이콘", style: .primary, size: size, left: .appleLogo, right: .kakaoLogo) + } + + // MARK: - Button Builders + + private func addButton(_ title: String, style: BKButtonStyle, size: BKButtonSize) { + let button = BKButton(style: style, size: size) + button.title = "[\(size.label)] \(title)" + containerView.addArrangedSubview(button) + } + + private func addIconButton( + _ title: String, + style: BKButtonStyle, + size: BKButtonSize, + left: BKIcon? = nil, + right: BKIcon? = nil + ) { + let button = BKButton(style: style, size: size) + button.title = "[\(size.label)] \(title)" + button.leftIcon = left?.image + button.rightIcon = right?.image + containerView.addArrangedSubview(button) + } + + private func setupIndependentButtons() { + let sampleView = UIView() + scrollView.addSubview(sampleView) + sampleView.snp.makeConstraints { + $0.top.equalTo(containerView.snp.bottom).offset(40) + $0.centerX.equalToSuperview() + $0.bottom.lessThanOrEqualToSuperview() + } + + let buttons: [BKButton] = [ + .primary(title: "[Free] Apple 로그인", size: .large), + .secondary(title: "[Free] Secondary", size: .large), + .tertiary(title: "[Free] Tertiary", size: .large), + .primary(title: "[Free] Apple 로그인", size: .medium), + .secondary(title: "[Free] Secondary", size: .small), + .tertiary(title: "[Free] Tertiary", size: .rounded) + ] + + buttons[0].leftIcon = BKIcon.appleLogo.image + buttons[2].rightIcon = BKIcon.kakaoLogo.image + + var last: UIView? + for button in buttons { + sampleView.addSubview(button) + button.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.top.equalTo(last?.snp.bottom ?? sampleView.snp.top).offset(16) + } + last = button + } + } + +} + + +public extension BKButtonSize { + var label: String { + switch self { + case .large: return "Large" + case .medium: return "Medium" + case .small: return "Small" + case .rounded: return "Rounded" + } + } +} From ea6a195f3a8c8f1b63c6a8da9b1907f6c3257b89 Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Thu, 10 Jul 2025 00:10:46 +0900 Subject: [PATCH 12/18] =?UTF-8?q?[BOOK-114]=20feat:=20BKButtonGroup=20?= =?UTF-8?q?=EA=B0=9C=EB=B0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Button/BKButtonGroup.swift | 196 ++++++++++++++++++ 1 file changed, 196 insertions(+) create mode 100644 src/Projects/BKDesign/Sources/Components/Button/BKButtonGroup.swift diff --git a/src/Projects/BKDesign/Sources/Components/Button/BKButtonGroup.swift b/src/Projects/BKDesign/Sources/Components/Button/BKButtonGroup.swift new file mode 100644 index 00000000..4a606b2b --- /dev/null +++ b/src/Projects/BKDesign/Sources/Components/Button/BKButtonGroup.swift @@ -0,0 +1,196 @@ +// Copyright © 2025 Booket. All rights reserved + +import SnapKit +import UIKit + +public class BKButtonGroup: UIView { + + // MARK: - Types + public enum Layout { + case horizontal + case vertical + case fullWidth + } + + // MARK: - Properties + private let stackView = UIStackView() + private var buttons: [BKButton] = [] + + public var layout: Layout = .horizontal { + didSet { + updateLayout() + } + } + + public var spacing: CGFloat = BKSpacing.spacing2 { + didSet { + stackView.spacing = spacing + } + } + + // MARK: - Initialization + + public init( + buttons: [BKButton], + layout: Layout = .horizontal, + spacing: CGFloat = BKSpacing.spacing2 + ) { + self.buttons = buttons + self.layout = layout + self.spacing = spacing + super.init(frame: .zero) + setupView() + } + + public required init?(coder: NSCoder) { + super.init(coder: coder) + setupView() + } + + // MARK: - Setup + private func setupView() { + addSubview(stackView) + stackView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + updateLayout() + updateButtons() + } + + // MARK: - Public Methods + public func setButtons(_ buttons: [BKButton]) { + self.buttons = buttons + updateButtons() + } + + public func addButton(_ button: BKButton) { + buttons.append(button) + updateButtons() + } + + public func removeButton(_ button: BKButton) { + if let index = buttons.firstIndex(of: button) { + buttons.remove(at: index) + updateButtons() + } + } + + public func removeAllButtons() { + buttons.removeAll() + updateButtons() + } + + // MARK: - Private Methods + private func updateLayout() { + switch layout { + case .horizontal: + stackView.axis = .horizontal + stackView.distribution = .fillProportionally + stackView.alignment = .fill + buttons.forEach { $0.isFullWidth = false } + + case .vertical: + stackView.axis = .vertical + stackView.distribution = .fill + stackView.alignment = .fill + buttons.forEach { $0.isFullWidth = true } + + case .fullWidth: + stackView.axis = .horizontal + stackView.distribution = .fillEqually + stackView.alignment = .fill + buttons.forEach { $0.isFullWidth = true } + } + + stackView.spacing = spacing + } + + private func updateButtons() { + stackView.arrangedSubviews.forEach { view in + stackView.removeArrangedSubview(view) + view.removeFromSuperview() + } + + buttons.forEach { button in + button.setContentHuggingPriority(.required, for: .horizontal) + stackView.addArrangedSubview(button) + } + + updateLayout() + } + +} + +// MARK: - Convenience Initializers +extension BKButtonGroup { + public static func twoButtonGroup( + leftTitle: String = "확인", + rightTitle: String = "취소", + leftAction: (() -> Void)? = nil, + rightAction: (() -> Void)? = nil + ) -> BKButtonGroup { + let leftButton = BKButton.secondary(title: leftTitle) + let rightButton = BKButton.primary(title: rightTitle) + + if let action = leftAction { + leftButton.addAction(UIAction { _ in action() }, for: .touchUpInside) + } + + if let action = rightAction { + rightButton.addAction(UIAction { _ in action() }, for: .touchUpInside) + } + + return BKButtonGroup(buttons: [leftButton, rightButton], layout: .fullWidth) + } + + public static func singleFullButton( + title: String = "다음", + action: (() -> Void)? = nil + ) -> BKButtonGroup { + let nextButton = BKButton.primary(title: title, size: .large) + + if let action = action { + nextButton.addAction(UIAction { _ in action() }, for: .touchUpInside) + } + + return BKButtonGroup(buttons: [nextButton], layout: .vertical) + } + + /// 3개 버튼 수평 그룹 + public static func threeButtonGroup( + leftTitle: String, + centerTitle: String, + rightTitle: String, + leftAction: (() -> Void)? = nil, + centerAction: (() -> Void)? = nil, + rightAction: (() -> Void)? = nil + ) -> BKButtonGroup { + let leftButton = BKButton.tertiary(title: leftTitle) + let centerButton = BKButton.secondary(title: centerTitle) + let rightButton = BKButton.primary(title: rightTitle) + + if let action = leftAction { + leftButton.addAction(UIAction { _ in action() }, for: .touchUpInside) + } + + if let action = centerAction { + centerButton.addAction(UIAction { _ in action() }, for: .touchUpInside) + } + + if let action = rightAction { + rightButton.addAction(UIAction { _ in action() }, for: .touchUpInside) + } + + return BKButtonGroup(buttons: [leftButton, centerButton, rightButton], layout: .horizontal) + } + + /// 수직 버튼 그룹 + public static func verticalButtonGroup( + buttons: [BKButton], + spacing: CGFloat = BKSpacing.spacing2 + ) -> BKButtonGroup { + buttons.forEach { $0.isFullWidth = true } + return BKButtonGroup(buttons: buttons, layout: .vertical, spacing: spacing) + } +} From 37e998051d737ea5992883c4544efd64a5bf42e2 Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Thu, 10 Jul 2025 00:11:01 +0900 Subject: [PATCH 13/18] =?UTF-8?q?[BOOK-114]=20test:=20Button=20Group=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BKButtonGroupDemoViewController.swift | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 src/Projects/BKDesign/PreviewApp/Sources/View/BKButtonGroupDemoViewController.swift diff --git a/src/Projects/BKDesign/PreviewApp/Sources/View/BKButtonGroupDemoViewController.swift b/src/Projects/BKDesign/PreviewApp/Sources/View/BKButtonGroupDemoViewController.swift new file mode 100644 index 00000000..f31b030f --- /dev/null +++ b/src/Projects/BKDesign/PreviewApp/Sources/View/BKButtonGroupDemoViewController.swift @@ -0,0 +1,105 @@ +// Copyright © 2025 Booket. All rights reserved + +import BKDesign +import SnapKit +import UIKit + +public final class BKButtonGroupDemoViewController: UIViewController { + + private let scrollView = UIScrollView() + private let containerView = UIStackView() + + public override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = .white + setupScrollView() + setupDemoGroups() + } + + private func setupScrollView() { + view.addSubview(scrollView) + scrollView.snp.makeConstraints { $0.edges.equalToSuperview() } + + scrollView.addSubview(containerView) + containerView.axis = .vertical + containerView.spacing = 16 + containerView.alignment = .fill + containerView.distribution = .fill + containerView.snp.makeConstraints { + $0.edges.equalToSuperview().inset(20) + $0.width.equalToSuperview().inset(20) + } + } + + private func setupDemoGroups() { + addSection(title: "Custom 구성 (직접 만든 그룹)") + containerView.addArrangedSubview(BKButtonGroup( + buttons: [ + makeButton(title: "[Custom] S", size: .small), + makeButton(title: "[Custom] M", size: .medium), + makeButton(title: "[Custom] L", size: .large) + ], + layout: .horizontal + )) + + addDivider() + + addSection(title: "TwoButtonGroup") + containerView.addArrangedSubview(BKButtonGroup.twoButtonGroup( + leftTitle: "취소", rightTitle: "확인", + leftAction: { print("취소 tapped") }, + rightAction: { print("확인 tapped") } + )) + + addDivider() + + addSection(title: "ThreeButtonGroup") + containerView.addArrangedSubview(BKButtonGroup.threeButtonGroup( + leftTitle: "이전", centerTitle: "중간", rightTitle: "다음", + leftAction: { print("이전 tapped") }, + centerAction: { print("중간 tapped") }, + rightAction: { print("다음 tapped") } + )) + + addDivider() + + addSection(title: "SingleFullButton") + containerView.addArrangedSubview(BKButtonGroup.singleFullButton( + title: "계속하기", + action: { print("계속하기 tapped") } + )) + + addDivider() + + addSection(title: "VerticalGroup (Rounded 버튼)") + containerView.addArrangedSubview(BKButtonGroup.verticalButtonGroup(buttons: [ + makeButton(title: "둥글1", size: .rounded), + makeButton(title: "둥글2", size: .rounded) + ])) + } + + private func makeButton(title: String, size: BKButtonSize) -> BKButton { + let button = BKButton.primary(title: title, size: size) + button.addAction(UIAction { _ in + print("Tapped: \(title)") + }, for: .touchUpInside) + return button + } + + private func addSection(title: String) { + let label = UILabel() + label.text = title + label.font = UIFont.systemFont(ofSize: 14, weight: .bold) + label.textColor = .darkGray + containerView.addArrangedSubview(label) + } + + private func addDivider() { + let divider = UIView() + divider.backgroundColor = UIColor.lightGray.withAlphaComponent(0.4) + divider.snp.makeConstraints { make in + make.height.equalTo(1) + } + containerView.addArrangedSubview(divider) + } +} From 881a61aff429eba3217ac3c5bc105e3fe0db86c9 Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Thu, 10 Jul 2025 00:16:44 +0900 Subject: [PATCH 14/18] =?UTF-8?q?[BOOK-114]=20fix:=20=EC=BB=A4=EC=8A=A4?= =?UTF-8?q?=ED=85=80=20=EB=B2=84=ED=8A=BC=20=EC=BB=AC=EB=9F=AC=EC=85=8B=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Components/Button/BKButtonStyle.swift | 29 ++++++++++++++----- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/Projects/BKDesign/Sources/Components/Button/BKButtonStyle.swift b/src/Projects/BKDesign/Sources/Components/Button/BKButtonStyle.swift index 0a95f626..34ba4fc1 100644 --- a/src/Projects/BKDesign/Sources/Components/Button/BKButtonStyle.swift +++ b/src/Projects/BKDesign/Sources/Components/Button/BKButtonStyle.swift @@ -9,13 +9,16 @@ import UIKit public enum BKButtonStyle { /// 강한 강조의 기본 버튼 스타일 case primary - + /// 중간 강조의 보조 버튼 스타일 case secondary - + /// 가장 낮은 강조의 텍스트 중심 스타일 case tertiary + /// 직접 색상을 정의하는 커스텀 스타일 + case custom(background: BKButtonColorSet, foreground: BKButtonColorSet) + /// 스타일에 따른 배경색 세트입니다. /// /// 버튼 상태별 색상(normal, pressed, disabled)을 포함합니다. @@ -34,13 +37,16 @@ public enum BKButtonStyle { pressed: .bkBackgroundColor(.secondaryPressed), disabled: .bkBackgroundColor(.disable) ) - + case .tertiary: return BKButtonColorSet( normal: .bkBackgroundColor(.tertiary), pressed: .bkBackgroundColor(.tertiaryPressed), disabled: .bkBackgroundColor(.disable) ) + + case .custom(let background, _): + return background } } @@ -62,29 +68,31 @@ public enum BKButtonStyle { pressed: .bkContentColor(.primary), disabled: .bkContentColor(.disable) ) - + case .tertiary: return BKButtonColorSet( normal: .bkContentColor(.brand), pressed: .bkContentColor(.brand), disabled: .bkContentColor(.disable) ) + + case .custom(_, let foreground): + return foreground } } - } /// 버튼의 상태별 색상 세트를 정의하는 구조체입니다. public struct BKButtonColorSet { /// 기본(normal) 상태에서의 색상 let normal: UIColor - + /// 눌림(pressed) 상태에서의 색상 let pressed: UIColor - + /// 비활성화(disabled) 상태에서의 색상 let disabled: UIColor - + /// 버튼의 상태에 맞는 색상을 반환합니다. /// /// - Parameter state: 버튼의 현재 상태 @@ -96,4 +104,9 @@ public struct BKButtonColorSet { case .disabled: return disabled } } + + /// 단일 색상을 모든 상태에 적용하는 간단 생성자 + public static func solid(_ color: UIColor) -> BKButtonColorSet { + BKButtonColorSet(normal: color, pressed: color, disabled: color) + } } From f4d6d4e7fcd8c4abe23994577a1083e2812e1883 Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Thu, 10 Jul 2025 00:30:09 +0900 Subject: [PATCH 15/18] =?UTF-8?q?[BOOK-114]=20fix:=20equtable=20=EC=A1=B0?= =?UTF-8?q?=EA=B1=B4=20=EB=A7=8C=EC=A1=B1=ED=95=98=EA=B2=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/Components/Button/BKButtonStyle.swift | 10 +++++++++- .../BKDesign/Sources/Extensions/UIColor+.swift | 7 +++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/Projects/BKDesign/Sources/Components/Button/BKButtonStyle.swift b/src/Projects/BKDesign/Sources/Components/Button/BKButtonStyle.swift index 34ba4fc1..2d24cc18 100644 --- a/src/Projects/BKDesign/Sources/Components/Button/BKButtonStyle.swift +++ b/src/Projects/BKDesign/Sources/Components/Button/BKButtonStyle.swift @@ -6,7 +6,7 @@ import UIKit /// /// 각 스타일은 고유한 배경색 및 텍스트 색상을 가지며, /// 버튼의 목적과 사용 맥락에 따라 적절한 시각적 표현을 제공합니다. -public enum BKButtonStyle { +public enum BKButtonStyle: Equatable { /// 강한 강조의 기본 버튼 스타일 case primary @@ -110,3 +110,11 @@ public struct BKButtonColorSet { BKButtonColorSet(normal: color, pressed: color, disabled: color) } } + +extension BKButtonColorSet: Equatable { + public static func == (lhs: BKButtonColorSet, rhs: BKButtonColorSet) -> Bool { + return lhs.normal.isEqual(to: rhs.normal) && + lhs.pressed.isEqual(to: rhs.pressed) && + lhs.disabled.isEqual(to: rhs.disabled) + } +} diff --git a/src/Projects/BKDesign/Sources/Extensions/UIColor+.swift b/src/Projects/BKDesign/Sources/Extensions/UIColor+.swift index eb718ee9..f31e15e0 100644 --- a/src/Projects/BKDesign/Sources/Extensions/UIColor+.swift +++ b/src/Projects/BKDesign/Sources/Extensions/UIColor+.swift @@ -101,3 +101,10 @@ public extension UIColor { } } } + +// UIColor 비교를 위한 확장 +extension UIColor { + func isEqual(to color: UIColor) -> Bool { + return self.cgColor.__equalTo(color.cgColor) + } +} From 732db678c2b9a2925a2866c184a4609367522ec7 Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Thu, 10 Jul 2025 17:12:46 +0900 Subject: [PATCH 16/18] =?UTF-8?q?[BOOK-114]=20fix:=20icon=20spacing=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../PreviewApp/Sources/SceneDelegate.swift | 3 +- .../View/BKButtonTestViewController.swift | 6 +-- .../Sources/Components/Button/BKButton.swift | 46 +++++++++---------- .../Components/Button/BKButtonSize.swift | 10 ++++ 4 files changed, 37 insertions(+), 28 deletions(-) diff --git a/src/Projects/BKDesign/PreviewApp/Sources/SceneDelegate.swift b/src/Projects/BKDesign/PreviewApp/Sources/SceneDelegate.swift index d684a969..29e6c422 100644 --- a/src/Projects/BKDesign/PreviewApp/Sources/SceneDelegate.swift +++ b/src/Projects/BKDesign/PreviewApp/Sources/SceneDelegate.swift @@ -14,7 +14,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { guard let windowScene = scene as? UIWindowScene else { return } let window = UIWindow(windowScene: windowScene) - let viewController = BKButtonGroupDemoViewController() + let viewController = BKButtonTestViewController() +// let viewController = BKButtonGroupDemoViewController() window.rootViewController = UINavigationController(rootViewController: viewController) window.makeKeyAndVisible() diff --git a/src/Projects/BKDesign/PreviewApp/Sources/View/BKButtonTestViewController.swift b/src/Projects/BKDesign/PreviewApp/Sources/View/BKButtonTestViewController.swift index c8318f7b..83f4ee84 100644 --- a/src/Projects/BKDesign/PreviewApp/Sources/View/BKButtonTestViewController.swift +++ b/src/Projects/BKDesign/PreviewApp/Sources/View/BKButtonTestViewController.swift @@ -16,13 +16,13 @@ public final class BKButtonTestViewController: UIViewController { super.viewDidLoad() view.backgroundColor = .white setupScrollView() - setupIndependentButtons() -// setupStackView() +// setupIndependentButtons() + setupStackView() } public override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) -// setupAllTestButtons() + setupAllTestButtons() } // MARK: - Setup Scroll & Stack diff --git a/src/Projects/BKDesign/Sources/Components/Button/BKButton.swift b/src/Projects/BKDesign/Sources/Components/Button/BKButton.swift index 83a20663..fe453e76 100644 --- a/src/Projects/BKDesign/Sources/Components/Button/BKButton.swift +++ b/src/Projects/BKDesign/Sources/Components/Button/BKButton.swift @@ -137,20 +137,25 @@ public class BKButton: UIButton, BKButtonProtocol { customContainerView.addSubview(stackView) stackView.axis = .horizontal stackView.alignment = .center - stackView.distribution = .fill + stackView.distribution = .equalCentering stackView.isUserInteractionEnabled = false - + stackView.addArrangedSubview(leftIconView) stackView.addArrangedSubview(customTitleLabel) stackView.addArrangedSubview(rightIconView) stackView.snp.makeConstraints { make in - make.edges.equalToSuperview() + make.centerX.equalToSuperview() + make.left.greaterThanOrEqualToSuperview().offset(size.horizontalPadding) + make.right.lessThanOrEqualToSuperview().offset(-size.horizontalPadding) + + make.centerY.equalToSuperview() + make.top.greaterThanOrEqualToSuperview().offset(size.verticalPadding) + make.bottom.lessThanOrEqualToSuperview().offset(-size.verticalPadding) } setupIconViews() setupTitleLabel() - updatePadding() } private func setupIconViews() { @@ -192,7 +197,6 @@ public class BKButton: UIButton, BKButtonProtocol { customTitleLabel.font = size.font updateCornerRadius() updateIconSizes() - updatePadding() invalidateIntrinsicContentSize() } @@ -224,16 +228,6 @@ public class BKButton: UIButton, BKButtonProtocol { make.width.height.equalTo(iconSize) } } - - private func updatePadding() { - stackView.isLayoutMarginsRelativeArrangement = true - stackView.layoutMargins = UIEdgeInsets( - top: 0, - left: size.horizontalPadding, - bottom: 0, - right: size.horizontalPadding - ) - } private func updateLayout() { stackView.spacing = 0 @@ -297,6 +291,7 @@ public class BKButton: UIButton, BKButtonProtocol { ) } } + // MARK: - Layout override public func layoutSubviews() { @@ -312,23 +307,26 @@ public class BKButton: UIButton, BKButtonProtocol { layer.cornerRadius = min(minimumRadius, dynamicRadius) } + } public override var intrinsicContentSize: CGSize { + let stackSize = stackView.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize) + + let minimumTotalPadding = size.horizontalPadding * 2 + + let currentButtonWidth = bounds.width + let calculatedPadding = max((currentButtonWidth - stackSize.width), minimumTotalPadding) + + let intrinsicWidth = stackSize.width + calculatedPadding + if isFullWidth { return CGSize(width: UIView.noIntrinsicMetric, height: size.height) } - let stackSize = stackView.systemLayoutSizeFitting( - UIView.layoutFittingCompressedSize - ) - let totalPadding = size.horizontalPadding * 2 - - return CGSize( - width: stackSize.width + totalPadding, - height: size.height - ) + return CGSize(width: intrinsicWidth, height: size.height) } + } // MARK: - Factory Methods diff --git a/src/Projects/BKDesign/Sources/Components/Button/BKButtonSize.swift b/src/Projects/BKDesign/Sources/Components/Button/BKButtonSize.swift index 7fc47d93..f6f00ad7 100644 --- a/src/Projects/BKDesign/Sources/Components/Button/BKButtonSize.swift +++ b/src/Projects/BKDesign/Sources/Components/Button/BKButtonSize.swift @@ -42,6 +42,16 @@ public enum BKButtonSize { } } + /// 버튼 상하 패딩 값 + var verticalPadding: CGFloat { + switch self { + case .small, .rounded: + BKSpacing.spacing2 + case .medium, .large: + BKSpacing.spacing3 + } + } + /// 버튼 텍스트에 사용할 폰트 var font: UIFont { switch self { From 72b44fe8d30c60c0b69681ec350e7ac9ce10be0d Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Thu, 10 Jul 2025 17:20:33 +0900 Subject: [PATCH 17/18] =?UTF-8?q?[BOOK-114]=20fix:=20=ED=86=A0=EB=81=BC=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=EC=A0=9C=EC=95=88=20=EC=9D=BC=EB=B6=80=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BKDesign/Sources/Components/Button/BKButtonGroup.swift | 2 ++ src/Projects/BKDesign/Sources/Extensions/SetNeeds.swift | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Projects/BKDesign/Sources/Components/Button/BKButtonGroup.swift b/src/Projects/BKDesign/Sources/Components/Button/BKButtonGroup.swift index 4a606b2b..d717c544 100644 --- a/src/Projects/BKDesign/Sources/Components/Button/BKButtonGroup.swift +++ b/src/Projects/BKDesign/Sources/Components/Button/BKButtonGroup.swift @@ -186,6 +186,8 @@ extension BKButtonGroup { } /// 수직 버튼 그룹 + /// + /// - Warning: 입력된 버튼들의 isFullWidth 속성이 true로 변경됩니다. public static func verticalButtonGroup( buttons: [BKButton], spacing: CGFloat = BKSpacing.spacing2 diff --git a/src/Projects/BKDesign/Sources/Extensions/SetNeeds.swift b/src/Projects/BKDesign/Sources/Extensions/SetNeeds.swift index 942abd2d..e70cddc3 100644 --- a/src/Projects/BKDesign/Sources/Extensions/SetNeeds.swift +++ b/src/Projects/BKDesign/Sources/Extensions/SetNeeds.swift @@ -36,7 +36,7 @@ public struct SetNeeds { } // 뷰를 설정하는 메서드 - mutating func configure(with view: UIView) { + public mutating func configure(with view: UIView) { self.view = view } From d828e9fbfccb435ae79d457108c636538cd649a6 Mon Sep 17 00:00:00 2001 From: doyeonk429 <80318425+doyeonk429@users.noreply.github.com> Date: Thu, 10 Jul 2025 19:13:28 +0900 Subject: [PATCH 18/18] =?UTF-8?q?[BOOK-114]=20fix:=20convention=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BKDesign/Sources/Components/Button/BKButtonStyle.swift | 5 ++++- src/Projects/BKDesign/Sources/Extensions/SetNeeds.swift | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/Projects/BKDesign/Sources/Components/Button/BKButtonStyle.swift b/src/Projects/BKDesign/Sources/Components/Button/BKButtonStyle.swift index 2d24cc18..c59dbdea 100644 --- a/src/Projects/BKDesign/Sources/Components/Button/BKButtonStyle.swift +++ b/src/Projects/BKDesign/Sources/Components/Button/BKButtonStyle.swift @@ -112,7 +112,10 @@ public struct BKButtonColorSet { } extension BKButtonColorSet: Equatable { - public static func == (lhs: BKButtonColorSet, rhs: BKButtonColorSet) -> Bool { + public static func == ( + lhs: BKButtonColorSet, + rhs: BKButtonColorSet + ) -> Bool { return lhs.normal.isEqual(to: rhs.normal) && lhs.pressed.isEqual(to: rhs.pressed) && lhs.disabled.isEqual(to: rhs.disabled) diff --git a/src/Projects/BKDesign/Sources/Extensions/SetNeeds.swift b/src/Projects/BKDesign/Sources/Extensions/SetNeeds.swift index e70cddc3..72e02987 100644 --- a/src/Projects/BKDesign/Sources/Extensions/SetNeeds.swift +++ b/src/Projects/BKDesign/Sources/Extensions/SetNeeds.swift @@ -15,7 +15,10 @@ public struct SetNeeds { private weak var view: UIView? // 초기화 - init(wrappedValue: Value, _ needs: Need...) { + init( + wrappedValue: Value, + _ needs: Need... + ) { self.value = wrappedValue self.needs = Set(needs) self.view = nil