728x90
SMALL
Swift의 FileManager를 사용해 이미지를 저장하고, 저장된 경로를 활용해 불러오거나 삭제하는 기능을 구현할 수 있습니다.
이번 글에서는 단순히 코드만 보여주는 것이 아니라 이미지를 저장하고 관리하는 전체 과정을 어떤 흐름으로 설계하고 구현해나가는지에 대해 설명합니다.
✅ 최종 목표
- 사용자가 입력한 지출 내역에 이미지가 있을 경우, 해당 이미지를 FileManager로 저장하고 저장 경로를 String으로 저장
- 저장된 경로를 이용하여 이미지 불러오기
- 수정 또는 삭제 시 해당 폴더의 이미지 제거
🧱 전체 구조 설계: 어떤 흐름으로 진행할까?
💡 목표: 이미지를 저장하고, 그에 대한 경로를 기억하기
1. 이미지를 저장할 디렉토리 위치 지정 (예: Documents 폴더)
2. 거래(transaction) 단위로 고유한 폴더 생성 (UUID 등으로 구분)
3. 이미지를 해당 폴더에 저장 (ex. image.jpg)
4. 저장된 경로를 문자열로 반환하여, 데이터 모델에 저장
5. 저장된 경로를 활용해 다시 이미지를 불러오거나 삭제
🧭 순서대로 코드를 설계한다면?
1. 이미지를 저장할 위치를 정한다
private func getDocumentsDirectory() -> URL {
guard let url = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else {
fatalError("❌ Document 디렉토리를 찾을 수 없습니다.")
}
return url
}
📌 설명:
앱 내부의 Documents 디렉토리는 사용자 데이터 저장에 적합한 위치입니다.
모든 저장은 이 경로를 기준으로 이뤄집니다.
2. 거래 ID(UUID)를 기준으로 폴더를 생성한다
let transactionFolder = getDocumentsDirectory().appendingPathComponent(transactionID)
if fileManager.fileExists(atPath: transactionFolder.path) {
try? fileManager.removeItem(at: transactionFolder) // 기존 데이터 제거
}
try fileManager.createDirectory(at: transactionFolder, withIntermediateDirectories: true)
📌 설명:
transactionID를 기반으로 개별 폴더를 생성하면, 각 지출 데이터에 대응되는 고유 이미지 저장 공간을 만들 수 있습니다.
3. 이미지를 JPEG로 변환하여 저장한다
let fileName = "image.jpg"
let fileURL = transactionFolder.appendingPathComponent(fileName)
guard let imageData = image.jpegData(compressionQuality: 1.0) else {
return nil
}
try imageData.write(to: fileURL)
📌 설명:
UIImage → Data → File 형태로 저장하며, 이후 String 경로는 "UUID/image.jpg" 형식으로 저장합니다.
4. 저장된 이미지 경로를 문자열로 반환한다
return "\(transactionID)/\(fileName)"
📌 설명:
이 경로 문자열은 Core Data나 사용자 데이터 모델(ExpenseModel)에 저장됩니다.
나중에 불러올 때 이 문자열을 기반으로 이미지 로드가 가능합니다.
5. 이미지를 불러올 때는 해당 경로로 접근
func loadImage(from relativePath: String) -> UIImage? {
let fullPath = getDocumentsDirectory().appendingPathComponent(relativePath)
return UIImage(contentsOfFile: fullPath.path)
}
6. 삭제할 때는 폴더 단위로 제거
func deleteFolder(for transactionID: String) -> Bool {
let folder = getDocumentsDirectory().appendingPathComponent(transactionID)
try fileManager.removeItem(at: folder)
}
🧠 왜 이런 방식이 좋을까?
- 고유 폴더 구조 유지 → 지출 내역마다 개별 저장공간 확보
- 문자열 기반 경로 관리 → 이미지 자체를 Core Data에 저장하지 않아도 됨
- 불러오기/삭제가 단순해짐 → loadImage(from:), deleteFolder(for:)처럼 관리가 쉬움
- 확장성 → 향후 이미지 여러 개, 썸네일 추가 등도 폴더 내 파일 구조로 확장 가능
🛠 전체 클래스 코드 다시 보기
class TransactionFileManager {
static let shared = TransactionFileManager()
private init() { }
private let fileManager = FileManager.default
private func getDocumentsDirectory() -> URL {
guard let url = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else {
fatalError("❌ Document 디렉토리를 찾을 수 없습니다.")
}
return url
}
func saveImage(_ image: UIImage, _ transactionID: String) -> String? {
let transactionFolder = getDocumentsDirectory().appendingPathComponent(transactionID)
if fileManager.fileExists(atPath: transactionFolder.path) {
try? fileManager.removeItem(at: transactionFolder)
}
do {
try fileManager.createDirectory(at: transactionFolder,
withIntermediateDirectories: true,
attributes: nil)
} catch {
print("❌ 디렉토리 생성 실패: \(error.localizedDescription)")
}
let fileName = "image.jpg"
let fileURL = transactionFolder.appendingPathComponent(fileName)
guard let imageData = image.jpegData(compressionQuality: 1.0) else {
print("❌ 이미지 데이터를 JPEG로 변환 실패")
return nil
}
do {
try imageData.write(to: fileURL)
return "\(transactionID)/\(fileName)"
} catch {
print("❌ 이미지 저장 실패: \(error.localizedDescription)")
return nil
}
}
func loadImage(from relativePath: String) -> UIImage? {
let fullPath = getDocumentsDirectory().appendingPathComponent(relativePath)
if !fileManager.fileExists(atPath: fullPath.path) {
print("❌ 이미지 경로 없음: \(fullPath.path)")
return nil
}
return UIImage(contentsOfFile: fullPath.path)
}
func deleteFolder(for transactionID: String) -> Bool {
let folder = getDocumentsDirectory().appendingPathComponent(transactionID)
if fileManager.fileExists(atPath: folder.path) {
do {
try fileManager.removeItem(at: folder)
return true
} catch {
print("❌ 폴더 삭제 실패: \(error.localizedDescription)")
return false
}
}
return false
}
}728x90
LIST
'Project > ReceiptMind' 카테고리의 다른 글
| 📚Core Data로 지출 내역 불러오기(Read) 정리 (0) | 2025.08.02 |
|---|---|
| 💾 Core Data와 FileManager로 지출 내역 저장하기 (2) | 2025.08.01 |
| 📱 iOS 가계부 앱 데이터 모델 설계하기 (3) | 2025.07.30 |
| ✅ 앱 수정 후 재심사 제출하는 방법 (App Store Connect) (3) | 2025.07.30 |
| ❌ 앱 심사 거절 사례: Guideline 5.1.1 Privacy – Purpose String 부족 (1) | 2025.07.30 |