Giter Club home page Giter Club logo

ios-project-manager's People

Contributors

yagom avatar yeahg-dev avatar

ios-project-manager's Issues

ProjectHeaderView의 bottom에만 border적용하는 방법

ProjectHeaderView의 bottom에 border 적용하기

스크린샷 2022-05-08 오후 4 08 08

UITableView에 sectionHeader와 Cell 사이의 seperator를 구현하기 위해서
ProjectHeaderView의 bottom에만 border를 그려주도록 구현하고자함

✅ 구현방법

ProjectHeaderView에 seperator가 될 sublayer를 추가

override func layoutSubviews() {
        super.layoutSubviews()
        let seperator = CALayer()
        seperator.frame = CGRect(x: 0, y: self.bounds.height - 0.5, width: self.bounds.width, height: 1)
        seperator.backgroundColor = UIColor.gray.cgColor
        self.layer.addSublayer(seperator)
    }

처음엔 override init(frame: CGRect)에서 위 코드를 구현했지만 적용이 되지 않음.
init이 호출되는 시점엔 self의 레이아웃이 결정되기 전이기 때문에 bounds가 없어서 적용이 되지 않는 다는 것을 알게됨.
따라서 layoutSubviews를 override해서 self의 bounds가 결정된 이후, sublayer를 설정하고 적용하도록 수정함.

STEP2-2 : 로컬DB, 클라우드DB 구현

STEP 2️⃣-2 : 로컬 DB, 클라우드 DB 구현

  • CoreData를 사용하는 ProjectLocalDataBase 구현
  • RemoteDataBase 프로토콜 정의 -> 로컬, 리모트의 공용 DB 인터페이스인 DataSource 프로토콜 구현
  • FirseStore API 사용하는 ProjectFirestoreBase 구현

STEP 2-1 : Refactor

STEP2️⃣ -1 : Refactor

  • 줄바꿈 컨벤션 통일
  • 모호한 네이밍 수정
  • 문자열들을 네임 스페이스로 관리
  • DetailViewController, CreatorViewController 공통화
  • Todo,Doing,DoneTableViewController의 공통 클래스 구현

ViewController의 view에 translatesAutoresizingMaskIntoConstraints = false가 적용이 안되는 이슈

⛔️ 문제 상황

  1. 뷰의 translatesAutoresizingMaskIntoConstraints를 false를 줌에도 오토레이아웃 제약과 충돌하는 이슈

image

  1. 마진으로 준 스택뷰와 뷰 사이의 제약(10)이 뷰에 반영되지 않는 이슈
    image

🧐 원인 분석

제약간 충돌 때문에 마진으로 준 제약(10)이 적용이 안된다고 판단했지만,
제약 충돌을 해결하고도 10이 반영이 되지 않는 것을 확인

뷰 디버거에서 보여지는 Popover의 사이즈보다 실제 화면에서 보여지는 뷰의 사이즈가 더 작은 것을 확인
스크린샷 2022-03-25 오후 6 45 14

🔨 해결 방법

1. 오토레이아웃 충돌

self.view의 넓이와 높이를 정해주는 제약을 없애고, self.preferredContentSize = CGSize(width: 300, height: 150)로 넓이 높이를 정의

  override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        self.preferredContentSize = CGSize(width: 300, height: 150)
    }
    
    // MARK: - Method
    private func configureViewHeirachry() {
        self.view.addSubview(contentStackView)
    }
    
    private func configureLayout() {
        NSLayoutConstraint.activate([
            contentStackView.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 30),
            contentStackView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
            contentStackView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
            contentStackView.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -30)
        ])
    }

2. 마진 미적용

스택뷰와 뷰간의 제약을 더 크게(30)을 줌으로서 마진이 적용되도록 수정

STEP2-1-3 : UI 구현

STEP 2️⃣-1 : 프로젝트 매니저 앱 설계 및 구현 (DB 미적용)

STEP 2️⃣-1-3 : UI 구현

  • 3개의 테이블 뷰를 담을 ProjectBoardViewController 구현
  • 프로젝트 상세보기(추가/수정) 화면 ProjectDetailViewController 구현
  • 네비게이션바의 + 버튼 터치시 프로젝트 추가 화면 표시 구현
  • ProjectPreviewTableViewCell 구현
    • Cell 사이의 spacing을 위한 Cell 사이즈 조정
    • Cell 그림자 효과 구현
  • ProjectTableViewHeaderView 구현
    • projectCountLabel 뱃지 형태로 구현
    • SectionHeader 업데이트 구현
  • projectTableView 의 섹션과 셀 사이의 seperator 구현
  • TodoTableViewController 구현, DiffableDataSource 구현
  • DoingTableViewController 구현, DiffableDataSource 구현
  • DoneTableViewController 구현, DiffableDataSource 구현
  • 셀 길게 터치시 진행 상태 옮기기 팝오버 구현
  • 셀 터치시 프로젝트 상세 화면 표시 구현
  • 셀 스와이프해서 프로젝트 삭제 기능 구현

keyboardWillShowNotification이 여러번 불리는 이슈

keyboardWillShowNotification이 여러번 불려서 노티에 대한 selector가 계속 호출되는 문제

keyboardWillShowNotification에대한 selector 메서드 keyboardWillShow(_:)return을 누를 때 도 호출됨.
키보드가림 현상을 보완하기위한 view의 bounds를 수정이 계속되어 view가 과도하게 위로 올라가는 버그 발생
May-08-2022 23-46-59

STEP 2-2 : Refactor

🌡 Bug Fix

  • 앱의 전체화면 잘리는 이슈해결
  • ProjectTableViewCell의 말줄임표 기능
  • DataSourceConfigViewController 뷰 제약 충돌
  • Done 프로젝트의 deadline 글자색 항상 검정색으로 표시되도록

🍎 Refactor

  • final 키워드 적용
  • 네임스페이스 구조 개편

✅ UI 개선

  • 구름 위치 수정(아이패드 프로에서 짤림)
  • 키보드 등장시 프로젝트 편집창 스크롤시켜서 TextField의 시야 확보
  • 프로젝트 편집창의 description TextView의 placeholder 구현
  • 프로젝트 편집창의 description TextView 글자수 1000자 제한 기능

UITableViewDiffableDataSource 사용시 Header를 업데이트 하지 못하는 이슈

문제 상황

UITableViewDiffableDataSource로 테이블 뷰의 dataSource를 구현했습니다.
tableView의 Header를 커스텀하게 만들기 위해 UITableViewHeaderFooterView를 상속한 클래스를 만들어서,
tableViewDelegate에서 지정해주었습니다.

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let header = projectTableView.dequeueReusableHeaderFooterView(
            withClass: ProjectTableViewHeaderView.self)

        let snapshot = dataSource.snapshot()
        let projectCount = snapshot.numberOfItems(inSection: .main)

        header.configureContent(status: String(describing: projectStatus),
                                projectCount: String(projectCount))
        return header
    }

    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return 60
    }

tableView의 Cell에 CRUD가 일어날 때 마다 프로젝트 갯수를 카운팅하는 projectCountLabel을 업데이트 해줘야 하는데요,
그러기 위해선 SectionHeader를 업데이트 해야합니다.

시도 1️⃣

UITableViewDiffableDataSource 레벨에서 처리해주기 위해 API를 찾아봤지만,
UITableViewDiffableDataSource에서는 UICollectionViewDiffableDataSource와는 달리 SupplementaryView를 설정해주는 API가 제공되지 않고 있었습니다.

시도 2️⃣

UITableViewreloadData( )로 Header를 업데이트 할 수 있었지만, reloadData( ) 모든 Section과 Row를 그리는 건 무거운 작업이고,
diff만 계산해서 효율적으로 뷰를 그려주는 UIDiffableDataSource의 이점을 살리지 못하는 방법이라고 생각했습니다.

✅ 해결 방법

  • UITableViewDelegate로 UITableViewHeaderFooterView를 리턴하는 방법 포기
  • HeaderView를 뷰 컨트롤러의 프로퍼티로 구현한 뒤, UITableView위에 배치했습니다.
  • Cell의 CRUD가 일어날 때마다 HeaderView의 updateProjectCells() 를 업데이트 해주었습니다.

TableView의 높이와 같은 adapative한 Popover사이즈 결정하기

TableView의 높이와 같은 adapative한 Popover사이즈 결정하는 방법

ProjectHistoryViewController의 view사이즈를 historyTableView의 사이즈와 동일하게 만들어,
tableview와 핏한 뷰를 만들고자 함

구현 시도

1️⃣ viewDidLayoutSubview

구글링을 한 결과 viewDidLayoutSubview에서 아래와 같은 코드를 구현하면 가능하다고 함.

override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        self.preferredContentSize = self.historyTableView.contentSize
    }

하지만 실행결과 tableView의 실제 높이를 반영하지 못하고 오류를 발생시킴.
preferredContentSize가 계속해서 layout을 조정하고, 따라서 재귀적으로 viewDidLayoutSubview를 호출시킴
스크린샷 2022-05-03 오전 11 56 46

2️⃣ .systemLayoutSizeFitting

https://useyourloaf.com/blog/self-sizing-popovers/

override func viewDidLoad() {
        super.viewDidLoad()
        self.configureViewLayout()
        self.configureHistoryTableView()
        self.preferredContentSize = self.view.systemLayoutSizeFitting(UIView.layoutFittingCompressedSize)
    }

❌ contstraints 충돌 발생
테이블뷰의 셀이 아직 deque되지 않은 시점이기 때문에 테이블뷰의 높이를 반영하지 못함

TableViewCell 간의 spacing 구현

구현 시도 1️⃣

Cell contentView frame의 inset을 부여해서 Cell간의 spacing을 구현했습니다.

 override func layoutSubviews() {
        super.layoutSubviews()
        self.contentView.frame = contentView.frame.inset(by: UIEdgeInsets(
                                                                          top: 5,
                                                                          left: 0,
                                                                          bottom: 5,
                                                                          right: 0)
         )
    }

그러나 기존에는 정상적으로 표시되던 StackView안의 Label들이 씹히고, Label간의 spacing이 일정하지 않은 문제가 발생했습니다.
Simulator Screen Shot - iPad mini (6th generation) - 2022-03-12 at 00 03 36


구현 시도 2️⃣ ✅

spacing효과를 주기 위해서 Cell ContentView안에 깡통뷰를 만들고, 깡통뷰와 contentView 사이에 제약을 설정했습니다.

  private func configureLayout() {
        NSLayoutConstraint.activate([
           // cellContainerView ◀️ 깡통뷰
            cellContainerView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 5),
            cellContainerView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
            cellContainerView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
            cellContainerView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -5)
        ])
        
        let margin = CGFloat(15)
        NSLayoutConstraint.activate([
            stackView.topAnchor.constraint(equalTo: cellContainerView.topAnchor, constant: margin),
            stackView.bottomAnchor.constraint(equalTo: cellContainerView.bottomAnchor, constant: -margin),
            stackView.leadingAnchor.constraint(equalTo: cellContainerView.leadingAnchor, constant: margin),
            stackView.trailingAnchor.constraint(equalTo: cellContainerView.trailingAnchor, constant: -margin)
        ])
    }

다크모드 지원

🖤 다크모드 지원

LightMode DarkMode
image   Simulator Screen Shot - iPad mini (6th generation) - 2022-05-02 at 11 44 36
image  image

🖤 지원 방법

1. system color 사용

.label .systemBackground , .systemRed 와 같은 systemColor를 이용해서 자동으로 대응하도록 구현

 private var titleTextField: UITextField = {
        let textField = UITextField()
        ...
        textField.backgroundColor = .systemBackground
        textField.textColor = .label
        textField.layer.shadowColor = UIColor.shadowColor.cgColor
        ...
        return textField
    }()

2. TableViewCell : colorset을 활용

ProjectTableViewCell의 backgroundcolor의 다크모드 색상은 커스텀하게 설정하기 위해 colorset 생성
스크린샷 2022-05-02 오전 11 35 22

self.cellContainerView.backgroundColor = UIColor(named: "projectCellColor")

3. layer.shadowColor : func traitCollectionDidChange(_:)

// shadowColor정의
extension UIColor {
    static var shadowColor: UIColor {
        if #available(iOS 13, *) {
            return UIColor { (traitCollection: UITraitCollection) -> UIColor in
                if traitCollection.userInterfaceStyle == .light {
                    return .systemGray
                } else {
                    return .white
                }
            }
        } else {
            return .�systemGray
        }
    }
}

CGColor는 모드가 변경될때마다 각 모드에 대응하는 컬러를 자동으로 리턴하지 못함으로
func traitCollectionDidChange(_:)내에서 업데이트시킴

 override func traitCollectionDidChange(_ previousTraitCollection: UITraitCollection?) {
        super.traitCollectionDidChange(previousTraitCollection)

        guard #available(iOS 13, *) else { return }

        guard traitCollection.userInterfaceStyle != previousTraitCollection?.userInterfaceStyle else { return }
        titleTextField.layer.shadowColor = UIColor.shadowColor.cgColor
        descriptionTextView.layer.shadowColor = UIColor.shadowColor.cgColor
    }   

STEP2-1-2 : MVC 아키텍처 설계

STEP 2️⃣-1 : 프로젝트 매니저 앱 설계 및 구현 (DB 미적용)

STEP 2️⃣-1-2 : MVC 아키텍처 설계

  • Controller, View 객체 설계
  • UML 작성
    Project Manager App - MVC

UITextView의 shadow 구현시 bound 영역외 text가 노출되는 이슈

문제 상황

  private var descriptionTextView: UITextView = {
        let textView = UITextView(frame: .zero)
        textView.translatesAutoresizingMaskIntoConstraints = false
        textView.backgroundColor = .systemGray6
        textView.font = .preferredFont(forTextStyle: .body)
        textView.textContainerInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
        textView.layer.shadowOffset = CGSize(width: 3, height: 3)
        textView.layer.shadowOpacity = 0.3
        textView.layer.shadowRadius = 3
        textView.layer.masksToBounds = false // 그림자는 bound밖에 생기므로, true이면 그림자가 보이지 않음
        return textView
    }()

UITextView에 그림자를 넣기 위해 layer.masksToBound = false 를 하면
bound영역 밖의 text들도 보여지는 문제가 발생합니다.

Untitled


해결 방법

TextView를 담는 Container View를 만들고, Container View에 그림자효과를 줬습니다.

private var descriptionTextViewContainer: UIView = {
        let view = UIView(frame: .zero)
        view.layer.shadowOffset = CGSize(width: 3, height: 3)
        view.layer.shadowOpacity = 0.3
        view.layer.shadowRadius = 3
        view.layer.masksToBounds = false
        return view
    }()
    
    private var descriptionTextView: UITextView = {
        let textView = UITextView(frame: .zero)
        textView.translatesAutoresizingMaskIntoConstraints = false
        textView.backgroundColor = .systemGray6
        textView.font = .preferredFont(forTextStyle: .body)
        textView.textContainerInset = UIEdgeInsets(top: 8, left: 8, bottom: 8, right: 8)
        textView.layer.masksToBounds = true
        return textView
    }()

STEP2-1-1 : 모델 구현

STEP 2️⃣-1 : 프로젝트 매니저 앱 설계 및 구현 (DB 미적용)

STEP 2️⃣-1-1 : 모델 설계 및 구현

  • 프로젝트 매니저 모델 설계
  • 모델 UML 작성
  • 모델 구현

Project Manager App  (1)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.