ㅎㅇ여
이번에는 마커를 클릭했을 때 하단의 정보창을 띄우기 위한 과정을 포스팅 해볼게요
찾아보니 이걸 bottomSheet라고 하더라구요?
apple 공식 문서에서는 아래와 같은 코드를 제시함
func showMyViewControllerInACustomizedSheet() {
let viewControllerToPresent = MyViewController()
// 1. sheetPresentationController 가져오기
if let sheet = viewControllerToPresent.sheetPresentationController {
// 2. detents: 시트 높이 단계 지정
sheet.detents = [.medium(), .large()]
// → medium: 화면 반쯤
// → large: 화면 꽉 채우기
// 3. dimming(뒤 배경 어둡게) 설정
sheet.largestUndimmedDetentIdentifier = .medium
// → medium 크기일 땐 뒤 배경 어두워지지 않음
// → large 로 올리면 어두워짐
// 4. 스크롤 확장 관련 옵션
sheet.prefersScrollingExpandsWhenScrolledToEdge = false
// → 스크롤 뷰 맨 위에서 더 끌어도 자동 확장 안 되게
// 5. compact height (아이폰 가로 모드 같은 상황)에서
// 화면 하단에 붙을지 여부
sheet.prefersEdgeAttachedInCompactHeight = true
// 6. compact 환경일 때, 시트가 가로 폭에 맞게 붙을지 여부
sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true
}
// 7. 시트 띄우기
present(viewControllerToPresent, animated: true, completion: nil)
}
주석은 제가 코드 파악하기 위해서 달아놓음
저는 미듐, 라지가 아닌 높이의 30%정도만 되는 바텀 시트를 정보창으로 띄우고 싶단 말이죠?
그럼 어케 해야할까
https://developer.apple.com/documentation/uikit/uisheetpresentationcontroller/detents
detents | Apple Developer Documentation
The array of heights where a sheet can rest.
developer.apple.com
private func showMyViewControllerInACustomizedSheet() {
let viewControllerToPresent = SmokingAreaBottomSheetViewController()
if let sheet = viewControllerToPresent.sheetPresentationController {
let customDetent = UISheetPresentationController.Detent.custom(identifier: .init("thirtyPercent")) { context in
return context.maximumDetentValue * 0.3
}
sheet.detents = [customDetent, .large()]
sheet.selectedDetentIdentifier = .init("thirtyPercent") // 처음 뜰 때 30%로
sheet.largestUndimmedDetentIdentifier = .large
sheet.prefersScrollingExpandsWhenScrolledToEdge = false
sheet.prefersEdgeAttachedInCompactHeight = true
sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true
}
present(viewControllerToPresent, animated: true, completion: nil)
}
이렇게 커스텀으로 만들 수가 있다네요...
솔직히 짚히티의 도움 없이는 못햇을..^^

바텀 시트는 일단 그냥 빨간화면으로,,
아래는 마커 터치 이벤트가 발생하면 바텀 시트를 띄우는 코드인데요 by Gemini
요즘 공부 중인 observer / observable / subject / bind 등등에 대한 이해가 필요해서 일단 코드만 팁해두겠음..
import CoreLocation
import FirebaseCore
import FirebaseFirestore
import NMapsMap
import RxSwift
import RxCocoa
import SnapKit
import Then
import UIKit
final class HomeViewController: UIViewController {
// MARK: Constant
private enum Metric {
static let addButtonTrailing: CGFloat = 24
static let addButtonBottom: CGFloat = 40
}
// MARK: UI
private let mapView = NMFNaverMapView()
private let addButton = UIButton().then {
$0.setImage(UIImage(named: "plusButton"), for: .normal)
}
// MARK: Property
private let db = Firestore.firestore()
private let locationManager = CLLocationManager()
// 지도에 표시된 마커들을 관리하기 위한 배열
private var markers: [NMFMarker] = []
private let markerTapped = PublishSubject<(name: String, description: String)>()
private let disposeBag = DisposeBag()
// MARK: LifeCycle
override func viewDidLoad() {
super.viewDidLoad()
self.setup()
self.addSubviews()
self.makeConstraints()
self.setLocationManager()
self.bind()
self.smokingAreas()
}
// MARK: Setup
private func setup() { self.navigationItem.title = "Home" }
private func addSubviews() {
self.view.addSubview(self.mapView)
self.view.addSubview(self.addButton)
}
private func makeConstraints() {
self.mapView.snp.makeConstraints {
$0.edges.equalToSuperview()
}
self.addButton.snp.makeConstraints {
$0.trailing.equalToSuperview().inset(Metric.addButtonTrailing)
$0.bottom.equalToSuperview().inset(Metric.addButtonBottom)
}
}
// MARK: Action
private func bind() {
// addButton 탭 이벤트 구독
self.addButton.rx.tap
.subscribe(onNext : { [weak self] in
let markerPositionSeletorVC = MarkerPositionSelectorViewController()
self?.navigationController?.pushViewController(markerPositionSeletorVC, animated: true)
})
.disposed(by: self.disposeBag)
// markerTapped Subject 구독
self.markerTapped
.subscribe(onNext: { [weak self] (name, description) in
self?.showBottomSheet(name: name, description: description)
})
.disposed(by: self.disposeBag)
}
// MARK: Area Marker
private func smokingAreas() {
db.collection("smokingAreas").addSnapshotListener { [weak self] snapshot, error in
guard let self = self, let snapshot = snapshot else {
// TODO: Handle error
return
}
// 기존 마커들을 지도에서 제거하고 배열을 비웁니다.
for marker in self.markers {
marker.mapView = nil
}
self.markers.removeAll()
for doc in snapshot.documents {
let data = doc.data()
guard let name = data["name"] as? String,
let description = data["description"] as? String,
let areaLat = data["areaLat"] as? Double,
let areaLng = data["areaLng"] as? Double else { continue }
let areaMarker = NMFMarker()
areaMarker.iconImage = NMFOverlayImage(name: "marker_Pin")
areaMarker.position = NMGLatLng(lat: areaLat, lng: areaLng)
// 마커 터치 시 markerTapped Subject에 데이터를 담아 onNext 이벤트를 보냅니다.
areaMarker.touchHandler = { (overlay) -> Bool in
self.markerTapped.onNext((name: name, description: description))
return true
}
areaMarker.mapView = self.mapView.mapView
// 새로 생성된 마커를 배열에 추가하여 관리합니다.
self.markers.append(areaMarker)
}
}
}
private func showBottomSheet(name: String, description: String) {
let bottomSheetVC = SmokingAreaBottomSheetViewController()
// 데이터를 바텀 시트 ViewController에 전달합니다.
bottomSheetVC.name = name
bottomSheetVC.descriptionText = description
if let sheet = bottomSheetVC.sheetPresentationController {
let customDetent = UISheetPresentationController.Detent.custom(identifier: .init("thirtyPercent")) { context in
return context.maximumDetentValue * 0.3
}
sheet.detents = [customDetent, .large()]
sheet.selectedDetentIdentifier = .init("thirtyPercent")
sheet.largestUndimmedDetentIdentifier = .large
sheet.prefersScrollingExpandsWhenScrolledToEdge = false
sheet.prefersEdgeAttachedInCompactHeight = true
sheet.widthFollowsPreferredContentSizeWhenEdgeAttached = true
}
present(bottomSheetVC, animated: true, completion: nil)
}
}
// MARK: Location / Camera
extension HomeViewController: CLLocationManagerDelegate {
private func setLocationManager() {
self.locationManager.delegate = self
self.locationManager.desiredAccuracy = kCLLocationAccuracyBestForNavigation
self.locationManager.distanceFilter = kCLDistanceFilterNone
self.locationManager.activityType = .otherNavigation
self.locationManager.pausesLocationUpdatesAutomatically = false
self.locationManager.requestWhenInUseAuthorization()
self.locationManager.startUpdatingLocation()
}
func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
guard let bestLocation = locations.last else { return }
let userLat = bestLocation.coordinate.latitude
let userLng = bestLocation.coordinate.longitude
print("1. 사용자의 위치 : (\(userLat), \(userLng))")
self.cameraUpdate(lat: userLat, lng: userLng)
// 처음 위치를 잡은 후, 위치 업데이트를 중단하여 불필요한 카메라 이동을 방지합니다.
self.locationManager.stopUpdatingLocation()
}
private func cameraUpdate(lat: Double, lng: Double) {
let cameraUpdate = NMFCameraUpdate(scrollTo: NMGLatLng(lat: lat, lng: lng))
cameraUpdate.animation = .easeIn
self.mapView.mapView.moveCamera(cameraUpdate)
}
}
아이고 어려워
'iOS > 흡구오디 -> 어딨쥐' 카테고리의 다른 글
| [흡구오디] Swift 사진 찍는 기능 구현하기 | UIImagePickerController (0) | 2025.09.14 |
|---|---|
| [흡구오디] 지도 위 정보를 띄우는 '바텀 시트' 만들기 (feat. FloatingPanel) (2) | 2025.09.05 |
| [흡구오디] NMFMapView와 NMFNaverMapView의 차이를 알아보자! (0) | 2025.08.31 |
| [흡구오디] FireStore의 데이터 가져와서 화면에 띄워주기 (1) | 2025.08.31 |
| [흡구오디] 어디까지 개발했나 알려드림 | UI 라이브러리 | Firestore 연동 (4) | 2025.08.29 |
댓글