LoginSignup
69

More than 5 years have passed since last update.

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

Last updated at Posted at 2014-08-15

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
    • この記事に関するコミット -> CRUD

1. CRUDの作成

  • 前提として、Parseとは連携済みで、テキストを投稿するユーザー登録も完了している状態です。この部分に関しては、過去の記事をご覧ください。 -> こちら

1-1. 生成 (Create)

■FirstViewとSecondViewの紐付け

  • FirstViewであるtimelineTableViewControllerのNavigation部分にBar Button Itemを追加し、右カラムのインスペクタからIdentifierを”Add"にする。(FirstViewについては、過去記事を参照:こちら
  • テキスト投稿用の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)

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"で結びつける。
  • 結びつけた際に出てくる線のアイコンの部分をクリックし、右カラムの4つ目のインスペクタで、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 :)

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
69