안냐세요
바텀시트 완성해 왔서요
제가 원하는 기능은 지도 위의 특정 마커를 터치하면 그 장소에 관한 정보를 바텀 시트로 띄워주는 것이었어요

도전기
1. 커스텀 바텀 시트 구현
: 커스텀 해줘야 할거 너무 많아서 포기
예를들면 화면을 터치하면 얼만큼 내려오고,
마커를 터치하면 얼만큼 올라오고 이런 것들을 다 계산해서 코드로 지정해줘야 해서
넘나 복잡한 것
기각!
2. PanModal 라이브러리 사용
: 쉽게 바텀 시트를 구현할 수 있는 유용한 라이브러리 인데요
얘는 바텀시트가 올라왔을때 뒤에 화면이 활성화가 안되서 기각
저는 바텀 시트 올라온 상태에서도 다른 마커 터치하면 그 마커에 대한 바텀 시트로 바껴야 하거등요
3. FloatingPanel 라이브러리 사용
: 결론적으로 얘를 사용했구요
위에서 말한 제 요구사항을 만족해서 이걸 사용 함
https://github.com/scenee/FloatingPanel
GitHub - scenee/FloatingPanel: A clean and easy-to-use floating panel UI component for iOS
A clean and easy-to-use floating panel UI component for iOS - scenee/FloatingPanel
github.com
FloatinfPanel을 이용한 바텀 시트 구현기
그럼 본격적으로 어떻게 구현했나 알아봅시다.
1. FloatingPanel 라이브러리 import
먼저, 라이브러리를 프로젝트에 추가해야 합니다.
전 SDK로 깔고
import FloatingPanel
ㄱㄱ
2. 두 개의 ViewController 준비
바텀 시트 구현의 핵심은 두 개의 ViewController를 사용하는 것!
아무래도 띄울 화면 하나, 띄워질 화면 하나 웅
- HomeViewController
: 메인 화면입니다. 지도를 보여주고, 사용자의 인터랙션(마커 탭 등)을 감지하는 역할을 합니다. - SmokingAreaBottomSheetViewController
: 바텀 시트의 내용물입니다. 마커에 해당하는 상세 정보를 보여주는 UI를 담고 있습니다.
HomeViewController가 FloatingPanelController라는 '틀'을 만들고,
그 안에 SmokingAreaBottomSheetViewController라는 '내용물'을 쏙 집어넣는 구조인 것임
3. HomeViewController: 바텀 시트의 '틀' 만들기
HomeViewController에서 바텀 시트를 어떻게 설정하고 보여주는지 단계별로 살펴봅시다
1단계: 바텀 시트 초기 설정 (showBottomSheet 함수)
viewDidLoad에서 호출되는 이 함수는 바텀 시트를 만들고 초기화하는 역할을 함
전 extension으로 따로 뺌
// HomeViewController.swift
extension HomeViewController: FloatingPanelControllerDelegate {
func showBottomSheet() {
// 1. FloatingPanelController 인스턴스를 생성합니다. 이거시 바로 바텀 시트의 '본체'
floatingPanelController = FloatingPanelController()
// 2. 바텀 시트의 외형을 커스텀 (모서리 둥글게)
floatingPanelController?.surfaceView.layer.cornerRadius = 15
floatingPanelController?.surfaceView.layer.masksToBounds = true
// 3. delegate를 self로 설정하여 바텀 시트의 움직임 등을 감지할 수 있게 함
floatingPanelController?.delegate = self
// 4. 가장 중요! 바텀 시트 안에 보여줄 '내용물' ViewController를 지정 (set)
floatingPanelController?.set(contentViewController: smokingAreaBottomSheetVC)
// 5. 현재 ViewController(HomeViewController)에 바텀 시트를 추가
floatingPanelController?.addPanel(toParent: self)
// 6. 처음에는 보이지 않도록 '숨김(.hidden)' 상태로 설정
floatingPanelController?.move(to: .hidden, animated: true)
}
}
인스턴스 생성 -> 델리게이트 설정 -> 바텀 시트 지정 -> 바텀 시트 추가 -> 초기 상태 설정
솔직히 잘 이해안되는데요
이해하는게 아니라 이렇게 하는거라고 하니까,,,그런갑다
저는 처음에는 아예 안보였으면 좋겠어서 hidden 처리함
.tip이나 .half도 있어요
2단계: 마커 탭과 바텀 시트 연결 (bind 함수)
사용자가 마커를 탭 했을 때, 바텀 시트가 '짠'하고 나타나야 합니다.
이 연결고리는 요즘 공부 중인 RxSwift를 사용해볼게요 (어려어)
- 사용자가 마커를 탭 하면 markerTapped라는 PublishSubject가
해당 마커의 SmokingArea 데이터를 담아 이벤트를 방출!
(smokingAreas 함수 내 touchHandler 참고)
참고 : touchHandler는 네이버 공식 문서에서 발췌함 - bind 함수는 이 이벤트를 구독(subscribe) 하고 있다가, 데이터가 들어오면 행동 ㄱㄱ
// HomeViewController.swift
private func bind() {
// markerTapped 이벤트를 구독
markerTapped
.subscribe(onNext: { [weak self] areaData in // 데이터(areaData)가 들어왔을 때 실행
guard let self = self else { return }
// 1. 바텀 시트 내용물(VC)에 데이터를 전달하여 UI를 업데이트
self.smokingAreaBottomSheetVC.configure(with: areaData)
// 2. 숨겨져 있던 바텀 시트를 중간(.half) 높이까지 올림
self.floatingPanelController?.move(to: .half, animated: true)
})
.disposed(by: disposeBag) // 메모리 관리를 위해 disposeBag에 추가
}
정리하자면,
마커 탭 → markerTapped 이벤트 발생 → bind 함수가 감지 → 바텀 시트 내용 업데이트 후 화면에 표시의 흐름
아이고 어렵다
4. SmokingAreaBottomSheetViewController: '내용물' 채우기
이제 바텀 시트 안에 들어갈 SmokingAreaBottomSheetViewController를 살펴보아요
이 ViewController의 역할은 데이터를 받아 화면을 그리는 것!
configure(with data: SmokingArea) 함수
아래는 HomeViewController로부터 SmokingArea 데이터를 전달받는 핵심 메서드임
// SmokingAreaBottomSheetViewController.swift
public func configure(with data: SmokingArea) {
DispatchQueue.main.async { // UI 업데이트는 반드시 메인 스레드에서!
// 1. 전달받은 데이터로 이름과 설명 레이블의 text를 설정
self.nameLabel.text = data.name
self.descriptionLabel.text = data.description
// 2. 이전에 표시되던 태그가 있다면 모두 제거
for section in self.tagSections {
section.removeFromSuperview()
}
self.tagSections.removeAll()
// 3. 새로운 데이터로 태그 섹션(환경, 유형, 시설)을 다시 만듦
let envSection = self.makeTagSection(title: "환경", tags: data.selectedEnvironmentTags)
let typeSection = self.makeTagSection(title: "유형", tags: data.selectedTypeTags)
let facilitySection = self.makeTagSection(title: "시설", tags: data.selectedFacilityTags)
self.tagSections = [envSection, typeSection, facilitySection]
// 4. 새로 만든 태그 섹션들을 화면에 추가
for section in self.tagSections {
self.rootFlexContainer.flex.addItem(section).marginTop(20)
}
// 5. FlexLayout을 사용하여 레이아웃을 다시 계산하고 적용
self.rootFlexContainer.flex.layout()
}
}
태그들 만들고 추가하고 관리하느라 겁나 복잡해졌는데요
어쨋든 큰 틀은 "데이터 연결 -> (태그들 만들고) -> 다시 띄워 -> 지워 -> 다시 만들어 ..."를 메인 쓰레드에서 해!!!
이거예요
아닌가..
하여간 이 configure 메서드 덕분에 SmokingAreaBottomSheetViewController는 어떤 데이터가 들어오든 유연하게 안의 내용을 바꿀 수 있습니다.
5. 전체 흐름 다시 보기
- HomeViewController: showBottomSheet()으로 바텀 시트의 '틀'을 미리 만들어 둠
- 사용자: 지도 위의 마커를 탭!
- HomeViewController: 마커 탭을 감지하고, bind() 함수를 통해 바텀 시트의 '내용물'인 smokingAreaBottomSheetVC에게 configure(with:) 메서드로 마커 정보를 전달
- SmokingAreaBottomSheetViewController: 전달받은 정보로 자신의 UI(이름, 설명, 태그 등)를 업데이트
- HomeViewController: 내용이 채워진 바텀 시트를 move(to: .half) 명령으로 화면에 나타나게 함
- 사용자: 지도를 탭 하면 didTapMap 델리게이트 메서드가 호출되어 바텀 시트가 다시 아래로 사라짐 ㅃㅃ
대박 복잡하쥬
솔직히 저 다는 이해못함 ㅠㅠ
하지만 이것만 붙잡고 있을 순 없으니께
이렇게 글로 정리 함 하고 넘어가 볼게요...
그렇게 완성된 모습!!!

바텀 시트도 바텀 시트인데 flexlayout 땜에 미쳐돌뻔함

그럼,,,안녕히
'iOS > 흡구오디 -> 어딨쥐' 카테고리의 다른 글
| [흡구오디] Swift - Cloud Storage로 사진 업로드/다운로드 하기 | 사진 업로드해서 보여주기 (0) | 2025.09.14 |
|---|---|
| [흡구오디] Swift 사진 찍는 기능 구현하기 | UIImagePickerController (0) | 2025.09.14 |
| [흡구오디] 흡연구역 정보를 띄우기 위한 bottomSheet (2) | 2025.08.31 |
| [흡구오디] NMFMapView와 NMFNaverMapView의 차이를 알아보자! (0) | 2025.08.31 |
| [흡구오디] FireStore의 데이터 가져와서 화면에 띄워주기 (1) | 2025.08.31 |
댓글