[SwiftUI] FileManager

Junyoung Park·2022년 8월 20일
0

SwiftUI

목록 보기
26/136
post-thumbnail
post-custom-banner

Save data and images to FileManager in Xcode | Continued Learning #26

FileManager

  • 유저 디폴트, 코어 데이터 이외의 데이터를 저장하는 로컬 공간
  • 코어 데이터가 인식하지 않는 데이터 타입을 그대로 저장 가능

구현 목표

  • 파일 매니저 디렉토리 접근 방법
  • 디렉토리 경로 → 데이터 저장 및 삭제

구현 태스크

  1. Assets 이미지를 로드한다.
  2. FileManager 디렉토리 경로를 생성 후 얻는다.
  3. 해당 디렉토리 경로에 특정 이미지 이름 파일을 세이브한다.
  4. 해당 디렉토리 경로에서 특정 이미지 이름 파일을 로드한다.
  5. 해당 디렉토리를 삭제한다.

핵심 코드

func saveImage(image: UIImage, name: String) -> String {
        guard let data = image.pngData(), let path = getPathForImage(name: name) else {
            return "ERROR GETTING PATH"
        }
           
        do {
            try data.write(to: path)
            return "SUCCESS SAVING"
        } catch {
            print(error.localizedDescription)
            return "ERROR SAVING IMAGE"
        }
    }
func loadImage(name: String) -> UIImage? {
        guard
            let path = getPathForImage(name: name)?.path,
            FileManager.default.fileExists(atPath: path) else {
            print("ERROR GETTTING PATH")
            return nil
        }
        
        return UIImage(contentsOfFile: path)
    }

func getPathForImage(name: String) -> URL? {
        guard
            let path = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)
                .first?
                .appendingPathComponent(cachePathName)
                .appendingPathComponent("\(name).png")
        else {
            return nil
        }
        return path
    }

소스 코드

import SwiftUI

class LocalFileManager {
    static let instance = LocalFileManager()
    private let cachePathName: String = "MyApp_Images"
    private init() {
        createFolderIfNeeded()
    }
    
    func createFolderIfNeeded() {
        guard
            let path = FileManager
                .default
                .urls(for: .cachesDirectory, in: .userDomainMask)
                .first?
                .appendingPathComponent(cachePathName)
                .path else {
            return
        }
        
        if !FileManager.default.fileExists(atPath: path) {
            do {
                try FileManager.default.createDirectory(atPath: path, withIntermediateDirectories: true, attributes: nil)
                print("SUCCESS CREATING FOLDER")
            } catch {
                print("ERROR CREATING DIRECTORY")
                print(error.localizedDescription)
            }
        }
    }
    
    func deleteFolder() -> String {
        guard
            let path = FileManager
                .default
                .urls(for: .cachesDirectory, in: .userDomainMask)
                .first?
                .appendingPathComponent(cachePathName)
                .path else {
            return "ERROR GETTING PATH"
        }
        
        do {
            try FileManager.default.removeItem(atPath: path)
            return "SUCCESS DELETING FOLDER"
        } catch {
            print(error.localizedDescription)
            return "ERROR DELETING FOLDER"
        }
    }
    
    func saveImage(image: UIImage, name: String) -> String {
        guard let data = image.pngData(), let path = getPathForImage(name: name) else {
            return "ERROR GETTING PATH"
        }
           
        do {
            try data.write(to: path)
            return "SUCCESS SAVING"
        } catch {
            print(error.localizedDescription)
            return "ERROR SAVING IMAGE"
        }
    }
    
    func loadImage(name: String) -> UIImage? {
        guard
            let path = getPathForImage(name: name)?.path,
            FileManager.default.fileExists(atPath: path) else {
            print("ERROR GETTTING PATH")
            return nil
        }
        
        return UIImage(contentsOfFile: path)
    }
    
    func deleteImage(name: String) -> String {
        guard
            let path = getPathForImage(name: name),
            FileManager.default.fileExists(atPath: path.path) else {
            return "ERROR GETTING PATH"
        }
        
        do {
            try FileManager.default.removeItem(at: path)
        } catch {
            print(error.localizedDescription)
            return "ERROR DELETING IMAGE"
        }
        return "SUCCESS DELETING IMAGE"
    }
    
    func getPathForImage(name: String) -> URL? {
        guard
            let path = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)
                .first?
                .appendingPathComponent(cachePathName)
                .appendingPathComponent("\(name).png")
        else {
            return nil
        }
        return path
    }
}
  • 가장 핵심이 되는 파일 매니저 담당 매니저 클래스
  • 싱글턴 구현: 여러 뷰 모델에서의 동일한 접근 허용 → 파일 의존성 이슈, 안티 패턴 위험성이 커질 때에는 DI를 통해 캐치
  • 인스턴스 생성 시 createFolderIfNeeded를 통해 캐시 디렉토리에 해당 디렉토리 존재 여부 파악 후 생성
  • 특정 이미지 이름이 저장될 경로 파악 → 저장 가능
  • 특정 이미지 이름이 캐시 디렉토리에 존재하는지 파악 → 삭제 가능
class FileManagerViewModel: ObservableObject {
    @Published var image: UIImage? = nil
    @Published var infoMessage = ""
    var infoMessageColor = Color.black
    var imageName = ""
    let manager = LocalFileManager.instance
    
    init() {
    }
    
    func getImageFromFileManager() {
        if let image = manager.loadImage(name: imageName) {
            infoMessage = "SUCCESS LOADING FM"
            infoMessageColor = .green
            self.image = image
        } else {
            infoMessage = "ERROR GETTING FM"
            infoMessageColor = .pink
        }
    }
    
    func getImageFromAssetFolder() {
        if let image = UIImage(named: imageName) {
            infoMessage = "SUCCESS LOADING ASSET"
            infoMessageColor = .green
            self.image = image
        } else {
            infoMessage = "ERROR GETTING ASSET"
            infoMessageColor = .pink
        }
    }
    
    func saveImage() {
        guard let image = image else { return }
        infoMessage = manager.saveImage(image: image, name: imageName)
    }
    
    func deleteImage() {
        infoMessage = manager.deleteImage(name: imageName)
    }
    
    func deleteFolder() {
        infoMessage = manager.deleteFolder()
    }
    
}
  • 해당 뷰의 데이터 정보를 관리, 바인딩하는 뷰 모델 클래스
  • 현재 뷰에 표시되는 이미지의 이름 및 함수 상태 정보를 전달
struct FileManagerBootCamp: View {
    @StateObject private var viewModel = FileManagerViewModel()
    
    var body: some View {
        NavigationView {
            VStack {
                if let image = viewModel.image {
                    Image(uiImage: image)
                        .resizable()
                        .scaledToFit()
                        .frame(width: 200, height: 200)
                        .clipped()
                        .cornerRadius(10)
                }
                Button {
                    viewModel.imageName = "Polar_Bear1"
                    viewModel.getImageFromAssetFolder()
                } label: {
                    ButtonLabel(buttonText: "LOAD FROM ASSET1", backgroundColor: .blue)
                }
                Button {
                    viewModel.imageName = "Polar_Bear2"
                    viewModel.getImageFromAssetFolder()
                } label: {
                    ButtonLabel(buttonText: "LOAD FROM ASSET2", backgroundColor: .blue)
                }
                Button {
                    viewModel.saveImage()
                } label: {
                    ButtonLabel(buttonText: "SAVE TO FM", backgroundColor: .orange)
                }
                Button {
                    viewModel.imageName = "Polar_Bear1"
                    viewModel.getImageFromFileManager()
                } label: {
                    ButtonLabel(buttonText: "LOAD FROM FM1", backgroundColor: .purple)
                }
                Button {
                    viewModel.imageName = "Polar_Bear2"
                    viewModel.getImageFromFileManager()
                } label: {
                    ButtonLabel(buttonText: "LOAD FROM FM2", backgroundColor: .purple)
                }
                Button {
                    viewModel.deleteImage()
                } label: {
                    ButtonLabel(buttonText: "DELETE FROM FM", backgroundColor: .pink)
                }
                Button {
                    viewModel.deleteFolder()
                } label: {
                    ButtonLabel(buttonText: "DELETE FM FOLDER", backgroundColor: .red)
                }
                
                Text(viewModel.infoMessage)
                    .font(.headline)
                    .fontWeight(.semibold)
                    .foregroundColor(viewModel.infoMessageColor)

                Spacer()
            }
            .navigationTitle("FILE MANAGER")
        }
    }
}

struct ButtonLabel: View {
    let buttonText: String
    let backgroundColor: Color
    var body: some View {
        Text(buttonText)
            .font(.headline)
            .fontWeight(.semibold)
            .foregroundColor(.white)
            .padding()
            .padding(.horizontal)
            .background(backgroundColor)
            .cornerRadius(10)
    }
}
  • UI 뷰
  • 기본적으로 어셋에서 이미지 로드
  • 로드한 이미지를 파일 매니저 디렉토리 저장 가능
  • 저장되어 있다면 파일 매니저 디렉토리 로드 가능
  • 저장된 이미지를 파일 매니저 디렉토리에서 삭제 가능
  • 파일 매니저 디렉토리 자체를 삭제 가능 → createdFolderIfNeeded 호출이 인스턴스 생성 시 호출되기 때문에 현 구현 단계에서는 다시 뷰가 실행되어야 함

구현 화면

profile
JUST DO IT
post-custom-banner

0개의 댓글