このページを読む前に、次の3つのページの内容を理解することをお勧めします。
UIViewController のサブクラスとして3つのクラスを生成します。 それぞれのクラス名とファイル名は次のようにします。
クラス名 | subclass of: | ファイル名 |
EditViewController | UIViewController | EditViewController.swift |
ListViewController | UIViewController | ListViewController.swift |
SelectViewController | UIViewController | SelectViewController.swift |
project navigator のプロジェクト名の上でマウスを右ドラッグして "New File..." を選択します。 iOS の Source で Cocoa Touch Class を選んで Next をクリックします。 Class: には "EditViewController", Subclass of: には "UIViewController" Language: には "Swift" を選びます(クラス名は自由に選んで構いません)。 EditViewController.swift がプロジェクトに追加されます。
これをもう2回繰り返して、合計で3つのファイルを追加します。
これ以降、Main.storyboard 上の4個のUIViewController のサブクラスを それぞれ ViewController, EditViewController, ListViewController, SelectViewController と呼ぶことにします。
種類 | connection | 変数名またはメソッド名 |
"rows" Label | なし | ... |
TextField | Outlet | rowsField変数 |
Action (Editing Did End) | rowsEditingEnd()関数 | |
"columns" Label | なし | ... |
UITextField | Outlet | colsField変数 |
Action (Editing Did End) | colsEditingEnd()関数 | |
"Back" Button | Action (Touch Up Inside) | tapBack()関数 |
"Save" Button | Action (Touch Up Inside) | tapSave()関数 |
"0" Button | Action (Touch Up Inside) | tapButton0()関数 |
"1" Button | Action (Touch Up Inside) | tapButton1()関数 |
"2" Button | Action (Touch Up Inside) | tapButton2()関数 |
"3" Button | Action (Touch Up Inside) | tapButton3()関数 |
UIImageView | Outlet | myImageView変数 |
Attribute Inspector から Interaction の "User Interaction Enabled" と "Multiple Touch" にチェックを入れます。
EditViewController.swiftに追加するコード(赤字部分) |
import UIKit class EditViewController: UIViewController { var rows: Int! var cols: Int! var mode: Int! var map: [Int]! let colors:[[CGFloat]] = [[ 0.7, 0.7, 0.7, 1.0 ], [ 1.0, 0.0, 0.0, 1.0 ], [ 0.0, 1.0, 0.0, 1.0 ], [ 0.0, 0.0, 1.0, 1.0 ]] @IBOutlet weak var rowsField: UITextField! @IBOutlet weak var colsField: UITextField! @IBOutlet weak var myImageView: UIImageView! @IBAction func rowsEditingEnd(sender: AnyObject) { if let t = Int(rowsField.text!) { changeMapSize(t,cols) } rowsField.text = String(rows) } @IBAction func colsEditingEnd(sender: AnyObject) { if let t = Int(colsField.text!) { changeMapSize(rows,t) } colsField.text = String(cols) } @IBAction func tapButton0(sender: AnyObject) { mode = 0 } @IBAction func tapButton1(sender: AnyObject) { mode = 1 } @IBAction func tapButton2(sender: AnyObject) { mode = 2 } @IBAction func tapButton3(sender: AnyObject) { mode = 3 } @IBAction func tapSave(sender: AnyObject) { let format = NSDateFormatter() format.dateFormat = "yyyy-MM-dd_HH-mm-ss" format.timeZone = NSTimeZone(abbreviation: "JST") let now = format.stringFromDate(NSDate()) let path = NSHomeDirectory() + "/Documents/" + now + ".txt" var contents: String = "\(rows) \(cols)" for m in map { contents = contents + " " + String(m) } do { try contents.writeToFile(path, atomically:true, encoding:NSUTF8StringEncoding) } catch let error as NSError { let alert = UIAlertController(title:"Save", message: "error occurred: "+String(error), preferredStyle: UIAlertControllerStyle.Alert) alert.addAction(UIAlertAction(title:"Cancel", style:UIAlertActionStyle.Cancel,handler:nil)) presentViewController(alert,animated:true,completion:nil) } } @IBAction func tapBack(sender: AnyObject) { dismissViewControllerAnimated(true, completion: nil) } @IBAction func tapImageView(sender: UITapGestureRecognizer) { let pos = sender.locationInView(myImageView) let sz:CGSize = myImageView.bounds.size let bw = sz.width / CGFloat(cols) let bh = sz.height / CGFloat(rows) let c = Int(pos.x / bw) let r = Int(pos.y / bh) map[r * cols + c] = mode drawMap() } func drawMap() { let sz:CGSize = myImageView.bounds.size let bw = sz.width / CGFloat(cols); let bh = sz.height / CGFloat(rows); UIGraphicsBeginImageContext(sz) let context: CGContextRef = UIGraphicsGetCurrentContext()! CGContextSetLineWidth(context, 2.0) CGContextSetRGBStrokeColor(context, 0.5, 0.5, 0.5, 1.0) for i in 0..<(rows*cols) { let r = CGFloat(i / cols) let c = CGFloat(i % cols) let m = map[i] CGContextSetRGBFillColor(context, colors[m][0], colors[m][1], colors[m][2], colors[m][3]) let rect = CGRect(x: c * bw, y: r * bh, width: bw, height: bh) CGContextFillRect(context, rect) CGContextStrokeRect(context, rect) } myImageView.image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() } func changeMapSize(rs:Int, _ cs:Int) { print("map = \(rows) x \(cols) to \(rs) x \(cs)") var map2 = Array<Int>(count: (rs*cs), repeatedValue: 0) for r in 0..<rs { for c in 0..<cs { if (r < rows) && (c < cols) { map2[r * cs + c] = map[r * cols + c] } } } map = map2; rows = rs; cols = cs drawMap() } override func viewDidLoad() { super.viewDidLoad() rows = rows ?? 8 cols = cols ?? 8 mode = mode ?? 0 rowsField.text = String(rows) colsField.text = String(cols) map = Array<Int>(count: (rows*cols), repeatedValue: 0) drawMap() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } } |
最初の画面で "Edit" ボタンを選択すると、マップ編集画面に遷移します。 rowsやcolumnsの横のテキストフィールドに数値を代入するとTabを押した瞬間、マップのサイズが変更されます。 数字のボタンをタップしてから、ImageViewをタップすると矩形領域の色が変ります。 Save ボタンでデータをファイルに保存します。ファイル名は"そのときの日時.txt" となります。
ファイルの保存ができたので、作成したファイルの一覧を見たり、ファイルをロードする部分を作成します。
種類 | connection | 変数名またはメソッド名 |
TableView | Outlet | myTableView |
"Back" Button | Action (Touch Up Inside) | tapBack()関数 |
ListViewController.swiftに追加するコード(赤字部分) |
import UIKit class ListViewController: 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: SelectViewController = storyboard.instantiateViewControllerWithIdentifier("SelectViewController") as! SelectViewController self.presentViewController(controller, animated: true, completion: nil) } func refreshPaths() { paths = manager.subpathsAtPath(fullPath) } 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() + "/Documents" } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } } |
最初の画面で "List" ボタンを選択すると、一覧表示画面に遷移します。 ここでは2つのファイルが生成されていることがわかります。 TableViewの項目をタップすると SelectViewController に遷移しますが、 そちらはまだ何も記述していないので戻る手段はありません。
ファイル一覧から特定のファイルが選択されたとき、そのファイルを削除したり、 データをロードしたりできるようにします。
UIView オブジェクト | connection | 変数名またはメソッド名 |
Label | Outlet | myLabel変数 |
Text View | Outlet | myTextView |
"Back" Button | Action (Touch Up Inside) | tapBack()関数 |
"Load" Button | Action (Touch Up Inside) | tapLoad()関数 |
"Remove" Button | Action (Touch Up Inside) | tapRemove()関数 |
SelectViewController.swiftに追加するコード(赤字部分) |
import UIKit class SelectViewController: 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 tapLoad(sender: AnyObject) { if let txt = myTextView.text { let a:[String] = txt.componentsSeparatedByString(" ") if a.count > 2 { let rows = Int(a[0]) let cols = Int(a[1]) if (rows != nil) && (cols != nil) { var map = Array<Int>() for s in a { if let n = Int(s) { map.append(n) } else { return } } // success let appDelegate:AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate appDelegate.rows = rows appDelegate.cols = cols appDelegate.map = map print("\(rows) \(cols) \(map.count)") 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() + "/Documents/" + path myLabel.text = path } override func viewDidAppear(animated: Bool) { setup() fileContents() } override func viewDidLoad() { super.viewDidLoad() } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } } |
SelectViewController に遷移する前に、SelectViewController の path 変数に選択されたファイルの名前を代入するように変更します。 1行追加する必要があります(緑字の部分)。
ListViewController.swiftに追加するコード(緑字部分) |
import UIKit class ListViewController: 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: SelectViewController = storyboard.instantiateViewControllerWithIdentifier("SelectViewController") as! SelectViewController controller.path = paths[indexPath.row] self.presentViewController(controller, animated: true, completion: nil) } func refreshPaths() { paths = manager.subpathsAtPath(fullPath) } 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() + "/Documents" } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } } |
Loadされたデータは AppDelegateオブジェクトの変数 rows: Int!, cols: Int!, map:[Int]! に保存されています (この例では活用していません)。 ゲーム画面を追加して、そちらから AppDelegate にアクセスしてデータを利用することを想定しています。
AddDelegate.swiftに追加するコード(赤字部分) |
import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var rows: Int! var cols: Int! var map: [Int]! var window: UIWindow? func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { return true } func applicationWillResignActive(application: UIApplication) { } func applicationDidEnterBackground(application: UIApplication) { } func applicationWillEnterForeground(application: UIApplication) { } func applicationDidBecomeActive(application: UIApplication) { } func applicationWillTerminate(application: UIApplication) { } } |
まず最初の画面で List ボタンを選択し、 一覧からファイルを選択すると SelectViewController の画面に遷移します。 そちらで、ファイルからデータをロードしたり、ファイルを消去したりできます。
簡単のため、 フォルダの詳細を表示した場合は、ファイルの内容は"[[Directory]]" とだけ表示します。 また、フォルダでない通常のファイルでも拡張子が".txt"でない場合は "[[not directory, but has no ".txt" suffix]]" と表示します。
ファイルの詳細を表示中に "Remove" ボタンをタップすると、そのファイルやフォルダを消すことができます。 フォルダの中に他のファイルやフォルダが存在しても丸ごと消せるので注意すること。