# fire3chat Live Demo <img src="https://i.imgur.com/pzsEm1X.png" style="width:200px;"> <img src="https://i.imgur.com/pWmXTY3.png" style="width:200px;"> ###### Source Code:https://github.com/roniboblee/fire3chat --- ### 1. 拉UI 第一頁 EnterViewController 第二頁 ChatView navigation bar control drag 1st view to 2nd view segue: show ,identifier: goChat --- 建好三個檔案 >EnterViewController: - UITextFieldDelegate - textField userName - go button action >ChatViewController: - UITextFieldDelegate - UItableview delegate+datasource - tableview(didset) - numberOfRowsInSection/ cellForRowAtIndexPath - textfield message - send button action >ChatTableCell: - 三個label 其中兩個是同一UILabel 每隻都跟UI連好 讓row自動調整高度 ```= tableView.estimatedRowHeight = 200 tableView.rowHeight = UITableViewAutomaticDimension ``` ~~鍵盤調整要在這先不管~~ --- ### 3. 將Firebase/Database + Messaging加入專案 先關掉專案 > pod init > vi PodFile >> pod 'Firebase' >> pod 'Firebase/Messaging' >> pod 'Firebase/Database' > > pod install > open myFireChat.xcworkspace/ 回到[Firebase Console](https://console.firebase.google.com/project/chatapp-53089/settings/general/ios:ladybobstudio.Chat) **下載plist檔 丟到專案中** 在appdelegate.swift中 ``` import Firebase didFinishLaunching...{ FIRApp.configure() } ``` 這樣就可以在專案裡使用firebase服務了 --- ### 4. 將realtime database事件寫進viewController data結構: / ``` "messages": { hashID: { "username": "bob" "message": "" "time": "" } } "tokens": { username: instanceID } ``` 一筆post的結構class: Post.swift ```= class Post { var userName: String? var message: String? var time: String? init(data: NSDictionary) { userName = data["username"] as? String ?? "" message = data["message"] as? String ?? "" time = data["time"] as? String ?? "" } } ``` ChatViewController.swift 讀取資料 將NSDictionary 轉成Post class型態,丟到陣列中 ```= import Firebase var posts = [Post]() var myName = String! let ref = FIRDatabase.database().reference() viewDidLoad { fetchData() } func fetchData(){ ref.child("messages").observeEventType(.ChildAdded, withBlock:{ snap in if let messageAdd = snap.value as? NSDictionary { let post = Post(data: messageAdd) self.posts.append(post) tableView.reloadData() } else { print("fetchData failed") } }) } ``` 也要把抓到的東西放進tableView裡,順便判斷一下是不是自己發出的訊息,return不同的cell ```= func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return posts.count } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { if posts[indexPath.row].userName == myName { let cell = tableView.dequeueReusableCellWithIdentifier("MyMessageCell") as! ChatTableViewCell cell.message.text = posts[indexPath.row].message return cell } else { let cell = tableView.dequeueReusableCellWithIdentifier("OtherMessageCell") as! ChatTableViewCell cell.message.text = posts[indexPath.row].message cell.userName.text = posts[indexPath.row].userName! + ":" return cell } } ``` userName是前一個viewController傳來的 在EnterViewController.swift寫prepareforSegue ```= var myName: String? @IBAction func goChat() { if userName.text! != "" { myName = userName.text! performSegueWithIdentifier("goChat", sender: nil) } } override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) { if segue.identifier == "goChat" { if let chatvc = segue.destinationViewController as? ChatViewController { chatvc.myName = myName! } } } ``` 到目前為止可以看到別人傳的訊息囉 接者是送新訊息到firebase realtime database 回到 ChatViewController 加上updateChildValues事件 ```= ... let ref = FIRDatabase.database().reference() let formatter = NSDateFormatter() @IBAction func send() { if message.text! != "" { let key = ref.child("messages").childByAutoId().key formatter.dateFormat = "yyyy/MM/dd, H:mm:ss" let timeStr = formatter.stringFromDate(NSDate()) let post = ["\(key)":["username": myName, "message": message.text!, "time": timeStr]] ref.child("messages").updateChildValues(post) message.text = "" } } ``` 成功聊天啦! 然後把專案權限開給web端共同使用同一個db web就可以跟app對話了 --- ### 5. 使用APNs服務 要怎麼使用server推播服務呢 - 首先你要有開發者帳號 - 在project capabilities將push notification打開 - 為了要讓通知可在app背景執行呼叫,也要打開 background modes 裡面的remote notification - 到[開發者網站](https://developer.apple.com/account/) ->Certificates, Identifiers & Profiles ->Identifiers -> appID -> - 點開專案 - 有兩種憑證 Development/Production SSL Certificate 開發用與發佈用 - 從開發用這邊製造你的憑證,丟到鑰匙圈匯出成p12檔 - 上傳p12檔到firebase messaging服務 - 那麼APNs就信任這個provider了 --- ### 6. 把notification setting寫進AppDelegate.swift 開啟app時 經過使用者同意開啟通知功能 ```= didFinishLaunchingWithOptions:... { let notiSetting = UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil) application.registerUserNotificationSettings(notiSetting) } ``` 當已設定好notification時 開啟remote推播功能 ```= didRegisterUserNotificationSettings:... { application.registerForRemoteNotifications() } ``` 當registerForRemoteNotifications( ) 註冊後會執行didRegisterForRemoteNotificationsWithDeviceToken:去獲取devicetoken 並將devicetoken轉成FIRInstanceID ```= didRegisterForRemoteNotificationsWithDeviceToken: ... { FIRInstanceID.instanceID().setAPNSToken(deviceToken, type: FIRInstanceIDAPNSTokenType.Unknown) } ``` 將InstanceID存在NSUserDefault ```= let defaults = NSUserDefaults.standardUserDefaults() let insID = FIRInstanceID.instanceID().token()! defaults.setObject(insID, forKey: "myInstanceID") ``` 在EnterViewController裡的goChat 把instanceID跟username綁在一起丟給db ```= @IBAction func goChat() { if userName.text! != "" { myName = userName.text! if let myInsID = defaults.objectForKey("myInstanceID") as? String { pushToken(myName!, insID: myInsID) } performSegueWithIdentifier("goChat", sender: nil) } } func pushToken(username: String, insID: String) { let token = [username: insID] ref.child("tokens").updateChildValues(token) } ``` --- ### 7. 試試用firebase console推推看 可用bundle id(地圖炮)或instanceID(單一裝置)推