使用wilddog实现的iOS平台简易聊天软件

在逛论坛与看公众号文章时看到了铺天盖地的wilddog广告,由于没接触过这种东东,而且看官网很简洁清爽,符合个人口味,遂想写个简单的IM软件来实践一下。


简介

先上代码:Dogchat_Swift

上学期课余时间写的,实现了一些简单的功能,主要是用wilddog的服务器与其API实现的:

  • 私聊(文字, 定位)
  • 群聊(文字, 图片)
  • 通讯录
  • 消息推送
  • 其他功能

测试时用两台测试机与模拟器进行群聊,一台测试机与模拟器进行私聊与定位,模拟器定位用手动设置的location,响应都挺快的。

环境:

  • OS X 10.11.6
  • Xcode 7.3
  • Swift

实现

准备工作

运行代码时需要在ConstantValue.swift文件中将WilddogURL的值改成你自己的wilddog数据库地址。

在AppDelegate里修改根视图可直接跳过登录界面。

方便起见,直接使用wilddog的身份认证功能添加用户。

登录

file:LoginViewController.swift

使用Wilddog对象的authUser方法登录,用刚才添加的账号即可,不过要修改一下authUserLogin方法中的transmitAccountMsg函数参数,影响登陆后数据的获取。

登录界面只缺一个美工系列:


用户登录:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func authUserLogin(user:String,password:String){
let myRootRef = Wilddog(url:WilddogURL)
myRootRef.authUser(user, password: password) {
error, authData in
if error != nil {
print(error)
let alertController = UIAlertController(title: "错误", message: "账号或密码错误", preferredStyle: .Alert)
let defaultAction = UIAlertAction(title: "OK", style: .Default, handler: nil)
alertController.addAction(defaultAction)
self.presentViewController(alertController, animated: true, completion: nil)
} else {
let mainVC = ViewController()
mainVC.transmitAccountMsg(user)
let nvc=UINavigationController(rootViewController:mainVC)
self.presentViewController(nvc, animated: true, completion: nil)
}
}
}

数据存储

后台数据,相关操作使用的wilddog提供的api:

  • PrivateMsg:私聊与定位数据
  • UserAccount:用户信息
  • UserMsg:群聊数据

群聊

file:ViewController.swift

在AppDelegate里修改根视图可直接跳过登录界面。

登录进去后的界面就是群聊界面,聊天内容所有成员可见。

有个方块旋转的加载动画,加载后如图:


群聊数据初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func msgInitialAdd(){
let myRootRef = Wilddog(url:WilddogURL)
let userRef = myRootRef.childByAppendingPath("UserMsg")
userRef.observeSingleEventOfType(.Value, withBlock: { snapshot in
let count = snapshot.value.count
var i = 0
self.observerHandle = userRef.queryOrderedByKey().observeEventType(.ChildAdded, withBlock: { snapshot in
if snapshot.value != nil{
self.dic[snapshot.key] = snapshot.value as? Dictionary
self.chatMsgArray.append(snapshot.key)
i=i+1
if i == count {
self.loadingView.hidden = true
self.timeAnime.tupleToAnime(self.parse.parseStr(self.dic[snapshot.key]!["time"]!))
userRef.removeAllObservers()
self.initialAdds = false
self.tableView!.reloadData()
}
}
})
})
}


发送图片(ImagePickerControllerDelegate的委托方法):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func imagePickerController(picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : AnyObject])
{
self.dismissViewControllerAnimated(true, completion: nil)
if let image = info[UIImagePickerControllerOriginalImage] as? UIImage
{
print("select photo")
let newImageThumbnail = imageCompressForWidth(image, targetWidth: 100)
let data = UIImageJPEGRepresentation(newImageThumbnail, 1.0)!
thumbnailImageStr = data.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.Encoding64CharacterLineLength)
let str = "image:" + thumbnailImageStr
let myRootRef = Wilddog(url:WilddogURL)
let msgRef = myRootRef.childByAppendingPath("UserMsg")
msgRef.removeObserverWithHandle(self.observerHandle)
let Ref = msgRef.childByAutoId()
let msg = ["ID":self.userID,"nickname":self.userNickName,"message":str,"time":self.getTime()]
Ref.setValue(msg)
}
}

后台数据存储:

可以输入文字聊天,可以选择相册中的图片并发送,图片转成base64码存储。

背景是个动画,至于这个后面说。

通讯录

file:ContactsViewController.swift

点击群聊界面左上角的contact进入通讯录,读取wilddog端的UserAccount数据:


获取通讯录数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func transmitAccountMsg(mail:String,id:String,nickname:String){
print("Enter Contacts View")
self.userID = id
self.userNickname = nickname
self.userArray = Array<String>()
let myRootRef = Wilddog(url:WilddogURL)
let accountRef = myRootRef.childByAppendingPath("UserAccount")
accountRef.queryOrderedByChild("mail").observeSingleEventOfType(.Value, withBlock: { snapshot in
for i in (snapshot.value as! Dictionary<String,Dictionary<String,String>>) {
if i.1["mail"]! != mail {
self.userArray.append(i.1["nickname"]!)
self.mailArray.append(i.1["mail"]!)
}
}
})
}

后台数据存储:

私聊

file:SingleChatViewController.swift

点击通讯录中的任一条目即可开始私聊。

聊天:


私聊数据初始化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func initMessage(){
let userRef = myRootRef.childByAppendingPath("PrivateMsg"+"/"+self.userNickname+"/"+self.title!)
userRef.observeSingleEventOfType(.Value, withBlock: { snapshot in
if snapshot.value != nil{
print("Get msg!")
self.msgPath = "PrivateMsg"+"/"+self.userNickname+"/"+self.title!
self.refreshMessage()
let count = snapshot.value.count
var i = 0
userRef.queryOrderedByKey().observeEventType(.ChildAdded, withBlock: { snapshot in
self.dic[snapshot.key] = snapshot.value as? Dictionary
self.chatMsgArray.append(snapshot.key)
i += 1
if i == count {
userRef.removeAllObservers()
self.tableView!.reloadData()
self.initLoad = false
}
})
}else {
self.initMessageAgain()
}
})
print("init successed,\(self.msgPath)")
}

后台数据存储:

共享定位:

存在bug,还没fix,时好时坏。


自身位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func MyLocationInit(){
if !CLLocationManager.locationServicesEnabled() {
print("定位服务当前可能尚未打开,请设置打开!")
}
if (CLLocationManager.authorizationStatus() == CLAuthorizationStatus.NotDetermined){
locationManager.requestWhenInUseAuthorization()
}else if(CLLocationManager.authorizationStatus() == CLAuthorizationStatus.AuthorizedWhenInUse){
locationManager.delegate = self
locationManager.desiredAccuracy = kCLLocationAccuracyBest
let distance:CLLocationDistance = 2.0
locationManager.distanceFilter = distance
locationManager.startUpdatingLocation()
}
mapView.userTrackingMode = MKUserTrackingMode.Follow
}


他人位置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
func otherLocationInit(){
print("OtherPositon is \(OtherPostion)")
let objectAnnotation = MKPointAnnotation()
let lati = (OtherPostion["latitude"]! as NSString).doubleValue
let longi = (OtherPostion["longitude"]! as NSString).doubleValue
objectAnnotation.coordinate = CLLocation(latitude: lati,
longitude: longi).coordinate
objectAnnotation.title = "\(OtherName)的位置"
objectAnnotation.subtitle = "subtitle"
self.mapView.addAnnotation(objectAnnotation)
mapSetRegion(longi, latitude: lati, isMy: false)
}
func mapSetRegion(longitude:Double,latitude:Double,isMy:Bool){
let latDelta = 0.05
let longDelta = 0.05
let currentLocationSpan:MKCoordinateSpan = MKCoordinateSpanMake(latDelta, longDelta)
let center:CLLocation = CLLocation(latitude: latitude, longitude: longitude)
let currentRegion:MKCoordinateRegion = MKCoordinateRegion(center: center.coordinate,
span: currentLocationSpan)
if isMy {
(self.longitude,self.latitude) = (center.coordinate.longitude,center.coordinate.latitude)
}
mapView.setRegion(currentRegion, animated: true)
}

消息推送

身边暂时没真机没法截图,以前测试过是正常的,如图:


消息推送:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
func newMsgNotifi(title:String?,body:String?){
var msgBody:String!
if body!.hasPrefix("image:") {
msgBody = "发来一张图片"
}else{
msgBody = ":" + body!
}
var notification = UILocalNotification()
notification.timeZone = NSTimeZone.localTimeZone()
notification.alertTitle = title
notification.alertBody = title! + msgBody
notification.alertAction = "OK"
notification.soundName = UILocalNotificationDefaultSoundName
notification.applicationIconBadgeNumber = 1
var userInfo:[NSObject : AnyObject] = [NSObject : AnyObject]()
userInfo["kLocalNotificationID"] = "LocalNotificationID"
userInfo["key"] = "Attention Please"
notification.userInfo = userInfo
UIApplication.sharedApplication().presentLocalNotificationNow(notification)
}

其它

在群聊界面中右上角的那个Other按钮点进去是这样的:

一个七巧板view,本来是个OC库,加个bridge改个调用函数就行了。忘了以前哪找的了,原作者是yuancan,作为附加功能的一个集合。

放了些有趣的东东,选择相册图片生成字符画,手写板,字符随机出现的文章,按钮动画等等。

关于主界面的那个背景动画

因为发送聊天记录时数据都会包含时间字段,我就想根据这个时间分析出季节、节日、早晚、天气等信息,然后通过动画展示出来。不过水平有限,只实现出了最简单的效果,太阳和月亮与它们出现的高度代表一天中的时刻,背景色代表季节,节日解析出来了不过还没处理,天气也还没写获取的相关代码。具体解析和动画代码在ParseDateStringClass.swiftResponseToAnimeView.swift中。


1.上传聊天记录时保存时间:

1
2
3
4
5
6
7
func getTime() -> String {
let date = NSDate()
let timeFormatter = NSDateFormatter()
timeFormatter.dateFormat = "yyy-MM-dd.HH:mm:ss"
let strNowTime = timeFormatter.stringFromDate(date) as String
return strNowTime
}


2.过滤时间字符串:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
func parseStr(str:String) -> (Int,Int,String,Int){
let onceSplit = str.characters.split{$0 == "."}.map(String.init)
let month = String(Int(onceSplit[0].characters.split{$0 == "-"}.map(String.init)[1]
)!)
let day = String(Int(onceSplit[0].characters.split{$0 == "-"}.map(String.init)[2])!)
let date = month + "-" + day
let dayTime = Int(onceSplit[1].characters.split{$0 == ":"}.map(String.init)[0])
var season : Season
var daytime : DayTimePeriod
var weather : Weather = Weather.Sunny
var festival : Festival
switch Int(month)! {
case 12,1,2 : season = Season.Winter
case 3...5 : season = Season.Spring
case 6...8 : season = Season.Summer
case 9...11 : season = Season.Autumn
default : season = Season.Spring
}
switch dayTime! {
case 6...9: daytime = DayTimePeriod.morning
case 10...14: daytime = DayTimePeriod.noon
case 15...18: daytime = DayTimePeriod.afternoon
case 19...22: daytime = DayTimePeriod.evening
case 22,23,24,0...5: daytime = DayTimePeriod.night
default: daytime = DayTimePeriod.morning
}
switch day {
case "1-1": festival = Festival.NewYearsDay
case "3-8": festival = Festival.WomensDay
case "3-12": festival = Festival.ArborDay
case "5-1": festival = Festival.LaborDay
case "5-4": festival = Festival.YouthDay
case "6-1": festival = Festival.ChildrensDay
case "10-1": festival = Festival.NationalDay
case "12-31": festival = Festival.NewYearsEve
default: festival = Festival.NewYearsDay
}
return (season.rawValue,daytime.rawValue,festival.rawValue,weather.rawValue)
}


3.转换为动画:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
func tupleToAnime(tuple:(Int,Int,String,Int))
{
if cloudIsFired == false {
cloudTimer.fire()
cloudIsFired = true
}
seasonNo = tuple.0
daytimeNo = tuple.1
festivalNo = tuple.2
weatherNo = tuple.3
self.sunAndMoonWithTime(daytimeNo)
}

本来设置的是背景动画随着当前屏幕中tableview最底部显示的聊天记录的时间而变化的,就是拨动列表时这个动画是随着聊天记录的时间渐变的,不过也有些bug,暂时禁用了,一直显示一个固定时间的动画。

最后

不得不说的是这个云的名儿起的的确很奔放。。。

文章目录
  1. 1. 简介
  2. 2. 实现
    1. 2.1. 准备工作
    2. 2.2. 登录
    3. 2.3. 数据存储
    4. 2.4. 群聊
    5. 2.5. 通讯录
    6. 2.6. 私聊
    7. 2.7. 共享定位:
    8. 2.8. 消息推送
    9. 2.9. 其它
      1. 2.9.1. 关于主界面的那个背景动画
    10. 2.10. 最后
|