본문 바로가기

학습 노트/iOS (2021)

178. File Manager #1

class FileManagerTableViewController: UITableViewController {
   
   var currentDirectoryUrl: URL?
   
   var contents = [Content]()
   
   var formatter: ByteCountFormatter = {
	  let f = ByteCountFormatter()
	  f.isAdaptive = true
	  f.includesUnit = true
	  return f
   }()

currentDirectoryUrl의 형식은 URL이다.
iOS에 경로는 URL 구조체로 표현한다.
만약 클래스로 서치하고자 한다면 NSURL 클래스를 사용한다.
이 둘은 파일과 네트워크 URL을 모두 표현할 수 있고, 이를 조작하기 위한 속성과 메서드를 제공한다.

  • currentDirectoryUrl은 Table View에 표시할 경로를 저장한다.
  • 만약 최초로 Scene에 진입한다면 Container의 Root Directory를 표시한다.
   override func viewDidLoad() {
	  super.viewDidLoad()
	   if currentDirectoryUrl == nil {
		   currentDirectoryUrl = URL(fileURLWithPath: NSHomeDirectory())
	   }
   }

currentDirectoryUrl이 비었다면 Root Directory를 표시하도록 수정한다.
이 때 사용한 메서드는 URL(fileURLWithPath:)메서드로 파일의 경로를 생성한다.

해당 메서드 외에 URL(string:)메서드는 네트워크와 파일 모두에 쓰일 수 있지만,
주로 네트워크에 사용된다.

파라미터로 전달한 NSHomeDirectory 메서드는 사용자의 HomeDirectory를 반환한다.
iOS의 경우 Sandbox의 DataContainerDirectory를 반환한다.

Optional(file:///var/mobile/Containers/Data/Application/D58492D5-E045-436D-88A8-BDFE4E398CBC/)

위와 같은 형태의 문자열이다.

   override func viewWillAppear(_ animated: Bool) {
	  super.viewWillAppear(animated)
	  
	  refreshContents()
	  updateNavigationTitle()
   }

viewWillAppear에서는 refreshContents와 updateNavigationTitle 메서드를 호출하고있다.
각각 Directory 내용과 NavigationBar의 Title을 업데이트한다.

struct Content {
   let url: URL
   
   var name: String {
	  return ""
   }
   
   var size: Int {
	  return 0
   }
   
   var type: Type {
	  return .file
   }
   
   var isExcludedFromBackup: Bool {
	  return false
   }
}

경로와 파일은 위와 같은 Content 구조체로 Contents 배열에 저장한다.

enum Type: Int {
   case directory
   case file
}

경로와 파일은 type으로 구분하고,
type은 Type 열거형을 정의돼있다.
원시값이 Int이므로 이를 사용해 경로가 먼저 표시되도록 만들수 있다.

viewWillAppear

   override func viewWillAppear(_ animated: Bool) {
	  super.viewWillAppear(animated)
	  
	  refreshContents()
	  updateNavigationTitle()
   }

refreshContents 메서드와 updateNavigationTitle 메서드를 호출해
데이터를 불러오고, 업데이트 된 데이터를 기준으로 Navivation Title을 업데이트한다.

refreshContents

func refreshContents() {
	   contents.removeAll()
	   
	   defer {
		   tableView.reloadData()
	   }
	   
	   guard let url = currentDirectoryUrl else { fatalError("url is empty") }
	   
	   do {
		   let properties: [URLResourceKey] = [.localizedNameKey, .isDirectoryKey, .fileSizeKey, .isExcludedFromBackupKey]
		   let currentContentsUrls = try FileManager.default.contentsOfDirectory(at: url, includingPropertiesForKeys: properties, options: .skipsHiddenFiles)
		   
		   for url in currentContentsUrls {
			   let content = Content(url: url)
			   contents.append(content)
		   }
		   
		   contents.sort { lhs, rhs -> Bool in
			   if lhs.type == rhs.type {
				   return lhs.name.lowercased() < rhs.name.lowercased()
			   }
			   
			   return lhs.type.rawValue < rhs.type.rawValue
		   }
	   } catch {
		   print(error)
	   }
   }
  • contents 배열 초기화
   func refreshContents() {
	   contents.removeAll()

 

removeAll()

 

Apple Developer Documentation

 

developer.apple.com

  • currentDirectoryUrls 배열에 대상 URL 저장

파일 관련 처리는 FileManager 클래스를 사용.
기본적인 동작들은 default 속성으로 해결 되지만 이벤트 처리 등은 별도의 인스턴스 생성 필요.

contentsOfDirectory(at:includingPropertiesForKeys:options:)

 

Apple Developer Documentation

 

developer.apple.com

두번째 파라미터인 includingPropertiesForKeys에 전달되는 URLResourceKeys로 전달 받을 속성 선택 가능
강의에서는 localizedNameKey(이름), isDirectoryKey(경로), fileSizeKey(크기), isExcludedFromBackupKey(백업플래그) 사용

세번째 파라미터인 DirectoryEnumeration 구조체로 열거 형식 선택 가능
강의에서는 skipsHiddenFiles(숨긴 파일 제외) 사용

  • 배열을 순회하며 contents 배열에 Content 구조체로 저장

Content 구조체

struct Content {
   let url: URL
   
   var name: String {
	   let values = try? url.resourceValues(forKeys: [.localizedNameKey])
	   return values?.localizedName ?? "???"
   }
   
   var size: Int {
	   let values = try? url.resourceValues(forKeys: [.fileSizeKey])
	   return values?.fileSize ?? 0
   }
   
   var type: Type {
	   let values = try? url.resourceValues(forKeys: [.isDirectoryKey])
	   return values?.isDirectory == true ? .directory : .file
   }
   
   var isExcludedFromBackup: Bool {
	   let values = try? url.resourceValues(forKeys: [.isExcludedFromBackupKey])
	   return values?.isExcludedFromBackup ?? false
   }
}

전달된 URL을 사용해 적절한 데이터를 받아와 반환.

resourceVlaues(forKeys:)

 

Apple Developer Documentation

 

developer.apple.com

파라미터로 전달되는 Key들은 URL 생성시에 전달한 properties 배열에 존재하는 Key이다.

  • contents 배열을 조건에 맞게 정렬

비교 대상의 type이 동일하다면 대소문자에 관계 없이 알파벳 순으로 정렬한다.
동일하지 않다면 원시값의 크기에 따라 순서대로 정렬한다.

더보기

Type 열거형

enum Type: Int {
   case directory
   case file
}
  • 작업이 끝나면 Table View 새로고침, 혹은 표시할 경로가 존재하지 않는 경우 error

defer문을 사용해 refreshContents 메서드가 실행 완료 되면 tableView를 새로고침 하도록 한다.

 

 

결과

 

Directory 이동

   func move(to url: URL) {
	   do {
		   let reachable = try url.checkResourceIsReachable()
		   if !reachable {
			   return
		   }
	   } catch {
		   print(error)
		   return
	   }
	   
	   if let vc = storyboard?.instantiateViewController(withIdentifier: "FileManagerTableViewController") as? FileManagerTableViewController {
		   vc.currentDirectoryUrl = url
		   
		   navigationController?.pushViewController(vc, animated: true)
	   }
   }
  • URL 유효성 검사

checkResourceIsReachable

 

Apple Developer Documentation

 

developer.apple.com

유효한 URL이면 true를 반환한다.

해당 메서드를 사용해 분기한다.

  • 기본 View Controller를 재사용 하는 방식으로 Directory 이동

경로를 선택하면 이동하되, 계속 새로운 경로가 존재한다면 이론상 View Controller가 무한히 필요하다.
매번 새 View Controller를 만드는 대신 이미 생성된 View Controller를 재사용 하는 방식으로 구현한다.

StoryboardID를 파라미터로 넘겨 해당 View Controller를 불러 오고,
현재 URL에 해당하는 currentDirectoryUrl 변수에 전달 된 url을 저장하고, 이를 navigation Controller에 push한다.

 

결과

'학습 노트 > iOS (2021)' 카테고리의 다른 글

180. File Manager #3  (0) 2022.01.19
179. File Manager #2  (0) 2022.01.19
177. Data Persistence Overview  (0) 2022.01.10
176. GCD in Action  (0) 2022.01.10
175. Dispatch Group, Dispatch Semaphore  (0) 2022.01.10