Skip to content

경북대학교 소모임 프로젝트

Notifications You must be signed in to change notification settings

kimseongj/Parting

 
 

Repository files navigation

👬 새로운 사람들과 새로운 모임을 가져볼까요? Parting!



👬 Parting

개발기간: 2024.01.01~ing

Party 와 -ing의 합성어로 이용자가 원하는 카테고리의 일회성 모임, 번개 모임 등을 생성하고 참여할 수 있는 앱입니다. 다양한 카테고리의 모임을 가까운 지역에서 찾을 수 있습니다.



⚒️ 기능 소개

다양한 사람들과 새로운 모임을 해보고 싶나요?

Parting을 통해 시작해보세요!

원하는 카테고리의 모임을 찾아보고 참여해보세요!

지도를 통해서 근처 파티를 찾아보세요!

Parting UI

앱진입

로그인 상세정보 기입 카테고리 설정 세부 카테고리 설정

메인화면

메인화면 파티리스트 파티리스트 세부설정 달력UI

기타 탭바

NaverMap 마이페이지 프로필 수정


⚙️ 기술 스택





📝 아키텍쳐- MVVM/C

MVVM - C



🏃기술적 도전

MVVM - C

MVVM - C

MVVM-C 아키텍쳐 패턴은 MVVM패턴을 유지하며 Coordinator패턴을 통해 화면 전환을 하는 복합패턴입니다. 대부분 View에서 Coordinator를 의존하는 형태이지만 해당 프로젝트에서는 Coordinator를 ViewModel에서 의존하고 있습니다. ViewModel이 Coordinator를 의존하고 있는 이유는 ViewModel의 input이 View의 사용자 이벤트, lifeCycle 등을 enum으로 관리하고 있기 때문입니다. 이 input을 binding하여 해당 이벤트가 발생할 때, ViewModel에서 그 이벤트에 맞는 반응을 해줘야하기 때문에 ViewModel이 Coordinator를 의존하고 있는 형태를 띄고 있습니다.

🔥 결론은 MVVM과 Coordinator의 합성에 있어 Coordinator의 의존성을 받는 곳이 View이든 ViewModel이든 기능에 맞게 설계할 수 있습니다.

RxSwift

RxSwift

RxSwift는 반응형 프로그래밍을 구축하기 위한 라이브러리로, Xcode 내장 프레임워크인 Combine이 만들어지기 전에 사용되던 라이브러리입니다. RxSwift는 현재도 많은 기업에서 채택하고 있는 라이브러리로 해당 프로젝트를 진행하면서 다뤄볼 수 있었습니다. RxSwift는 Observable(= Publisher)와 Observer(= subscriber)로 이루어져있습니다.

  • Observable은 비동기 이벤트가 일어날 때마다 item을 방출합니다.
  • Observer는 Observable를 구독하여 비동기처리가 끝나는 시점에 이벤트를 실행할 수 있는 closure를 지원해줍니다.
  • Subject는 Observer이기 때문에 Observable을 구독할 수 있으며, 동시에 Observable이기 때문에 새로운 항목들을 방출할 수 있습니다.
NaverMap API활용

NaverMap

NaverMap API를 활용하여 카메라 이동에 따른 API호출을 구현할 수 있었습니다. 또한 카메리 이동시 호출된 데이터를 통해 마커를 표시하여 현재 카메라에 있는 파티들을 띄어주는 기능을 구현했습니다.

🔥 해당 이벤트 호출에 있어 카메라 위치가 바뀔때마다 호출되는 mapViewCameraIdle(_ mapView: NMFMapView)메서드가 있습니다. 해당 메서드는 카메라를 움직일때마다 바로바로 호출되기 때문에 너무 많은 API호출을 하게 될 수 있습니다. 이를 방지하기 위해 아래와 같이 timeInterval을 사용했습니다.

func startAPICallDebouncer() {
        timer.invalidate()
        timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(handleAPICall), userInfo: nil, repeats: false)
    }
    
    @objc func handleAPICall() {
        let southWest = rootView.mapView.mapView.projection.latlngBounds(fromViewBounds: self.view.frame).southWest
        let northEast = rootView.mapView.mapView.projection.latlngBounds(fromViewBounds: self.view.frame).northEast
        viewModel.searchPartyOnMap(searchHighLatitude: northEast.lat, searchHighLongitude: northEast.lng, searchLowLatitude: southWest.lat, searchLowLongitude: southWest.lng)
    }
    
    func mapViewCameraIdle(_ mapView: NMFMapView) {
        startAPICallDebouncer()
    }
    
ViewModel Input/Output/State 분리

Input/Output

ViewModel에서 Input과 Output을 나누는 것은 코드 가독성에 이점을 줍니다.

  • Input의 경우 enum을 Relay로 생성하여 구독하는 형태로 enum case에 여러 이벤트들을 설정해줍니다.
enum Input {
        case BirthTextFieldTrigger
        case viewdidLoadTrigger
        case editCompleteButtonClicked
}
    var input = PublishRelay<Input>()
  • Output의 경우 struct 구조로 다양한 Relay들을 인스턴스로 가지고 있습니다.
struct Output {
        var nameTextField = BehaviorRelay<String>(value: "")
        var selectedGender = BehaviorRelay(value: GenderCase.man)
        var birthTextField = BehaviorRelay<String>(value: "")
        var regionTextField = BehaviorRelay<String>(value: "")
        var introduceTextView = BehaviorRelay<String>(value: "")
        var completeButtonIsValidState = BehaviorRelay(value: false)
}

🔥 Input, Output을 Relay로 사용하는 이유 : Subject의 경우 Completed, Error 등의 여러 closure를 제공합니다. 하지만 input과 output의 경우 Error처리 및 Completed를 처리할 필요가 없습니다. 그렇기 때문에 Next closure만 제공하는 Relay를 사용합니다. (Error처리가 필요할 경우 Subject로 바꿀 필요 있음)

페이징 구현

페이징 구현

API호출 시 다량의 데이터가 한번에 호출되는 경우를 방지하기 위해 TableView의 페이징을 구현했습니다. page를 나누고 데이터 배열에서 10개씩 가져오도록 구현하였으며, TableView의 RootView에 Activity Indicator를 삽입하여 페이징이 진행되고 있음을 UI에 구현하였습니다.

  • Paging 구현 시 제일 중요한 메서드인 scrollViewDidScroll(_ scrollView: UIScrollView)를 사용하였습니다. 해당 메서드는 ViewController 내부에서 실행되는 Scrolling Event를 감지합니다.
  • 아래 그림과 같이 Height1(Scrolling한 길이)가 Height2(TableView ContentView의 Height - 디바이스의 Height)를 넘어서면 분기처리를 하여 Paging이벤트를 실행시킵니다.
    func scrollViewDidScroll(_ scrollView: UIScrollView) {
        let offsetY = scrollView.contentOffset.y
        let contentHeight = scrollView.contentSize.height
        let height = scrollView.frame.height
        
        if offsetY > (contentHeight - height) && !isPaging && viewModel.output.hasNextPage {
            rootView.activityIndicator.startAnimating()
            beginPaging()
        } 
    }
    
    func beginPaging() {
        isPaging = true
        pagepartyListTableView()
    }
    
    func pagepartyListTableView() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) { [weak self] in
            guard let self = self else { return }
            
            viewModel.pagePartyList()
            self.rootView.activityIndicator.stopAnimating()
            self.isPaging = false
        }
    }

About

경북대학교 소모임 프로젝트

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Swift 100.0%