# 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(單一裝置)推