/TutorDot_iOS

ํ•ต๋ฉ‹์žˆ๋Š” iOS part๐Ÿ‘ฉ๐Ÿปโ€๐Ÿ’ป

Primary LanguageSwift

๐Ÿ‘ฉ๐Ÿปโ€๐Ÿซ ํŠœํ„ฐ๋ง ๊ด€๋ฆฌ์˜ ๋งˆ์นจํ‘œ, TutorDot


๐ŸŽ 26๊ธฐ OUR SOPT APP-JAM 'TutorDot' ๐Ÿ

iOS ํŒŒํŠธ : YB ๋ฅ˜์„ธํ™”, YB ์ตœ์ธ์ •

ํ”„๋กœ์ ํŠธ ๊ธฐ๊ฐ„ : 2020.6.27 ~ 2020.7.18


1๏ธโƒฃ ์—ญํ•  ๋ถ„๋‹ด

View Git Branch ๋‹ด๋‹น์ž
CalendarView calendar_branch ์„ธํ™”
AlertView alert_branch ์„ธํ™”
JournalView journal_branch ์ธ์ •
MyPageView mypage_branch ์ธ์ •
SplashView splash_branch ์ธ์ •
LoginSignUpView login_branch ์„ธํ™”

2๏ธโƒฃ Git ๊ด€๋ จ ํ˜‘์˜ (์›Œํฌํ”Œ๋กœ์šฐ, ๋ธŒ๋žœ์น˜, ๋ฆฌ๋“œ๋ฏธ ๋“ฑ)

  • ์›Œํฌํ”Œ๋กœ์šฐ : master(์ตœ์ข…๋ณธ) - dev(ํ†ตํ•ฉ๊ด€๋ฆฌ) - ๊ฐ ๊ธฐ๋Šฅ๋ณ„ ๋ธŒ๋žœ์น˜(๋‹ด๋‹น์ž๊ฐ€ ๊ด€๋ฆฌ)
  • readme ์ž‘์„ฑ์— ๋Œ€ํ•ด์„œ
    • ๊ฒฐ๋ก  : ํ‹ˆํ‹ˆํžˆ ์ตœ๋Œ€ํ•œ ์ž์„ธํžˆ ์ ๊ธฐ (๋‚˜์ค‘์— ์ •๋ฆฌ)
    • ๋ฏธ๋ฃจ์ง€ ๋ง๊ณ  ์ž‘์—…ํ•  ๋•Œ๋งˆ๋‹ค ๊ธฐ๋กํ•ด๋†“์ž! (๊ธฐ๋กํ•˜๋Š” ์Šต๊ด€ ์žŠ์ง€๋ง๊ธฉ!)
  • Git commit message ํ˜•์‹ ํ†ต์ผ
    • Message๋Š” 3๊ฐ€์ง€ ๋ผ๋ฒจ๋งŒ ์‚ฌ์šฉ
      • Add : ์•„์˜ˆ ์ƒˆ๋กœ์šด ํŒŒ์ผ(swift, storyboard, VC ํŒŒ์ผ ๋“ฑ) ์ถ”๊ฐ€
      • Update : ๊ธฐ์กด ํŒŒ์ผ์— ๊ธฐ๋Šฅ, UI์š”์†Œ ์ถ”๊ฐ€
      • Fix : ๊ธฐ์กด ๊ธฐ๋Šฅ ์ˆ˜์ •์ด๋‚˜ ์—๋Ÿฌ ํ•ด๊ฒฐ ๋“ฑ
    • Format : ๋ผ๋ฒจ + commit comment
  • ์šฐ๋ฆฌ์˜ Git Workflow ์ตœ์ข… ์ •๋ฆฌ ๋…ธ์…˜ ๋งํฌ ๐Ÿ”ฅ

3๏ธโƒฃ Coding Convention ์ •ํ•˜๊ธฐ

  • view controller : Upper Camel Case ํƒญ ์ด๋ฆ„ + VC
  • eg. CalendarVC, NotesVC
  • UI ์š”์†Œ ๋„ค์ด๋ฐ : Upper Camel Case UI์š”์†Œ ์ด๋ฆ„ + View Cell
    • eg. CalendarCollectionViewCell
    • Xib ํŒŒ์ผ์€ ViewCell ํŒŒ์ผ์ด๋ž‘ ๋˜‘๊ฐ™์ด ๋„ค์ด๋ฐ
  • ๋ณ€์ˆ˜๋ช…, ์ƒ์ˆ˜๋ช… : Lower Camel Case
    // ๋ณ€์ˆ˜๋ช…
    var dropDownButton: UIButton!
    // ์ƒ์ˆ˜๋ช…
    let headerView = JournalDateHeaderView(frame: CGRect(x:0, y:0, width: 375, height: 16))
  • ํ•จ์ˆ˜๋ช…: Lower Camel Case
    • Action ํ•จ์ˆ˜ ๋„ค์ด๋ฐ: '์ฃผ์–ด+๋™์‚ฌ+๋ชฉ์ ์–ด'
    func backButtonDidTap() {
      // ...
    }
  • Extension ์ด๋ฆ„ : Extensions+ํ™•์žฅํด๋ž˜์Šค
    • eg.Extensions+String
  • Optional์€ gaurd let ์œผ๋กœ ์„ ์–ธํ•˜๊ธฐ

4๏ธโƒฃ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ

โ€‹  


5๏ธโƒฃ ์‚ฌ์šฉํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

โ€‹       


6๏ธโƒฃ ํด๋”๋ง ๋ฐฉ์‹

KakaoTalk_Photo_2020-07-08-18-42-01



7๏ธโƒฃ ์‹คํ–‰ ํ™”๋ฉด

์ „์ฒด ์‹คํ–‰ ํ™”๋ฉด ๋ฐ ์ƒˆ๋กœ ์•Œ๊ฒŒ๋œ ๊ฒƒ๋“ค๊ณผ ์–ด๋ ค์› ๋˜ ๊ธฐ๋Šฅ๋“ค ์†Œ๊ฐœ

๐ŸŽ Splash

       

โ–ถ๏ธ Lottie ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์Šคํ”Œ๋ž˜์‹œ ํ™”๋ฉด ์• ๋‹ˆ๋ฉ”์ด์…˜ ๊ตฌํ˜„

Lottie ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— animationView()๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ frame ํฌ๊ธฐ์™€ aniamteion JSONํŒŒ์ผ์„ ์ง€์ •ํ•ด์ค„ ์ˆ˜ ์žˆ๋‹ค. ๋ฐ˜๋ณตํ•  ํšŸ์ˆ˜๋ฅผ ํ•œ๋ฒˆ์œผ๋กœ ์ง€์ •ํ•˜์—ฌ ์žฌ์ƒํ•˜๋„๋ก setupํ•จ์ˆ˜๋ฅผ ์ž‘์„ฑํ•˜์˜€๋‹ค.

let animationView = AnimationView()
func setup(){ //lottie ๋ฒ„์ „
        animationView.frame = view.bounds //animationView ํฌ๊ธฐ๊ฐ€ view์™€ ๊ฐ™๊ฒŒ
        animationView.animation = Animation.named("data2") //์–ด๋–ค jsonvํŒŒ์ผ์„ ์“ธ์ง€
        animationView.contentMode = .scaleAspectFill //ํ™”๋ฉด์— ์ ํ•ฉํ•˜๊ฒŒ
        animationView.loopMode = .playOnce //view์•ˆ์— Subview๋กœ ๋„ฃ์–ด์ค€๋‹ค,
        view.addSubview(animationView)
        animationView.play()  //์žฌ์ƒ
    }

โ–ถ๏ธ N์ดˆ ํ›„ ์ž๋™ ํ™”๋ฉด์ „ํ™˜ ๊ตฌํ˜„

์Šคํ”Œ๋ž˜์‹œํ™”๋ฉด์—์„œ ์ง€์ •๋œ ์‹œ๊ฐ„(์ดˆ)์ด ์ง€๋‚œ ํ›„ ์ž๋™์œผ๋กœ ํ™”๋ฉด์ด ์ „ํ™˜๋˜๋„๋ก ๊ตฌํ˜„ํ•˜์˜€๋‹ค. asyncAfter ๋ฉ”์†Œ๋“œ์— seconds ํŒŒ๋ผ๋ฏธํ„ฐ์— ์‹œ๊ฐ„ ์ดˆ๋ฅผ ์ง€์ •ํ•ด์ฃผ๊ณ  ํ•จ์ˆ˜ ๋ธ”๋ก ์•ˆ์— ์ „ํ™˜๋  ๋ทฐ๋ฅผ ์ •์˜ํ•˜๋ฉด ํ•ด๋‹น ์‹œ๊ฐ„์ด ์ง€๋‚œ ํ›„ ์ž๋™์œผ๋กœ ํ™”๋ฉด์ „ํ™˜์ด ์ด๋ฃจ์–ด์ง„๋‹ค.

DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(4), execute: { //Code }

๐ŸŽ Onboarding

       

์˜จ๋ณด๋”ฉ1 ์˜จ๋ณด๋”ฉ2 ์˜จ๋ณด๋”ฉ3 ์˜จ๋ณด๋”ฉ4

โ–ถ๏ธ ์˜ค๋ฅธ์ชฝ/์™ผ์ชฝ Swipe๋กœ ์ด๋ฏธ์ง€ ์ „ํ™˜

์˜ค๋ฅธ์ชฝ / ์™ผ์ชฝ์œผ๋กœ ์Šค์™€์ดํ”„ ์ œ์Šค์ณ์— ๋”ฐ๋ผ ํ˜ธ์ถœ๋˜๋Š” ํ•จ์ˆ˜์— ์ด๋ฏธ์ง€ ๋ฐฐ์—ด์— ์žˆ๋Š” ์˜จ๋ณด๋”ฉ ์ด๋ฏธ์ง€ 4๊ฐœ์˜ index ๊ฐ’์„ ๊ณ„์‚ฐํ•˜์—ฌ ๊ฐ index์— ํ•ด๋‹นํ•˜๋Š” ์ด๋ฏธ์ง€ ์ด๋ฆ„์„ ์ด๋ฏธ์ง€ ๋ทฐ์— ์ ์šฉํ•ด์ฃผ์—ˆ๊ณ , ์ถ”๊ฐ€๋กœ ์ด๋ฏธ์ง€ ์ „ํ™˜ ํšจ๊ณผ๋ฅผ ์ฃผ์—ˆ๋‹ค.

@objc func respondToSwipeGesture(_ gesture: UIGestureRecognizer) {
        if let swipeGesture = gesture as? UISwipeGestureRecognizer
            if swipeGesture.direction == UISwipeGestureRecognizer.Direction.left
                //imege ๋ฐฐ์—ด ์ธ๋ฑ์Šค ์กฐ์ •ํ•˜์—ฌ ์ด๋ฏธ์ง€ ์ „ํ™˜ & ์•ŒํŒŒ๊ฐ’ ์กฐ์ • ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ฝ”๋“œ
            else if swipeGesture.direction == UISwipeGestureRecognizer.Direction.right
                //imege ๋ฐฐ์—ด ์ธ๋ฑ์Šค ์กฐ์ •ํ•˜์—ฌ ์ด๋ฏธ์ง€ ์ „ํ™˜ & ์•ŒํŒŒ๊ฐ’ ์กฐ์ • ์• ๋‹ˆ๋ฉ”์ด์…˜ ์ฝ”๋“œ 
 }    

๐ŸŽ Login & SignUp

       

๋กœ๊ทธ์ธ ํšŒ์›๊ฐ€์ž…-๊ธฐ๋ณธ ํšŒ์›๊ฐ€์ž…-ํ‚ค๋ณด๋“œ

โ–ถ๏ธ ํ‚ค๋ณด๋“œ ์—ด๋ฆด ๋•Œ์˜ Animation

์•„์ดํฐ 8 ์ฒ˜๋Ÿผ ํ™”๋ฉด์ด ์ž‘๊ฑฐ๋‚˜ ํ…์ŠคํŠธ ํ•„๋“œ๊ฐ€ ๋ทฐ์˜ ๋ฐ‘์— ์œ„์น˜ํ•ด์žˆ์„ ๊ฒฝ์šฐ ํ‚ค๋ณด๋“œ๊ฐ€ ์—ด๋ ธ์„ ๋•Œ ํ…์ŠคํŠธ ํ•„๋“œ๊ฐ€ ๊ฐ€๋ ค์ง„๋‹ค. ๊ทธ๋Ÿด๋•Œ ํ•„์š”ํ•œ ๊ธฐ๋Šฅ ๋‘๊ฐ€์ง€์ธ 1. ๋ทฐ์˜ ์•„๋ฌด๊ณณ์ด๋‚˜ ํ„ฐ์น˜ํ–ˆ์„ ๋•Œ ํ‚ค๋ณด๋“œ ๋‹ซํžˆ๊ธฐ 2. ํ…์ŠคํŠธํ•„๋“œ ์œ„๋กœ ๋ฐ€๋ฆฌ๊ธฐ ๊ธฐ๋Šฅ๋“ค์„ ๋กœ๊ทธ์ธ, ํšŒ์›๊ฐ€์ž… ํ™”๋ฉด์— ์ถ”๊ฐ€ํ–ˆ๋‹ค!

    // ํƒญํ–ˆ์„ ๋•Œ ํ‚ค๋ณด๋“œ action
    func initGestureRecognizer() { //
        let textFieldTap = UITapGestureRecognizer(target: self, action: #selector(handleTapTextField(_:)))
        textFieldTap.delegate = self
        self.view.addGestureRecognizer(textFieldTap)
    }
    
    // ๋‹ค๋ฅธ ์œ„์น˜ ํƒญํ–ˆ์„ ๋•Œ ํ‚ค๋ณด๋“œ ์—†์–ด์ง€๊ธฐ
    @objc func handleTapTextField(_ sender: UITapGestureRecognizer) { //
        self.emailTextField.resignFirstResponder()
        self.passWordTextField.resignFirstResponder()
    }
    
    // ํ‚ค๋ณด๋“œ๊ฐ€ ์ƒ๊ธธ ๋–„ ํ…์ŠคํŠธ ํ•„๋“œ ์œ„๋กœ ๋ฐ€๊ธฐ
    @objc func keyboardWillShow(_ notification: NSNotification) {
        guard let duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey] as? Double else { return }
        guard let curve = notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey] as? UInt else { return }
        guard let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? NSValue else { return }
        
        let keyboardHeight: CGFloat // ํ‚ค๋ณด๋“œ์˜ ๋†’์ด
        if #available(iOS 11.0, *) {
            keyboardHeight = keyboardFrame.cgRectValue.height - self.view.safeAreaInsets.bottom
        } else {
            keyboardHeight = keyboardFrame.cgRectValue.height
        }
        UIView.animate(withDuration: duration, delay: 0.0, options: .init(rawValue: curve), animations: {
            self.imageView.alpha = 0 // ์ด๋ฏธ์ง€๋ทฐ ์ˆจ๊ธฐ๊ธฐ
            self.imageToTextHeightConstraint.constant = 0 // constraint ์กฐ์ •
            self.bottomViewConstraint.constant = +keyboardHeight/2 + 100 // constraint ์กฐ์ •
        })
        self.view.layoutIfNeeded()
    }
    

๐ŸŽ ์บ˜๋ฆฐ๋”

       

์บ˜๋ฆฐ๋”-์˜ค๋Š˜๋‚ ์งœ,๋‹ค๋ฅธ๋‚ ์งœ ์บ˜๋ฆฐ๋”-์ผ์ •์—†๋Š”๋‚  ์บ˜๋ฆฐ๋”-ํ† ๊ธ€๋ฒ„ํŠผ ์บ˜๋ฆฐ๋”-์ผ์ •ํŽธ์ง‘ ์บ˜๋ฆฐ๋”-์ผ์ •์ถ”๊ฐ€ํ”ผ์ปค๋ทฐ ์บ˜๋ฆฐ๋”-์ผ์ •์ถ”๊ฐ€

โ–ถ๏ธ ์บ˜๋ฆฐ๋”์˜ ํ•„์ˆ˜ ๊ธฐ๋Šฅ

CalendarCollectionView (๋‹ฌ๋ ฅ ๋ทฐ)์™€ TutorCollectionView (ํ•˜๋‹จ์˜ ์ผ์ • ๋ทฐ)์— ๋™์‹œ์— ๋ฐ˜์˜๋˜์–ด์•ผ ํ•˜๋Š” ๊ธฐ๋Šฅ๋“ค์ด ๋งŽ์•„ ๋กœ์ง์„ ์งœ๋Š” ๊ฒƒ์ด ๋งค์šฐ ์–ด๋ ค์› ๋‹ค..! ํ•˜๋‹จ์˜ ๊ธฐ๋Šฅ๋“ค์ด ์บ˜๋ฆฐ๋” ํƒญ์— ๋“ค์–ด๊ฐ€์•ผ ํ•˜๋Š” ์ฃผ์š” ๊ธฐ๋Šฅ๋“ค์ด๋‹ค.

  1. ์บ˜๋ฆฐ๋”์…‹์—…: ์ด๋ฒˆ ๋‹ฌ ์ˆซ์ž๋Š” ๊ฒ€์ •์ƒ‰์œผ๋กœ ํ‘œ์‹œ, ์ด์ „ ๋‹ฌ, ๋‹ค์Œ๋‹ฌ ์ˆซ์ž๋Š” ํšŒ์ƒ‰์œผ๋กœ ํ‘œ์‹œ
  2. ๋ทฐ ์ฒ˜์Œ ๋กœ๋“œ์‹œ ์บ˜๋ฆฐ๋”์˜ ๋‚ ์งœ์— ์ผ์ •์ด ์กด์žฌํ•˜๋ฉด ์ƒ‰๊น” ์ ์œผ๋กœ ํ‘œ์‹œ
  3. ๋ทฐ ์ฒ˜์Œ ๋กœ๋“œ์‹œ ์บ˜๋ฆฐ๋”์— ๋””ํดํŠธ๋กœ ์˜ค๋Š˜ ๋‚ ์งœ ์„ ํƒ
  4. ์บ˜๋ฆฐ๋”์˜ ๋‚ ์งœ ์„ ํƒ์‹œ ๋ฐ‘์˜ ๋ทฐ์— ํ•ด๋‹น ๋‚ ์งœ์˜ ์ผ์ • ํ‘œ์‹œ
  5. ์ƒ๋‹จ์˜ ํ† ๊ธ€(์ˆ˜์—…์ผ์ •)์— ๋”ฐ๋ผ ์บ˜๋ฆฐ๋”๋ทฐ์™€ ์ƒ์„ธ ์ผ์ •์ด ์ „ํ™˜๋˜์–ด์•ผ ํ•จ

โ–ถ๏ธ ์บ˜๋ฆฐ๋”์˜ ๋‚ ์งœ ์„ ํƒ์‹œ ๋ฐ‘์˜ ๋ทฐ์— ํ•ด๋‹น ๋‚ ์งœ์˜ ์ผ์ • ํ‘œ์‹œ

๋“ค์–ด์˜ค๋Š” ์ „์ฒด ์ˆ˜์—… ๋ฐ์ดํ„ฐ classList2๋ฅผ CalendarData๋ผ๋Š” ๊ตฌ์กฐ์ฒด์— ์ €์žฅํ•ด์ฃผ์—ˆ๋‹ค. classList2[i]์˜ ๋‚ ์งœ ์ŠคํŠธ๋ง ๊ฐ’์„ ๊ฐ๊ฐ ์›”, ์ผ๋กœ ํŒŒ์‹ฑํ•ด์ฃผ๊ณ  ์บ˜๋ฆฐ๋” ์…€์˜ ์ผ์ž๋ฐ์ดํ„ฐ์™€ ์ „์ฒด ์บ˜๋ฆฐ๋”์˜ ํ˜„์žฌ ์›”๊ฐ’๊ณผ ์ผ์น˜ํ•  ๋•Œ classDateList๋ผ๋Š” ์ƒˆ ๋ฆฌ์ŠคํŠธ์— ์ €์žฅํ•ด์ค€ ํ›„ ํ•ด๋‹น ๋ฆฌ์ŠคํŠธ๋ฅผ TutorCollectionView์˜ cellForItem์— ์‚ฌ์šฉํ•ด์ฃผ์—ˆ๋‹ค.

// CalendarCollectionView
for index in 0..<classList2.count {
  print(classList2[index].classDate)
  let dateMonthInt = currentMonthIndex + 1
  let date2 = Int(date)
  let dayMove = String(format: "%02d", arguments: [dateMonthInt])
  let dayMove2 = String(format: "%02d", date2 as! CVarArg)
  if classList2[index].classDate == "\(currentYear)-\(dayMove)-\(dayMove2)" {
    classDateList.append(classList2[index])
    tutorCollectionView.reloadData()
  }}

//TutorCollectionView
tutorInfoCell.infoView.frame.size.width = tutorInfoCell.frame.size.width/2
tutorInfoCell.set(classDateList[indexPath.row])
for i in 0..<self.classDateList.count {
  let hourTimes =  "\(self.classDateList[i].times)ํšŒ์ฐจ, \(self.classDateList[i].hour)์‹œ๊ฐ„"
  tutorInfoCell.classHourLabel.text = hourTimes}
return tutorInfoCell

โ–ถ๏ธ ๋ทฐ ์ฒ˜์Œ ๋กœ๋“œ์‹œ ์บ˜๋ฆฐ๋”์˜ ๋‚ ์งœ์— ์ผ์ •์ด ์กด์žฌํ•˜๋ฉด ์ƒ‰๊น” ์ ์œผ๋กœ ํ‘œ์‹œ

// CalendarCollectionView
if classDateMonthZeros == dayMove && classDateDay == todaysDate {
  let imageName = classList2[i].color
  if calendarCell.image1.image == UIImage(named: "") {
    calendarCell.image1.image = UIImage(named: imageName)
      } else if calendarCell.image2.image == UIImage(named: "") {
        calendarCell.image2.image = UIImage(named: imageName)
      } else {
        calendarCell.image3.image = UIImage(named: imageName)
      }
}

โ–ถ๏ธ ์ƒ๋‹จ์˜ ํ† ๊ธ€(์ˆ˜์—…์ผ์ •)์— ๋”ฐ๋ผ ์บ˜๋ฆฐ๋”๋ทฐ์™€ ์ƒ์„ธ ์ผ์ •์ด ์ „ํ™˜๋˜์–ด์•ผ ํ•จ

์„œ๋ฒ„์—์„œ ํ•ด๋‹น ์‚ฌ์šฉ์ž๊ฐ€ ์—ฐ๊ฒฐ๋˜์–ด์žˆ๋Š” ์ˆ˜์—… ๋ฆฌ์ŠคํŠธ๋ฅผ ๋ฐ›์•„์™€ dropDown.DataSource ๋ฆฌ์ŠคํŠธ์— ์ €์žฅํ•ด์ฃผ์—ˆ๋‹ค. ํ•ด๋‹น ์ŠคํŠธ๋ง ๊ฐ’์„ ์„ ํƒํ•  ์‹œ ์ŠคํŠธ๋ง๊ฐ’๊ณผ ์ˆ˜์—… ์ด๋ฆ„์ด ์ผ์น˜ํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ classListToggle์— appendํ•ด์ฃผ๊ณ  classList2์— classListToggle๋ฅผ ๋ณต์‚ฌํ•ด CalendarCollectionView์™€ TutorCollectionView์— ๋ฟŒ๋ ค์คฌ๋‹ค.

// ๋“œ๋กญ๋ฐ•์Šค ๋ชฉ๋ก ๋‚ด์—ญ
        dropDownButton.addTarget(self, action: #selector(dropDownToggleButton), for: .touchUpInside)
        self.dateCollectionView.reloadData()
        self.tutorCollectionView.reloadData()
        // ๋“œ๋กญ๋ฐ•์Šค ์ˆ˜์—… ์ œ๋ชฉ ์„ ํƒํ•  ๋•Œ ์บ˜๋ฆฐ๋” ์ปฌ๋ ‰์…˜๋ทฐ, ํŠœํ„ฐ ์ปฌ๋ ‰์…˜๋ทฐ ๋ฐ์ดํ„ฐ ๋ฐ”๊ฟ”์ฃผ๊ธฐ
        dropDown?.selectionAction = { [unowned self] (index: Int, item: String) in
            self.dropDownLabelButton.setTitle(item, for: .normal)
            self.classListToggle.removeAll()
            self.tutorCollectionView.reloadData()
            // ์ „์ฒด ์„ ํƒ์‹œ
            if self.dropDownLabelButton.title(for: .normal) == "์ „์ฒด" {
                self.classList2 = self.classList2Copy
                print(self.classList2)
                self.dateCollectionView.reloadData()
                self.tutorCollectionView.reloadData()
            } else {
                // ์ˆ˜์—… ๋ฆฌ์ŠคํŠธ ์„ ํƒ์‹œ
                self.classList2 = self.classList2Copy
                for i in 0..<self.classList2.count {
                    if self.dropDownLabelButton.title(for: .normal) == self.classList2[i].lectureName {
                        self.classListToggle.append(self.classList2[i])
                    }
                    print("์ƒˆ ๋ฆฌ์ŠคํŠธ", self.classListToggle)
                }
                self.classList2.removeAll()
                self.classList2 = self.classListToggle
                self.dateCollectionView.reloadData()
                self.tutorCollectionView.reloadData()   
            }
        }

โ–ถ๏ธ ReloadData์˜ ์ค‘์š”์„ฑ๐Ÿ”ฅ๐Ÿ”ฅ

CollectionView, TableView๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ํ•ด๋‹น ์š”์†Œ์˜ ๋ทฐ์— ๋‚ด๊ฐ€ ํ•œ ์ˆ˜์ •์ด ๋ฐ˜์˜๋˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค. ์ด๋Ÿฐ ๊ฒฝ์šฐ์—๋Š” ๊ผญ ๊ทธ ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋˜๋Š” ๊ณณ์—์„œ collectionView.reloadData()๋ฅผ ์‹คํ–‰์‹œ์ผœ์ค˜์•ผ ์ˆ˜์ •์‚ฌํ•ญ์ด ๋ฐ˜์˜๋œ๋‹ค!

๋ฐ‘์˜ ์ฝ”๋“œ์—์„œ๋„ classList2์— append๋ฅผ ํ•ด์ค˜๋„ ์ปฌ๋ ‰์…˜๋ทฐ๋“ค์— ๋ฆฌ์ŠคํŠธ๊ฐ€ ์—…๋ฐ์ดํŠธ ๋˜์ง€ ์•Š์•˜๋Š”๋ฐ for ๋ฌธ์ด ๋๋‚œ ํ›„ reloadData๋ฅผ ํ•ด์ฃผ์—ˆ๋”๋‹ˆ ์„ฑ๊ณต์ ์œผ๋กœ ๋ฐ˜์˜๋๋‹ค!

// ์„œ๋ฒ„ ํ†ต์‹  : ์บ˜๋ฆฐ๋” ์ „์ฒด ๋ฐ์ดํ„ฐ ๊ฐ€์ ธ์˜ค๊ธฐ
    func getClassList() {
        ClassInfoService.classInfoServiceShared.getAllClassInfo() { networkResult in
            switch networkResult {
            case .success(let resultData):
                print("successssss")
                //guard let data = resultData as? [CalendarData] else { return }
                guard let data = resultData as? [CalendarData] else { return print(Error.self) }
                print("try")
                for index in 0..<data.count {
                    let item = CalendarData(classId: data[index].classId, lectureName: data[index].lectureName, color: data[index].color, times: data[index].times, hour: data[index].hour, location: data[index].location, classDate: data[index].classDate, startTime: data[index].startTime, endTime: data[index].endTime)
                    self.classList2.append(item)
                    self.classList2Copy = self.classList2
                }
                self.dateCollectionView.reloadData()
                self.tutorCollectionView.reloadData()
                self.nextDate = 0
            case .pathErr : print("Patherr")
            case .serverErr : print("ServerErr")
            case .requestErr(let message) : print(message)
            case .networkFail:
                print("networkFail")
            }
        }
    }

๐ŸŽ ์ˆ˜์—…์ผ์ง€

์ผ์ง€-์ฒ˜์Œ์ˆ˜์—…์ผ์ง€-ํ† ๊ธ€ ์ˆ˜์—…์ผ์ง€-ํ”„๋กœ๊ทธ๋ž˜์Šค,๋ฒ„ํŠผ ์ˆ˜์—…์ผ์ง€-์ผ์ง€์ˆ˜์ • ์ˆ˜์—…์ผ์ง€-์ผ์ง€์ˆ˜์ •(์ž…๋ ฅ)

โ–ถ๏ธ ๋ฒ„ํŠผ ์•„๋ž˜๋กœ ๋‚˜์˜ค๋Š” ๋“œ๋กญ๋‹ค์šด ๋ฐ•์Šค์˜ offset customํ•˜๊ธฐ!

BottomOffset๋กœ ์•„๋ž˜์ชฝ์— ํŽผ์ณ์ง€๋Š” ๋“œ๋กญ๋ฐ•์Šค์˜ ์œ„์น˜๋ฅผ ์ง์ ‘ ์ง€์ •ํ•  ์ˆ˜ ์žˆ๋‹ค. (๋” ์„ธ๋ฐ€ํ•˜๊ฒŒ ์ปจํŠธ๋กค ๊ฐ€๋Šฅ!) y์ถ•์— ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ์“ฐ๋ฉด ๋ฒ„ํŠผ ๋†’์ด ๋งŒํผ offset์ด ์ง€์ •๋˜์–ด ๋ฐ”๋กœ ์•„๋ž˜์ชฝ์—์„œ ๋“œ๋กญ๋ฐ•์Šค๊ฐ€ ํŽผ์ณ์ง€๊ฒŒ ๋˜๋Š”๋ฐ ์กฐ๊ธˆ ์—ฌ์œ ๋ฅผ ๋‘๊ณ  ํŽผ์ณ์งˆ ์ˆ˜ ์žˆ๋„๋ก +6(pt)์„ ํ•ด์ฃผ์—ˆ๋‹ค.

dropDown?.bottomOffset = CGPoint(x: 0, y:(dropDown?.anchorView?.plainView.bounds.height)!+6)

โ–ถ๏ธ Constraints ์กฐ์ •ํ•ด์„œ ๋ทฐ ์ˆจ๊ธฐ๊ธฐ

์ˆ˜์—…์ผ์ง€ ๋ทฐ์—์„œ ๊ณผ์™ธ๋ฅผ ์„ ํƒํ–ˆ์„๋•Œ๋งŒ Progress View๊ฐ€ ๋‚˜์˜ค๊ณ  ์„ ํƒํ•˜์ง€ ์•Š์œผ๋ฉด Progress View๋ฅผ ์ˆจ๊ธฐ๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„์ด ํ•„์š”ํ–ˆ๋‹ค. ์Šคํƒ๋ทฐ์— ํ•ด๋‹น view๋ฅผ hidden ์‹œํ‚ค๊ณ  Constranints height๋ฅผ 0์œผ๋กœ ์กฐ์ ˆํ•˜๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ View๋ฅผ ์ˆจ๊ธฐ๊ณ , ๋ณด์ผ ์ˆ˜ ์žˆ๋„๋ก ํ•˜์˜€๋‹ค.

func classHeaderHidden(_ ishide: Bool){
        progressViewWrap.subviews[0].isHidden = ishide
        if ishide  //true(์•ˆ๋ณด์ผ๋•Œ)
            tableViewTopMargin.constant = 191-117
        else  //false (๋ณด์ผ๋•Œ)
            tableViewTopMargin.constant = 191
}

๐ŸŽ ์•Œ๋ฆผ

       

์•Œ๋ฆผ์•Œ๋ฆผ-ํ† ๊ธ€๋ฒ„ํŠผแ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2020-07-18 แ„‹แ…ฉแ„Œแ…ฅแ†ซ 4 45 31
โ–ถ๏ธ ์˜ค๋ฅธ์ชฝ์œผ๋กœ ์Šค์™€์ดํ”„ ํ•ด์„œ ์•Œ๋ฆผ์‚ญ์ œ, ํ™•์ธ

์•Œ๋ฆผ์„ ์˜ค๋ฅธ์ชฝ์œผ๋กœ ์Šค์™€์ดํ”„ํ•ด์„œ ์ƒˆ๋กœ์šด ์•Œ๋ฆผ์„ ํ™•์ธ์ฒดํฌํ•  ์ˆ˜ ์žˆ๊ณ , ์‚ญ์ œ๋„ ํ•  ์ˆ˜ ์žˆ๋‹ค.

 func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
        let delete = UIContextualAction(style: .destructive, title: "์‚ญ์ œ") { (action, sourceView, completionHandler) in
            self.noticeList.remove(at: indexPath.row)
            self.noticeTableView.reloadData()
            print(self.noticeList.count)
            //completionHandler(true)
        }
        let confirm = UIContextualAction(style: .normal, title: "ํ™•์ธ") { (action, sourceView, completionHandler) in
            print("index path of edit: \(indexPath)")
            self.noticeList[indexPath.row].newNotice = false
            self.noticeTableView.reloadData()
            completionHandler(true)
        }
        let swipeActionConfig = UISwipeActionsConfiguration(actions: [delete, confirm])
        swipeActionConfig.performsFirstActionWithFullSwipe = false
        return swipeActionConfig
    }

๐ŸŽ MyPage

๋งˆ์ดํŽ˜์ด์ง€-๊ธฐ๋ณธ ๋งˆ์ดํŽ˜์ด์ง€-์ˆ˜์—…์ถ”๊ฐ€ ๋งˆ์ดํŽ˜์ด์ง€-์ˆ˜์—…์ •๋ณด ์ดˆ๋Œ€์ฝ”๋“œ ๋งˆ์ดํŽ˜์ด์ง€-์ˆ˜์—…ํ•ด์ œ ๋งˆ์ดํŽ˜์ด์ง€-ํ”„๋กœํ•„์ˆ˜์ • ๋งˆ์ดํŽ˜์ด์ง€-์„œ๋น„์Šคํƒˆํ‡ด ๋งˆ์ดํŽ˜์ด์ง€-๊ฐœ๋ฐœ์ž์ •๋ณด ๋งˆ์ดํŽ˜์ด์ง€-๋กœ๊ทธ์•„์›ƒ
                                   


โ–ถ๏ธ ํ…Œ์ด๋ธ” ๋ทฐ์—์„œ ์ž…๋ ฅ๋œ ๋ฐ์ดํ„ฐ๋ฅผ VC์— ์ „๋‹ฌํ•˜๊ธฐ

๋งˆ์ดํŽ˜์ด์ง€์—์„œ ์ •๊ทœ ์ˆ˜์—…์‹œ๊ฐ„์„ ์ž…๋ ฅํ•˜๋Š” ๋ถ€๋ถ„์€ ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์— ๋”ฐ๋ผ ๋™์ ์œผ๋กœ ํ…์ŠคํŠธ ํ•„๋“œ๊ฐ€ ์ƒ์„ฑ๋˜๋„๋ก ํ•˜๊ธฐ ์œ„ํ•ด tableView๋กœ ๊ตฌํ˜„ํ•˜์˜€๋Š”๋ฐ, ํ…Œ์ด๋ธ” ๋ทฐ ์…€ ๋‚ด์—์„œ ์ž‘์„ฑ๋œ ๋ฐ์ดํ„ฐ๋ฅผ VC๋กœ ์ „๋‹ฌํ•˜๋Š” ๋ถ€๋ถ„์—์„œ ์–ด๋ ค์›€์ด ์žˆ์—ˆ๋‹ค. ๋ณ€์ˆ˜์— ์ง์ ‘๋ฐ์ดํ„ฐ๋ฅผ ํ• ๋‹นํ•ด๋ณด๊ณ , ๋ฆฌ์ŠคํŠธ์— append๋ฅผ ํ•ด๋ด๋„ VC๋‚ด ๋‹ค๋ฅธ ํ•จ์ˆ˜์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ํ˜ธ์ถœํ•˜๋ ค๊ณ  ํ•˜๋ฉด nil ๊ฐ’์ด ์ถœ๋ ฅ๋˜์—ˆ๋‹ค. ์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ์œ„ํ•ด cell์— protocol์„ ์ •์˜ํ•˜๊ณ  delegate๋ฅผ ์„ค์ •ํ•ด์ฃผ์—ˆ๋‹ค.

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        cell.delegate = self  // ๋งค์šฐ์ค‘์š”!! tableView cellForRowAt ํ•จ์ˆ˜ ๋‚ด๋ถ€์— delegate ์„ ์–ธ
}
// Cell ํŒŒ์ผ ๋‚ด๋ถ€์— protocol ์„ ์–ธ
protocol AddRegularClassTimeCellDelegate: class {
    func setScheduler(_ date: String, _ start: String, _ end: String)
}
// Cell ํŒŒ์ผ์— delegate ๋ณ€์ˆ˜ ์„ ์–ธ ๋ฐ ํ•จ์ˆ˜ ํ˜ธ์ถœ
var delegate: AddRegularClassTimeCellDelegate?
if let delegate = delegate {
    delegate.setScheduler(days, startTime, endTime)
}
// ํ•ด๋‹น VC์— extention์œผ๋กœ ์…€ํŒŒ์ผ protocol์— ์ •์˜๋œ ํ•จ์ˆ˜๋ถ€๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค.
extension MyClassAddVC: AddRegularClassTimeCellDelegate {
    func setScheduler(_ date: String, _ start: String, _ end: String) {
        let newSchedule = Schedules(day: date, orgStartTime: start, orgEndTime: end)
        schedule.append(newSchedule)
    }
}

โ–ถ๏ธ VC ๋‚ด๋ถ€์˜ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅผ ๋•Œ๋งˆ๋‹ค table view Cell ๋™์ ์œผ๋กœ ์ฆ๊ฐ€ ์‹œํ‚ค๊ธฐ

VC๋‚ด์— ๋นˆ ๋ฆฌ์ŠคํŠธ regularClassTime์„ ๋‘๊ณ  ๋ฒ„ํŠผ์„ ๋ˆŒ๋ €์„ ๋•Œ ๋ฆฌ์ŠคํŠธ์— append๋ฅผ ํ•ด์„œ ๊ฐœ์ˆ˜๋ฅผ ๋Š˜๋ ค ์ค€ ๋‹ค์Œ tableView.reloadData()๋ฅผ ํ•ด์ฃผ๋ฉด (ํ…Œ์ด๋ธ” row ๊ฐœ์ˆ˜๋Š” ๋ฆฌ์ŠคํŠธ regularClassTime.count์ด๋‹ค) cell์ด ๋™์ ์œผ๋กœ ์ฆ๊ฐ€๋œ๋‹ค.

 @IBAction func regularClassAddButton(_ sender: Any) {
        regularClassTime.append("์…€ ์ถ”๊ฐ€")
        tableView.reloadData()
 }

โ–ถ๏ธ ์ •๊ทœ ์ˆ˜์—…์‹œ๊ฐ„์„ ์ž…๋ ฅ๋ฐ›์„ Picker View Custom ํ•˜๊ธฐ

ํ…์ŠคํŠธ ํ•„๋“œ์— ํ‚ค๋ณด๋“œ ๋Œ€์‹  ํ”ผ์ปค๋ทฐ๋กœ ์ž…๋ ฅ์„ ๋ฐ›์œผ๋ฉฐ, toolbar์˜ bar button๋“ค๊ณผ ํ”ผ์ปค๋ทฐ ๋ชฉ๋ก์„ ์ปค์Šคํ…€ ํ•˜์—ฌ ์ •๊ทœ ์ˆ˜์—…์‹œ๊ฐ„์„ ์ž…๋ ฅ ๋ฐ›๋„๋ก ํ–ˆ๋‹ค. ๋˜ํ•œ ์‹œ์ž‘์‹œ๊ฐ„์„ ์ž…๋ ฅํ–ˆ์„ ๋•Œ, ๋ ์‹œ๊ฐ„์ด ์ž๋™์œผ๋กœ ์‹œ์ž‘์‹œ๊ฐ„๊ณผ ๋งž์ถฐ์ง€๋„๋ก didSelectRow ํ•จ์ˆ˜ ๋‚ด์— ์•„๋ž˜ ์†Œ์Šค์ฝ”๋“œ๋ฅผ ๊ตฌํ˜„ํ–ˆ๋‹ค.

pickerView.delegate = self
pickerView.dataSource = self
// ์ดํ›„ delegate ์™€ detaSource ํ•จ์ˆ˜๋ฅผ extention์œผ๋กœ ์ปดํฌ๋จผํŠธ ๊ฐœ์ˆ˜ ์ง€์ • ๋ฐ ๋ชฉ๋ก ๋ฐ์ดํ„ฐ ๋ฆฌ์ŠคํŠธ ์ ์šฉ

if startHours[pickerView.selectedRow(inComponent: 1)] != "00" { //์‹œ์ž‘์‹œ๊ฐ„์ด ์ž…๋ ฅ๋˜์—ˆ์œผ๋ฉด ๋๋‚˜๋Š” ์‹œ๊ฐ„๋„ ์‹œ์ž‘์‹œ๊ฐ„๊ณผ ๋™์ผํ•˜๋„๋ก ์ž๋™์œผ๋กœ ํ•ด๋‹น component์˜ wheel์ด ๋Œ์•„๊ฐ€๋ฉด์„œ ์„ค์ •๋จ
    startH = startHours[pickerView.selectedRow(inComponent: 1)]
    startrow = row
    pickerView.selectRow(startrow, inComponent: 3, animated: true)
    endH = endHours[pickerView.selectedRow(inComponent: 3)]
}

8๏ธโƒฃ TutorDot ๊ธฐ๋Šฅ ๋ช…์„ธ์„œ & ๊ธฐ๋Šฅ๋ณ„ ๊ฐœ๋ฐœ ์—ฌ๋ถ€

๊ธฐ๋Šฅ ์ด๋ฆ„ ์šฐ์„ ์ˆœ์œ„ ๋‹ด๋‹น์ž ๋ทฐ ๊ตฌํ˜„ ์—ฌ๋ถ€
์Šคํ”Œ๋ž˜์‹œ P1 ์ธ์ • ์Šคํ”Œ๋ž˜์‹œ O
์Šคํ”Œ๋ž˜์‹œ ์• ๋‹ˆ๋ฉ”์ด์…˜ P3 ์ธ์ • ์Šคํ”Œ๋ž˜์‹œ O
์•ฑ ์„ค๋ช… ์˜จ๋ณด๋”ฉ P2 ์ธ์ • ํšŒ์›๊ฐ€์ž… & ๋กœ๊ทธ์ธ O
ํšŒ์›๊ฐ€์ž… & ์—ญํ•  ์„ ํƒ P1 ์„ธํ™” ํšŒ์›๊ฐ€์ž… & ๋กœ๊ทธ์ธ O
์ด์šฉ์•ฝ๊ด€, ๊ฐœ์ธ์ •๋ณด๋ณดํ˜ธ์ •์ฑ… P3 ์„ธํ™” ํšŒ์›๊ฐ€์ž… & ๋กœ๊ทธ์ธ O
๋กœ๊ทธ์ธ P1 ์„ธํ™” ํšŒ์›๊ฐ€์ž… & ๋กœ๊ทธ์ธ O
์ž๋™ ๋กœ๊ทธ์ธ P3 ์„ธํ™” ํšŒ์›๊ฐ€์ž… & ๋กœ๊ทธ์ธ O
ํšŒ์›๊ฐ€์ž…/๋กœ๊ทธ์ธ ์„œ๋ฒ„ ์—ฐ๋™ P3 ์„ธํ™” ํšŒ์›๊ฐ€์ž… & ๋กœ๊ทธ์ธ O
์บ˜๋ฆฐ๋”ํ† ๊ธ€ P1 ์„ธํ™” ์บ˜๋ฆฐ๋” O
์บ˜๋ฆฐ๋” ์›” ๋ณ€๊ฒฝ (์ขŒ์šฐ ํ™”์‚ดํ‘œ) P1 ์„ธํ™” ์บ˜๋ฆฐ๋” O
์บ˜๋ฆฐ๋”์— ์ˆ˜์—… ์ผ์ • ํ‘œ์‹œ P1 ์„ธํ™” ์บ˜๋ฆฐ๋” O
์„ ํƒ ๋‚ ์งœ์˜ ์ผ์ • ํ‘œ์‹œ P1 ์„ธํ™” ์บ˜๋ฆฐ๋” O
ํ”Œ๋กœํŒ… ๋ฒ„ํŠผ + P1 ์„ธํ™” ์บ˜๋ฆฐ๋” O
์ผ์ • ์ •๋ณด ํ™”๋ฉด P1 ์„ธํ™” ์บ˜๋ฆฐ๋” O
์ผ์ • ํŽธ์ง‘/์‚ญ์ œ P2 ์„ธํ™” ์บ˜๋ฆฐ๋” O
์ผ์ • ์ถ”๊ฐ€ P1 ์„ธํ™” ์บ˜๋ฆฐ๋” O
์บ˜๋ฆฐ๋” ์„œ๋ฒ„ ์—ฐ๋™ P3 ์„ธํ™” ์บ˜๋ฆฐ๋” O
์ˆ˜์—…์ผ์ง€ํ† ๊ธ€ P1 ์ธ์ • ์ˆ˜์—…์ผ์ง€ O
์ˆ˜์—… ์ผ์ง€ (์›” ๋‹จ์œ„) P1 ์ธ์ • ์ˆ˜์—…์ผ์ง€ โ–ณ
์ˆ˜์—… ์ผ์ง€ ์ˆ˜์ • (์ž…๋ ฅ) P1 ์ธ์ • ์ˆ˜์—…์ผ์ง€ O
์ˆ˜์—… ์ผ์ง€ ์›” ๋ณ€๊ฒฝ (์ขŒ์šฐ ํ™”์‚ดํ‘œ) P3 ์ธ์ • ์ˆ˜์—…์ผ์ง€ O
๊ณผ์™ธ ์‹œ๊ฐ„ ๋‹ฌ์„ฑ๋ฅ  (๋ง‰๋Œ€๊ทธ๋ž˜ํ”„) P2 ์ธ์ • ์ˆ˜์—…์ผ์ง€ O
ํŠœํ‹ฐ ์ผ์ง€ ํŽธ์ง‘ ๋ฐฉ์ง€ P3 ์ธ์ • ์ˆ˜์—…์ผ์ง€ O
์ˆ˜์—… ์ผ์ง€ ์„œ๋ฒ„ ์—ฐ๋™ P3 ์ธ์ • ์ˆ˜์—…์ผ์ง€
์•Œ๋ฆผํ† ๊ธ€ P1 ์„ธํ™” ์•Œ๋ฆผ O
์•Œ๋ฆผ P1 ์„ธํ™” ์•Œ๋ฆผ O
์•Œ๋ฆผ ์‚ญ์ œ, ํ™•์ธ ๊ธฐ๋Šฅ P2 ์„ธํ™” ์•Œ๋ฆผ O
๋ฐ์ดํ„ฐ์— ๋”ฐ๋ฅธ ์•Œ๋ฆผ ๋ฉ”์‹œ์ง€ P2 ์„ธํ™” ์•Œ๋ฆผ
๊ฐ„ํŽธ ํ”„๋กœํ•„ P1 ์ธ์ • ๋‚ด์ •๋ณด O
ํ”„๋กœํ•„ ํŽธ์ง‘ P2 ์ธ์ • ๋‚ด์ •๋ณด O
์ˆ˜์—… ๋ฒ„ํŠผ P1 ์ธ์ • ๋‚ด์ •๋ณด O
์ˆ˜์—… ๋ฒ„ํŠผ์˜ ํ”„๋กœํ•„ P2 ์ธ์ • ๋‚ด์ •๋ณด O
์ˆ˜์—… ์ถ”๊ฐ€ P1 ์ธ์ • ๋‚ด์ •๋ณด O
์ˆ˜์—… ์ดˆ๋Œ€ P1 ์ธ์ • ๋‚ด์ •๋ณด O
์ดˆ๋Œ€ ์ฝ”๋“œ P1 ์ธ์ • ๋‚ด์ •๋ณด โ–ณ
์ˆ˜์—… ์ •๋ณด P1 ์ธ์ • ๋‚ด์ •๋ณด O
์ˆ˜์—… ์ •๋ณด ํŽธ์ง‘ P2 ์ธ์ • ๋‚ด์ •๋ณด O
๊ณ„์ขŒ ์ •๋ณด ๋ณต์‚ฌ ๋ฒ„ํŠผ P2 ์ธ์ • ๋‚ด์ •๋ณด O
๋‚ด์ •๋ณด ์„œ๋ฒ„ ์—ฐ๋™ P3 ์ธ์ • ๋‚ด์ •๋ณด
๋ฒ„์ „ ์ •๋ณด P3 ์ธ์ • ๋‚ด์ •๋ณด O
๊ฐœ๋ฐœ์ž ์ •๋ณด P3 ์ธ์ • ๋‚ด์ •๋ณด O
๋กœ๊ทธ์•„์›ƒ P3 ์ธ์ • ๋‚ด์ •๋ณด O




แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2020-07-18 แ„‹แ…ฉแ„Œแ…ฅแ†ซ 5 46 04 แ„‰แ…ณแ„แ…ณแ„…แ…ตแ†ซแ„‰แ…ฃแ†บ 2020-07-18 แ„‹แ…ฉแ„Œแ…ฅแ†ซ 5 46 54

Contributors

  ๐Ÿฐ26๊ธฐ iOSํŒŒํŠธ YB ๋ฅ˜์„ธํ™” SehwaRyu  |  @soonsophu

  ๐Ÿฑ26๊ธฐ iOSํŒŒํŠธ YB ์ตœ์ธ์ • InjeongChoi  |  @leanjeong