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

박스오피스II [STEP 2] BMO, Serena #108

Open
wants to merge 24 commits into
base: ic_9_serena
Choose a base branch
from

Conversation

serena0720
Copy link

@GREENOVER
안녕하세요! 그린🌳
BMO🤖 & Serena🐷팀입니다!

박스오피스 프로젝트 II Step2 PR 보냅니다.

이번 리뷰도 잘 부탁드립니다!😆


구현한 내용

  • 메인화면의 모드를 ListCollectionView, IconCollectionView 중 변경 가능
  • 아이콘 모드의 경우에도 날짜변경 및 새로고침이 가능
  • UserDefaults에 화면 모드를 저장하여 앱 새로 시작 시 모드 유지 가능
  • Dynamic Type 적용 가능

주요 내용

  • UICollectionViewDiffableDataSource, UIToolBar, UserDefaults

고민한 부분

🔥 setCollectionViewLayout을 활용하여 애니메이션 효과

  • 모드 변경 시 순간적으로 컬렉션뷰의 레이아웃이 깨지는 현상이 있었습니다.
  • 그러던 와중 setCollectionViewLayout 메서드를 활용하면 이런 부분을 해결할 수 있다는 것을 알게되어 이 부분을 수정하였습니다.
    수정 전 수정 후

    수정 전 코드

    self.collectionView?.collectionViewLayout = self.createIconLayout()

    수정 후 코드

    self.collectionView?.setCollectionViewLayout(self.createIconLayout(), animated: true)

🔥 UINavigationController - UIToolbar 사용

  • 스탭2 요구사항에 따르면 컬렉션뷰 하단에 '화면 모드 변경' 버튼이 일정 영역을 차지하고 있습니다. 저희는 컬렉션뷰 아래에 해당 버튼을 위치시키기 위해서는 view가 다음과 같은 구조가 되어야 한다고 생각했습니다.
    view
    ├── contentView
    │   └── collectionView
    └── buttonView
        └── button
    
  • 그리고 화면에 맞게 제약사항을 주기 위해서는 아래와 같이 긴 코드를 작성해야 했습니다.
    ...
    let contentView = UIView()
    let buttonView = UIView()
    let button = UIButton()
    button.setTitle("화면 모드 변경", for: .normal)
    button.setTitleColor(.systemBlue, for: .normal)
    
    view.addSubview(contentView)
    view.addSubview(buttonView)
    buttonView.addSubview(button)
    
    contentView.translatesAutoresizingMaskIntoConstraints = false
    buttonView.translatesAutoresizingMaskIntoConstraints = false
    button.translatesAutoresizingMaskIntoConstraints = false
    
    NSLayoutConstraint.activate([
        contentView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
        contentView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
        contentView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
        contentView.bottomAnchor.constraint(equalTo: buttonView.topAnchor)
    ])
    
    NSLayoutConstraint.activate([
        buttonView.topAnchor.constraint(equalTo: button.topAnchor, constant: -8),
        buttonView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
        buttonView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
        buttonView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
    ])
    buttonView.backgroundColor = .systemGray6
    
    NSLayoutConstraint.activate([
        button.centerXAnchor.constraint(equalTo: view.safeAreaLayoutGuide.centerXAnchor),
        button.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
    ])
    
    let action = UIAction { _ in
        let sheet = UIAlertController(title: "화면모드변경", message: nil, preferredStyle: .actionSheet)
        sheet.addAction(UIAlertAction(title: "아이콘", style: .default))
        sheet.addAction(UIAlertAction(title: "취소", style: .cancel))
        self.present(sheet, animated: true)
    }
    
    button.addAction(action, for: .touchUpInside)
    ...
    contentView.addSubview(collectionView ?? UICollectionView())
    ...
  • 하지만 UINavigationController 문서를 보던 중 built-in toolbar가 있다는 것을 알게되었습니다. 해당 toolbarisToolbarHidden 프로퍼티를 false를 주는것만으로 toolbar를 노출시킬 수 있습니다.
  • 따라서 위와같은 긴 코드가 필요하지 않게 되었고 내장 기능을 활용하여 코드를 간략화 할 수 있었습니다.
    navigationController?.isToolbarHidden = false
    
    let modeButton = UIBarButtonItem(title: "화면 모드 변경", primaryAction: showSelector())
    let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil)
    
    let items = [flexibleSpace, modeButton, flexibleSpace]
    toolbarItems = items
    
    activityIndicatorView.center = view.center

🔥 UserDefaults + Enum을 이용한 ViewMode 변경

  • 저희는 현재 리스트 혹은 아이콘 보기 설정 값을 앱 재실행 시에도 유지시키고 싶었습니다. 해당 기능을 위해 UserDefaults를 사용하기로 했습니다.
  • 그리고 모드 관리를 위해 새로운 Enum 타입을 추가했습니다.
    enum ViewMode: String {
        case list
        case icon
        ...
    }
  • 하지만 UserDefaults에 값을 저장할 때 Enum타입을 넣을 수 없었기 때문에 extension으로 추가 작업을 해주었습니다.
    extension UserDefaults {
        private enum UserDefaultsKeys: String {
            case viewMode
        }
    
        var viewMode: String {
            get { string(forKey: UserDefaultsKeys.viewMode.rawValue) ?? ViewMode.list.rawValue }
            set { setValue(newValue, forKey: UserDefaultsKeys.viewMode.rawValue) }
        }
    }
  • 그리고 해당 내용을 읽을 수 있는 내용을 활용할 수 있는 코드를 모두 적용하여 매직 리터럴을 최대한 줄였습니다.
    final class BoxOfficeViewController: UIViewController {
        ...
        private var viewMode: ViewMode {
            return ViewMode(rawValue: UserDefaults.standard.viewMode) ?? ViewMode.list
        }
        ...
        private func showSelector() -> UIAction {
            let action = UIAction { _ in
                let sheet = UIAlertController(title: "화면모드변경", message: nil, preferredStyle: .actionSheet)
                sheet.addAction(self.createViewModeChangeAction())
                sheet.addAction(UIAlertAction(title: "취소", style: .cancel))
                self.present(sheet, animated: true)
            }
    
            return action
        }
        
        private func createViewModeChangeAction() -> UIAlertAction {
            let action = UIAlertAction(title: viewMode.anotherOption, style: .default, handler: { _ in
                switch self.viewMode {
                case .list:
                    self.collectionView?.setCollectionViewLayout(self.createIconLayout(), animated: true)
                    UserDefaults.standard.viewMode = ViewMode.icon.rawValue
                case .icon:
                    self.collectionView?.setCollectionViewLayout(self.createListLayout(), animated: true)
                    UserDefaults.standard.viewMode = ViewMode.list.rawValue
                }
    
                self.reloadSnapshot()
            })
    
            return action
        }
        ...
    }

참고자료

bubblecocoa and others added 22 commits August 11, 2023 10:32
setCalendarRange는 limitCalendarRange로 변경
@GREENOVER GREENOVER self-requested a review August 16, 2023 09:57
Copy link
Member

@GREENOVER GREENOVER left a comment

Choose a reason for hiding this comment

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

@serena0720 @bubblecocoa
이번에도 스텝 2 고생 많으셨습니다ㅎㅎ
저번부터 느끼지만 잘 구현해주셔서 크게 코멘트 드릴게 없네요 😁
이번에도 각 리스트, 아이콘 셀 들을 잘 잡아주시고 코드로 잘 구현해주셔서 코드적으로 수정될 부분은 보이지 않았습니다.
빠르게 다음 스텝을 위해 확인하는 정도만 거쳐도 좋을듯합니다!


칭찬 드리고 싶은 부분

  • 세심한 사용성 디테일까지 잡아주신점 아주 좋아요 💯
  • 각 셀을 잘 만들어 활용해주셨네요 💯

보완되면 좋을 부분

  • 현재 리스트 -> 아이콘으로 뷰모드가 전환될때 아이콘 셀들의 최상단이 아닌 3순위 정도부터 걸쳐져서 보이고 있는것 같아요!
    이 부분이 화면 전환 시 offset이 1,2 순위부터 최상단 보이도록 하면 어떨까요~?

정말 잘 구현해주셔서 어프로브 해두겠습니다!
필요하시면 바로 머지 요청해주세요 ㅎㅎ


import Foundation

extension UserDefaults {
Copy link
Member

Choose a reason for hiding this comment

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

오... 디테일한 부분까지 신경쓰셨군요 👍

case list
case icon

var anotherOption: String {
Copy link
Member

Choose a reason for hiding this comment

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

anotherOption이란 네이밍이 조금 어색해보이는데 탄생하게된 배경이 궁금합니다! 🙋🏻

Choose a reason for hiding this comment

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

7023922
해당부분 저희도 어색해하던 부분이라 좀 더 고민 후 수정했습니다 😄

}

private func configureUI() {
addSubview(contentStackView)
Copy link
Member

Choose a reason for hiding this comment

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

contentView.addSubview(contentStackView) 처럼 사용할때와 어떤 차이가 있을까요~?
만약 있다면 어떤 방법이 레이아웃 관리에 좀 더 도움이 될까요!? 🙋🏻

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants