iOS/Toy project

[iOS : Toy Project] Todo List 만들기 (1)

yevdev 2022. 8. 15. 19:05

이번주차 스터디 과제로, 클론 프로젝트를 간단하게 해보기로 했었다!

스터디장님이 올려주신 것 중 나는 흔히 간단하게 다들하는,, Todo List 를 만들어보기로 결정!

 

이미 만들어진 Project를 따라 코딩하면서 화면 구성AutoLayout 그리고 Todo List에 필요한 디테일한 기능을 다뤄볼 예정

 

 

자세한 코드는 여기로!

 

GitHub - yexjin/iOS_Study: iOS 토이프로젝트 모음집📱

iOS 토이프로젝트 모음집📱. Contribute to yexjin/iOS_Study development by creating an account on GitHub.

github.com

 

 

8/15

탭바로 묶기 전에 일단 화면부터 구성해보기로!

나 증말.. 지지리도 Auto Layout 못해서 화면 구성조차 오래걸린다 .. ㅎ~

 

- 일단 TodoListViewController 생성

- 아 CollectionReusableView는 처음본당! 이거는 Collection View에서 Header View Cell 과 같은 의미! "Reusable"이 붙은 뷰는 헤더뷰와 푸터뷰에 사용되는 특수위젯. (나중에 포스팅 예정)

- 메인화면 구성 완료

Main.storyboard
아직 collectionView 연결을 안해줘서.. 시뮬레이터에서는 요롷게

- 대충 구성만 아주 사알짝 해놓고

 

 

이제 Storage.swift를 작성해보자..

( Storage.swift 파일은 FileManager을 통해, 말그대로 앱 공간을 관리하는 Storage를 위한건데, todo 데이터를 저장, 회복, 초기화, 삭제 등을 할 수 있는 메소드들을 만들어 놓은 곳)

 

 

아래는 Storage.swift를 작성하며 모르는 메소드들 기록해둔 것!

File Manager ?

- 아이폰 마다 자신의 앱 공간을 가지고 있는데 이를 관리하는 Manager!

FileManager.SearchPathDirectory : 탐색할 디렉토리 지정

FileManager.SearchPathDirectory.documentDirectory : Document directory

FileManager.SearchPathDirectory.cachesDirectory : 삭제가능한 Caches file(library/cache)

FileManager.default.contents(atPath:) : 파일의 내용을 포함한 NSData 객체

FileManager.default.contentsOfDirectory(atPath:) : 해당 디렉토리 안의 파일리스트를 배열로 반환 [String]

 

FileManager.default.urls(for: , in: )

urls(for:, in:) ? SearchPathDomainMask 범위에서 SearchPathDirectory를 찾는 메소드

 첫번째 파라미터(for) ? SearchPathDirectory : enum 형태로 구현되어 있어, 실제로 사용자가 사용하는 디렉토리 경로를 지칭한다.

 두번째 파라미터(in) ?  SearchPathDomainMask : userDomainMask로 설정할 경우, /User 보다 위에 있는 디렉토리는 접근 X

즉, SearchPathDomainMask보다 상위에 있는 디렉토리를 찾으려고 할 경우 에러가 난다는 말!

 

 

JSONEncoder() : JSON 데이터로 인코딩하는 객체

JSONDecoder() : JSON 데이터를 디코딩하는 객체

 

 

 

Storage.swift

import Foundation

public class Storage {
    
    private init() { }
    
    
    // TODO: FileManager을 통해, 앱 공간을 관리!
    enum Directory {
        case documents
        case caches
        
        var url: URL {
            let path: FileManager.SearchPathDirectory
            switch self {
            case .documents:
                path = .documentDirectory
            case .caches:
                path = .cachesDirectory
            }
            return FileManager.default.urls(for: path, in: .userDomainMask).first!
        }
    }
    
    
    // TODO: store 로직
    // Cadable encode : JSON 타입으로
    static func store<T: Encodable>(_ obj: T, to directory: Directory, as fileName: String) {
        
        // appendingPathComponent : 경로를 새롭게 추가
        let url = directory.url.appendingPathComponent(fileName, isDirectory: false)
        print("---> save to here: \(url)")
        
        let encoder = JSONEncoder()
        encoder.outputFormatting = .prettyPrinted   // 출력 줄바꿈 설정
        
        do {
            let data = try encoder.encode(obj)
            if FileManager.default.fileExists(atPath: url.path) {
                try FileManager.default.removeItem(at: url)
            }
            FileManager.default.createFile(atPath: url.path, contents: data, attributes: nil)
        } catch let error {
            print("---> Failed to store msg: \(error.localizedDescription)")
        }
        
    }
    
    
    
    // TODO: retrive 로직
    // 파일을 Data 타입 형태로 읽기
    // Codable decode : Data 타입으로
    static func retrive<T: Decodable>(_ fileName: String, from directory: Directory, as type: T.Type) -> T? {
        let url = directory.url.appendingPathComponent(fileName, isDirectory: false)
        guard FileManager.default.fileExists(atPath: url.path) else { return nil }
        guard let data = FileManager.default.contents(atPath: url.path) else { return nil }
        
        let decoder = JSONDecoder()
        
        do {
            let model = try decoder.decode(type, from: data)
            return model
        } catch let error {
            print("---> Failed to decode msg: \(error.localizedDescription)")
            return nil
        }
    }
    
    
    // TODO: remove 로직
    static func remove(_ fileName: String, from directory: Directory) {
        let url = directory.url.appendingPathComponent(fileName, isDirectory: false)
        guard FileManager.default.fileExists(atPath: url.path) else { return }
        
        do {
            try FileManager.default.removeItem(at: url)
        } catch let error {
            print("---> Failed to remove msg: \(error.localizedDescription)")
        }
        
    }
    
    
    // TODO: clear 로직
    static func clear(from directory: Directory) {
        let url = directory.url
        do {
            let contents = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: nil, options: [])   // contents 안에는 파일리스트가 배열로 들어가게 됨.
            for content in contents {   // 하나하나 다 지워!
                try FileManager.default.removeItem(at: content)
            }
        } catch {
            print("---> Failed to clear directory ms: \(error.localizedDescription)")
        }
    }
}