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)
- この部分に関しては、過去記事を参照してくださいm(_ _)m
- mBaaSを使ってみよう!超簡単にSwiftでTwitterライクなポスト機能を作る
- Swift & Parseを使ったユーザー登録、ログイン、ログアウト
- 一応、コードは貼っておきます。(後で使うので)
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 :)