iOSプログラミング with Swift 2


2016.06.29: created by 2016.06.29: revised by

Swiftでファイルシステムを扱う

  1. Xcode を起動して "Create a new Xcode project" で "Single View Application" として新しいプロジェクトを開きます。 ここではプロジェクト名を SwiftFileViewer としています。



  2. ViewController の新しいサブクラスを作ります。
  3. UIViewController のサブクラスとして3つのクラスを生成します。 それぞれのクラス名とファイル名は次のようにします。

    クラス名subclass of:ファイル名
    FileAddUIViewControllerFileAdd.swift
    FileListUIViewControllerFileList.swift
    FileSelectUIViewControllerFileSelect.swift

    project navigator のプロジェクト名の上でマウスを右ドラッグして "New File..." を選択します。 iOS の Source で Cocoa Touch Class を選んで Next をクリックします。 Class: には "FileList", Subclass of: には "UIViewController" Language: には "Swift" を選びます(クラス名は自由に選んで構いません)。 FileList.swift がプロジェクトに追加されます。













    これをもう2回繰り返して、合計で3つのファイルを追加します。







  4. Main.storyboard上の ViewController の右隣に、ウィンドウ右下の "Object library" から "View Controller" をドラッグして3個配置します。 さらに、新しいView Controller が選択されている状態でウィドウ右上の identity inspector からCustom cluss: に今作成した UIViewController のサブクラス を設定します。 また、Identity の Storyboard IDにも クラスと同じ名前をつけましょう(この名前は自由につけて構いませんが、説明を簡単にするためです)。






  5. Main.storyboard上の ViewController 上に Button を2個配置し、表示を と Table View を配置し、 Buttonの表示を "List Files" と "Add File" にします。 さらに、"List Files" ボタンから FileListクラスのビューコントローラにセグウェイを設定します。 また、"Add File" ボタンから FileAddクラスのビューコントローラにもセグウェイを設定します。
  6. (注意)下の画像ではMain.storyboard 全体を表示するために 50% で表示しています。表示を縮小したMain.storyview ではいろいろな操作が行えないので100%表示にして操作をして下さい。








  7. Storyboard上の FileList クラスの UIViewController 上に Button と Table View を配置し、 Buttonの表示を "Back" にします。さらに、FileList.swiftにconnectします。
  8. UIView オブジェクトconnection変数名またはメソッド名
    ButtonAction (Touch Up Inside)tapBack()関数
    Table ViewOutletmyTableView






  9. Storyboard上の FileSelect クラスの UIViewController 上に Label を1個、Button を2個、Text Viewを1個配置し、 Buttonの表示を "Back" と "Remove" にします。さらに、FileSelect.swiftにconnectします。
  10. UIView オブジェクトconnection変数名またはメソッド名
    LabelOutletmyLabel変数
    Text ViewOutletmyTextView
    "Back" ButtonAction (Touch Up Inside)tapBack()関数
    "Remove" ButtonAction (Touch Up Inside)tapRemove()関数






  11. Storyboard上の FileAdd クラスの UIViewController 上に Label を3個、Button を3個、Text Field と Text Viewを1個ずつ配置します。 さらに、Labelの表示をそれぞれ "Add File", "Path", "Contents" に変更し、 またButtonの表示をそれぞれ "Back", "AddFile", "Make Dir" にします。 さらに、Button 3個と、Text Field, Text View を FileAdd.swift にconnectします。
  12. UIView オブジェクトconnection変数名またはメソッド名
    Text FieldOutletmyTextField
    Text ViewOutletmyTextView
    "Back" ButtonAction (Touch Up Inside)tapBack()関数
    "Add File" ButtonAction (Touch Up Inside)tapAdd()関数
    "Make Dir" ButtonAction (Touch Up Inside)tapMkdir()関数






  13. ViewController.swift を変更します。
  14. ViewController.swiftに追加するコード(赤字部分)
    import UIKit
    
    class ViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
        }
    
    }
    
  15. FileList.swift を変更します。
  16. FileList.swiftに追加するコード(赤字部分)
    import UIKit
    
    class FileList: UIViewController, UITableViewDataSource, UITableViewDelegate {
        
        var manager: NSFileManager!
        var fullPath: String!
        var paths: Array<String>!
    
        @IBOutlet weak var myTableView: UITableView!
        
        @IBAction func tapBack(sender: AnyObject) {
            dismissViewControllerAnimated(true, completion: nil)
        }
        
        func tableView(tableView: UITableView, numberOfRowsInSection section:Int) -> Int {
            if paths == nil {
                return 1
            }
            return paths.count;
        }
        
        func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
            let cell: UITableViewCell = UITableViewCell(style: UITableViewCellStyle.Subtitle, reuseIdentifier: "Cell")
            cell.textLabel?.text = paths[indexPath.row]
            return cell
        }
        
        func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
            let storyboard = UIStoryboard(name:"Main",bundle:nil)
            let controller: FileSelect = storyboard.instantiateViewControllerWithIdentifier("FileSelect") as! FileSelect
            controller.path = paths[indexPath.row]
            self.presentViewController(controller, animated: true, completion: nil)
        }
    
        func refreshPaths() {
            paths = manager.subpathsAtPath(fullPath)
            print("fullPath = \(fullPath)")
            if paths != nil {
                print("paths.count = \(paths.count)")
            }
        }
        
        override func viewWillAppear(animated: Bool) {
            refreshPaths()
            self.myTableView.reloadData()
        }
        
        override func viewDidLoad() {
            super.viewDidLoad()
            myTableView.delegate = self
            myTableView.dataSource = self
            manager = NSFileManager.defaultManager()
            fullPath = NSHomeDirectory()
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
        }
    
    }
    
  17. FileAdd.swift を変更します。
  18. FileAdd.swiftに追加するコード(赤字部分)
    import UIKit
    
    class FileAdd: UIViewController {
    
        @IBOutlet weak var myTextField: UITextField!
        @IBOutlet weak var myTextView: UITextView!
        @IBAction func tapCancel(sender: AnyObject) {
            dismissViewControllerAnimated(true, completion: nil)
        }
        @IBAction func tapAdd(sender: AnyObject) {
            var path:String = myTextField.text ?? ""
            if path.hasPrefix("/") {
                path = path.substringFromIndex(path.startIndex.successor())
            }
            path = NSHomeDirectory() + "/" + path
            let contents: String = myTextView.text ?? ""
            do {
                try contents.writeToFile(path, atomically:true, encoding:NSUTF8StringEncoding)
            } catch let error as NSError {
                let alert: UIAlertController = UIAlertController(title:"Add File", message: "error occurred: "+String(error),preferredStyle: UIAlertControllerStyle.Alert)
                alert.addAction(UIAlertAction(title:"Cancel",style:UIAlertActionStyle.Cancel,handler:nil))
                presentViewController(alert,animated:true, completion:nil)
                return;
            }
            dismissViewControllerAnimated(true, completion: nil)
        }
        @IBAction func tapMkdir(sender: AnyObject) {
            var path:String = myTextField.text ?? ""
            if path.hasPrefix("/") {
                path = path.substringFromIndex(path.startIndex.successor())
            }
            path = NSHomeDirectory() + "/" + path
            do {
                try NSFileManager.defaultManager().createDirectoryAtPath(path,withIntermediateDirectories:true, attributes: nil)
            } catch let error as NSError {
                let alert: UIAlertController = UIAlertController(title:"Make Dir", message: "error occurred: "+String(error),preferredStyle: UIAlertControllerStyle.Alert)
                alert.addAction(UIAlertAction(title:"Cancel",style:UIAlertActionStyle.Cancel,handler:nil))
                presentViewController(alert,animated:true, completion:nil)
                return;
            }
            dismissViewControllerAnimated(true, completion: nil)
        }
        
        override func viewDidLoad() {
            super.viewDidLoad()
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
        }
    
    }
    
  19. FileSelect.swift を変更します。
  20. FileSelect.swiftに追加するコード(赤字部分)
    import UIKit
    
    class FileSelect: UIViewController {
        
        var path: String!   // this value should be set from the outer
        var fullPath: String!
    
        @IBOutlet weak var myLabel: UILabel!
        @IBOutlet weak var myTextView: UITextView!
        
        @IBAction func tapBack(sender: AnyObject) {
            dismissViewControllerAnimated(true, completion: nil)
        }
        
        @IBAction func tapRemove(sender: AnyObject) {
            do {
                try NSFileManager.defaultManager().removeItemAtPath(fullPath)
                dismissViewControllerAnimated(true, completion: nil)
            } catch let error as NSError {
                let alert: UIAlertController = UIAlertController(title:"Selected File",
                                                               message: "error occurred: "+String(error),
                                                        preferredStyle: UIAlertControllerStyle.Alert)
                alert.addAction(UIAlertAction(title:"Cancel",style:UIAlertActionStyle.Cancel,handler:nil))
                presentViewController(alert,animated:true, completion:nil)
            }
        }
        
        func fileContents() {
            let manager:NSFileManager = NSFileManager.defaultManager()
            var isDir: ObjCBool = false
            let flag = manager.fileExistsAtPath(fullPath, isDirectory:&isDir)
            if flag && Bool(isDir) {
                myTextView.text = "[[Directory]]"
            } else if flag {
                if fullPath.hasSuffix(".txt") {
                    do {
                        myTextView.text = try NSString(contentsOfFile: fullPath, encoding: NSUTF8StringEncoding) as String
                    } catch let error as NSError {
                        let alert: UIAlertController = UIAlertController(title:"Selected File",
                                                                         message: "cannot read .txt file: "+String(error),
                                                                         preferredStyle: UIAlertControllerStyle.Alert)
                        alert.addAction(UIAlertAction(title:"Cancel",style:UIAlertActionStyle.Cancel,handler:nil))
                        presentViewController(alert,animated:true, completion:nil)
                        
                    }
                } else {
                    myTextView.text = "[[not directory, but has no \".txt\" suffix]]"
                }
            } else {
                let alert: UIAlertController = UIAlertController(title:"Selected File",
                                                                 message: "No such file exists",
                                                                 preferredStyle: UIAlertControllerStyle.Alert)
                alert.addAction(UIAlertAction(title:"Cancel",style:UIAlertActionStyle.Cancel,handler:nil))
                presentViewController(alert,animated:true, completion:nil)
            }
        }
        
        func setup() {
            if path == nil {
                let alert: UIAlertController = UIAlertController(title:"Selected File",
                                                               message: "path is nil: ",
                                                        preferredStyle: UIAlertControllerStyle.Alert)
                alert.addAction(UIAlertAction(title:"Cancel",style:UIAlertActionStyle.Cancel,handler:nil))
                presentViewController(alert,animated:true, completion:nil)
                path = ""
            }
            fullPath = NSHomeDirectory() + "/" + path
            myLabel.text = path
        }
        
        override func viewDidAppear(animated: Bool) {
            setup()
            fileContents()
        }
        
        override func viewDidLoad() {
            super.viewDidLoad()
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
        }
    
    }
    
  21. プログラムを実行する。
  22. まず最初の画面で "List Files"ボタンと "Add File" ボタンが表示される。 "List Files"ボタンを押すと、ファイルの一覧が表示される。 アプリ毎にiOS上のファイル・システムの一部が割り当てられており、以下のような方針でファイルを配置する。

    List Filesボタン
    -->
    Backボタン
    -->
    最初の画面へ

    最初の画面で"Add File" ボタンをタップすると、FileAddクラスの画面となる。 Pathを指定して"Make Dir"ボタンをタップするとそのパスに沿ってフォルダが生成される。

    Add Fileボタン
    -->
    Make Dirボタン
    -->
    フォルダ生成。
    最初の画面へ
    List Filesボタン
    -->
    生成を確認。
    Backボタン
    -->
    最初の画面へ

    最初の画面で"Add File" ボタンをタップすると、FileAddクラスの画面となる。 Pathを指定して、"Add File" ボタンをタップするとファイルが生成され、ファイルの内容はText View中に記述した文字列となる。

    Add Fileボタン
    -->
    Add Fileボタン
    --> ファイルが生成される
    List Filesボタン
    -->
    生成を確認。
    項目をタップ
    -->
    c.txtの詳細

    簡単のため、 フォルダの詳細を表示した場合は、ファイルの内容は"[[Directory]]" とだけ表示する。 また、フォルダでない通常のファイルでも拡張子が".txt"でない場合は "[[not directory, but has no ".txt" suffix]]" と表示する。




    ファイルの詳細を表示中に "Remove" ボタンをタップすると、そのファイルやフォルダを消すことができる。 フォルダの中に他のファイルやフォルダが存在しても丸ごと消せるので注意すること。

  23. サンプルのプロジェクトはこちら。(Xcode 7.3.1版)


http://nw.tsuda.ac.jp