Skip to content

Latest commit

History

314 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

👩🏻‍🏫 튜터링 관리의 마침표, TutorDot


🍎 26기 OUR SOPT APP-JAM 'TutorDot' 🍏

iOS 파트 : YB 류세화, YB 최인정

프로젝트 기간 : 2020.6.27 ~ 2020.7.18


1️⃣ 역할 분담

ViewGit Branch담당자
CalendarViewcalendar_branch세화
AlertViewalert_branch세화
JournalViewjournal_branch인정
MyPageViewmypage_branch인정
SplashViewsplash_branch인정
LoginSignUpViewlogin_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
    // 변수명
    vardropDownButton:UIButton!
    // 상수명
    letheaderView=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함수를 작성하였다.

letanimationView=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에 해당하는 이미지 이름을 이미지 뷰에 적용해주었고, 추가로 이미지 전환 효과를 주었다.

@objcfunc 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(){ //
lettextFieldTap=UITapGestureRecognizer(target:self, action: #selector(handleTapTextField(_:)))
textFieldTap.delegate =selfself.view.addGestureRecognizer(textFieldTap)}
// 다른 위치 탭했을 때 키보드 없어지기
@objcfunc handleTapTextField(_ sender:UITapGestureRecognizer){ //
self.emailTextField.resignFirstResponder()self.passWordTextField.resignFirstResponder()}
// 키보드가 생길 떄 텍스트 필드 위로 밀기
@objcfunc keyboardWillShow(_ notification:NSNotification){guardlet duration = notification.userInfo?[UIResponder.keyboardAnimationDurationUserInfoKey]as?Doubleelse{return}guardlet curve = notification.userInfo?[UIResponder.keyboardAnimationCurveUserInfoKey]as?UIntelse{return}guardlet keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey]as?NSValueelse{return}letkeyboardHeight: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
forindexin0..<classList2.count {print(classList2[index].classDate)letdateMonthInt= currentMonthIndex +1letdate2=Int(date)letdayMove=String(format:"%02d", arguments:[dateMonthInt])letdayMove2=String(format:"%02d", date2 as!CVarArg)ifclassList2[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])foriin0..<self.classDateList.count {lethourTimes="\(self.classDateList[i].times)회차, \(self.classDateList[i].hour)시간"
tutorInfoCell.classHourLabel.text = hourTimes}
return tutorInfoCell

▶️ 뷰 처음 로드시 캘린더의 날짜에 일정이 존재하면 색깔 점으로 표시

// CalendarCollectionView
if classDateMonthZeros == dayMove && classDateDay == todaysDate {letimageName=classList2[i].color
if calendarCell.image1.image ==UIImage(named:""){
calendarCell.image1.image =UIImage(named: imageName)}elseif 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)inself.dropDownLabelButton.setTitle(item, for:.normal)self.classListToggle.removeAll()self.tutorCollectionView.reloadData()
// 전체 선택시
ifself.dropDownLabelButton.title(for:.normal)=="전체"{self.classList2 =self.classList2Copy
print(self.classList2)self.dateCollectionView.reloadData()self.tutorCollectionView.reloadData()}else{
// 수업 리스트 선택시
self.classList2 =self.classList2Copy
foriin0..<self.classList2.count {ifself.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 inswitch networkResult {case.success(let resultData):print("successssss")
//guard let data = resultData as? [CalendarData] else { return }
guardlet data = resultData as?[CalendarData]else{returnprint(Error.self)}print("try")forindexin0..<data.count {letitem=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 =0case.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?{letdelete=UIContextualAction(style:.destructive, title:"삭제"){(action, sourceView, completionHandler)inself.noticeList.remove(at: indexPath.row)self.noticeTableView.reloadData()print(self.noticeList.count)
//completionHandler(true)
}letconfirm=UIContextualAction(style:.normal, title:"확인"){(action, sourceView, completionHandler)inprint("index path of edit: \(indexPath)")self.noticeList[indexPath.row].newNotice =falseself.noticeTableView.reloadData()completionHandler(true)}letswipeActionConfig=UISwipeActionsConfiguration(actions:[delete, confirm])
swipeActionConfig.performsFirstActionWithFullSwipe =falsereturn 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 선언
protocolAddRegularClassTimeCellDelegate:class{func setScheduler(_ date:String, _ start:String, _ end:String)}
// Cell 파일에 delegate 변수 선언 및 함수 호출
vardelegate:AddRegularClassTimeCellDelegate?iflet delegate = delegate {
delegate.setScheduler(days, startTime, endTime)}
// 해당 VC에 extention으로 셀파일 protocol에 정의된 함수부를 구현한다.
extensionMyClassAddVC:AddRegularClassTimeCellDelegate{func setScheduler(_ date:String, _ start:String, _ end:String){letnewSchedule=Schedules(day: date, orgStartTime: start, orgEndTime: end)
schedule.append(newSchedule)}}

▶️ VC 내부의 버튼을 누를 때마다 table view Cell 동적으로 증가 시키기

VC내에 빈 리스트 regularClassTime을 두고 버튼을 눌렀을 때 리스트에 append를 해서 개수를 늘려 준 다음 tableView.reloadData()를 해주면 (테이블 row 개수는 리스트 regularClassTime.count이다) cell이 동적으로 증가된다.

@IBActionfunc regularClassAddButton(_ sender:Any){
regularClassTime.append("셀 추가")
tableView.reloadData()}

▶️ 정규 수업시간을 입력받을 Picker View Custom 하기

텍스트 필드에 키보드 대신 피커뷰로 입력을 받으며, toolbar의 bar button들과 피커뷰 목록을 커스텀 하여 정규 수업시간을 입력 받도록 했다. 또한 시작시간을 입력했을 때, 끝 시간이 자동으로 시작시간과 맞춰지도록 didSelectRow 함수 내에 아래 소스코드를 구현했다.

pickerView.delegate =self
pickerView.dataSource =self
// 이후 delegate 와 detaSource 함수를 extention으로 컴포먼트 개수 지정 및 목록 데이터 리스트 적용
ifstartHours[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

About

튜터링 관리의 마침표 TutorDot - iOS part👩🏻‍💻

Resources

Stars

14 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages