안녕하세요
이제 가을인가봐요
겨울이 오기 전에 이 앱을 출시하겠어요!
(사유 : 전국방방곳곳 뛰댕겨야함)
오늘은 유저가 흡연구역을 추가할 때
사진을 찍어서 올리고
마커를 탭하면 그 사진 + 흡연구역에 대한 정보를 보여주는
기능을 구현해보았답니다 : )
사진 업로드/다운로드 과정
먼저 과정 설계를 해야겠쥬
파이어스토어는 텍스트/숫자 같은 데이터만 저장할 수 있고, 이미지 저장은 안돼요 🙅
그래서 Cloud Storage를 같이 써야 함!
기존 흐름
사용자가 입력 폼 적어서 저장 -> firestore에 장소 이름/장소 설명/장소 태그 등을 저장
사용자가 마커를 탭하면 -> firestore에 있는 장소 이름/장소 설명/장소 태그 등을 불러와서 보여주기
앞으로는
사용자가 사진 찍고, 입력 폼 적어서 저장
-> 사진은 Cloud Storage에 업로드,
firestore에 이미지 URL + 이름/설명/태그 등을 저장
사용자가 마커를 탭하면
-> Firestore 데이터 + 이미지 URL 이용해 사진까지 보여주기
이렇게 제 3의 저장소에 이미지를 저장하고 그 저장소의 url을 가져와서 이렇게 저렇게 해줘야한다는 말씀!!!
저번 포스팅에서 카메라 켜서 사진 찍는거까지 했고
이번 포스팅에서는 본격적으로 사진을 서버에 업로드하고 그 사진을 가져와서 보여주는 방법에 대해 알아보겠어요.

Cloud Storage 활성화 하기
SDK로 FireStorage 설정해주고
https://github.com/firebase/firebase-ios-sdk
GitHub - firebase/firebase-ios-sdk: Firebase SDK for Apple App Development
Firebase SDK for Apple App Development. Contribute to firebase/firebase-ios-sdk development by creating an account on GitHub.
github.com
Cloud Storage도 활성화해 주세요
Apple 플랫폼에서 Cloud Storage 시작하기 | Cloud Storage for Firebase
의견 보내기 Apple 플랫폼에서 Cloud Storage 시작하기 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Cloud Storage for Firebase를 사용하면 이미지와 동영상 등 사용
firebase.google.com
그럼 여기까지 됐다 치고~
사진 업로드하기
func uploadImage(_ image: UIImage) {
guard let imageData = image.jpegData(compressionQuality: 0.8) else { return }
let storageRef = Storage.storage().reference()
let fileName = "smokingAreas/\(UUID().uuidString).jpg"
let imageRef = storageRef.child(fileName)
imageRef.putData(imageData, metadata: nil) { [weak self] _, error in
if let error = error {
print("이미지 업로드 실패", error)
return
}
imageRef.downloadURL { url, _ in
self?.capturedImageUrl = url?.absoluteString
print("업로드 완료 : ", self?.capturedImageUrl)
}
}
}
핵심 정리
- 참조(reference): 클라우드 파일 위치 가리키는 포인터
- child: images/abc.jpg 같은 하위 경로 생성
- putData: 실제 이미지 업로드
- downloadURL: 업로드 후 접근 가능한 URL 가져오기
참조 만들기
// Get a reference to the storage service using the default Firebase App
let storage = Storage.storage()
// Create a storage reference from our storage service
let storageRef = storage.reference()
파일 업로드, 다운로드, 삭제, 메타데이터 가져오기 또는 업데이트를 하려면 참조를 만들어줘야 해요
참조는 클라우드의 파일을 가리키는 포인터로 생각하면 됩니다.
참조는 메모리에 부담을 주지 않으므로 원하는 만큼 만들 수 있으며 여러 작업에서 재사용할 수도 있다고 하네요
참조 child 만들기
let fileName = "smokingAreas/\(UUID().uuidString).jpg"
let imageRef = storageRef.child(fileName)
기존 참조에 child 메서드를 사용하여 'images/space.jpg'와 같이 트리에서 하위 위치를 가리키는 참조를 만들 수도 있습니다.
저는 file 이름을 위와 같이 만들어서 imageRef를 만들어 줬어요
putData
공식 문서의 아래 내용 참고해서 만들어줬어요
// Data in memory
let data = Data()
// Create a reference to the file you want to upload
let riversRef = storageRef.child("images/rivers.jpg")
// Upload the file to the path "images/rivers.jpg"
let uploadTask = riversRef.putData(data, metadata: nil) { (metadata, error) in
guard let metadata = metadata else {
// Uh-oh, an error occurred!
return
}
// Metadata contains file metadata such as size, content-type.
let size = metadata.size
// You can also access to download URL after upload.
riversRef.downloadURL { (url, error) in
guard let downloadURL = url else {
// Uh-oh, an error occurred!
return
}
}
}
저희는 위에서 만들어준 이미지 원본을 업로드해줄 것이기 때문에
// 위에서 이 변수 정의 해줘야함 private var capturedImageUrl: String?
imageRef.putData(imageData, metadata: nil) { [weak self] _, error in
if let error = error {
print("이미지 업로드 실패", error)
return
}
imageRef.downloadURL { url, _ in
self?.capturedImageUrl = url?.absoluteString
print("업로드 완료 : ", self?.capturedImageUrl)
}
}
이렇게 푸시해줍니다.

그럼 이렇게 잘 올라오는 것을 확인할 수 있어요
그럼 imageURL까지해서 저장하는 코드는
private func didTappedSaveButton() {
self.saveButton.rx.tap.subscribe(
onNext: { [weak self] in
self?.saveSmokinAreaInfo()
self?.navigationController?.popToRootViewController(animated: true)
})
.disposed(by: self.disposeBag)
}
private func saveSmokinAreaInfo() {
guard
let name = self.nameTextField.text, !name.isEmpty,
let description = self.descriptionTextView.text, !description.isEmpty,
let lat = self.markerLat,
let lng = self.markerLng
else {
// Alert 같은 걸 띄워서 사용자한테 알려주기
let alert = UIAlertController(title: "입력 오류", message: "이름, 설명은 필수 입력 항목입니다.", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "확인", style: .default))
self.present(alert, animated: true)
return
}
// 모델로 만들기
let smokingArea = SmokingArea(
imageURL: capturedImageUrl,
name: name,
description: description,
areaLat: lat,
areaLng: lng,
selectedEnvironmentTags: self.selectedEnvironmentTags,
selectedTypeTags: self.selectedTypeTags,
selectedFacilityTags: self.selectedFacilityTags
)
// Firestore 저장
db.collection("smokingAreas").addDocument(data: smokingArea.asDictionary) { error in
if let error = error {
print("저장 실패: \(error.localizedDescription)")
} else {
print("저장 성공")
}
}
}
}
참고로 모델은
//
// SmokingArea.swift
// SmokingAreaOdi
//
// Created by 이상지 on 8/29/25.
//
import Foundation
struct SmokingArea {
var imageURL: String? //요거 추가함
var name: String
var description: String
var areaLat: Double
var areaLng: Double
var selectedEnvironmentTags: [String]
var selectedTypeTags: [String]
var selectedFacilityTags: [String]
// var uploadUser: String
// var uploadDate
var asDictionary: [String: Any] {
return [
"imageURL": imageURL, //요거 추가함
"name": name,
"description": description,
"areaLat": areaLat,
"areaLng": areaLng,
"environmentTags": selectedEnvironmentTags,
"typeTags": selectedTypeTags,
"facilityTags": selectedFacilityTags
]
}
}
이렇게 구성되어 있음
사진 다운로드(사용하기)
기존에는 파이어베이스의 데이터들만 받아서 띄워줬잖아요?
이제는 클라우드 스토리지 사진도 받아줘야한답니다.
public func configure(with data: SmokingArea) {
DispatchQueue.main.async {
self.nameLabel.text = data.name
self.descriptionLabel.text = data.description
//--------여기가 추가됨---------------------------
// 이미지 로딩 전, 기본 이미지(placeholder) 설정 또는 nil로 초기화
self.areaImageView.image = nil
self.loadImage(from: data.imageURL)
//---------------------------------------------
self.tagSections.forEach { $0.removeFromSuperview() }
self.tagSections.removeAll()
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].filter { !$0.subviews.isEmpty }
for section in self.tagSections {
self.rootFlexContainer.flex.addItem(section).marginTop(20)
}
self.rootFlexContainer.flex.layout()
}
}
진짜진짜 최종
이미지 가져오기
private func loadImage(from urlString: String?) {
guard let urlString = urlString, let url = URL(string: urlString) else { return }
URLSession.shared.dataTask(with: url) { data, _, _ in
if let data = data, let image = UIImage(data: data) {
DispatchQueue.main.async {
self.areaImageView.image = image
}
}
}.resume()
}
저는 이렇게 URLSession을 통해 가져왔어요
이렇게 가져온 이미지 데이터는 디스패티 큐 통해 areaImageView에 업데이트 시켜줬어요!
이렇게 완성된 최종 모습!

내용과 사진은 그냥 아무거나 넣어놨어요
왜저리 늦게 뜨는지는 아직 잘 모르겠지만
지금 이 포스팅 쓰느라 완전 기졸초풍 직전이라 이만 집에 갈게요
그럼 안녕~
'iOS > 흡구오디 -> 어딨쥐' 카테고리의 다른 글
| [흡구오디] Swift - firebase Authentication로 이메일 로그인 구현하기 (0) | 2025.10.03 |
|---|---|
| [흡구오디] 각 서비스별 로그인 버튼 가이드 따라가기 (0) | 2025.09.24 |
| [흡구오디] Swift 사진 찍는 기능 구현하기 | UIImagePickerController (0) | 2025.09.14 |
| [흡구오디] 지도 위 정보를 띄우는 '바텀 시트' 만들기 (feat. FloatingPanel) (2) | 2025.09.05 |
| [흡구오디] 흡연구역 정보를 띄우기 위한 bottomSheet (2) | 2025.08.31 |
댓글