-
Notifications
You must be signed in to change notification settings - Fork 1
[Feat-T3-167] 추천 루틴 화면 수정된 디자인 반영 #51
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
9b48011
3fb6013
5614489
1658214
3e0aa51
6bc3032
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "info" : { | ||
| "author" : "xcode", | ||
| "version" : 1 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| { | ||
| "colors" : [ | ||
| { | ||
| "color" : { | ||
| "color-space" : "srgb", | ||
| "components" : { | ||
| "alpha" : "1.000", | ||
| "blue" : "0xC7", | ||
| "green" : "0xF5", | ||
| "red" : "0xFF" | ||
| } | ||
| }, | ||
| "idiom" : "universal" | ||
| } | ||
| ], | ||
| "info" : { | ||
| "author" : "xcode", | ||
| "version" : 1 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| { | ||
| "info" : { | ||
| "author" : "xcode", | ||
| "version" : 1 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| { | ||
| "images" : [ | ||
| { | ||
| "filename" : "connect_icon.png", | ||
| "idiom" : "universal", | ||
| "scale" : "1x" | ||
| }, | ||
| { | ||
| "filename" : "connect_icon@2x.png", | ||
| "idiom" : "universal", | ||
| "scale" : "2x" | ||
| }, | ||
| { | ||
| "filename" : "connect_icon@3x.png", | ||
| "idiom" : "universal", | ||
| "scale" : "3x" | ||
| } | ||
| ], | ||
| "info" : { | ||
| "author" : "xcode", | ||
| "version" : 1 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| { | ||
| "images" : [ | ||
| { | ||
| "filename" : "grow_icon.png", | ||
| "idiom" : "universal", | ||
| "scale" : "1x" | ||
| }, | ||
| { | ||
| "filename" : "grow_icon@2x.png", | ||
| "idiom" : "universal", | ||
| "scale" : "2x" | ||
| }, | ||
| { | ||
| "filename" : "grow_icon@3x.png", | ||
| "idiom" : "universal", | ||
| "scale" : "3x" | ||
| } | ||
| ], | ||
| "info" : { | ||
| "author" : "xcode", | ||
| "version" : 1 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| { | ||
| "images" : [ | ||
| { | ||
| "filename" : "outside_icon.png", | ||
| "idiom" : "universal", | ||
| "scale" : "1x" | ||
| }, | ||
| { | ||
| "filename" : "outside_icon@2x.png", | ||
| "idiom" : "universal", | ||
| "scale" : "2x" | ||
| }, | ||
| { | ||
| "filename" : "outside_icon@3x.png", | ||
| "idiom" : "universal", | ||
| "scale" : "3x" | ||
| } | ||
| ], | ||
| "info" : { | ||
| "author" : "xcode", | ||
| "version" : 1 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| { | ||
| "images" : [ | ||
| { | ||
| "filename" : "rest_icon.png", | ||
| "idiom" : "universal", | ||
| "scale" : "1x" | ||
| }, | ||
| { | ||
| "filename" : "rest_icon@2x.png", | ||
| "idiom" : "universal", | ||
| "scale" : "2x" | ||
| }, | ||
| { | ||
| "filename" : "rest_icon@3x.png", | ||
| "idiom" : "universal", | ||
| "scale" : "3x" | ||
| } | ||
| ], | ||
| "info" : { | ||
| "author" : "xcode", | ||
| "version" : 1 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| { | ||
| "images" : [ | ||
| { | ||
| "filename" : "shine_icon.png", | ||
| "idiom" : "universal", | ||
| "scale" : "1x" | ||
| }, | ||
| { | ||
| "filename" : "shine_icon@2x.png", | ||
| "idiom" : "universal", | ||
| "scale" : "2x" | ||
| }, | ||
| { | ||
| "filename" : "shine_icon@3x.png", | ||
| "idiom" : "universal", | ||
| "scale" : "3x" | ||
| } | ||
| ], | ||
| "info" : { | ||
| "author" : "xcode", | ||
| "version" : 1 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| { | ||
| "images" : [ | ||
| { | ||
| "filename" : "wakeup_icon.png", | ||
| "idiom" : "universal", | ||
| "scale" : "1x" | ||
| }, | ||
| { | ||
| "filename" : "wakeup_icon@2x.png", | ||
| "idiom" : "universal", | ||
| "scale" : "2x" | ||
| }, | ||
| { | ||
| "filename" : "wakeup_icon@3x.png", | ||
| "idiom" : "universal", | ||
| "scale" : "3x" | ||
| } | ||
| ], | ||
| "info" : { | ||
| "author" : "xcode", | ||
| "version" : 1 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,166 @@ | ||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||
| // RoutineCardView.swift | ||||||||||||||||||||||||||||||||||||||||||||
| // Presentation | ||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||
| // Created by 최정인 on 8/16/25. | ||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import Domain | ||||||||||||||||||||||||||||||||||||||||||||
| import SnapKit | ||||||||||||||||||||||||||||||||||||||||||||
| import UIKit | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| final class RoutineCardView: UIView { | ||||||||||||||||||||||||||||||||||||||||||||
| private enum Layout { | ||||||||||||||||||||||||||||||||||||||||||||
| static let horizontalMargin: CGFloat = 16 | ||||||||||||||||||||||||||||||||||||||||||||
| static let cornerRadius: CGFloat = 12 | ||||||||||||||||||||||||||||||||||||||||||||
| static let headerInfoStackViewSpacing: CGFloat = 10 | ||||||||||||||||||||||||||||||||||||||||||||
| static let headerInfoStackViewTopSpacing: CGFloat = 14 | ||||||||||||||||||||||||||||||||||||||||||||
| static let subRoutineStackViewSpacing: CGFloat = 2 | ||||||||||||||||||||||||||||||||||||||||||||
| static let subRoutineStackViewTopSpacing: CGFloat = 10 | ||||||||||||||||||||||||||||||||||||||||||||
| static let subRoutineStackViewBottomSpacing: CGFloat = 14 | ||||||||||||||||||||||||||||||||||||||||||||
| static let subRoutineLabelHeight: CGFloat = 20 | ||||||||||||||||||||||||||||||||||||||||||||
| static let categoryIconSize: CGFloat = 32 | ||||||||||||||||||||||||||||||||||||||||||||
| static let plusImageSize: CGFloat = 24 | ||||||||||||||||||||||||||||||||||||||||||||
| static let plusButtonTopSpacing: CGFloat = 14 | ||||||||||||||||||||||||||||||||||||||||||||
| static let plusButtonTrailingSpacing: CGFloat = 7 | ||||||||||||||||||||||||||||||||||||||||||||
| static let plusButtonSize: CGFloat = 32 | ||||||||||||||||||||||||||||||||||||||||||||
| static let grayLineTopSpacing: CGFloat = 10 | ||||||||||||||||||||||||||||||||||||||||||||
| static let grayLineHeight: CGFloat = 1 | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| private let headerInfoStackView = UIStackView() | ||||||||||||||||||||||||||||||||||||||||||||
| private let categoryIconView = RoutineCategoryIcon(routineCategory: .connection) | ||||||||||||||||||||||||||||||||||||||||||||
| private let titleLabel = UILabel() | ||||||||||||||||||||||||||||||||||||||||||||
| private let editButton = UIButton() | ||||||||||||||||||||||||||||||||||||||||||||
| private let deleteButton = UIButton() | ||||||||||||||||||||||||||||||||||||||||||||
| private let plusButton = UIButton() | ||||||||||||||||||||||||||||||||||||||||||||
| private let grayLine = UIView() | ||||||||||||||||||||||||||||||||||||||||||||
| private let subRoutineLabel = UILabel() | ||||||||||||||||||||||||||||||||||||||||||||
| private let subRoutineStackView = UIStackView() | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| init() { | ||||||||||||||||||||||||||||||||||||||||||||
| super.init(frame: .zero) | ||||||||||||||||||||||||||||||||||||||||||||
| configureAttribute() | ||||||||||||||||||||||||||||||||||||||||||||
| configureLayout() | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| required init?(coder: NSCoder) { | ||||||||||||||||||||||||||||||||||||||||||||
| fatalError("init(coder:) has not been implemented") | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| private func configureAttribute() { | ||||||||||||||||||||||||||||||||||||||||||||
| backgroundColor = .white | ||||||||||||||||||||||||||||||||||||||||||||
| layer.masksToBounds = true | ||||||||||||||||||||||||||||||||||||||||||||
| layer.cornerRadius = Layout.cornerRadius | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| headerInfoStackView.axis = .horizontal | ||||||||||||||||||||||||||||||||||||||||||||
| headerInfoStackView.spacing = Layout.headerInfoStackViewSpacing | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| titleLabel.text = "개운하게 일어나기" | ||||||||||||||||||||||||||||||||||||||||||||
| titleLabel.font = BitnagilFont(style: .body1, weight: .semiBold).font | ||||||||||||||||||||||||||||||||||||||||||||
| titleLabel.textColor = BitnagilColor.gray10 | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| let plusImage = BitnagilIcon.plusIcon? | ||||||||||||||||||||||||||||||||||||||||||||
| .resizeAspectFit(to: CGSize(width: Layout.plusImageSize, height: Layout.plusImageSize)) | ||||||||||||||||||||||||||||||||||||||||||||
| plusButton.setImage(plusImage, for: .normal) | ||||||||||||||||||||||||||||||||||||||||||||
| plusButton.tintColor = BitnagilColor.gray10 | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| grayLine.backgroundColor = BitnagilColor.gray97 | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| subRoutineStackView.axis = .vertical | ||||||||||||||||||||||||||||||||||||||||||||
| subRoutineStackView.spacing = Layout.subRoutineStackViewSpacing | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| subRoutineLabel.text = "세부 루틴" | ||||||||||||||||||||||||||||||||||||||||||||
| subRoutineLabel.font = BitnagilFont(style: .body2, weight: .medium).font | ||||||||||||||||||||||||||||||||||||||||||||
| subRoutineLabel.textColor = BitnagilColor.gray40 | ||||||||||||||||||||||||||||||||||||||||||||
| subRoutineLabel.snp.makeConstraints { make in | ||||||||||||||||||||||||||||||||||||||||||||
| make.height.equalTo(Layout.subRoutineLabelHeight) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| subRoutineStackView.addArrangedSubview(subRoutineLabel) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| ["물 마시기", "물 마시기", "물 마시기"].forEach { | ||||||||||||||||||||||||||||||||||||||||||||
| let subRoutineTitleLabel = UILabel() | ||||||||||||||||||||||||||||||||||||||||||||
| subRoutineTitleLabel.text = "• \($0)" | ||||||||||||||||||||||||||||||||||||||||||||
| subRoutineTitleLabel.font = BitnagilFont(style: .body2, weight: .medium).font | ||||||||||||||||||||||||||||||||||||||||||||
| subRoutineTitleLabel.textColor = BitnagilColor.gray40 | ||||||||||||||||||||||||||||||||||||||||||||
| subRoutineTitleLabel.snp.makeConstraints { make in | ||||||||||||||||||||||||||||||||||||||||||||
| make.height.equalTo(Layout.subRoutineLabelHeight) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| subRoutineStackView.addArrangedSubview(subRoutineTitleLabel) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+81
to
+90
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 반복 구간에서도 동일한 제약 설치 순서 문제 발생 각 subRoutineTitleLabel도 UIStackView에 추가하기 전에 제약을 설치하고 있습니다. 순서를 바꾸세요. 또한 더미 텍스트는 다음 PR에서 실제 데이터 바인딩으로 교체 예정이므로 TODO 표기 권장합니다. 수정 예시는 다음과 같습니다. - ["물 마시기", "물 마시기", "물 마시기"].forEach {
+ // TODO(T3-167-followup): 실제 서버 v2 연동 시 바인딩된 세부 루틴 목록으로 교체
+ ["물 마시기", "물 마시기", "물 마시기"].forEach {
let subRoutineTitleLabel = UILabel()
subRoutineTitleLabel.text = "• \($0)"
subRoutineTitleLabel.font = BitnagilFont(style: .body2, weight: .medium).font
subRoutineTitleLabel.textColor = BitnagilColor.gray40
- subRoutineTitleLabel.snp.makeConstraints { make in
- make.height.equalTo(Layout.subRoutineLabelHeight)
- }
- subRoutineStackView.addArrangedSubview(subRoutineTitleLabel)
+ subRoutineStackView.addArrangedSubview(subRoutineTitleLabel)
+ subRoutineTitleLabel.snp.makeConstraints { make in
+ make.height.equalTo(Layout.subRoutineLabelHeight)
+ }
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| private func configureLayout() { | ||||||||||||||||||||||||||||||||||||||||||||
| [categoryIconView, titleLabel].forEach { | ||||||||||||||||||||||||||||||||||||||||||||
| headerInfoStackView.addArrangedSubview($0) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| addSubview(headerInfoStackView) | ||||||||||||||||||||||||||||||||||||||||||||
| addSubview(plusButton) | ||||||||||||||||||||||||||||||||||||||||||||
| addSubview(grayLine) | ||||||||||||||||||||||||||||||||||||||||||||
| addSubview(subRoutineStackView) | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| categoryIconView.snp.makeConstraints { make in | ||||||||||||||||||||||||||||||||||||||||||||
| make.size.equalTo(Layout.categoryIconSize) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| headerInfoStackView.snp.makeConstraints { make in | ||||||||||||||||||||||||||||||||||||||||||||
| make.top.equalToSuperview().offset(Layout.headerInfoStackViewTopSpacing) | ||||||||||||||||||||||||||||||||||||||||||||
| make.leading.equalToSuperview().offset(Layout.horizontalMargin) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| plusButton.snp.makeConstraints { make in | ||||||||||||||||||||||||||||||||||||||||||||
| make.top.equalToSuperview().offset(Layout.plusButtonTopSpacing) | ||||||||||||||||||||||||||||||||||||||||||||
| make.trailing.equalToSuperview().inset(Layout.plusButtonTrailingSpacing) | ||||||||||||||||||||||||||||||||||||||||||||
| make.size.equalTo(Layout.plusButtonSize) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| grayLine.snp.makeConstraints { make in | ||||||||||||||||||||||||||||||||||||||||||||
| make.top.equalTo(headerInfoStackView.snp.bottom).offset(Layout.grayLineTopSpacing) | ||||||||||||||||||||||||||||||||||||||||||||
| make.leading.equalToSuperview().offset(Layout.horizontalMargin) | ||||||||||||||||||||||||||||||||||||||||||||
| make.trailing.equalToSuperview().inset(Layout.horizontalMargin) | ||||||||||||||||||||||||||||||||||||||||||||
| make.height.equalTo(Layout.grayLineHeight) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| subRoutineStackView.snp.makeConstraints { make in | ||||||||||||||||||||||||||||||||||||||||||||
| make.top.equalTo(grayLine.snp.bottom).offset(Layout.subRoutineStackViewTopSpacing) | ||||||||||||||||||||||||||||||||||||||||||||
| make.leading.equalToSuperview().offset(Layout.horizontalMargin) | ||||||||||||||||||||||||||||||||||||||||||||
| make.trailing.equalToSuperview().inset(Layout.horizontalMargin) | ||||||||||||||||||||||||||||||||||||||||||||
| make.bottom.equalToSuperview().inset(Layout.subRoutineStackViewBottomSpacing) | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| fileprivate class RoutineCategoryIcon: UIView { | ||||||||||||||||||||||||||||||||||||||||||||
| private enum Layout { | ||||||||||||||||||||||||||||||||||||||||||||
| static let cornerRadius: CGFloat = 3.76 | ||||||||||||||||||||||||||||||||||||||||||||
| static let iconSize: CGFloat = 24 | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| private let routineCategoryIcon = UIImageView() | ||||||||||||||||||||||||||||||||||||||||||||
| private let routineCategory: RoutineCategoryType | ||||||||||||||||||||||||||||||||||||||||||||
| init(routineCategory: RoutineCategoryType) { | ||||||||||||||||||||||||||||||||||||||||||||
| self.routineCategory = routineCategory | ||||||||||||||||||||||||||||||||||||||||||||
| super.init(frame: .zero) | ||||||||||||||||||||||||||||||||||||||||||||
| configureAttribute() | ||||||||||||||||||||||||||||||||||||||||||||
| configureLayout() | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| required init?(coder: NSCoder) { | ||||||||||||||||||||||||||||||||||||||||||||
| fatalError("init(coder:) has not been implemented") | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| private func configureAttribute() { | ||||||||||||||||||||||||||||||||||||||||||||
| layer.masksToBounds = true | ||||||||||||||||||||||||||||||||||||||||||||
| layer.cornerRadius = Layout.cornerRadius | ||||||||||||||||||||||||||||||||||||||||||||
| backgroundColor = routineCategory.iconBackgroundColor ?? BitnagilColor.yellow10 | ||||||||||||||||||||||||||||||||||||||||||||
| routineCategoryIcon.image = routineCategory.iconImage ?? BitnagilIcon.shineIcon | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| private func configureLayout() { | ||||||||||||||||||||||||||||||||||||||||||||
| addSubview(routineCategoryIcon) | ||||||||||||||||||||||||||||||||||||||||||||
| routineCategoryIcon.snp.makeConstraints { make in | ||||||||||||||||||||||||||||||||||||||||||||
| make.size.equalTo(Layout.iconSize) | ||||||||||||||||||||||||||||||||||||||||||||
| make.center.equalToSuperview() | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -5,6 +5,7 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Created by 최정인 on 7/24/25. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import SnapKit | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import UIKit | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| final class SelectableItemCell: UITableViewCell { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -28,7 +29,7 @@ final class SelectableItemCell: UITableViewCell { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private func configureAttribute() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| titleLabel.font = BitnagilFont(style: .body1, weight: .regular).font | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| titleLabel.textColor = .black | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| titleLabel.textColor = BitnagilColor.gray10 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let checkImage = BitnagilIcon.checkIcon? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .resizeAspectFit(to: CGSize(width: Layout.checkIconSize, height: Layout.checkIconSize))? | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -54,7 +55,24 @@ final class SelectableItemCell: UITableViewCell { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func configureCell(item: SelectableItem, isSelected: Bool) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| titleLabel.text = item.title | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checkIcon.isHidden = !isSelected | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| guard let displayName = item.displayName else { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| titleLabel.text = item.description | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let attributedString = NSMutableAttributedString(string: item.description) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| attributedString.addAttribute( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .font, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| value: BitnagilFont(style: .body1, weight: .regular).font, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| range: NSRange(location: 0, length: item.description.count)) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if let range = item.description.range(of: displayName) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let nsRange = NSRange(range, in: item.description) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| attributedString.addAttributes([ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| .font: BitnagilFont(style: .body1, weight: .semiBold).font | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ], range: nsRange) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| titleLabel.attributedText = attributedString | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+60
to
77
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. String.count로 NSRange 구성 → 크래시 가능성, 그리고 타이포그래피 속성 누락
아래와 같이 수정하면 안전한 범위 계산과 디자인 토큰 일관 적용을 동시에 해결할 수 있습니다. - guard let displayName = item.displayName else {
- titleLabel.text = item.description
- return
- }
- let attributedString = NSMutableAttributedString(string: item.description)
- attributedString.addAttribute(
- .font,
- value: BitnagilFont(style: .body1, weight: .regular).font,
- range: NSRange(location: 0, length: item.description.count))
-
- if let range = item.description.range(of: displayName) {
- let nsRange = NSRange(range, in: item.description)
- attributedString.addAttributes([
- .font: BitnagilFont(style: .body1, weight: .semiBold).font
- ], range: nsRange)
- }
- titleLabel.attributedText = attributedString
+ // 기본 타이포그래피(라인하이트/커닝 포함)를 BitnagilFont 토큰으로 구성
+ let baseFont = BitnagilFont(style: .body1, weight: .regular)
+ let attributedString = NSMutableAttributedString(
+ attributedString: baseFont.attributedString(text: item.description)
+ )
+
+ // displayName이 존재하면 해당 구간만 강조(세미볼드)
+ if let displayName = item.displayName,
+ let range = item.description.range(of: displayName) {
+ let nsRange = NSRange(range, in: item.description) // UTF-16 안전 변환
+ attributedString.addAttributes([
+ .font: BitnagilFont(style: .body1, weight: .semiBold).font
+ ], range: nsRange)
+ }
+ titleLabel.attributedText = attributedString📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
UIStackView에 추가되기 전에 제약을 설치하고 있어 런타임 크래시 위험이 있습니다.
SnapKit 제약은 공통 슈퍼뷰가 있어야 설치 가능합니다. subRoutineLabel을 arrangedSubview로 추가한 뒤 제약을 설정하세요.
아래와 같이 순서를 교체해 주세요.
📝 Committable suggestion
🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
superView에 접근하는 것은 아니라 괜찮을 것 같은데, 문제가 될까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
Length of output: 6313
🌐 Web query:
💡 Result:
SnapKit requires that constraints are made between views that share a common superview in the hierarchy, and for views within a UIStackView, constraints should not typically reference the stackView itself as superview; instead, you should constrain subviews within the stack to their own content, or use container views if more control is needed[3][4][7][1]. When using
UIStackView’saddArrangedSubviewmethod, the order you add subviews determines their display order, and eachaddArrangedSubviewalso makes the view a subview of the stack view—there’s no extra step needed[5][2][8].Context and Details:
SnapKit Superview Requirement:
SnapKit creates constraints between views, usually to their superview, using
makeConstraintsorequalToSuperview()syntax[4][7][1].Example:
The constrained view must already be in its superview's hierarchy before constraints are defined; otherwise, layout errors may occur[4][7][10].
UIStackView and Constraints:
UIStackView manages its arrangedSubviews’ layout automatically. If you directly constrain an arrangedSubview’s edges to the stackView (e.g.
make.left.right.equalToSuperview()inside the stackView’s arrangedSubview), only certain constraints will work; others may be ignored or conflict, because the stackView itself only manages the placement of arrangedSubviews and not their internal layout[3][5].Example (from [3]):
arrangedSubviews and Hierarchy:
When you use
addArrangedSubview(view)orinsertArrangedSubview(view, at:), UIKit automatically adds the view as a subview of the stackView (i.e., you do not need to calladdSubviewyourself)[5].The order in which you call
addArrangedSubvieworinsertArrangedSubviewdetermines how the views appear in the stack (top-to-bottom or left-to-right depending on stack axis)[2][5][8].Removal:
stackView.removeArrangedSubview(view)(removes layout involvement, but not from the view hierarchy)[5].view.removeFromSuperview()[5].Constraint Pitfalls:
make.left.right.equalToSuperview()) may not always work as expected, due to how UIStackView manages the frames of its arrangedSubviews[3].Summary Table: SnapKit + UIStackView Behavior
In summary:
addArrangedSubviewfor display order[5][2].Citations:
@taipaise 좋은 질문입니다! 기술적으로 살펴보면 다음과 같습니다:
현재 코드가 당장 크래시를 일으키지 않을 수 있는 이유:
make.height.equalTo(Layout.subRoutineLabelHeight)는 다른 뷰를 참조하지 않는 intrinsic constraint입니다하지만 여전히 문제가 될 수 있는 이유:
권장하는 순서:
현재 코드가 동작할 가능성이 높지만, 베스트 프랙티스를 따르는 것이 더 안전하고 유지보수에 좋습니다. 어떻게 생각하시나요?
✏️ Learnings added