diff --git a/DailyQuest/DailyQuest.xcodeproj/project.pbxproj b/DailyQuest/DailyQuest.xcodeproj/project.pbxproj index e85c3d3..def25fb 100644 --- a/DailyQuest/DailyQuest.xcodeproj/project.pbxproj +++ b/DailyQuest/DailyQuest.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 3413139F291E48A100E607E1 /* Realm in Frameworks */ = {isa = PBXBuildFile; productRef = 3413139E291E48A100E607E1 /* Realm */; }; 3449AD5B2922164B00B87619 /* Quest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3449AD5A2922164B00B87619 /* Quest.swift */; }; 3449AD5D2922197000B87619 /* User.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3449AD5C2922197000B87619 /* User.swift */; }; + 3449AD6029222B3900B87619 /* UserInfoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3449AD5F29222B3900B87619 /* UserInfoCell.swift */; }; 34ACC32D291DE9C000741371 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34ACC32C291DE9C000741371 /* AppDelegate.swift */; }; 34ACC32F291DE9C000741371 /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34ACC32E291DE9C000741371 /* SceneDelegate.swift */; }; 34ACC336291DE9C100741371 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 34ACC335291DE9C100741371 /* Assets.xcassets */; }; @@ -47,6 +48,7 @@ /* Begin PBXFileReference section */ 3449AD5A2922164B00B87619 /* Quest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Quest.swift; sourceTree = ""; }; 3449AD5C2922197000B87619 /* User.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = ""; }; + 3449AD5F29222B3900B87619 /* UserInfoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoCell.swift; sourceTree = ""; }; 34ACC329291DE9C000741371 /* DailyQuest.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DailyQuest.app; sourceTree = BUILT_PRODUCTS_DIR; }; 34ACC32C291DE9C000741371 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 34ACC32E291DE9C000741371 /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; }; @@ -106,6 +108,7 @@ 3449AD5E292219D600B87619 /* Common */ = { isa = PBXGroup; children = ( + 3449AD5F29222B3900B87619 /* UserInfoCell.swift */, ); path = Common; sourceTree = ""; @@ -344,6 +347,7 @@ 3449AD5D2922197000B87619 /* User.swift in Sources */, 34ACC32D291DE9C000741371 /* AppDelegate.swift in Sources */, 3449AD5B2922164B00B87619 /* Quest.swift in Sources */, + 3449AD6029222B3900B87619 /* UserInfoCell.swift in Sources */, 34ACC32F291DE9C000741371 /* SceneDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/DailyQuest/DailyQuest/Assets.xcassets/LightGray.colorset/Contents.json b/DailyQuest/DailyQuest/Assets.xcassets/LightGray.colorset/Contents.json new file mode 100644 index 0000000..b61f52a --- /dev/null +++ b/DailyQuest/DailyQuest/Assets.xcassets/LightGray.colorset/Contents.json @@ -0,0 +1,38 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0.859", + "green" : "0.859", + "red" : "0.859" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "srgb", + "components" : { + "alpha" : "1.000", + "blue" : "1.000", + "green" : "1.000", + "red" : "1.000" + } + }, + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/DailyQuest/DailyQuest/Domain/Entities/Quest.swift b/DailyQuest/DailyQuest/Domain/Entities/Quest.swift index 081ac7b..8b91da1 100644 --- a/DailyQuest/DailyQuest/Domain/Entities/Quest.swift +++ b/DailyQuest/DailyQuest/Domain/Entities/Quest.swift @@ -19,6 +19,13 @@ struct Quest { return currentCount == totalCount } + /** + 현재 목표달성량(currentCount)에 인자값만큼 더합니다. + Note. 현재 목표달성량은 전체량(totalCount)를 넘을 수 없습니다. + + - Parameters: + - value: 0보다 큰 정수값입니다. 기본값은 1입니다. + */ mutating func increaseCount(with value: Int=1) { guard currentCount + value <= totalCount else { self.currentCount = totalCount @@ -27,6 +34,13 @@ struct Quest { self.currentCount += value } + /** + 현재 목표달성량(currentCount)에 인자값만큼 뺍니다. + Note. 현재 목표달성량은 0보다 작을 수 없습니다. + + - Parameters: + - value: 0보다 큰 정수값입니다. 기본값은 1입니다. + */ mutating func decreaseCount(with value: Int=1) { guard currentCount - value >= 0 else { self.currentCount = 0 diff --git a/DailyQuest/DailyQuest/Presentation/Common/UserInfoCell.swift b/DailyQuest/DailyQuest/Presentation/Common/UserInfoCell.swift new file mode 100644 index 0000000..773694a --- /dev/null +++ b/DailyQuest/DailyQuest/Presentation/Common/UserInfoCell.swift @@ -0,0 +1,145 @@ +// +// UserInfoCell.swift +// DailyQuest +// +// Created by jinwoong Kim on 2022/11/14. +// + +import UIKit + +import SnapKit + +final class UserInfoCell: UITableViewCell { + /// dequeuResusable을 위한 아이덴티파이어입니다. + static let reuseIdentifier = "UserInfoCell" + + // MARK: - Components + /** + 이미지와 유저이름 레이블을 담기 위한 스택뷰입니다. + 수평 스택뷰이며, 중앙 정렬을 통해 각 아이템을 수직축 기준으로 중앙에 위치시켰습니다. + Note. Color를 assets에 임의로 등록하였습니다. 동료들이 동일한 이름으로 동일한 색상을 등록할 수도 있으므로, + 이는 회고시간에 반드시 언급해야 합니다. + */ + private lazy var container: UIStackView = { + let container = UIStackView() + container.axis = .horizontal + container.alignment = .center + container.backgroundColor = UIColor(named: "LightGray") + container.spacing = 10 + container.isLayoutMarginsRelativeArrangement = true + container.layoutMargins = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 0) + container.layer.cornerRadius = 15 + + return container + }() + + /** + 유저의 프로필 이미지가 들어갈 UIImageView입니다. + Note. 원형의 틀을 만들기 위해, render loop의 `layoutSubviews()`메서드를 오버라이드하였음에 유념하세요. + */ + private lazy var userImage: UIImageView = { + let userImage = UIImageView() + userImage.image = UIImage(systemName: "heart.fill") + userImage.clipsToBounds = true + userImage.backgroundColor = .white + + return userImage + }() + + /** + 유저의 닉네임이 들어갈 레이블입니다. + */ + private lazy var userName: UILabel = { + let userName = UILabel() + userName.text = " " + + return userName + }() + + // MARK: - Methods + override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { + super.init(style: style, reuseIdentifier: reuseIdentifier) + + configureUI() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func layoutSubviews() { + super.layoutSubviews() + userImage.layer.cornerRadius = userImage.frame.height / 2 + } + + /** + UI의 constraints를 설정하기 위한 메서드입니다. + constraints를 설정하기 전에, 해당 뷰를 먼저 add해야함을 유념하세요. + */ + private func configureUI() { + container.addArrangedSubview(userImage) + container.addArrangedSubview(userName) + addSubview(container) + + container.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + + userImage.snp.makeConstraints { make in + make.height.equalToSuperview().offset(-40) + make.width.equalTo(userImage.snp.height) + } + } + + /** + 인자로 받은 Entity User타입을 통해 그 정보를 기반으로 cell에 아이템을 넣습니다. + + - Parameters: + - user: User타입의 엔티티입니다. + */ + func setup(with user: User) { + userName.text = user.nickName + guard let image = UIImage(data: user.profile) else { return } + userImage.image = image + } +} + +/** + SwiftUI 프리뷰 기능을 사용하기 위한 코드들 입니다. + */ +#if canImport(SwiftUI) && DEBUG +import SwiftUI +struct UIViewPreview: UIViewRepresentable { + let view: View + + init(_ builder: @escaping () -> View) { + view = builder() + } + + // MARK: - UIViewRepresentable + + func makeUIView(context: Context) -> UIView { + return view + } + + func updateUIView(_ view: UIView, context: Context) { + view.setContentHuggingPriority(.defaultHigh, for: .horizontal) + view.setContentHuggingPriority(.defaultHigh, for: .vertical) + } +} +#endif + +#if canImport(SwiftUI) && DEBUG +import SwiftUI + +struct UserInfoCellPreview: PreviewProvider{ + static var previews: some View { + UIViewPreview { + let cell = UserInfoCell(frame: .zero) + cell.setup(with: User(uuid: UUID(), nickName: "jinwoong", profile: Data(), backgroundImage: Data(), description: "")) + return cell + } + .previewLayout(.fixed(width: 350, height: 80)) + } +} +#endif