Skip to content
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

[GWL-13] 운동 선택 화면 UI 구현 #59

Merged
merged 8 commits into from
Nov 18, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
//
// ExerciseCardCell.swift
// RecordFeature
//
// Created by MaraMincho on 11/16/23.
// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved.
//

import DesignSystem
import UIKit

// MARK: - ExerciseCardCell

class ExerciseCardCell: UICollectionViewCell {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3

상속받지 않는 Cell 이라면 final 키워드를 붙여줘서 Static하게 동작하도록 만들어도 좋을것 같아요~

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3

이름 관련해서도 승현님이 이전 PR에 Workout으로 올려주셔서 저도 Workout으로 진행하고 있습니다!
혹시 괜찮으시다면 �Excercise -> Workout으로 변경해주실 수 있을까요?

이 부분을 미리 협의 했어야됐는데요 ㅠㅠ

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그렇네요..ㅎㅎ 다음에는 같은 Flow가 있을 때 이름 맞춰 가요 :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 확인했습니다. 네이밍 바꾸겠습니다!

static let identifier = "ExerciseCardCell"

override init(frame: CGRect) {
super.init(frame: frame)
makeShaodwAndRounded()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p1: 메서드에 오타가 났습니다. ㅎㅎ

Suggested change
makeShaodwAndRounded()
makeShadowAndRounded()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다~

backgroundColor = .white
setupConstraints()
}

override var isSelected: Bool {
didSet {
if isSelected {
makeSelectUI()
} else {
makeDeslectUI()
}
}
}

private let exerciseIconDescriptionLagel: UILabel = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p1: 여기도 오타났어용

Suggested change
private let exerciseIconDescriptionLagel: UILabel = {
private let exerciseIconDescriptionLabel: UILabel = {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다~

let label = UILabel()
label.font = UIFont.preferredFont(forTextStyle: .title3)
label.textAlignment = .center
label.text = "달리기에용"
label.contentMode = .scaleAspectFit

label.translatesAutoresizingMaskIntoConstraints = false
return label
}()

private let exerciseIcon: UIImageView = {
let config = UIImage.SymbolConfiguration(font: .systemFont(ofSize: 120))
let icon = UIImage(systemName: "figure.run", withConfiguration: config)
let imageView = UIImageView(image: icon)
imageView.contentMode = .scaleAspectFit
imageView.tintColor = DesignSystemColor.primaryText

imageView.translatesAutoresizingMaskIntoConstraints = false
return imageView
}()

required init?(coder: NSCoder) {
super.init(coder: coder)
}
}

private extension ExerciseCardCell {
func setupConstraints() {
contentView.addSubview(exerciseIconDescriptionLagel)
exerciseIconDescriptionLagel.bottomAnchor
.constraint(equalTo: contentView.bottomAnchor, constant: -12).isActive = true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p2: 메서드명은 Constraints를 설정하는 역할을 담당하는 것으로 보이는데요. view의 레이아웃을 설정하는 작업은 별도의 메서드로 빼는 것도 좋아보입니다.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

음 예전부터 해왔던 생각인데, addSubview코드와 constraints담는 코드를 분리하는 경우가 종종 있었습니다.

뭐가 표준인지는 모르겠지만, hierarchy가 함수 하나로 잘 보여서 보통 저는 함수 하나로 선언하고는 합니다.

혹시 다른분들 의견 궁금합니다. 승현님은 따로 Func만드는거 확인했습니다.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

표준이기보다는 선호에 가까운 것 같습니다.
만약 하나의 함수로 처리한다면 저는 setupViewHierarchies 로 적을 것 같아요. 그러면 뷰의 계층을 등록하고 제약조건을 설정하는 작업까지 담당하는 것을 바로 알 수 있을 것 같습니다 :)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다!

exerciseIconDescriptionLagel.leadingAnchor
.constraint(equalTo: contentView.leadingAnchor, constant: 0).isActive = true
exerciseIconDescriptionLagel.trailingAnchor
.constraint(equalTo: contentView.trailingAnchor, constant: 0).isActive = true

contentView.addSubview(exerciseIcon)
exerciseIcon.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 5).isActive = true
exerciseIcon.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -5).isActive = true
exerciseIcon.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5).isActive = true
exerciseIcon.bottomAnchor.constraint(equalTo: exerciseIconDescriptionLagel.topAnchor, constant: -15).isActive = true
}

func makeShaodwAndRounded() {
let radius: CGFloat = 10
contentView.layer.cornerRadius = radius
contentView.layer.borderWidth = 1
contentView.layer.borderColor = UIColor.clear.cgColor
contentView.layer.masksToBounds = true

layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0, height: 1.0)
layer.shadowRadius = 2.0
layer.shadowOpacity = 0.5
layer.masksToBounds = false
layer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: radius).cgPath
layer.cornerRadius = radius
}

func makeSelectUI() {
exerciseIcon.tintColor = DesignSystemColor.main03
exerciseIcon.makeShadow()
exerciseIconDescriptionLagel.textColor = DesignSystemColor.main03
exerciseIconDescriptionLagel.font = .preferredFont(forTextStyle: .title3, with: .traitBold)
}

func makeDeslectUI() {
exerciseIcon.tintColor = DesignSystemColor.primaryText
exerciseIcon.disableShadow()
exerciseIconDescriptionLagel.textColor = DesignSystemColor.primaryText
exerciseIconDescriptionLagel.font = .preferredFont(forTextStyle: .title3)
}
}

private extension UIImageView {
func makeShadow() {
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: -2, height: 2)
layer.shadowRadius = 2.0
layer.shadowOpacity = 0.3
layer.masksToBounds = false
}

func disableShadow() {
layer.shadowOpacity = 0
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//
// ExerciseEnvironmentSetupViewController.swift
// RecordFeature
//
// Created by MaraMincho on 11/15/23.
// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved.
//

import DesignSystem
import UIKit

// MARK: - ExerciseEnvironmentSetupViewController

public final class ExerciseEnvironmentSetupViewController: UIViewController {
public init() {
super.init(nibName: nil, bundle: nil)
}

public required init?(coder: NSCoder) {
super.init(coder: coder)
}

override public func viewDidLoad() {
super.viewDidLoad()
setup()
}

override public func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)

insertTempSource()
}

lazy var contentNAV: UINavigationController = {
let nav = UINavigationController(rootViewController: exerciseSelectView)

return nav
}()

private let exerciseSelectView = ExerciseSelectViewController()

private let pageControl: GWPageControl = {
let pageControl = GWPageControl(count: Const.countOfPage)

pageControl.translatesAutoresizingMaskIntoConstraints = false
return pageControl
}()

var dataSource: UICollectionViewDiffableDataSource<Int, UUID>!
var exerciseCardCollectionView: UICollectionView!
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3

강제언래핑은 지양해도 좋을 것 같다는 의견입니다~

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵.

실제 ViewModel 기능 구현 할 때 제거하도록 하겠습니다...!

}

private extension ExerciseEnvironmentSetupViewController {
func setup() {
view.backgroundColor = .systemBackground
setupConstraints()
exerciseCardCollectionView = exerciseSelectView.exerciseCardCollectionView

configureDataSource()
}

func configureDataSource() {
dataSource = .init(collectionView: exerciseCardCollectionView, cellProvider: { collectionView, indexPath, _ in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ExerciseCardCell.identifier, for: indexPath)
return cell
})
}

func insertTempSource() {
var snapshot = dataSource.snapshot()
snapshot.deleteAllItems()
snapshot.appendSections([0])
snapshot.appendItems([.init(), .init(), .init(), .init(), .init()])

dataSource.apply(snapshot)
}

func setupConstraints() {
let safeArea = view.safeAreaLayoutGuide

view.addSubview(pageControl)
pageControl.topAnchor.constraint(equalTo: safeArea.topAnchor, constant: 10).isActive = true
pageControl.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor, constant: 23).isActive = true
pageControl.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor, constant: -23).isActive = true
pageControl.heightAnchor.constraint(equalToConstant: 30).isActive = true

view.addSubview(contentNAV.view)
contentNAV.view.translatesAutoresizingMaskIntoConstraints = false
contentNAV.view.leadingAnchor.constraint(equalTo: safeArea.leadingAnchor).isActive = true
contentNAV.view.trailingAnchor.constraint(equalTo: safeArea.trailingAnchor).isActive = true
contentNAV.view.topAnchor.constraint(equalTo: pageControl.bottomAnchor).isActive = true
contentNAV.view.bottomAnchor.constraint(equalTo: safeArea.bottomAnchor).isActive = true
}

enum Const {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p3: 풀네임은 어떠신가요..? ㅎ

Suggested change
enum Const {
enum Constant {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

확인했습니다 ㅎㅎ

static let countOfPage = 2
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//
// ExerciseSelectViewController.swift
// RecordFeature
//
// Created by MaraMincho on 11/16/23.
// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved.
//

import DesignSystem
import UIKit

// MARK: - ExerciseSelectViewController

final class ExerciseSelectViewController: UIViewController {
override init(nibName _: String?, bundle _: Bundle?) {
super.init(nibName: nil, bundle: nil)
}

override func viewDidLoad() {
super.viewDidLoad()
setupConstraints()
navigationController?.setNavigationBarHidden(true, animated: false)
}

required init?(coder: NSCoder) {
super.init(coder: coder)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p2: 사용하지 않는다면 제거하는 편이 깔끔할 것 같아요.

Suggested change
override init(nibName _: String?, bundle _: Bundle?) {
super.init(nibName: nil, bundle: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
setupConstraints()
navigationController?.setNavigationBarHidden(true, animated: false)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
override func viewDidLoad() {
super.viewDidLoad()
setupConstraints()
navigationController?.setNavigationBarHidden(true, animated: false)
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 코드에서는, 둘이 들어갈 필요가 없어 보입니다.


private let exerciseSelectDescriptionLabel: UILabel = {
let label = UILabel()
label.font = .preferredFont(forTextStyle: .title1, with: .traitBold)
label.textAlignment = .left
label.text = "1. 운동을 선택하세요"

label.translatesAutoresizingMaskIntoConstraints = false
return label
}()

lazy var exerciseCardCollectionView: UICollectionView = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p3: private을 지정하지 않은 이유가 있나요? 외부에서 View를 접근해야하는 경우가 있는건지 궁금하네요.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 뷰컨트롤러 구조가 다음 입니다.
ExerciseEnvironmentSetupViewController -> NavigationController -> ExerciseSelectViewController

뷰컨 산하에 뷰컨이 있는 구조인데요, ExerciseSelectViewController의 CollectionView의 DataSource와 움직임을 상위 ExerciseEnvironmentSetupViewController로 위임시킬려고 했습니다. DiffableDataSource를 상위 뷰컨을 통해서 관리할려고 접근 제어자를 internal로 설정했습니다.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아 그러면 어쩔 수 없네요..답변 감사합니다.

let collectionView = UICollectionView(frame: .zero, collectionViewLayout: makeCollectionViewLayout())
collectionView.register(ExerciseCardCell.self, forCellWithReuseIdentifier: ExerciseCardCell.identifier)

collectionView.translatesAutoresizingMaskIntoConstraints = false
return collectionView
}()

private let nextButton: UIButton = {
let button = UIButton()
button.configurationUpdateHandler = UIButton.Configuration.main(label: "다음")

button.translatesAutoresizingMaskIntoConstraints = false
return button
}()
}

private extension ExerciseSelectViewController {
func makeCollectionViewLayout() -> UICollectionViewLayout {
let itemSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(0.5), heightDimension: .fractionalHeight(1))

let item = NSCollectionLayoutItem(layoutSize: itemSize)
item.contentInsets = .init(
top: Const.CellInset,
leading: Const.CellInset,
bottom: Const.CellInset,
trailing: Const.CellInset
)

let groupSize = NSCollectionLayoutSize(widthDimension: .fractionalWidth(1.0),
heightDimension: .fractionalWidth(0.55))
let group = NSCollectionLayoutGroup.horizontal(layoutSize: groupSize, subitems: [item])

let section = NSCollectionLayoutSection(group: group)

return UICollectionViewCompositionalLayout(section: section)
}

func setupConstraints() {
let safeArea = view.safeAreaLayoutGuide

view.addSubview(exerciseSelectDescriptionLabel)
exerciseSelectDescriptionLabel.topAnchor.constraint(equalTo: safeArea.topAnchor).isActive = true
exerciseSelectDescriptionLabel.leadingAnchor
.constraint(equalTo: safeArea.leadingAnchor, constant: ConstraintsGuideLine.value).isActive = true
exerciseSelectDescriptionLabel.trailingAnchor
.constraint(equalTo: safeArea.trailingAnchor, constant: -ConstraintsGuideLine.value).isActive = true

view.addSubview(exerciseCardCollectionView)
exerciseCardCollectionView.topAnchor
.constraint(equalTo: exerciseSelectDescriptionLabel.bottomAnchor, constant: 12).isActive = true
exerciseCardCollectionView.leadingAnchor
.constraint(equalTo: safeArea.leadingAnchor, constant: ConstraintsGuideLine.value).isActive = true
exerciseCardCollectionView.trailingAnchor
.constraint(equalTo: safeArea.trailingAnchor, constant: -ConstraintsGuideLine.value).isActive = true
exerciseCardCollectionView.bottomAnchor
.constraint(equalTo: view.bottomAnchor, constant: -15).isActive = true

view.addSubview(nextButton)
nextButton.leadingAnchor
.constraint(equalTo: safeArea.leadingAnchor, constant: ConstraintsGuideLine.value).isActive = true
nextButton.trailingAnchor
.constraint(equalTo: safeArea.trailingAnchor, constant: -ConstraintsGuideLine.value).isActive = true
nextButton.bottomAnchor
.constraint(equalTo: safeArea.bottomAnchor, constant: -28).isActive = true
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p3: NSLayoutConstraint를 사용해보는 것도 좋은 방법이 될 것 같아요 ㅎㅎ


enum Const {
static let CellInset: CGFloat = 5
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

p2: 변수명이 Pascal Case로 되어있습니다! SwiftFormat이 안잡아주나보네요 흠 ?

Suggested change
enum Const {
static let CellInset: CGFloat = 5
}
enum Const {
static let cellInset: CGFloat = 5
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

상수 관리를 PascalCase로 해서 상수인것을 자명하게 하는것도 방법이라는 말을 들어서 그렇게 사용한 것 같네요 ㅎㅎ

문제점은, 코드에서 PascalCase와 SnakeCase가 혼용해서 쓰인 것 같습니다.

따라서 상수 부분에 대해서 종표님과 이야기 하면 좋아보입니다.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// ConstraintsGuideLine.swift
// DesignSystem
//
// Created by MaraMincho on 11/18/23.
// Copyright © 2023 kr.codesquad.boostcamp8. All rights reserved.
//

import Foundation

public enum ConstraintsGuideLine {
public static let value: CGFloat = 23
}