diff --git a/FoodDiary/App/Project.swift b/FoodDiary/App/Project.swift index 8a9d2c1b..4423953e 100644 --- a/FoodDiary/App/Project.swift +++ b/FoodDiary/App/Project.swift @@ -55,7 +55,8 @@ let project = Project( ], settings: .settings( base: [ - "MARKETING_VERSION": "1.0.0", + "MARKETING_VERSION": "1.1.0", + "CURRENT_PROJECT_VERSION": "26050725", "BASE_URL": "$(BASE_URL)", "SENTRY_DSN": "$(SENTRY_DSN)", "TARGETED_DEVICE_FAMILY": "1" diff --git a/FoodDiary/App/Resources/LaunchScreen.storyboard b/FoodDiary/App/Resources/LaunchScreen.storyboard index 9656dcbc..e427cd28 100644 --- a/FoodDiary/App/Resources/LaunchScreen.storyboard +++ b/FoodDiary/App/Resources/LaunchScreen.storyboard @@ -27,7 +27,7 @@ - + diff --git a/FoodDiary/DesignSystem/Resources/Assets.xcassets/Colors/gray040.colorset/Contents.json b/FoodDiary/DesignSystem/Resources/Assets.xcassets/Colors/gray040.colorset/Contents.json new file mode 100644 index 00000000..6056dafb --- /dev/null +++ b/FoodDiary/DesignSystem/Resources/Assets.xcassets/Colors/gray040.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF9", + "green" : "0xF6", + "red" : "0xF3" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FoodDiary/DesignSystem/Resources/Assets.xcassets/Colors/gray150.colorset/Contents.json b/FoodDiary/DesignSystem/Resources/Assets.xcassets/Colors/gray150.colorset/Contents.json new file mode 100644 index 00000000..e7d0cebf --- /dev/null +++ b/FoodDiary/DesignSystem/Resources/Assets.xcassets/Colors/gray150.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xE0", + "green" : "0xE0", + "red" : "0xE0" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FoodDiary/DesignSystem/Resources/Assets.xcassets/Colors/gray850.colorset/Contents.json b/FoodDiary/DesignSystem/Resources/Assets.xcassets/Colors/gray850.colorset/Contents.json new file mode 100644 index 00000000..44d234cd --- /dev/null +++ b/FoodDiary/DesignSystem/Resources/Assets.xcassets/Colors/gray850.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0x2C", + "green" : "0x2C", + "red" : "0x2C" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/FoodDiary/DesignSystem/Resources/Assets.xcassets/onboard-1.imageset/onboard-1.png b/FoodDiary/DesignSystem/Resources/Assets.xcassets/onboard-1.imageset/onboard-1.png index 0d981431..d9a0a281 100644 Binary files a/FoodDiary/DesignSystem/Resources/Assets.xcassets/onboard-1.imageset/onboard-1.png and b/FoodDiary/DesignSystem/Resources/Assets.xcassets/onboard-1.imageset/onboard-1.png differ diff --git a/FoodDiary/DesignSystem/Resources/Assets.xcassets/onboard-1.imageset/onboard-1@2x.png b/FoodDiary/DesignSystem/Resources/Assets.xcassets/onboard-1.imageset/onboard-1@2x.png index 2bdba671..d9a0a281 100644 Binary files a/FoodDiary/DesignSystem/Resources/Assets.xcassets/onboard-1.imageset/onboard-1@2x.png and b/FoodDiary/DesignSystem/Resources/Assets.xcassets/onboard-1.imageset/onboard-1@2x.png differ diff --git a/FoodDiary/DesignSystem/Resources/Assets.xcassets/onboard-1.imageset/onboard-1@3x.png b/FoodDiary/DesignSystem/Resources/Assets.xcassets/onboard-1.imageset/onboard-1@3x.png index 7b6e27d3..d9a0a281 100644 Binary files a/FoodDiary/DesignSystem/Resources/Assets.xcassets/onboard-1.imageset/onboard-1@3x.png and b/FoodDiary/DesignSystem/Resources/Assets.xcassets/onboard-1.imageset/onboard-1@3x.png differ diff --git a/FoodDiary/DesignSystem/Sources/Components/SearchTextField.swift b/FoodDiary/DesignSystem/Sources/Components/SearchTextField.swift index 161f5411..9f4f95e1 100644 --- a/FoodDiary/DesignSystem/Sources/Components/SearchTextField.swift +++ b/FoodDiary/DesignSystem/Sources/Components/SearchTextField.swift @@ -82,6 +82,34 @@ public final class SearchTextField: UITextField { } } + public func applyColors( + backgroundColor: UIColor, + textColor: UIColor, + placeholderColor: UIColor, + iconColor: UIColor, + borderColor: UIColor? = nil, + borderWidth: CGFloat = 0 + ) { + self.backgroundColor = backgroundColor + self.textColor = textColor + layer.borderColor = borderColor?.cgColor + layer.borderWidth = borderWidth + + if let placeholder = attributedPlaceholder?.string { + attributedPlaceholder = NSAttributedString( + string: placeholder, + attributes: [ + .font: DesignSystemFontFamily.Pretendard.regular.font(size: 14), + .foregroundColor: placeholderColor + ] + ) + } + + if let iconButton = rightView as? UIButton { + iconButton.tintColor = iconColor + } + } + @objc private func searchIconTapped() { onSearchIconTapped?() } diff --git a/FoodDiary/Presentation/Sources/AddressSearch/AddressSearchViewController.swift b/FoodDiary/Presentation/Sources/AddressSearch/AddressSearchViewController.swift index 69ef0060..9a2f3262 100644 --- a/FoodDiary/Presentation/Sources/AddressSearch/AddressSearchViewController.swift +++ b/FoodDiary/Presentation/Sources/AddressSearch/AddressSearchViewController.swift @@ -54,7 +54,7 @@ public final class AddressSearchViewController: UIViewController, UITextFieldDel private lazy var closeButton: UIButton = { let button = UIButton(type: .system) button.setImage(UIImage(systemName: "xmark"), for: .normal) - button.tintColor = .white + button.tintColor = .gray850 button.addTarget(self, action: #selector(closeButtonTapped), for: .touchUpInside) return button }() @@ -68,17 +68,14 @@ public final class AddressSearchViewController: UIViewController, UITextFieldDel private let guideLabel: UILabel = { let label = UILabel() - label.setText("이 식당을 찾고 계신가요?", style: .hd16) + label.setText("이 식당을 찾고 계신가요?", style: .hd16, color: .gray850) return label }() private let resultsContainerView: UIView = { let view = UIView() - view.backgroundColor = .clear - view.layer.borderColor = UIColor.sd800.cgColor - view.layer.borderWidth = 1 - - view.layer.cornerRadius = 12 + view.backgroundColor = .gray040 + view.layer.cornerRadius = 10 view.clipsToBounds = true view.isHidden = true return view @@ -86,7 +83,7 @@ public final class AddressSearchViewController: UIViewController, UITextFieldDel private let emptyResultLabel: UILabel = { let label = UILabel() - label.setText("검색 결과가 없습니다.", style: .p14, color: .gray400) + label.setText("검색 결과가 없습니다.", style: .p14, color: .gray500) label.textAlignment = .center label.isHidden = true return label @@ -95,8 +92,8 @@ public final class AddressSearchViewController: UIViewController, UITextFieldDel private lazy var resultsTableView: UITableView = { let tv = UITableView() tv.backgroundColor = .clear - tv.separatorColor = .sd600 - tv.separatorInset = UIEdgeInsets(top: 0, left: 16, bottom: 0, right: 16) + tv.separatorColor = .gray150 + tv.separatorInset = UIEdgeInsets(top: 0, left: 20, bottom: 0, right: 20) tv.delegate = tableViewHandler tv.dataSource = tableViewHandler tv.register( @@ -157,7 +154,13 @@ public final class AddressSearchViewController: UIViewController, UITextFieldDel // MARK: - Setup private func setupUI() { - view.backgroundColor = .sdBase + view.backgroundColor = .white + searchTextField.applyColors( + backgroundColor: .gray040, + textColor: .gray850, + placeholderColor: .gray600, + iconColor: .gray850 + ) view.addSubview(closeButton) view.addSubview(searchTextField) @@ -236,10 +239,10 @@ public final class AddressSearchViewController: UIViewController, UITextFieldDel switch state.mode { case .suggestions: - guideLabel.setText("이 식당을 찾고 계신가요?", style: .hd16) + guideLabel.setText("이 식당을 찾고 계신가요?", style: .hd16, color: .gray850) displayResults = state.suggestions case .searchResults: - guideLabel.setText("검색 결과", style: .hd16) + guideLabel.setText("검색 결과", style: .hd16, color: .gray850) displayResults = state.searchResults } @@ -250,13 +253,23 @@ public final class AddressSearchViewController: UIViewController, UITextFieldDel if displayResults.isEmpty { switch state.mode { case .suggestions: - emptyResultLabel.setText("추천 목록이 없습니다.", style: .p14, color: .gray400) + emptyResultLabel.setText("추천 목록이 없습니다.", style: .p14, color: .gray500) case .searchResults: - emptyResultLabel.setText("검색 결과가 없습니다.", style: .p14, color: .gray400) + emptyResultLabel.setText("검색 결과가 없습니다.", style: .p14, color: .gray500) } } emptyResultLabel.isHidden = !displayResults.isEmpty + let isSearching = !(state.searchKeyword.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty) + searchTextField.applyColors( + backgroundColor: .gray040, + textColor: .gray850, + placeholderColor: .gray600, + iconColor: .gray850, + borderColor: isSearching ? .primary : nil, + borderWidth: isSearching ? 1 : 0 + ) + Task { @MainActor [weak self] in guard let self else { return } self.containerHeightConstraint?.update(offset: self.view.bounds.height) diff --git a/FoodDiary/Presentation/Sources/AddressSearch/Components/AddressSearchResultCell.swift b/FoodDiary/Presentation/Sources/AddressSearch/Components/AddressSearchResultCell.swift index 6a456b8a..4fbd7c77 100644 --- a/FoodDiary/Presentation/Sources/AddressSearch/Components/AddressSearchResultCell.swift +++ b/FoodDiary/Presentation/Sources/AddressSearch/Components/AddressSearchResultCell.swift @@ -43,7 +43,7 @@ final class AddressSearchResultCell: UITableViewCell { let button = UIButton(type: .system) var config = UIButton.Configuration.plain() config.attributedTitle = { - var attributed = AttributedString(Typography.p14.styled("선택", color: .white)) + var attributed = AttributedString(Typography.p12.styled("선택", color: .gray850)) attributed.underlineStyle = .single return attributed }() @@ -104,8 +104,8 @@ final class AddressSearchResultCell: UITableViewCell { // MARK: - Configure func configure(with result: AddressSearchResult) { - placeNameLabel.setText(result.placeName, style: .hd16) - roadAddressLabel.setText(result.roadAddress, style: .p14, color: .gray400) + placeNameLabel.setText(result.placeName, style: .hd16, color: .gray850) + roadAddressLabel.setText(result.roadAddress, style: .p14, color: .gray800) } } diff --git a/FoodDiary/Presentation/Sources/Core/UIColor+DesignSystem.swift b/FoodDiary/Presentation/Sources/Core/UIColor+DesignSystem.swift index 0deb0932..26b2e2d0 100644 --- a/FoodDiary/Presentation/Sources/Core/UIColor+DesignSystem.swift +++ b/FoodDiary/Presentation/Sources/Core/UIColor+DesignSystem.swift @@ -59,6 +59,9 @@ public extension UIColor { static let gray300 = DesignSystemAsset.gray300.color static let gray200 = DesignSystemAsset.gray200.color static let gray100 = DesignSystemAsset.gray100.color + static let gray040 = DesignSystemAsset.gray040.color + static let gray150 = DesignSystemAsset.gray150.color static let gray050 = DesignSystemAsset.gray050.color static let gray020 = DesignSystemAsset.gray020.color + static let gray850 = DesignSystemAsset.gray850.color } diff --git a/FoodDiary/Presentation/Sources/Detail/DetailViewController.swift b/FoodDiary/Presentation/Sources/Detail/DetailViewController.swift index de1e5955..aac598ff 100644 --- a/FoodDiary/Presentation/Sources/Detail/DetailViewController.swift +++ b/FoodDiary/Presentation/Sources/Detail/DetailViewController.swift @@ -478,7 +478,7 @@ public final class DetailViewController: UIViewController { } private func formatRecordForShare(_ name: String, _ url: String) -> String { - return "\(name) 맛을 기억하시나요?\n\n\(url)" + return "\(name) 맛을 기억하시나요?\n뭐먹었지에서 확인해보세요.\n\(url)\n\n 나도 시작하기\nhttps://mumuk.ai.kr/" } private func presentShareSheet(items: [Any]) { diff --git a/FoodDiary/Presentation/Sources/ImagePicker/ImagePickerSectionHeaderView.swift b/FoodDiary/Presentation/Sources/ImagePicker/ImagePickerSectionHeaderView.swift index 96bd8771..91466a90 100644 --- a/FoodDiary/Presentation/Sources/ImagePicker/ImagePickerSectionHeaderView.swift +++ b/FoodDiary/Presentation/Sources/ImagePicker/ImagePickerSectionHeaderView.swift @@ -37,10 +37,10 @@ final class ImagePickerSectionHeaderView: UICollectionReusableView { } func configure(title: String, guideText: String? = nil) { - titleLabel.setText(title, style: .hd16, color: .white) + titleLabel.setText(title, style: .hd16, color: .gray900) if let guideText { - guideLabel.setText(guideText, style: .p14, color: .gray400) + guideLabel.setText(guideText, style: .p14, color: .gray500) guideLabel.isHidden = false } else { guideLabel.isHidden = true diff --git a/FoodDiary/Presentation/Sources/ImagePicker/ImagePickerViewController.swift b/FoodDiary/Presentation/Sources/ImagePicker/ImagePickerViewController.swift index b103f8f3..46e833ce 100644 --- a/FoodDiary/Presentation/Sources/ImagePicker/ImagePickerViewController.swift +++ b/FoodDiary/Presentation/Sources/ImagePicker/ImagePickerViewController.swift @@ -134,7 +134,7 @@ public final class ImagePickerViewController: } let label = UILabel() - label.setText("오늘은 촬영된 사진이 없어요", style: .p12, color: .gray050) + label.setText("오늘은 촬영된 사진이 없어요", style: .p12, color: .gray500) label.textAlignment = .center let stackView = UIStackView(arrangedSubviews: [imageView, label]) @@ -178,6 +178,7 @@ public final class ImagePickerViewController: setupUI() setupConstraints() loadPhotos() + navigationController?.navigationBar.tintColor = .gray900 resultPublisher .compactMap { if case .selected(let a) = $0 { return a } else { return nil } } @@ -240,12 +241,12 @@ public final class ImagePickerViewController: target: self, action: #selector(selectAllButtonTapped) ) - selectAllButton.tintColor = .white + selectAllButton.tintColor = .gray900 navigationItem.rightBarButtonItem = selectAllButton } private func setupUI() { - view.backgroundColor = DesignSystemAsset.sdBase.color + view.backgroundColor = .white view.addSubview(collectionView) view.addSubview(emptyView) diff --git a/FoodDiary/Presentation/Sources/MyPage/Components/ProfileHeaderView.swift b/FoodDiary/Presentation/Sources/MyPage/Components/ProfileHeaderView.swift index 510ac06c..5e2b315b 100644 --- a/FoodDiary/Presentation/Sources/MyPage/Components/ProfileHeaderView.swift +++ b/FoodDiary/Presentation/Sources/MyPage/Components/ProfileHeaderView.swift @@ -18,21 +18,21 @@ final class ProfileHeaderView: UIView { iv.layer.cornerRadius = 36 iv.clipsToBounds = true iv.layer.borderWidth = 3 - iv.layer.borderColor = UIColor.sd800.cgColor + iv.layer.borderColor = UIColor.white.cgColor return iv }() private let greetingLabel: UILabel = { let label = UILabel() - label.setText("안녕하세요,", style: .p12, color: .gray050) + label.setText("안녕하세요,", style: .p12, color: .gray500) label.textAlignment = .center return label }() private let nameLabel: UILabel = { let label = UILabel() - label.setText("홍길동님", style: .hd18, color: .white) + label.setText("홍길동님", style: .hd18, color: .gray900) label.textAlignment = .center return label }() @@ -55,12 +55,12 @@ final class ProfileHeaderView: UIView { private let separatorView: UIView = { let view = UIView() - view.backgroundColor = .sd800 + view.backgroundColor = .gray150 return view }() func configure(nickname: String) { - nameLabel.setText("\(nickname)님", style: .hd18, color: .white) + nameLabel.setText("\(nickname)님", style: .hd18, color: .gray900) } override init(frame: CGRect) { @@ -75,13 +75,13 @@ final class ProfileHeaderView: UIView { } private func setupUI() { - backgroundColor = .sd700 + backgroundColor = .gray040 [contentStackView, separatorView].forEach { addSubview($0) } } private func setupConstraints() { self.snp.makeConstraints { - $0.height.equalTo(280) + $0.height.equalTo(248) } characterImageView.snp.makeConstraints { @@ -89,7 +89,7 @@ final class ProfileHeaderView: UIView { } contentStackView.snp.makeConstraints { - $0.centerY.equalToSuperview().offset(40) + $0.centerY.equalToSuperview() $0.centerX.equalToSuperview() } diff --git a/FoodDiary/Presentation/Sources/MyPage/Components/WithdrawalFooterView.swift b/FoodDiary/Presentation/Sources/MyPage/Components/WithdrawalFooterView.swift index 8fa09a32..a3040cf8 100644 --- a/FoodDiary/Presentation/Sources/MyPage/Components/WithdrawalFooterView.swift +++ b/FoodDiary/Presentation/Sources/MyPage/Components/WithdrawalFooterView.swift @@ -13,7 +13,7 @@ final class WithdrawalFooterView: UIView { private let withdrawalLabel: UILabel = { let label = UILabel() - let base = Typography.p12.styled("탈퇴", color: .gray050) + let base = Typography.p12.styled("탈퇴", color: .gray900) let mutable = NSMutableAttributedString(attributedString: base) mutable.addAttribute(.underlineStyle, value: NSUnderlineStyle.single.rawValue, range: NSRange(location: 0, length: mutable.length)) label.attributedText = mutable @@ -27,7 +27,7 @@ final class WithdrawalFooterView: UIView { addSubview(withdrawalLabel) withdrawalLabel.snp.makeConstraints { - $0.trailing.equalToSuperview().offset(-28) + $0.trailing.equalToSuperview().offset(-16) $0.centerY.equalToSuperview() } diff --git a/FoodDiary/Presentation/Sources/MyPage/MyPageViewController.swift b/FoodDiary/Presentation/Sources/MyPage/MyPageViewController.swift index 90df47dd..b7a3107d 100644 --- a/FoodDiary/Presentation/Sources/MyPage/MyPageViewController.swift +++ b/FoodDiary/Presentation/Sources/MyPage/MyPageViewController.swift @@ -22,8 +22,8 @@ public final class MyPageViewController: UIViewController { var headerIcon: UIImage? { switch self { - case .notifications: return DesignSystemAsset.iconAlert.image - case .management: return DesignSystemAsset.iconSetting.image + case .notifications: return DesignSystemAsset.iconAlert.image.withRenderingMode(.alwaysTemplate) + case .management: return DesignSystemAsset.iconSetting.image.withRenderingMode(.alwaysTemplate) case .logout: return nil } } @@ -69,12 +69,13 @@ public final class MyPageViewController: UIViewController { // MARK: - State private var cancellables = Set() + private var withdrawalFooterView: WithdrawalFooterView? // MARK: - UI Components private lazy var scrollView: UIScrollView = { let sv = UIScrollView() - sv.backgroundColor = .sdBase + sv.backgroundColor = .white sv.showsVerticalScrollIndicator = false return sv }() @@ -86,7 +87,7 @@ public final class MyPageViewController: UIViewController { private lazy var tableView: UITableView = { let tv = UITableView(frame: .zero, style: .insetGrouped) - tv.backgroundColor = .sdBase + tv.backgroundColor = .white tv.showsVerticalScrollIndicator = false tv.separatorColor = .clear tv.isScrollEnabled = false @@ -136,17 +137,36 @@ public final class MyPageViewController: UIViewController { public override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() updateTableViewHeight() + updateTableFooterWidthIfNeeded() } // MARK: - Setup private func setupNavigation() { - title = "마이페이지" navigationController?.navigationBar.prefersLargeTitles = false + navigationController?.navigationBar.tintColor = .gray900 + + let appearance = UINavigationBarAppearance() + appearance.configureWithOpaqueBackground() + appearance.backgroundColor = .white + appearance.shadowColor = .gray150 + navigationController?.navigationBar.standardAppearance = appearance + navigationController?.navigationBar.scrollEdgeAppearance = appearance + navigationController?.navigationBar.compactAppearance = appearance + + navigationItem.leftBarButtonItem = UIBarButtonItem( + image: UIImage(systemName: "chevron.left"), + style: .plain, + target: self, + action: #selector(backButtonTapped) + ) + navigationItem.rightBarButtonItem = UIBarButtonItem( + customView: makeProfileIconView() + ) } private func setupUI() { - view.backgroundColor = .sdBase + view.backgroundColor = .white view.addSubview(scrollView) scrollView.addSubview(profileHeaderView) scrollView.addSubview(tableView) @@ -154,16 +174,16 @@ public final class MyPageViewController: UIViewController { private func setupConstraints() { scrollView.snp.makeConstraints { - $0.edges.equalToSuperview() + $0.edges.equalTo(view.safeAreaLayoutGuide) } profileHeaderView.snp.makeConstraints { - $0.top.equalTo(view.snp.top) + $0.top.equalTo(scrollView.contentLayoutGuide) $0.leading.trailing.equalTo(scrollView.frameLayoutGuide) } tableView.snp.makeConstraints { - $0.top.equalTo(profileHeaderView.snp.bottom).offset(30) + $0.top.equalTo(profileHeaderView.snp.bottom).offset(24) $0.leading.trailing.equalTo(scrollView.frameLayoutGuide) $0.bottom.equalTo(scrollView.contentLayoutGuide) tableViewHeightConstraint = $0.height.equalTo(0).constraint @@ -178,11 +198,21 @@ public final class MyPageViewController: UIViewController { private func setupTableFooter() { let footer = WithdrawalFooterView() - footer.frame = CGRect(x: 0, y: 0, width: tableView.frame.width, height: 44) + footer.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: 44) footer.onTap = { [weak self] in self?.showWithdrawalAlert() } tableView.tableFooterView = footer + withdrawalFooterView = footer + } + + private func updateTableFooterWidthIfNeeded() { + guard let footer = withdrawalFooterView else { return } + let width = tableView.bounds.width + guard width > 0, abs(footer.frame.width - width) > .ulpOfOne else { return } + + footer.frame = CGRect(x: 0, y: 0, width: width, height: footer.frame.height) + tableView.tableFooterView = footer } private func showWithdrawalAlert() { @@ -221,7 +251,7 @@ public final class MyPageViewController: UIViewController { if let cell = tableView.cellForRow(at: indexPath), let badge = cell.contentView.viewWithTag(999) as? UILabel { badge.text = isEnabled ? "ON" : "OFF" - badge.backgroundColor = isEnabled ? DesignSystemAsset.primary.color : DesignSystemAsset.gray600.color + badge.backgroundColor = isEnabled ? DesignSystemAsset.primary.color : DesignSystemAsset.sd800.color } else { tableView.reloadRows(at: [indexPath], with: .none) } @@ -258,14 +288,14 @@ public final class MyPageViewController: UIViewController { private func configureCell(_ cell: UITableViewCell, for row: Row, at indexPath: IndexPath) { var backgroundConfig = UIBackgroundConfiguration.listCell() - backgroundConfig.backgroundColor = .sd700 + backgroundConfig.backgroundColor = .gray040 cell.backgroundConfiguration = backgroundConfig cell.selectionStyle = .none cell.contentConfiguration = nil switch row { case .notificationSetting: - cell.textLabel?.setText("알림설정", style: .p14, color: .gray050) + cell.textLabel?.setText("알림설정", style: .p14, color: .gray900) cell.accessoryView = makeChevronAccessory() // 기존 배지 제거 (재사용 대비) @@ -279,7 +309,7 @@ public final class MyPageViewController: UIViewController { // 알림 권한 상태에 따라 배지 텍스트/색상 결정 let isEnabled = viewModel.state.isNotificationEnabled let badgeText = isEnabled ? "ON" : "OFF" - let badgeColor = isEnabled ? DesignSystemAsset.primary.color : DesignSystemAsset.gray600.color + let badgeColor = isEnabled ? DesignSystemAsset.primary.color : DesignSystemAsset.sd800.color let badge = makeBadgeLabel(text: badgeText, backgroundColor: badgeColor) badge.tag = 999 @@ -294,19 +324,19 @@ public final class MyPageViewController: UIViewController { ]) case .appVersion: - cell.textLabel?.setText("앱 버전", style: .p14, color: .gray050) + cell.textLabel?.setText("앱 버전", style: .p14, color: .gray900) cell.accessoryView = makeVersionLabel(text: viewModel.state.appVersion) case .privacy: - cell.textLabel?.setText("개인정보 처리방침", style: .p14, color: .gray050) + cell.textLabel?.setText("개인정보 처리방침", style: .p14, color: .gray900) cell.accessoryView = makeChevronAccessory() case .customerInquiry: - cell.textLabel?.setText("문의하기", style: .p14, color: .gray050) + cell.textLabel?.setText("문의하기", style: .p14, color: .gray900) cell.accessoryView = makeChevronAccessory() case .logout: - cell.textLabel?.setText("로그아웃", style: .p14, color: .gray050) + cell.textLabel?.setText("로그아웃", style: .p14, color: .gray900) cell.accessoryView = makeLogoutIconAccessory() } } @@ -315,18 +345,22 @@ public final class MyPageViewController: UIViewController { private func makeVersionLabel(text: String) -> UILabel { let label = UILabel() - label.setText(text, style: .p14, color: .gray700) + label.setText(text, style: .p14, color: .gray600) label.sizeToFit() return label } private func makeChevronAccessory() -> UIImageView { - let iv = UIImageView(image: DesignSystemAsset.iconNext.image) + let iv = UIImageView(image: DesignSystemAsset.iconArrow.image.withRenderingMode(.alwaysTemplate)) + iv.tintColor = .gray850 + iv.frame = CGRect(x: 0, y: 0, width: 18, height: 18) + iv.contentMode = .scaleAspectFit return iv } private func makeLogoutIconAccessory() -> UIImageView { - let iv = UIImageView(image: DesignSystemAsset.iconLogout.image) + let iv = UIImageView(image: DesignSystemAsset.iconLogout.image.withRenderingMode(.alwaysTemplate)) + iv.tintColor = .gray850 return iv } @@ -341,6 +375,20 @@ public final class MyPageViewController: UIViewController { label.textAlignment = .center return label } + + private func makeProfileIconView() -> UIImageView { + let imageView = UIImageView( + image: DesignSystemAsset.iconMypage.image.withRenderingMode(.alwaysTemplate) + ) + imageView.contentMode = .scaleAspectFit + imageView.tintColor = .gray850 + imageView.frame = CGRect(x: 0, y: 0, width: 24, height: 24) + return imageView + } + + @objc private func backButtonTapped() { + navigationController?.popViewController(animated: true) + } } // MARK: - UITableViewDataSource @@ -383,7 +431,7 @@ extension MyPageViewController: UITableViewDelegate { if let icon = sec.headerIcon { let iconImageView = UIImageView(image: icon) iconImageView.contentMode = .scaleAspectFit - iconImageView.tintColor = .gray400 + iconImageView.tintColor = .gray850 iconImageView.snp.makeConstraints { $0.size.equalTo(18) } @@ -391,7 +439,7 @@ extension MyPageViewController: UITableViewDelegate { } let label = UILabel() - label.setText(title, style: .p12, color: .gray050) + label.setText(title, style: .p12, color: .gray900) stackView.addArrangedSubview(label) headerView.addSubview(stackView) @@ -469,4 +517,3 @@ extension MyPageViewController: UITableViewDelegate { UIApplication.shared.open(url) } } - diff --git a/FoodDiary/Presentation/Sources/Onboarding/OnboardPageContentViewController.swift b/FoodDiary/Presentation/Sources/Onboarding/OnboardPageContentViewController.swift index 59f929a9..19ac48b8 100644 --- a/FoodDiary/Presentation/Sources/Onboarding/OnboardPageContentViewController.swift +++ b/FoodDiary/Presentation/Sources/Onboarding/OnboardPageContentViewController.swift @@ -8,11 +8,19 @@ import SnapKit import DesignSystem final class OnboardPageContentViewController: UIViewController { + private enum Constants { + static let imageSize: CGFloat = 180 + static let imageTopOffset: CGFloat = 104 + static let textTopSpacing: CGFloat = 36 + static let textHorizontalInset: CGFloat = 20 + } + let pageIndex: Int private let imageView: UIImageView = { let imageView = UIImageView() imageView.contentMode = .scaleAspectFit + imageView.clipsToBounds = true return imageView }() @@ -23,19 +31,16 @@ final class OnboardPageContentViewController: UIViewController { return label }() - private lazy var stackView: UIStackView = { - let stackView = UIStackView(arrangedSubviews: [imageView, textLabel]) - stackView.axis = .vertical - stackView.spacing = 40 - stackView.alignment = .center - return stackView - }() - init(pageIndex: Int, image: UIImage?, text: String) { self.pageIndex = pageIndex super.init(nibName: nil, bundle: nil) imageView.image = image - textLabel.setText(text, style: .p15) + textLabel.attributedText = Typography.p15.styled( + text, + color: .gray850, + alignment: .center, + lineSpacing: 3.75 + ) textLabel.textAlignment = .center } @@ -51,18 +56,19 @@ final class OnboardPageContentViewController: UIViewController { private extension OnboardPageContentViewController { func setupUI() { - view.backgroundColor = DesignSystemAsset.sdBase.color - view.addSubview(stackView) + view.backgroundColor = .white + view.addSubview(imageView) + view.addSubview(textLabel) - stackView.snp.makeConstraints { - $0.top.equalToSuperview().offset(130) - $0.leading.trailing.equalToSuperview().inset(65) + imageView.snp.makeConstraints { $0.centerX.equalToSuperview() + $0.top.equalToSuperview().offset(Constants.imageTopOffset) + $0.size.equalTo(Constants.imageSize) } - imageView.snp.makeConstraints { - $0.height.equalTo(220) - $0.width.equalTo(imageView.snp.height) + textLabel.snp.makeConstraints { + $0.top.equalTo(imageView.snp.bottom).offset(Constants.textTopSpacing) + $0.leading.trailing.equalToSuperview().inset(Constants.textHorizontalInset) $0.centerX.equalToSuperview() } } diff --git a/FoodDiary/Presentation/Sources/Onboarding/OnboardingViewController.swift b/FoodDiary/Presentation/Sources/Onboarding/OnboardingViewController.swift index d879afec..40812fa4 100644 --- a/FoodDiary/Presentation/Sources/Onboarding/OnboardingViewController.swift +++ b/FoodDiary/Presentation/Sources/Onboarding/OnboardingViewController.swift @@ -17,13 +17,15 @@ public final class OnboardingViewController: UIViewController { didCompleteSubject.eraseToAnyPublisher() } - @Published private var currentPage: Int = 0 - - private var cancellables = Set() + private var currentPage: Int = 0 { + didSet { + updateCurrentPageAppearance() + } + } private let pages: [(image: UIImage?, text: String)] = [ - (DesignSystemAsset.onboard1.image, "여기저기 흩어진 음식 기록을,\n간편히 정리해 드릴게요"), - (DesignSystemAsset.onboard2.image, "음식 기록은 간편하게 시작하고\n활용은 자유롭게 이어가세요."), + (DesignSystemAsset.onboard1.image, "여기저기 흩어진 음식 기록들,\n간편히 정리해 드릴게요"), + (DesignSystemAsset.onboard2.image, "음식 기록은 간편하게 시작하고,\n활용은 자유롭게 이어가세요."), (DesignSystemAsset.onboard3.image, "음식 사진을 올리면 식당, 메뉴,\n방문 정보를 자동으로 정리해줘요."), (DesignSystemAsset.onboard4.image, "정리된 기록을 블로그나 SNS에\n바로 활용할 수 있어요."), (DesignSystemAsset.onboard5.image, "기록이 쌓일수록 무엇을, 언제,\n얼마나 먹는지 한눈에 보여요.") @@ -42,21 +44,59 @@ public final class OnboardingViewController: UIViewController { return pageVC }() - private let pageControl: UIPageControl = { - let pageControl = UIPageControl() - pageControl.currentPageIndicatorTintColor = DesignSystemAsset.primary.color - pageControl.pageIndicatorTintColor = DesignSystemAsset.sd800.color - pageControl.isUserInteractionEnabled = false - return pageControl + private lazy var pageIndicatorDots: [UIView] = { + pages.indices.map { index in + let dot = UIView() + dot.backgroundColor = index == 0 ? .primary : .sd800 + dot.layer.cornerRadius = 3 + dot.clipsToBounds = true + dot.snp.makeConstraints { + $0.size.equalTo(6) + } + return dot + } + }() + + private lazy var pageIndicatorStackView: UIStackView = { + let stackView = UIStackView(arrangedSubviews: pageIndicatorDots) + stackView.axis = .horizontal + stackView.alignment = .center + stackView.distribution = .equalSpacing + stackView.spacing = 4 + return stackView + }() + + private let headerView: UIView = { + let view = UIView() + view.backgroundColor = .white + return view + }() + + private let headerSeparatorView: UIView = { + let view = UIView() + view.backgroundColor = .gray150 + return view + }() + + private lazy var skipButton: UIButton = { + let button = UIButton(type: .system) + button.setAttributedTitle( + Typography.p15.styled("건너뛰기", color: .black, alignment: .right), + for: .normal + ) + button.addTarget(self, action: #selector(skipButtonTapped), for: .touchUpInside) + return button }() private lazy var nextButton: UIButton = { let button = UIButton(type: .system) - button.titleLabel?.font = .systemFont(ofSize: 17, weight: .semibold) - button.setTitle("다음", for: .normal) + button.setAttributedTitle( + Typography.hd15.styled("다음", color: .sdBase, alignment: .center), + for: .normal + ) button.backgroundColor = DesignSystemAsset.primary.color - button.setTitleColor(DesignSystemAsset.sdBase.color, for: .normal) button.clipsToBounds = true + button.addTarget(self, action: #selector(nextButtonTapped), for: .touchUpInside) return button }() @@ -71,13 +111,20 @@ public final class OnboardingViewController: UIViewController { public override func viewDidLoad() { super.viewDidLoad() configureUI() - configureNavigationBar() - setupActions() - setupBindings() - pageControl.numberOfPages = pages.count + updateCurrentPageAppearance() pageViewController.setViewControllers([pageViewControllers[0]], direction: .forward, animated: false) } + public override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + navigationController?.setNavigationBarHidden(true, animated: animated) + } + + public override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + navigationController?.setNavigationBarHidden(false, animated: animated) + } + public override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() nextButton.layer.cornerRadius = nextButton.frame.height / 2 @@ -86,61 +133,67 @@ public final class OnboardingViewController: UIViewController { private extension OnboardingViewController { func configureUI() { - view.backgroundColor = DesignSystemAsset.sdBase.color + view.backgroundColor = .white + + view.addSubview(headerView) + headerView.addSubview(skipButton) + view.addSubview(headerSeparatorView) addChild(pageViewController) view.addSubview(pageViewController.view) pageViewController.didMove(toParent: self) - view.addSubview(pageControl) + view.addSubview(pageIndicatorStackView) view.addSubview(nextButton) - pageViewController.view.snp.makeConstraints { + headerView.snp.makeConstraints { $0.top.equalTo(view.safeAreaLayoutGuide.snp.top) $0.leading.trailing.equalToSuperview() - $0.bottom.equalTo(pageControl.snp.top).offset(-10) + $0.height.equalTo(60) } - pageControl.snp.makeConstraints { - $0.centerX.equalToSuperview() - $0.bottom.equalTo(nextButton.snp.top).offset(-100) + skipButton.snp.makeConstraints { + $0.trailing.equalToSuperview().inset(20) + $0.centerY.equalToSuperview() } - nextButton.snp.makeConstraints { - $0.leading.trailing.equalToSuperview().inset(16) - $0.height.equalTo(55) - $0.bottom.equalTo(view.safeAreaLayoutGuide).inset(18) + headerSeparatorView.snp.makeConstraints { + $0.top.equalTo(headerView.snp.bottom) + $0.leading.trailing.equalToSuperview() + $0.height.equalTo(1) } - } - func configureNavigationBar() { - let skipButton = UIBarButtonItem( - title: "건너뛰기", - style: .plain, - target: self, - action: #selector(skipButtonTapped) - ) + pageIndicatorStackView.snp.makeConstraints { + $0.centerX.equalToSuperview() + $0.bottom.equalTo(nextButton.snp.top).offset(-136) + } - navigationItem.rightBarButtonItem = skipButton - } + pageViewController.view.snp.makeConstraints { + $0.top.equalTo(headerSeparatorView.snp.bottom) + $0.leading.trailing.equalToSuperview() + $0.bottom.equalTo(pageIndicatorStackView.snp.top) + } - func setupActions() { - nextButton.addTarget(self, action: #selector(nextButtonTapped), for: .touchUpInside) + nextButton.snp.makeConstraints { + $0.leading.trailing.equalToSuperview().inset(16) + $0.height.equalTo(50) + $0.bottom.equalToSuperview().inset(36) + } } - func setupBindings() { - $currentPage - .receive(on: DispatchQueue.main) - .sink { [weak self] page in - guard let self = self else { return } - self.pageControl.currentPage = page - self.updateNextButton() - } - .store(in: &cancellables) + func updateCurrentPageAppearance() { + updateNextButtonTitle() + pageIndicatorDots.enumerated().forEach { index, dot in + dot.backgroundColor = index == currentPage ? .primary : .sd800 + } } - func updateNextButton() { - nextButton.setTitle(currentPage == pages.count - 1 ? "시작하기" : "다음", for: .normal) + func updateNextButtonTitle() { + let title = currentPage == pages.count - 1 ? "시작하기" : "다음" + nextButton.setAttributedTitle( + Typography.hd15.styled(title, color: .sdBase, alignment: .center), + for: .normal + ) } @objc func skipButtonTapped() {