-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] 내리기, 좋아요, 댓글 UIButton 및 게시물, 댓글 CollectionViewCell 컴포넌트 작성 #121
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
Conversation
JinUng41
left a comment
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.
상당히 많은 내용임에도 불구하고, 전부 구현하셨다니 대단하십니다! 👍👍
특히 셀의 상태 정의와 구현함에 있어 많은 고민을 하셨을 것 같고, 실제로 코드에서도 느껴졌어요.
코드가 일관도 있게 작성되어 이해하기에 수월했습니다.
코멘트는 천천히 확인해주세요. 고생하셨습니다!
| /// 사용자의 투명도를 관리하는 구조체. | ||
| /// 서버에서는 0~-100 사이의 정수로 전달되며, 앱에서는 이를 투명도 백분율과 실제 UI에 적용할 alpha 값으로 변환합니다. | ||
| /// | ||
| /// 사용 예시: | ||
| /// ```swift | ||
| /// // 서버에서 받은 값으로 초기화 | ||
| /// let opacity = Opacity(value: -30) | ||
| /// | ||
| /// // 화면에 표시할 백분율 값 (70%) | ||
| /// label.text = "\(opacity.displayedValue)%" | ||
| /// | ||
| /// // 실제 UI 요소에 적용할 alpha 값 (0.7) | ||
| /// view.alpha = CGFloat(opacity.alpha) | ||
| /// | ||
| /// // 투명도 감소 (신고 등으로 인해) | ||
| /// var userOpacity = Opacity(value: -10) | ||
| /// userOpacity.reduce() // value가 -11로 변경됨 | ||
| /// ``` |
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.
좋은 문서 작성이네요! 👍
|
|
||
| assert(hexFormatted.count == 6, "유효하지 않은 HEX 코드입니다.") |
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.
assert를 사용하신 이유가 있을까요?
|
|
||
| private func setupView() { | ||
| addSubviews( |
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.
UICollectionViewCell은 contentView에 addSubview를 해야 한다고 공식 문서에서 설명하더라구요.
참고해 주세요!
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.
수정했습니다! UIView랑 착각하고 있었네요 😭
| guard let profileURL = info.author.profileURL, | ||
| let fanTeam = info.author.fanTeam, | ||
| let createdDate = info.createdDate else { | ||
| return | ||
| } |
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.
else문 개행 부탁드립니다!
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.
반영했습니다!
| switch postType { | ||
| case .mine: | ||
| ghostButton.isHidden = true | ||
| case .others: | ||
| ghostButton.isHidden = false | ||
| } | ||
|
|
||
| switch commentType { | ||
| case .ripple: | ||
| contentLabel.attributedText = info.text.pretendardString(with: .body4) | ||
| commentButton.isHidden = false | ||
| case .reply: | ||
| infoView.snp.updateConstraints { | ||
| $0.leading.equalToSuperview().offset(36) | ||
| } | ||
|
|
||
| commentButton.isHidden = true | ||
| } | ||
|
|
||
| switch info.status { | ||
| case .normal: | ||
| ghostCell(opacity: info.opacity.alpha) | ||
| ghostButton.configureButton(type: .small, status: .normal) | ||
| case .ghost: | ||
| ghostCell(opacity: 0.15) | ||
| ghostButton.configureButton(type: .large, status: .disabled) | ||
| case .blind: | ||
| ghostCell(opacity: info.opacity.alpha) | ||
| contentLabel.removeFromSuperview() | ||
| addSubview(blindImageView) | ||
|
|
||
| blindImageView.snp.makeConstraints { | ||
| $0.top.equalTo(infoView.snp.bottom).offset(12) | ||
| $0.leading.equalTo(likeButton) | ||
| $0.trailing.equalTo(ghostButton) | ||
| $0.bottom.equalTo(ghostButton.snp.top).offset(-12) | ||
| $0.adjustedHeightEqualTo(50) | ||
| } | ||
|
|
||
| ghostButton.configureButton(type: .small, status: .normal) | ||
| } | ||
|
|
||
| contentLabel.attributedText = info.text.pretendardString(with: .body4) | ||
|
|
||
| infoView.configureView( | ||
| userProfileURL: profileURL, | ||
| userName: info.author.nickname, | ||
| userFanTeam: fanTeam, | ||
| opacity: info.opacity.displayedValue, | ||
| createdDate: createdDate, | ||
| postType: .comment | ||
| ) | ||
| commentButton.configureButton(commentCount: 0, type: .comment) | ||
| likeButton.configureButton( | ||
| isLiked: info.like.status, | ||
| likeCount: info.like.count, | ||
| postType: .comment | ||
| ) | ||
| } |
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.
configureCell 메서드의 내용이 많아졌네요!
메서드로 각각의 아이템들에 대해서 설정하는 메서드로 분리하고 이를 configureCell에서 호출하는 것은 어떠실까요?
메서드의 분리를 통해 작은 일만 담당하게 하여, 가독성을 향상 시킬 수 있을 것 같아요.
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.
switch 문을 기준으로 메서드 분리를 시도해보았습니다! 좋은 리뷰 감사드립니다 🙇♀️
configurePostType(postType: postType)
configureCommentType(info: info, commentType: commentType)
configurePostStatus(info: info)| /// 댓글 타입을 정의하는 열거형. | ||
| /// | ||
| /// - `ripple`: 게시글에 직접 달린 댓글 | ||
| /// - `reply`: 다른 댓글에 달린 답글 | ||
| enum CommentType { | ||
| case ripple | ||
| case reply | ||
| } |
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.
이 셀 이외에 CommentType을 사용하지 않는다면, CommentCollectionViewCell의 중첩 타입도 고려해 볼 수 있을 것 같아요.
중첩 타입으로 구현함으로써, 응집도를 높이고 의도를 명확히 표현하는 것이죠.
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.
좋은 것 같아요 !! 반영했습니다.
그러면 추가적으로 의문이 드는 게 여러 클래스에서 쓰이는 AuthorType Enum이 ContentCollectionViewCell에 있는데 따로 분리하는 게 나을까요?
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.
그렇다면, 분리하는 것이 맞다고 생각합니다!
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.
반영했습니다!
|
|
||
| configuration.image = image.withConfiguration(UIImage.SymbolConfiguration(pointSize: 24)) | ||
| configuration.imagePadding = 4 | ||
| configuration.attributedTitle = String(likeCount).pretendardString(with: .caption1) |
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.
'좋아요 수'에 대해서 AttributedString을 적용해야 할까 하는 생각이 들었습니다.
단순히 1 이상의 숫자를 보여줄텐데 자간과 행간을 구현해야 할까 하는 생각 말이죠.
무조건 AttributedString을 적용하는 것보다 단순한 것은 폰트만 적용해도 된다는 생각을 저도 깨닫게 되었네요.
감사합니다!
| @objc func likeButtonDidTap() { | ||
| isLiked ? (likeCount -= 1) : (likeCount += 1) | ||
| isLiked.toggle() | ||
|
|
||
| configureButton(isLiked: isLiked, likeCount: likeCount, postType: postType) | ||
| } |
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.
내부적으로 좋아요 상태와 수를 변경하고 있지만,
만약 네트워크 작업이 개입됐을 때를 고려하면 좋을 것 같아요.
일반적으로는 성공하겠지만, 만약 좋아요 API 요청을 했을 때 실패한다면 이를 뷰에서 반영하면 안될수도 있기 때문이죠.
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.
저도 구현하면서 그런 생각이 들어서 외부에서 오버라이딩해서 추가적으로 구현해 쓸 수 있으면 좋을 것 같다는 생각을 했어요.
이 부분은 근데 좋아요 버튼이 눌렸을 때 무조건 실행되어야 하는 부분이라 남겨두는 것도 좋을 것 같다는 생각이 듭니다...! 해당 부분을 구현하게 되었을 때 한번 같이 논의해보면 좋을 것 같은데 어떠신가요 ??!
|
|
||
| // MARK: - Extension | ||
|
|
||
| extension GhostButton { | ||
| /// 내리기 버튼 구성 메서드 | ||
| /// - Parameters: | ||
| /// - type: 버튼 크기 타입 (.large 또는 .small) | ||
| /// - status: 버튼 상태 (.normal 또는 .disabled) | ||
| func configureButton(type: GhostButtonType, status: GhostButtonStatus) { | ||
| var configuration = UIButton.Configuration.filled() | ||
| self.roundCorners([.all], radius: 16) | ||
| self.clipsToBounds = true | ||
|
|
||
| switch type { | ||
| case .large: | ||
| configuration.attributedTitle = "내리기".pretendardString(with: .caption3) | ||
| configuration.imagePadding = 4 | ||
| configuration.imagePlacement = .leading | ||
| configuration.contentInsets = NSDirectionalEdgeInsets(top: 6, leading: 10, bottom: 6, trailing: 10) | ||
| case .small: | ||
| configuration.contentInsets = NSDirectionalEdgeInsets(top: 8, leading: 8, bottom: 8, trailing: 8) | ||
| } | ||
|
|
||
| switch status { | ||
| case .normal: | ||
| configuration.attributedTitle?.foregroundColor = UIColor("556480") | ||
| configuration.image = .icGhostDefault.withTintColor(UIColor("556480")) | ||
| configuration.baseBackgroundColor = UIColor("DDE4F1") | ||
| self.isUserInteractionEnabled = true | ||
| case .disabled: | ||
| configuration.attributedTitle?.foregroundColor = UIColor("AEAEAE") | ||
| configuration.image = .icGhostDefault.withTintColor(.gray500) | ||
| configuration.baseBackgroundColor = UIColor("F7F7F7") | ||
| self.isUserInteractionEnabled = false | ||
| } | ||
|
|
||
| self.configuration = configuration | ||
| setupConstraint(type: type) | ||
| } | ||
| } |
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.
GhostButton의 경우, UIButton 익스텐션으로도 구현이 가능해 보입니다.
별도의 클래스를 선언했을 때의 이점이 잘 와닿지는 않은 것 같아요.
만약 GhostButton을 UIButton과 별도의 객체로 바라보겠다면, 별도의 생성자를 통해 생성이 가능한 객체로 구현하면 어떨까 싶은데요.
어떻게 생각하실까요?
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.
저도 따로 추가되는 프로퍼티나 컴포넌트가 없다는 점에서 고민을 많이 했었는데 결론적으로 UIButton의 익스텐션으로 작성했을 때 너무 개방적(?)으로 작성되는 느낌이라 해당 방법으로 구현하지 않았던 것 같습니다.
글로 설명하기가 어려운데 UIButton의 익스텐션으로 작성하게 되면 모든 버튼에서 .configureGhostButton() 형식으로 접근이 가능하다는 게 옳지 않다고 생각했어요. 이 의견에 대해서는 어떻게 생각하시는지 궁금합니닷!!
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.
타입 또는 상태를 생성자에서 초기 설정하고, 메서드를 통해 변경하는 방향도 좋을 것 같아요.
| /// 버튼이 속한 게시물 타입 (게시글/댓글) | ||
| private var type: PostType = .content |
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.
커스텀 클래스이기 때문에, 별도의 생성자로 강제하는 방법도 좋아보입니다!
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.
생성자를 강제했을 때 프로퍼티 선언 시 무조건 타입을 지정해줘야 해서 따로 선언하지 않았는데 PostType의 경우 해당 방법을 이용하는 것도 좋은 것 같습니다 좋은 의견 감사합니다 🙇♀️ 반영해두었습니다 !!
JinUng41
left a comment
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.
고생스기루 LGTM 👍
| private var type: PostType | ||
|
|
||
| // MARK: - LifeCycle | ||
|
|
||
| override init(frame: CGRect) { | ||
| super.init(frame: frame) | ||
| init(type: PostType) { |
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.
postType 변수가 변할 여지가 있나요?
[Feat] 내리기, 좋아요, 댓글 UIButton 및 게시물, 댓글 CollectionViewCell 컴포넌트 작성
👻 PULL REQUEST
📄 작업 내용
UIButton컴포넌트 및 게시물, 댓글CollectionViewCell컴포넌트를 작성했어요.UIColor익스텐션을 작성했어요.alpha를 이용한 게시글 투명도 적용과 화면에 보여지는 투명도를 표시하기 위해Opacity구조체를 수정했어요.💻 주요 코드 설명
alpha를 이용한 투명도 반영을 위한Opacity구조체 수정alpha값이 0.0에서 1.0 사이 값을 가져야 하기 때문에 그에 맞춰Opacity구조체를 수정했습니다.value에 저장되며, 사용자에게 표시되는 값(15~100)은displayedValue에 저장됩니다.alpha를 적용하기 위해 연산 프로퍼티alpha를 선언해 사용했습니다. 입력된 value 값을 이용해 사용자에게 보여지는 투명도 값을 계산해 리턴합니다.👀 기타 더 이야기해볼 점
kill cell
Comment의 네이밍을CommentInfo로 변경해 보았습니다.ContentInfo의 네이밍과 맞지 않는 것 같아서 변경했는데 의견 말씀해주시면 반영 후 머지하도록 하겠습니다!🔗 연결된 이슈