iOS/UIKIT

FileManager 사용하는 기본 방법 (이미지를 경로로 저장하여 코어 데이터에 가져다 사용하기)

밤새는 탐험가89 2024. 11. 13. 16:24

1단계: 파일 경로 설정하기

파일을 저장하거나 불러오기 위해 기본적으로 앱의 Documents Directory 경로를 설정합니다. 이 경로는 파일을 영구적으로 저장할 위치를 지정합니다.

import Foundation

// FileManager 기본 설정
let fileManager = FileManager.default
let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!

 

documentsDirectory는 사용자의 앱 데이터가 저장되는 기본 경로입니다. 이곳에 이미지를 저장하거나, 저장된 이미지 파일을 불러올 때 경로로 활용할 수 있습니다.

 

 

2단계: 폴더 만들기 (필요시)

파일을 관리하기 쉽게 개별 폴더를 만들고, 필요한 경우 폴더를 생성합니다. 예를 들어, 각 피드 항목마다 고유 폴더를 생성하여 이미지나 기타 데이터를 관리할 수 있습니다.

func createFolderIfNeeded(named folderName: String) -> URL {
    let folderURL = documentsDirectory.appendingPathComponent(folderName)
    
    if !fileManager.fileExists(atPath: folderURL.path) {
        try? fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil)
        print("폴더 생성: \(folderURL.path)")
    }
    
    return folderURL
}


설명: createFolderIfNeeded 함수는 지정된 폴더 이름으로 경로를 만들고, 해당 폴더가 없으면 생성합니다. 예를 들어, feedID 폴더를 만들고 해당 폴더에 이미지를 저장할 수 있습니다.

 

 

3단계: 파일 저장하기 (예: 이미지 저장)

UIImage를 저장할 때 jpegData 또는 pngData로 변환한 후 파일로 저장할 수 있습니다.

func saveImage(_ image: UIImage, to folder: String, withName name: String) -> String? {
    let folderURL = createFolderIfNeeded(named: folder)
    let fileURL = folderURL.appendingPathComponent("\(name).jpg")
    
    if let imageData = image.jpegData(compressionQuality: 1.0) {
        try? imageData.write(to: fileURL)
        print("이미지 저장: \(fileURL.path)")
        return "\(folder)/\(name).jpg"  // 상대 경로 반환
    }
    
    return nil
}

 

설명: saveImage 함수는 이미지를 특정 폴더에 저장하고, 저장된 파일의 상대 경로를 반환합니다. 이 상대 경로는 Core Data나 데이터베이스에 저장하여 나중에 해당 경로를 통해 이미지를 불러올 때 사용됩니다.

 

 

4단계: 파일 경로 저장하기 (Core Data에 경로만 저장)

이미지를 저장할 때 이미지 파일의 전체 경로 대신 상대 경로를 저장하는 것이 좋습니다. Core Data와 같은 데이터베이스에 경로만 저장하면, 전체 이미지 파일을 로드할 필요 없이 해당 경로를 이용해 효율적으로 접근할 수 있습니다.

let savedImagePath = saveImage(selectedImage, to: "FeedImages", withName: "image_1")
feedEntity.imagePath = savedImagePath  // Core Data에 저장할 때 이미지 경로만 저장

 

 

5단계: 파일 불러오기

파일을 불러올 때는 저장된 상대 경로를 사용하여 해당 위치에서 이미지를 불러옵니다.

func loadImage(from relativePath: String) -> UIImage? {
    let fileURL = documentsDirectory.appendingPathComponent(relativePath)
    return UIImage(contentsOfFile: fileURL.path)
}

 

설명: loadImage 함수는 relativePath로 전달된 경로를 사용하여 파일에서 이미지를 불러옵니다.

 

 

6단계: 파일 삭제하기 (필요 시)

더 이상 사용하지 않는 파일은 삭제하여 저장 공간을 관리할 수 있습니다.

func deleteImage(at relativePath: String) {
    let fileURL = documentsDirectory.appendingPathComponent(relativePath)
    
    do {
        try fileManager.removeItem(at: fileURL)
        print("파일 삭제 완료: \(fileURL.path)")
    } catch {
        print("파일 삭제 오류: \(error)")
    }
}

 

 

이미지 저장 및 로드 과정

// 이미지 저장
let relativePath = saveImage(selectedImage, to: "FeedImages", withName: "image_1")
feedEntity.imagePath = relativePath  // Core Data에 이미지 경로 저장

// 이미지 불러오기
if let imagePath = feedEntity.imagePath {
    let loadedImage = loadImage(from: imagePath)
    imageView.image = loadedImage
}

 

요약

  1. 파일 경로 설정: 기본적으로 documentsDirectory를 사용합니다.
  2. 폴더 생성: 필요 시 데이터를 조직적으로 저장할 수 있도록 폴더를 생성합니다.
  3. 파일 저장: 데이터를 저장하고, Core Data에는 파일의 상대 경로를 저장합니다.
  4. 파일 불러오기: 저장된 경로를 사용하여 파일을 로드합니다.
  5. 파일 삭제: 필요 없는 파일을 삭제하여 공간을 관리합니다.

 

 

FileManager를 통해 이미지를 저장하고 해당 이미지 파일 경로를 반환하는 메서드

import UIKit

class FeedStorageManager {
    
    // Documents 폴더 경로 가져오기
    private func getDocumentsDirectory() -> URL {
        FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
    }
    
    // 이미지 저장
    func saveImages(images: [UIImage], feedID: String) -> [String] {
        let feedFolder = getDocumentsDirectory().appendingPathComponent(feedID)
        
        // 폴더가 없으면 생성
        if !FileManager.default.fileExists(atPath: feedFolder.path) {
            try? FileManager.default.createDirectory(at: feedFolder, withIntermediateDirectories: true, attributes: nil)
        }
        
        var savedImagePaths: [String] = []
        
        for (index, image) in images.enumerated() {
            let fileName = "image_\(index).jpg"
            let fileURL = feedFolder.appendingPathComponent(fileName)
            
            if let imageData = image.jpegData(compressionQuality: 1.0) {
                try? imageData.write(to: fileURL)
                savedImagePaths.append("\(feedID)/\(fileName)") // 상대경로 저장
            }
        }
        
        return savedImagePaths
    }
    
    // 이미지 로드
    func loadImages(from relativePaths: [String]) -> [UIImage] {
        var images = [UIImage]()
        
        for relativePath in relativePaths {
            let fullPath = getDocumentsDirectory().appendingPathComponent(relativePath)
            if let image = UIImage(contentsOfFile: fullPath.path) {
                images.append(image)
            }
        }
        print("불러온 이미지", images)
        return images
    }
}

 

 

1. getDocumentsDirectory()

private func getDocumentsDirectory() -> URL {
    FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
}

 

 

  • 이 메서드는 FileManager를 통해 앱의 Documents Directory 경로를 반환합니다.
  • documentDirectory는 앱이 사용자 데이터를 저장할 수 있는 기본 위치이며, 앱이 삭제될 때까지 유지됩니다.
  • 이 함수는 저장하고 불러오는 작업에서 파일의 기본 경로로 사용됩니다.

 

2. saveImages(images: [UIImage], feedID: String) -> [String]

func saveImages(images: [UIImage], feedID: String) -> [String] {
    let feedFolder = getDocumentsDirectory().appendingPathComponent(feedID)
    
    // 폴더가 없으면 생성
    if !FileManager.default.fileExists(atPath: feedFolder.path) {
        try? FileManager.default.createDirectory(at: feedFolder, withIntermediateDirectories: true, attributes: nil)
    }
    
    var savedImagePaths: [String] = []
    
    for (index, image) in images.enumerated() {
        let fileName = "image_\(index).jpg"
        let fileURL = feedFolder.appendingPathComponent(fileName)
        
        if let imageData = image.jpegData(compressionQuality: 1.0) {
            try? imageData.write(to: fileURL)
            savedImagePaths.append("\(feedID)/\(fileName)") // 상대경로 저장
        }
    }
    
    return savedImagePaths
}

 

 

 

  • feedID를 사용하여 해당 이미지들을 저장할 고유 폴더를 생성합니다. 이 폴더는 Documents Directory 내에서 feedID라는 이름으로 저장됩니다.
    • 예를 들어, feedID가 12345라면 Documents/12345/ 폴더에 이미지가 저장됩니다.
  • FileManager.default.fileExists(atPath:)로 해당 폴더가 존재하는지 확인하고, 없을 경우 createDirectory를 사용해 생성합니다.
  • savedImagePaths 배열은 저장한 각 이미지 파일의 상대 경로를 담고, 이 배열이 Core Data에 저장될 예정입니다.
  • for (index, image) in images.enumerated():
    • 주어진 images 배열의 각 이미지를 순회하면서 image_0.jpg, image_1.jpg 등 고유한 파일명으로 저장합니다.
    • image.jpegData(compressionQuality: 1.0)을 사용하여 UIImage를 JPEG 데이터로 변환 후 write(to:)로 저장합니다.
  • 상대 경로로 저장:
    • 경로는 Documents Directory를 기준으로 savedImagePaths 배열에 상대 경로(feedID/image_x.jpg) 형태로 저장됩니다.
    • 이 경로는 앱 내에서 이미지를 불러올 때 사용되며, Core Data에는 전체 경로가 아닌 상대 경로만 저장하여 앱이 경로 구조를 변경해도 유연하게 대처할 수 있습니다.

 

 

3. loadImages(from relativePaths: [String]) -> [UIImage]

func loadImages(from relativePaths: [String]) -> [UIImage] {
    var images = [UIImage]()
    
    for relativePath in relativePaths {
        let fullPath = getDocumentsDirectory().appendingPathComponent(relativePath)
        if let image = UIImage(contentsOfFile: fullPath.path) {
            images.append(image)
        }
    }
    print("불러온 이미지", images)
    return images
}

 

 

  • relativePaths는 Core Data에 저장된 이미지들의 상대 경로 배열입니다. 이 경로를 사용해 이미지를 불러옵니다.
  • for relativePath in relativePaths:
    • 각 상대 경로를 순회하여 fullPath를 생성하고, UIImage(contentsOfFile:)로 해당 경로의 이미지를 불러옵니다.
    • UIImage(contentsOfFile:) 메서드는 파일 경로에 있는 이미지를 UIImage로 변환합니다.
  • 불러온 이미지들을 images 배열에 추가하고 최종적으로 반환합니다.
  • 핵심:
    • 이 방식은 앱의 파일 구조가 변경되지 않는 한 이미지 파일을 빠르게 로드할 수 있습니다.
    • Core Data에는 이미지 자체가 아닌 파일 경로만 저장되므로 앱의 성능을 향상시킬 수 있습니다.

 

 

코어데이터를 통해 텍스트 및 이미지는 파일 매니저로 저장한 상태에서 얻어온 이미지 경로를 관리하는 클래스 

import CoreData
import UIKit

class FeedDataManager {
    
    static let shared = FeedDataManager()
    
    private let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
    private let storageManager = FeedStorageManager()
    
    // Core Data에 저장
    func saveFeedItem(feedItem: FeedItem) {
        let feedEntity = FeedEntity(context: context)
        let feedID = UUID().uuidString // 각 피드마다 고유 ID 생성
        
        // FeedItem의 데이터 설정
        feedEntity.tripLog = feedItem.tripLog
        feedEntity.location = feedItem.location
        feedEntity.date = feedItem.date
        feedEntity.category = feedItem.category
        feedEntity.feedID = feedID
        
        // 이미지 저장 및 경로 설정
        if let images = feedItem.images {
            
            let imagePaths = storageManager.saveImages(images: images, feedID: feedID)
            feedEntity.imagePaths = imagePaths.joined(separator: ",")
        }
        
        do {
            try context.save()
            print("FeedItem이 성공적으로 저장되었습니다.")
            print("저장된 이미지 경로:", feedEntity.feedID!)
        } catch {
            print("FeedItem 저장 실패: \(error)")
        }
    }
    
    // Core Data에서 불러오기
    func fetchFeedItems() -> [FeedItem] {
        let request: NSFetchRequest<FeedEntity> = FeedEntity.fetchRequest()
        var feedItems: [FeedItem] = []
        
        do {
            let feedEntities = try context.fetch(request)
            
            for entity in feedEntities {
                
                let images: [UIImage]
                
                if let imagePathsString = entity.imagePaths,
                   let feedID = entity.feedID {
                    let imagePathsArray = imagePathsString.components(separatedBy: ",")
                    images = storageManager.loadImages(from: imagePathsArray)
                } else {
                    images = []
                }
                
                // FeedItem을 생성하고 데이터 할당
                let feedItem = FeedItem(
                    images: images,
                    tripLog: entity.tripLog,
                    location: entity.location,
                    date: entity.date,
                    category: entity.category
                )
                feedItems.append(feedItem)
            }
        } catch {
            print("데이터 로드 실패: \(error)")
        }
        
        return feedItems
    }
}

 

 

이 코드에서는 FeedDataManager라는 클래스를 통해 Core Data와 FileManager를 조합하여 데이터를 저장하고 관리합니다. 이 클래스는 Core Data로 텍스트와 메타데이터를 저장하고, FileManager를 통해 이미지를 파일로 저장하며, 이미지 경로만 Core Data에 저장해 영구적으로 관리하는 구조입니다.

 

1. 기본 설정

static let shared = FeedDataManager()
private let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
private let storageManager = FeedStorageManager()

 

 

  • shared: 싱글톤 패턴을 사용하여 앱 전반에서 FeedDataManager를 단일 인스턴스로 접근하게 합니다.
  • context: Core Data의 NSManagedObjectContext를 가져와 데이터베이스 작업에 사용합니다.
  • storageManager: 이미지 저장과 로드를 담당하는 FeedStorageManager 클래스 인스턴스입니다.

 

2. Core Data에 데이터 저장

func saveFeedItem(feedItem: FeedItem) {
    let feedEntity = FeedEntity(context: context)
    let feedID = UUID().uuidString // 각 피드마다 고유 ID 생성

 

 

 

  • feedItem 데이터를 저장하기 위해 FeedEntity를 생성하고, 각 FeedItem마다 고유한 UUID를 생성해 feedID로 사용합니다.
  • 이 feedID는 FileManager 내에서 폴더를 생성하는 데 사용되어 이미지 파일을 고유하게 구분할 수 있습니다.

텍스트 및 메타데이터 설정

feedEntity.tripLog = feedItem.tripLog
feedEntity.location = feedItem.location
feedEntity.date = feedItem.date
feedEntity.category = feedItem.category
feedEntity.feedID = feedID

 

 

 

  • feedEntity의 각 필드에 feedItem의 데이터를 설정합니다.
  • feedID를 feedEntity의 필드에 저장해, 추후 이미지를 불러올 때 이 ID를 사용해 폴더 경로를 찾습니다.

 

이미지 저장 및 경로 설정

if let images = feedItem.images {
    let imagePaths = storageManager.saveImages(images: images, feedID: feedID)
    feedEntity.imagePaths = imagePaths.joined(separator: ",")
}

 

 

 

  • feedItem이 가진 이미지 배열을 saveImages(images:feedID:) 함수로 전달합니다.
  • storageManager.saveImages는 이미지를 FileManager에 저장하고 저장된 이미지의 경로 목록을 반환합니다.
  • 경로 목록을 문자열로 ,로 구분해 imagePaths 필드에 저장하여 Core Data에 경로를 관리합니다.

 

데이터베이스에 저장

do {
    try context.save()
    print("FeedItem이 성공적으로 저장되었습니다.")
    print("저장된 이미지 경로:", feedEntity.feedID!)
} catch {
    print("FeedItem 저장 실패: \(error)")
}

 

 

  • 모든 데이터가 feedEntity에 저장되면 context.save()를 호출해 Core Data에 저장합니다.

 

3. Core Data에서 데이터 불러오기

func fetchFeedItems() -> [FeedItem] {
    let request: NSFetchRequest<FeedEntity> = FeedEntity.fetchRequest()
    var feedItems: [FeedItem] = []

 

 

  • fetchFeedItems는 Core Data에서 저장된 FeedEntity 데이터를 불러옵니다.
  • NSFetchRequest<FeedEntity>를 통해 FeedEntity의 모든 항목을 요청합니다.

 

 

데이터 로드 및 이미지 경로 처리

do {
    let feedEntities = try context.fetch(request)
    
    for entity in feedEntities {
        let images: [UIImage]
        
        if let imagePathsString = entity.imagePaths,
           let feedID = entity.feedID {
            let imagePathsArray = imagePathsString.components(separatedBy: ",")
            images = storageManager.loadImages(from: imagePathsArray)
        } else {
            images = []
        }

 

 

  • context.fetch(request)를 통해 Core Data에 저장된 모든 FeedEntity 데이터를 가져옵니다.
  • 각 entity에서 imagePaths 문자열을 불러오고, feedID를 이용해 관련 이미지 파일을 FileManager에서 찾습니다.
  • storageManager.loadImages(from:)를 통해 FileManager에서 이미지를 읽어들여 images 배열에 저장합니다.

 

FeedItem 생성 및 반환

let feedItem = FeedItem(
    images: images,
    tripLog: entity.tripLog,
    location: entity.location,
    date: entity.date,
    category: entity.category
)
feedItems.append(feedItem)

 

 

 

  • images와 텍스트 데이터를 이용해 FeedItem을 생성하고 feedItems 배열에 추가합니다.
  • 모든 데이터를 배열에 추가한 뒤 반환합니다.