-
[iOS] Coordinator Pattern 적용iOS/EARLY BUDDY | iOS 2020. 7. 31. 22:21
안녕하세요! 오랜만의 포스팅입니다 :-)
SOPT라는 동아리에서 Appjam을 진행했던 '얼리버디' 서비스를 다시 갈아엎는 과정을 하고 있는데요!
아무래도 앱잼은 2주 동안 빠르게 프로젝트를 완성하는 장기 해커톤이다 보니 그때의 프로젝트를 다시 열어보면 제가 개발했지만 도저히 뭐가 뭔지 모르겠더라고요..
폴더링도 나름대로 했지만 너무너무 파일이 많고! 패턴이라고는 하나도 없는 그런 상태였습니다.
얼리버디 서비스는 navigation이 거의 주를 이루고 있어서 ViewController의 책임이 아주 무거웠는데요.
ViewController을 가볍게 만들고 쉽게 navigation 처리를 해주기 위해서 저는 디벨롭 과정에서 Coordinator 패턴을 적용하기로 마음먹었습니다!
우선 Coordinator 패턴에 대해 기본적으로 알아봐야 될 것 같아요!
Coordinator Pattern
우선 프로젝트 구조 중에서 MVC-C, MVVM-C라는 구조 많이 들어보셨을 것 같은데요.
마지막의 C를 의미하는 것이 바로 Coordinator입니다.Coordinator란,
- 화면 간의 연결 , DI (의존성 주입)을 해결합니다.
- View Controller들의 계층 관리, 화면 간의 연결을 해결합니다.
- View Controller를 가볍게 관리하고, 재사용하기 쉽게 합니다.
한 화면의 레이아웃, 로직만을 뷰 컨트롤러에서 관리하고 나머지 처리는 코디네이터에 맡깁니다. - Coordinator 구현체 안에 Navigation 처리를 관리합니다.
결론적으로는 코디네이터 패턴을 사용하면 뷰 컨트롤러의 책임이 가벼워지고, Navigation 처리와 뷰 간의 데이터 전송 등의 관리를 따로 모아서 관리할 수 있습니다.
EARLY-BUDDY 내의 Coordinator Pattern
이런 식으로 모든 코디네이터의 부모 클래스인 MainCoordinator 안에 자식 코디네이터 클래스들 (AddScheduleCoordinator, CalendarCoordinator ...)이 존재하도록 구조를 짰습니다.
막무가내로 구글링을 해가며 코디네이터 패턴을 적용하려고 하다 보니, 데모 코드에는 childCoordinator, parentCoordinator 같은 것들이 있는데 어떻게 사용해야 하는지 감이 잡히지 않는 이슈들이 있었습니다.
Coordinator Pattern에서 Parent-Child 사용
우선 메인 뷰가 먼저 뜨고 일정 등록 뷰로 넘어갈 때 MainCoordinator에서 AddScheduleCoordinator로 넘어가야 합니다.
이 처리는 아래와 같이 합니다.
class MainCoordinator: Coordinator { var childCoordinators = [Coordinator]() var navigation: UINavigationController init(navigation: UINavigationController) { self.navigation = navigation } func start() { let vc = MainVC.instantiate(storyboardName: "Main") vc.coordinator = self self.navigation.pushViewController(vc, animated: false) } func addSchedule() { let child = AddScheduleCoordinator(navigation: navigation) child.parentCoordinator = self childCoordinators.append(child) child.start() } }
우선 메인 코디네이터에서 일정 등록 뷰로 넘어가는 addSchedule()이라는 함수를 만듭니다.
이 함수에는 AddScheduleCoordinator를 생성하는 child라는 상수가 있습니다.
자식 코디네이터의 부모 코디네이터를 self로 설정하고 MainCoordinator의 자식 코디네이터 배열에 child를 추가합니다.
그 후에 child에 있는 start() 함수를 실행하는 함수입니다.
class AddScheduleCoordinator: Coordinator { weak var parentCoordinator: MainCoordinator? var childCoordinators: [Coordinator] = [] var navigation: UINavigationController init(navigation: UINavigationController) { self.navigation = navigation } func start() { let vc = MainAddScheduleVC.instantiate(storyboardName: "AddSchedule") vc.navigationItem.title = "일정 등록" vc.navigationController?.navigationBar.backItem?.title = "" vc.coordinator = self navigation.pushViewController(vc, animated: true) } }
이렇게 자식 코디네이터인 AddScheduleCoordinator에 start() 함수는 메인 일정 등록 뷰를 보여주는 함수입니다.
위와 같이 설정하고 뷰 컨트롤러에 코디네이터를 할당한 후 디바이스를 실행하면 아래와 같은 화면이 나옵니다.
이렇게 슝~ 넘어갑니다!
(그런데 저기 backItem.title에 분명 빈 문자열 할당해줬는데 자꾸 왜 전 뷰의 title이 뜨는지 아시는 분 계신가요..? 왜 저러지..)Coordinator 간의 데이터 전달
우선 얼리버디 앱에서는 테이블 뷰의 장소를 클릭하면 맵 뷰로 넘어가는 부분이 있는데요.
그렇게 구현하려면 테이블 뷰 셀을 클릭했을 때, 셀 내에 있는 장소 이름, 장소의 카테고리, 장소 도로명 주소가 같이 넘어가서 뷰에 띄워져야 하고, 또한 해당 장소의 좌표가 넘어가서 맵을 띄워야 합니다!
이것을 구현해보겠습니다.
class SearchPathVC: UIViewController, Storyboarded { weak var coordinator: AddScheduleCoordinator? ... (생략) ... // MARK:- UITableView Delegate extension SearchPathVC: UITableViewDelegate { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { guard let cell = tableView.cellForRow(at: indexPath) as? PlaceCell else { return } guard let x = cell.model?.x, let y = cell.model?.y else { return } if Double(x) != nil, Double(y) != nil { selectXPos = Double(x) selectYPos = Double(y) } guard let latitude = selectYPos, let longtitude = selectXPos, let title = self.navigationItem.title else { return } coordinator?.showMap(lat: latitude, lng: longtitude, placeName: cell.placeNameLabel.text, category: cell.categoryLabel.text, roadAddress: cell.roadAddressLabel.text, navigationTitle: title) } } }
자 우선 테이블 뷰를 띄우는 뷰 컨트롤러 파일입니다.
딜리게이트를 적용해서 didSelectRowAt 함수 부분에 코디네이터를 적용했습니다.
cell에 적용된 latitude, longtitude 좌표와 placeName, category, roadAddress를 가져와서 코디네이터 함수에 적용합니다.
func showMap(lat: Double, lng: Double, placeName: String?, category: String?, roadAddress: String?, navigationTitle: String) { let vc = MapVC.instantiate(storyboardName: "AddSchedule") vc.navigationController?.navigationBar.backItem?.title = "" vc.lat = lat vc.lng = lng vc.placeName = placeName vc.category = category vc.roadAddress = roadAddress vc.navigationItem.title = navigationTitle vc.coordinator = self navigation.pushViewController(vc, animated: true) }
이거는 코디네이터의 showMap() 함수입니다.
데이터를 넘길 뷰 컨트롤러를 vc 상수로 할당한 후에 해당 뷰 컨트롤러에 있는 변수들에 하나씩 값을 넣어줍니다.
// MARK:- 변수들 var mapView: NMFNaverMapView? var lat: Double? var lng: Double? var placeName: String? var category: String? var roadAddress: String? var navigationTitle: String?
이렇게 변수들로 받아서
guard let mapView = mapView else { return } let cameraUpdate = NMFCameraUpdate(scrollTo: NMGLatLng(lat: latitude, lng: longtitude)) mapView.mapView.moveCamera(cameraUpdate) let marker = NMFMarker() marker.position = NMGLatLng(lat: latitude, lng: longtitude) marker.mapView = mapView.mapView marker.iconImage = NMFOverlayImage(name: "icMapMarker") searchPathTF.text = placeName searchPathTF.isEnabled = false placeNameLabel.text = placeName categoryLabel.text = category roadAddressLabel.text = roadAddress
위와 같이 하나씩 값을 할당해주면 됩니다.
아래는 위의 과정이 적용된 실행 결과입니다.
데이터랑 지도가 로딩되는데 시간이 좀 걸리긴 하지만! 추후에 로딩 뷰를 적용하는 것으로 하고 우선 데이터가 잘 넘어간 것을 확인할 수 있습니다.
포스팅 봐주셔서 감사합니다. 🤗
'iOS > EARLY BUDDY | iOS' 카테고리의 다른 글
[iOS] 테이블 뷰 셀 안에 컬렉션 뷰 넣기 (0) 2020.08.28 [iOS] FMDB를 사용한 최근 장소 검색 기능 구현 (0) 2020.08.15 [iOS] FMDB 사용하기 (0) 2020.08.14