SwiftとParseでの簡単にCRUD（生成、読み込み、更新、削除）。（Swift, Xcode6 beta5, Parse, UITableView, prepareForSegue)


![crud.mov.gif](https://qiita-image-store.s3.amazonaws.com/0/27810/7f125f66-cae3-08c7-a5a4-407340a0a609.gif "crud.mov.gif")

# A. 概要
- mBaaSであるParseを利用して、テキストをDBに生成し、それらをViewに表示、編集、更新、削除ができるようにする
- Viewでの表示はUITableViewを使用
- 複数のController間での値の受け渡しにはprepareForSegueを利用
- 筆者はiOS開発が初めてなので、まずキホンのCURDをやりたいと思ったので実行。
- 綺麗なコードでは無いので、是非プルリクやコメントお願いしますm(_ _)m

# B. 環境
- Xcode6 beta5
- Swift
- Parse.com

# C. Github

- この記事の内容を使っているプロジェクト -> [SwiftAndParseApp](https://github.com/kiiita/SwiftAndParseApp)
 - この記事に関するコミット -> [CRUD](https://github.com/kiiita/SwiftAndParseApp/commit/f720709f4cc37d7dad2a542f7104d7a227a82ba5)


# 1. CRUDの作成
- 前提として、Parseとは連携済みで、テキストを投稿するユーザー登録も完了している状態です。この部分に関しては、過去の記事をご覧ください。 -> [こちら](http://qiita.com/kiiita)

## 1-1. 生成 (Create)

### ■FirstViewとSecondViewの紐付け
- FirstViewであるtimelineTableViewControllerのNavigation部分にBar Button Itemを追加し、右カラムのインスペクタからIdentifierを”Add"にする。（FirstViewについては、過去記事を参照：[こちら](http://qiita.com/kiiita/items/bc62293f6c4d479993c1)）
- テキスト投稿用のControllerであるComposeViewController.swiftを作成する（cmd + NでNew Fileを開き、Cocoa touch classを選択して、Classには”ComposeViewController"、Subclassには"UIViewController"と入力する。
- ストーリーボードでもView Controllerを選択してストーリーボードにドラッグしてくる。右カラムのインスペクタの左から3つ目のボタンを押して、Custom Classとラベルがある一番上の部分のClassに先ほど作成した"ComposeViewController"と入力する。（ここで補完が出てこなかったら何かしら間違えてます）
- 最後にストーリーボードで、最初に追加したAddのBar Button Itemをcntr+クリックして、そのままComposeViewControllerにドラッグして、"push"を選択する
- ここまでできたら、一旦cmd+Rでシミュレータを立ち上げて、Addボタンを押したらComposeViewに遷移するかチェックしてください。

### ■ComposeViewControllerを作っていく
- まず、テキストの入力欄と、投稿用のボタンを用意する
 - テキスト入力欄はtext view、ボタンはbar button itemを選択して、それぞれComposeViewControllerにドラッグする。
- 次に、ComposeViewController.swiftを開き、下記のようにコードを書いていく。


```ComposeViewController.swift

import UIKit

class ComposeViewController: UIViewController,UITextViewDelegate {

	// 先ほど追加したtext viewに対応する
    @IBOutlet var tweetTextView: UITextView! = UITextView()
    
    required init(coder aDecoder: NSCoder!) {
        super.init(coder: aDecoder)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
		// text viewに関するデザイン部分
        tweetTextView.layer.borderColor = UIColor.blackColor().CGColor
        tweetTextView.layer.borderWidth = 0.5
        tweetTextView.layer.cornerRadius = 5
        tweetTextView.delegate = self
        
        tweetTextView.becomeFirstResponder()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
	
	// 投稿ボタンに関する部分
    @IBAction func sendTweet(sender: AnyObject) {
        // テキスト用のclass作成。
        var tweet:PFObject = PFObject(className: "Tweets")
        
        //Tweetsクラスに投稿ユーザーとテキストを生成する。
        tweet["content"] = tweetTextView.text
        tweet["tweeter"] = PFUser.currentUser()
        
        tweet.saveInBackground()
        
        self.navigationController.popToRootViewControllerAnimated(true)
        
    }
}
```

- 特にDB側の設定を何をしなくても、クラス（テーブル）やカラムは自動的に作成してくれる
- 上記コードのそれぞれの@IBActionにストーリーボードのアイテムをcntr + クリックからドラッグしてひもづける。

## 1-2. 読み込み (Read)

- この部分に関しては、過去記事を参照してくださいm(_ _)m
 - [mBaaSを使ってみよう！超簡単にSwiftでTwitterライクなポスト機能を作る](http://qiita.com/kiiita/items/bc62293f6c4d479993c1)
 - [Swift & Parseを使ったユーザー登録、ログイン、ログアウト](http://qiita.com/kiiita/items/1a1358437bb702a0af95)
- 一応、コードは貼っておきます。（後で使うので）

```timelineTableViewController.swift

import UIKit

class TimelineTableViewController: UITableViewController {
    
    var timelineData:NSMutableArray = NSMutableArray()
    
    @IBOutlet var currentUsername: UILabel!
    
    required init(coder aDecoder: NSCoder!) {
        super.init(coder: aDecoder)
    }
    
    @IBAction func loadData(){
        timelineData.removeAllObjects()
        
        // call databases
        var findTimelineData:PFQuery = PFQuery(className: "Tweets")
        
        findTimelineData.findObjectsInBackgroundWithBlock{
            (objects:[AnyObject]!, error:NSError!)->Void in
            
            if !error{
                for object in objects{
                    self.timelineData.addObject(object)
                }
                
                let array:NSArray = self.timelineData.reverseObjectEnumerator().allObjects
                self.timelineData = array as NSMutableArray
                
                self.tableView.reloadData()
            }
        }
    }

    
    override func viewDidAppear(animated: Bool) {
        
        // twwetのデータを取得
        self.loadData()
        
        // ログイン、サインアップのアラート
        if (!PFUser.currentUser()) {
            self.currentUsername.text = "You aren't logged in now."
            var loginAlert:UIAlertController = UIAlertController(title: "Sign UP / Loign", message: "Plase sign up or login", preferredStyle: UIAlertControllerStyle.Alert)
            
            loginAlert.addTextFieldWithConfigurationHandler({
                textfield in
                textfield.placeholder = "Your username"
            })
            
            loginAlert.addTextFieldWithConfigurationHandler({
                textfield in
                textfield.placeholder = "Your Password"
                textfield.secureTextEntry = true
            })
            
            loginAlert.addAction(UIAlertAction(title: "Login", style: UIAlertActionStyle.Default, handler: {
                    alertAction in
                    let textFields:NSArray = loginAlert.textFields as NSArray
                let usernameTextfield:UITextField = textFields.objectAtIndex(0) as UITextField
                let passwordTextfield:UITextField = textFields.objectAtIndex(1) as UITextField

                var tweeter:PFUser = PFUser()
                tweeter.username = usernameTextfield.text
                tweeter.password = passwordTextfield.text
                
                // Check already registerd user
                var checkExist = PFUser.query()
                checkExist.whereKey("username", equalTo: tweeter.username) // usernameをキーにしてDBを検索
                checkExist.findObjectsInBackgroundWithBlock {
                    (objects: [AnyObject]!, error: NSError!) -> Void in
                    if(objects.count > 0){
                        println("its username is taken \(objects.count)")
                        self.signIn(tweeter.username, password:tweeter.password) // Login for already registerd user
                    } else {                                                    
                        println("its username hasn't token yet. Let's register!")
                        self.signUp(tweeter) // Sign up for new user
                    }                       
                }
                self.currentUsername.text = "Logged in as \(tweeter.username)"
                
                }))
            
            self.presentViewController(loginAlert, animated: true, completion: nil)
        }else{
            self.currentUsername.text = "Logged in as \(PFUser.currentUser().username)"
        }
    }
    
    func signIn(username:NSString, password:NSString) {
        PFUser.logInWithUsernameInBackground(username, password: password) {
            (user: PFUser!, error: NSError!) -> Void in
            if user {
                println("existed user")
            } else {
                println("not existed user")
            }
        }
    }
    
    func signUp(tweeter:PFUser) {
        tweeter.signUpInBackgroundWithBlock{
            (success:Bool!, error:NSError!)->Void in
            if !error{
                println("Sign up succeeded.")
            }else{
                let errorString = error.userInfo["error"] as NSString
                println(errorString)
            }
        }
    }
    
    
    override func viewDidLoad() {
        super.viewDidLoad()

        var refresh = UIRefreshControl()
        refresh.attributedTitle = NSAttributedString(string: "Loading...")
        refresh.addTarget(self, action: "pullToRefresh", forControlEvents:.ValueChanged)
        
        self.refreshControl = refresh
    }
    
    func pullToRefresh(){
        self.loadData()
        refreshControl.endRefreshing()
        self.tableView.reloadData()
        println("reload finised")
        
    }
    

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

    // MARK: - Table view data source

    override func numberOfSectionsInTableView(tableView: UITableView!) -> Int {
        // #warning Potentially incomplete method implementation.
        // Return the number of sections.
        return 1
    }

    override func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
        // #warning Incomplete method implementation.
        // Return the number of rows in the section.
        // データの数だけrowを返す
        return timelineData.count
    }

    
    override func tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath!) -> UITableViewCell! {
        let cell:TweetTableViewCell = tableView!.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as TweetTableViewCell
        
        let tweet:PFObject = self.timelineData.objectAtIndex(indexPath!.row) as PFObject
        
        // デザイン部分
        cell.tweetTextView.alpha = 0
        cell.timestampLabel.alpha = 0
        cell.usernameLabel.alpha = 0
        
        // Tweetの内容をParse.comから取得
        cell.tweetTextView.text = tweet.objectForKey("content") as String
        
        var dataFormatter:NSDateFormatter = NSDateFormatter()
        dataFormatter.dateFormat = "yyyy-MM-dd HH:mm"
        cell.timestampLabel.text = dataFormatter.stringFromDate(tweet.createdAt)
        
        // objectIdをforeignKeyとして、user(tweeter)を取得
        var findTweeter:PFQuery = PFUser.query()
        findTweeter.whereKey("objectId", equalTo: tweet.objectForKey("tweeter").objectId)
        
        findTweeter.findObjectsInBackgroundWithBlock{
            (objects:[AnyObject]!, error:NSError!)->Void in
            if !error{
                let user:PFUser = (objects as NSArray).lastObject as PFUser
                cell.usernameLabel.text = "@\(user.username)"
                
                
                // CELLの表示を少しずつずらす
                UIView.animateWithDuration(0.5, animations: {
                    cell.tweetTextView.alpha = 1
                    cell.timestampLabel.alpha = 1
                    cell.usernameLabel.alpha = 1
                    })
            }
        }

        // Configure the cell...

        return cell
    }


}

```

## 1-3. 更新 (Update)

### ■更新用のControllerとViewの準備
- ここでの更新方法は、「TableViewのCellをクリックしたら新しいViewに遷移し、そこに現状のデータを表示し、編集して更新する」という形。
- 編集・更新をするために新しくControllerとViewを用意する
- EditViewController.swiftというものをComposeViewControllerの時と同じ要領で作成する。
 - ストーリーボードの方で、text viewとbuttonをドラッグして準備しておく。（編集部分と更新用のボタン）

### ■Cellを押したらそのCellの値を保持したままEditViewControllerに遷移させる
- tableViewのCellをctl+クリックして、EditViewControllerにドラッグして"push"で結びつける。
- 結びつけた際に出てくる線のアイコンの部分をクリックし、右カラムの４つ目のインスペクタで、Identifierに"editPath"と入力
- Cellの値を保持したまま遷移したいので、cellがあるtimelineTableViewController.swiftに下記を追加（下の方でOK）。

```timelineTableViewController.swift
    override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) {
        if (segue.identifier? == "editPath") {
            let nextViewController: EditViewController = segue.destinationViewController as EditViewController
            var ip: NSIndexPath! = self.tableView.indexPathForSelectedRow()
            nextViewController.param = self.timelineData.objectAtIndex(ip.row)
        }else{
            println("nothing to do")
        }
    }
```

- 次にEditViewControllerで上記の値を受け取る

```EditViewController.swift
import UIKit

class EditViewController: UIViewController {
    
    // timelineTableViewControllerからの受け取り
    var param:AnyObject!

	// テキストを編集するtext view
    @IBOutlet weak var tweet: UITextView! = UITextView()

    override func viewDidLoad() {
        super.viewDidLoad()
		// text viewに受け取ったcellの値を代入
        tweet.text = param.objectForKey("content") as String

        // Do any additional setup after loading the view.
    }
}    
```

- これで更新ページにCellに表示されていたテキストの内容が表示されます。

■ データの更新

- あとは更新に関する部分を追加する

```EditViewController.swift
import UIKit

class EditViewController: UIViewController {
    
    // timelineTableViewControllerからの受け取り
    var param:AnyObject!
	// ここを追加
    var query = PFQuery(className:"Tweets")
    @IBOutlet weak var tweet: UITextView! = UITextView()

    override func viewDidLoad() {
        super.viewDidLoad()
        tweet.text = param.objectForKey("content") as String

        // Do any additional setup after loading the view.
    }

    // ここを追加
    @IBAction func updateTweet(sender: AnyObject) {
        query.getObjectInBackgroundWithId(param.objectId) {
            (targetTweet: PFObject!, error: NSError!) -> Void in
            if error {
                NSLog("%@", error)
            } else {
                targetTweet["content"] = self.tweet.text
                targetTweet.saveInBackground()
                self.navigationController.popToRootViewControllerAnimated(true)
            }
        }
    }

```


## 1-4. 削除 (Delete)

- 削除ボタンはEditViewにつけることにしたので、上記コードにさらにDelete methodを追加

```EditViewController.swift 
    @IBAction func deleteTweet(sender: AnyObject) {
        query.getObjectInBackgroundWithId(param.objectId) {
            (targetTweet: PFObject!, error: NSError!) -> Void in
            if error {
                NSLog("%@", error)
            } else {
                targetTweet.deleteInBackground()
                self.navigationController.popToRootViewControllerAnimated(true)
            }
        }
    }
```

- ボタンはストーリーボードでドラッグで用意する。
- Bar buttonでも普通のButtonでもどちらでもOkです。
- あとは上記関数とボタンを結びつければ完了！

# D. ハマった点、よくわからなかった点
- prepareForSegueでifだけしか用意していないと、identifierが該当しない場合にエラーがクラッシュする。（今回だとComposeViewに関するものがなかったので。）

# E. 関連記事
- あとで書きます。

# F. 次にやってみたいこと
- あとで書きます。

by @kiiita :)