编程

当前位置:永利皇宫463登录 > 编程 > swift完成二个与智能手机器人聊天的app

swift完成二个与智能手机器人聊天的app

来源:http://www.makebuLuo.com 作者:永利皇宫463登录 时间:2019-09-12 14:29

图片 1autoLaoutsendButton的部分也是如此:make.right.equalTo(self.toolBar.snp_right)表示发送按钮右侧直接贴输入框的右侧,没有位移make.bottom.equalTo(self.toolBar.snp_bottom).offset发送按钮底部距离输入框底部4.5点这样是不是让autoLayout变得简单很多了?后面的项目我们就一直使用它来进行autoLayout设置了!现在没有错误了,cmd+R运行一下,啊哦,为啥是空白!作者你骗人!= =好吧,我们还差一步,记得吗,它要变成第一响应者才能弹出键盘哦,我们要重写一个方法它才能生效!在视图控制器类中增加以下方法:

运行之前还有一步,就是在AppDelegate.swiftapplication()方法里修改我们的初始视图控制器:

然而接受消息的气泡和它的关系是水平镜像,所以我们要用一个方法获得它的水平镜像图片:

图片 2UI结构那么这三部分怎样去实现呢,我先向大家做一些简单的介绍:1.导航栏这一部分实现比较简单,只要把视图控制器嵌套在一个导航控制器(UINavigationController)中即可,然后对其外观进行一些定制化操作。2.聊天窗口这一部分用UITableView来构建。仔细观察你会发现这里一共有三种UITableViewCell:

同样地,在SignUpViewController.swift中的viewDidLoad()方法里添加以下代码:

这是图片的原型,不难理解这是发送消息对应的聊天气泡,所以直接调用即可

首先从最简单的做起,实现自定义导航栏:打开初始项目你会看到模板文件已经全部建好:找到AppDelegate.swift文件中的以下方法:

 func keyboardDidShow(notification: NSNotification) { let userInfo = notification.userInfo as NSDictionary! let frameNew = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue() let insetNewBottom = tableView.convertRect(frameNew, fromView: nil).height //根据键盘高度设置Inset let contentOffsetY = tableView.contentOffset.y tableView.contentInset.bottom = insetNewBottom tableView.scrollIndicatorInsets.bottom = insetNewBottom // 优化,防止键盘消失后tableview有跳跃 if self.tableView.tracking || self.tableView.decelerating { tableView.contentOffset.y = contentOffsetY } }

这是一个Message类型的数组,数组的元素也是一个Message类型的数组。为什么要这样定义呢,这是为了区分聊天发生的时间,同一段时间发生的聊天打包到一起组成一个数组元素,超过这一段时间的聊天放到新开辟的数组元素中,这样做也便于我们的tableView确定分区和行,同一段时间的聊天放在同一个section,超过这段时间的聊天放在下一个section,每一分区中有几个消息,就有几行。找到viewDidLoad()方法,在super.viewDidLoad()这行代码下添加如下代码:

图片 3数据.png我们来看一眼Messages类里都有什么:

LogInViewController.swift中的viewDidLoad()方法里添加以下代码来自定义logo:

 let incoming: Bool let text: String let sentDate: NSDate init(incoming: Bool, text: String, sentDate: NSDate) { self.incoming = incoming self.text = text self.sentDate = sentDate }
pod 'Alamofire', '~> 1.3'pod 'SnapKit', '~> 0.12.0'pod 'Parse','~>1.7.1'pod 'ParseUI','~>1.1.3'
 for object in query.findObjects() as! [PFObject]{ let message = Message(incoming: object[incomingKey] as! Bool, text: object[textKey] as! String, sentDate: object[sentDateKey] as! NSDate) if let url = object[urlKey] as? String{ message.url = url } if index == 0{ currentDate = message.sentDate } let timeInterval = message.sentDate.timeIntervalSinceDate(currentDate!) if timeInterval < 120{ messages[section].append }else{ section++ messages.append([message]) } currentDate = message.sentDate index++ }
 let sentDateLabel: UILabel
$ pod install

Analyzing dependenciesDownloading dependenciesUsing Alamofire Using Bolts Using Parse Using ParseUI Using SnapKit Generating Pods projectIntegrating client projectSending stats

然后我们需要计算一些数据:

//1设置消息内容。//2删除聊天气泡的left或right约束,以便于根据消息类型重新进行设置。//3根据消息类型进行对应的设置,包括使用的图片还有约束条件。由于发送消息的聊天气泡是靠右的,而接受消息的聊天气泡是靠左的,所以发送消息的聊天气泡距离cell右边缘10点:

http://ruby.taobao.org added to sources

图片 4insetChangetableview的contentInset所指的是所图的红框部分。overflow指的是所有消息的总高度和键盘弹出前contentInset的差值,实际上就是没有显示部分的高度,也就是溢出的部分。然后通过UIKeyboardAnimationDurationUserInfoKeykey来得到键盘弹出动画的持续时间,设置自定义的动画闭包:

图片 5iOS Simulator Screen Shot 2015年9月5日.png

$ cd <Podfile所在目录>
import ParseUI

填充假的聊天数据

图片 6项目结构.png

图片 7cpu负荷图.png会看到一个瞬间cpu负荷暴涨到了32%!这样很不酷对不对?我们的解决办法就是,将这个占用cpu很多使用量的操作放在另一个线程中,但首先我们要找到这是哪个操作,细心的你一定注意到,当加载聊天界面的时候会比较慢,没错就是那个操作在作怪!所以呢,我们对initData()方法进行一些优化。首先改变我们从Parse服务器下载数据的方法query.findObjects(),这是同步下载数据,会占据我们很大一部分cpu负载,所以我们要改为异步下载,也就是放到其他线程执行,将以下代码修改一下:

let incomingTag = 0, outgoingTag = 1let bubbleTag = 8

在其中添加如下代码:

 let userInfo = notification.userInfo as NSDictionary!

你会感觉看到了一些奇怪的东西,所以我来解释一下这些代码:

在viewDidLoad里添加一行代码来设置keyboardDismissMode:

图片 8登录页面图片 9注册页面

返回一个结构体包含4种图片:发送消息气泡的正常和高亮图片,接收消息气泡的正常和高亮图片,以供调用。

是不是看起来简洁多了?我们来解释一下这段代码:每一个组件都有一个 snp_makeConstraints的闭包方法,用来设置约束,textView.snp_makeConstraints就是来设置textView的约束闭包中make.left.equalTo(self.toolBar.snp_left).offset这行代码可以用公式来表示:也就是textView.left = self.toolBar.left + 8,这样一看就很直观了,文字框的左侧距输入框左侧8点。make.top.equalTo(self.toolBar.snp_top).offset可以用公式textView.top = self.toolBar.top +7.5表示,剩下的代码以此类推,如下图所示:

图片 10可拉伸区域实际上这个可拉伸区域只有1x1像素,但是也够我们用了,因为这一部分可以无限地横向或纵向拉伸,接收消息气泡和发送消息气泡可拉伸区域唯一的区别就是水平方向上,所以把right和left的值互相交换即可。然后通过UIImageresizableImageWithCapInsets()方法,获取可拉伸图片:

嗯好嘞,废话不多说,下面我们就来一步一步地一一实现它们!

都加上

然后再次运行:

https://rubygems.org/ removed from sources
 query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in if error == nil { if objects!.count > 0 { for object in objects as! [PFObject] { if index == objects!.count - 1{ dispatch_async(dispatch_get_main_queue -> Void in self.tableView.reloadData } let message = Message(incoming: object["incoming"] as! Bool, text: object["text"] as! String, sentDate: object["sentDate"] as! NSDate) if let url = object["url"] as? String{ message.url = url } if index == 0{ currentDate = message.sentDate } let timeInterval = message.sentDate.timeIntervalSinceDate(currentDate!) if timeInterval < 120{ self.messages[section].append }else{ section++ self.messages.append([message]) } currentDate = message.sentDate index++ } } }else{ println("error (error?.userInfo)") } }

获取当前的日历,我们要使用其中的一些方法

用get的方式将输入框的组件进行配置:在大括号内部添加代码:

 let notificationCenter = NSNotificationCenter.defaultCenter() notificationCenter.addObserver(self, selector: "keyboardWillShow:", name: UIKeyboardWillShowNotification, object: nil) notificationCenter.addObserver(self, selector: "keyboardDidShow:", name: UIKeyboardDidShowNotification, object: nil)

重写tableView的代理方法,设置tableView的分区数和行数:

 override var inputAccessoryView: UIView! {}

我们看一下动画闭包内部做了些什么。首先判断tableView的滚动是否停止了,如果没有停止滚动就不做任何事情。tableView的滚动有两种情况:

 var dateFormatter = NSDateFormatter() dateFormatter.locale = NSLocale(localeIdentifier: "zh_CN")

打开项目,看一下项目的结构:

下面我们在viewDidLoad()中配置一下欢迎界面:

开始安装第三方库,打开终端,将当前目录转到Podfile所在目录:

 func signUpViewController(signUpController: PFSignUpViewController, shouldBeginSignUp info: [NSObject : AnyObject]) -> Bool { var infomationComplete = true for key in info.values { var field = key as! String if (field.isEmpty){ infomationComplete = false break } } if (!infomationComplete){ UIAlertView(title: "缺少信息", message: "请补全缺少的信息", delegate: self, cancelButtonTitle:"确定").show() return false } return true } func signUpViewController(signUpController: PFSignUpViewController, didSignUpUser user: PFUser) { self.dismissViewControllerAnimated(true, completion: nil) } func signUpViewController(signUpController: PFSignUpViewController, didFailToSignUpWithError error: NSError?) { println }
占位符 含义
YYYY 年份
MM 月份
dd
HH 小时
mm 分钟
ss
a 表示上午、下午等
EEEE 星期几
class InputTextView: UITextView { }
self.logInView?.logo = UIImageView(image: UIImage(named: "logo"))
import SnapKit

接下来我们来测试一下能否读取到这些数据,首先要获得该app的application ID和Client Key:

然后运行一下,同时看一下cpu的负荷率表:

 super.init(style: style, reuseIdentifier: reuseIdentifier) selectionStyle = .None contentView.addSubview(sentDateLabel)

选择刚才下载的文件:

 let frameNew = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue() let insetNewBottom = tableView.convertRect(frameNew, fromView: nil).height
cell.sentDateLabel.text = "(message.sentDate)"

然后等待几分钟,如果一切正常,没有出现错误的话,打开项目文件后你会看到workspace的文件,以后都要使用这个文件来打开项目。

图片 11添加新属性图片 12选择属性类型)User类是Parse默认的用户类,我们的类型用指针,指向用户类,将信息与用户进行绑定,这样就能知道该条信息属于哪个用户了。幸运的是Parse已经提供了登录的视图控制器,同样还有注册的视图控制器:PFLogInViewControllerPFSignUpViewController虽然它本身的语言是英文,但是我在初始项目里对他们进行了一下汉化修改,其实有更好的办法进行国际化,但这个只是为了演示。首先我们创建一个欢迎页面:图片 13屏幕快照 2015-09-15 上午8.55.18.png

let calendar = NSCalendar.currentCalendar()
 get { if toolBar == nil { toolBar = UIToolbar(frame: CGRectMake(0, 0, 0, toolBarMinHeight-0.5)) textView = InputTextView(frame: CGRectZero) textView.backgroundColor = UIColor(white: 250/255, alpha: 1) textView.delegate = self textView.font = UIFont.systemFontOfSize(messageFontSize) textView.layer.borderColor = UIColor(red: 200/255, green: 200/255, blue: 205/255, alpha:1).CGColor textView.layer.borderWidth = 0.5 textView.layer.cornerRadius = 5 // textView.placeholder = "Message" textView.scrollsToTop = false textView.textContainerInset = UIEdgeInsetsMake(4, 3, 3, 3) toolBar.addSubview sendButton = UIButton.buttonWithType as! UIButton sendButton.enabled = false sendButton.titleLabel?.font = UIFont.boldSystemFontOfSize sendButton.setTitle("发送", forState: .Normal) sendButton.setTitleColor(UIColor(red: 142/255, green: 142/255, blue: 147/255, alpha: 1), forState: .Disabled) sendButton.setTitleColor(UIColor(red: 0.05, green: 0.47, blue: 0.91, alpha: 1.0), forState: .Normal) sendButton.contentEdgeInsets = UIEdgeInsets(top: 6, left: 6, bottom: 6, right: 6) sendButton.addTarget(self, action: "sendAction", forControlEvents: UIControlEvents.TouchUpInside) toolBar.addSubview(sendButton) // 对组件进行Autolayout设置 textView.setTranslatesAutoresizingMaskIntoConstraints sendButton.setTranslatesAutoresizingMaskIntoConstraints toolBar.addConstraint(NSLayoutConstraint(item: textView, attribute: .Left, relatedBy: .Equal, toItem: toolBar, attribute: .Left, multiplier: 1, constant: 8)) toolBar.addConstraint(NSLayoutConstraint(item: textView, attribute: .Top, relatedBy: .Equal, toItem: toolBar, attribute: .Top, multiplier: 1, constant: 7.5)) toolBar.addConstraint(NSLayoutConstraint(item: textView, attribute: .Right, relatedBy: .Equal, toItem: sendButton, attribute: .Left, multiplier: 1, constant: -2)) toolBar.addConstraint(NSLayoutConstraint(item: textView, attribute: .Bottom, relatedBy: .Equal, toItem: toolBar, attribute: .Bottom, multiplier: 1, constant: -8)) toolBar.addConstraint(NSLayoutConstraint(item: sendButton, attribute: .Right, relatedBy: .Equal, toItem: toolBar, attribute: .Right, multiplier: 1, constant: 0)) toolBar.addConstraint(NSLayoutConstraint(item: sendButton, attribute: .Bottom, relatedBy: .Equal, toItem: toolBar, attribute: .Bottom, multiplier: 1, constant: -4.5)) } return toolBar }
 if let user = PFUser.currentUser(){ query.whereKey("createdBy", equalTo: user) messages = [[Message(incoming: true, text: "(user.username!)你好,请叫我灵灵,我是主人的贴身小助手!", sentDate: NSDate]] }

然而这两个图片并不能用,因为它的大小是固定的,但是我们的消息的长度是不定的,所以,要把它们做成大小可变的图片,首先设置可拉伸区域:

第一个是消息所用的字体大小,第二个是我们输入框的高度。添加一些组成输入框的组件:

首先获取通知中心的实例,然后添加两个观察者,第一个用来监控UIKeyboardWillShowNotification键值的变化,这是系统提供的键值,当键盘将要弹出时会改变;第二个监控 UIKeyboardDidShowNotification,同样地,这也是系统提供的,当键盘完全弹出时会改变。当这两个键值改变时,会向通知中心发送通知,然后由我们自定义的两个selector方法处理通知,下面定义这两个方法。首先第一个方法:

 UIGraphicsBeginImageContextWithOptions(image.size, false, image.scale)

图片 14图1

我们来实现一些代理方法,首先是登录的代理方法:

初始化cell

let messageFontSize: CGFloat = 17let toolBarMinHeight: CGFloat = 44

图片 15屏幕快照 2015-09-14 下午10.25.58.png

获取位图绘图上下文,并开始进行渲染操作

图片 16Parse注册.png

意思是有一个长运行时间的操作在主线程执行,由于主线程主要用于UI显示,所以如果有其他占用cpu的线程也在其中运行的话会使得UI显示变得很卡。虽然没有什么感觉,但是如果我们去看cpu的负荷图的话,如下图所示:

所以在这里日期就被表示为(以2015年9月3日上午10点为例):a HH:mm 对应上午 10:10``MM月dd日 a HH:mm EEEE对应 9月3日 上午 10:00 星期四``YYYY年MM月dd日 a HH:mm对应2015年9月3日 上午 10:00现在,在给日期赋值前,调用该方法进行格式化,修改下面这一行代码:

还有一个问题,用系统默认的代码实现autolayout看起来很难理解,所以这里可以用第三方库SnapKit来实现,把上面设置autolayout的代码替换成以下代码:

 func saveMessage(message:Message){ var saveObject = PFObject(className: "Messages") saveObject["incoming"] = message.incoming saveObject["text"] = message.text saveObject["sentDate"] = message.sentDate saveObject["url"] = message.url var user = PFUser.currentUser() saveObject["createdBy"] = user saveObject.saveEventually { (success, error) -> Void in if success{ println("消息保存成功!") }else{ println("消息保存失败!  } } }
 cell.sentDateLabel.text = formatDate(message.sentDate)
 override func canBecomeFirstResponder() -> Bool { return true }

这样就好了,运行一下,是不是感觉舒服多了?好的,下面我们解决下一个问题,在我们打开app的时候,会看到控制台显示如下内容:

获取图片大小

再次运行,你会发现黑洞洞的背景不见了,取而代之的是空白的TableView!而且键盘也实现了炫酷的效果!

我们在viewWillAppear()方法中实现欢迎页面逻辑,当已经登录时,显示欢迎语欢迎某某某,然后2s后进入聊天界面,否则显示未登录,进入登录界面:

 tableView.registerClass(MessageSentDateTableViewCell.self, forCellReuseIdentifier: NSStringFromClass(MessageSentDateTableViewCell))

连接Parse的服务器

然后通过UIKeyboardFrameEndUserInfoKeykey取出键盘的位置、大小信息,也就是frame,并将其的参考view设置为tableView,记录下它的高度

创建位图绘图上下文

找到以下方法

func delay(#seconds: Double, completion: { let popTime = dispatch_time(DISPATCH_TIME_NOW, Int64( Double(NSEC_PER_SEC) * seconds )) dispatch_after(popTime, dispatch_get_main_queue { completion() }}

接下来打开MessageBubbleTableViewCell.swift文件,增加新的属性:

图片 17截图

 if duration > 0 { let options = UIViewAnimationOptions(UInt((userInfo[UIKeyboardAnimationCurveUserInfoKey] as! NSNumber).integerValue << 16)) // http://stackoverflow.com/a/18873820/242933 UIView.animateWithDuration(duration, delay: 0, options: options, animations: animations, completion: nil) } else { animations() }
 super.init(style: .Default, reuseIdentifier: reuseIdentifier) selectionStyle = .None contentView.addSubview(bubbleImageView) bubbleImageView.addSubview(messageLabel)

你会发现有一个错误,这是因为我们的InputTextView是一个单独定义的类,它还没有定义,我们在之后会对他做一些操作,目前先不用管它,不过我们先把它定义出来,在视图控制器类之外定义该类:

 func keyboardWillShow(notification: NSNotification) { let userInfo = notification.userInfo as NSDictionary! let frameNew = (userInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue).CGRectValue() let insetNewBottom = tableView.convertRect(frameNew, fromView: nil).height let insetOld = tableView.contentInset let insetChange = insetNewBottom - insetOld.bottom let overflow = tableView.contentSize.height - (tableView.frame.height-insetOld.top-insetOld.bottom) let duration = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue let animations:  -> Void) = { if !(self.tableView.tracking || self.tableView.decelerating) { // 根据键盘位置调整Inset if overflow > 0 { self.tableView.contentOffset.y += insetChange if self.tableView.contentOffset.y < -insetOld.top { self.tableView.contentOffset.y = -insetOld.top } } else if insetChange > -overflow { self.tableView.contentOffset.y += insetChange + overflow } } } if duration > 0 { let options = UIViewAnimationOptions(UInt((userInfo[UIKeyboardAnimationCurveUserInfoKey] as! NSNumber).integerValue << 16)) // http://stackoverflow.com/a/18873820/242933 UIView.animateWithDuration(duration, delay: 0, options: options, animations: animations, completion: nil) } else { animations() } }

在import下面增加全局变量,用来标示cell的类型:

好吧,下面我们来用一个巧妙的办法来解决它。由于聊天页面是一个UITableView,所以我们可以使用UITableViewContoller来替代我们的UIViewContoller,这样我们的页面中就默认有了一个UITableView,然后它有一个非常实用的属性---keyboardDismissMode,我们把它设置为.Interactive也就是键盘的弹出和收回状态可以根据你对tableView的拖拽进行改变,也就是你的手指拖到哪里你的键盘就到哪里,是不是很酷。改变视图控制器的类型:

 func logInViewController(logInController: PFLogInViewController, shouldBeginLogInWithUsername username: String, password: String) -> Bool { if (!username.isEmpty && !password.isEmpty ) { return true } UIAlertView(title: "缺少信息", message: "请补全缺少的信息", delegate: self, cancelButtonTitle:"确定").show() return false } func logInViewController(logInController: PFLogInViewController, didLogInUser user: PFUser) { self.dismissViewControllerAnimated(true, completion: nil) } func logInViewController(logInController: PFLogInViewController, didFailToLogInWithError error: NSError?) { println }

进行autolayout设置

source 'https://github.com/CocoaPods/Specs.git'platform :ios, '8.4'use_frameworks!
  • 用KVO方法优化键盘弹出动画
  • 将同步下载消息改为异步,以减轻主线程的压力。
  • 实现app登录、注册的功能首先下载本章源代码:百度网盘地址在上一章结尾我提到:我们的app在键盘弹出时有一些问题:
  • 在我们点出键盘时会遮挡消息:

    图片 18iOS Simulator Screen Shot 2015年9月8日 下午4.14.55.png

  • 键盘弹出时把tableView拉到底部会有一个很难看的空白:图片 19iOS Simulator Screen Shot 2015年9月8日 下午4.15.21.png下面我们来解决它,我们需要在键盘弹出时修改tableView的一些属性和约束条件,所以我们需要在键盘弹出时得到通知,要做到这个,我们要使用KVO(Key-Value Observing)方法。在viewDidLoad()中的结尾添加以下代码来添加键值监控:

 let result = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return result

看到以下结果

 let duration = (userInfo[UIKeyboardAnimationDurationUserInfoKey] as! NSNumber).doubleValue let animations:  -> Void) = { if !(self.tableView.tracking || self.tableView.decelerating) { // 根据键盘位置调整Inset if overflow > 0 { self.tableView.contentOffset.y += insetChange if self.tableView.contentOffset.y < -insetOld.top { self.tableView.contentOffset.y = -insetOld.top } } else if insetChange > -overflow { self.tableView.contentOffset.y += insetChange + overflow } } }

对tableView进行一些必要的设置,由于tableView底部有一个输入框,因此会遮挡cell,所以要将tableView的内容inset增加一些底部位移:

图片 20Pods

终于有时间继续写我的文章了,这段时间在赶学校的软件课程设计,可算弄完了!下面继续我们的创造之旅~

 bubbleImageView.setTranslatesAutoresizingMaskIntoConstraints messageLabel.setTranslatesAutoresizingMaskIntoConstraints bubbleImageView.snp_makeConstraints {  -> Void in make.left.equalTo(contentView.snp_left).offset make.top.equalTo(contentView.snp_top).offset make.width.equalTo(messageLabel.snp_width).offset make.bottom.equalTo(contentView.snp_bottom).offset } messageLabel.snp_makeConstraints {  -> Void in make.centerX.equalTo(bubbleImageView.snp_centerX).offset make.centerY.equalTo(bubbleImageView.snp_centerY).offset messageLabel.preferredMaxLayoutWidth = 218 make.height.equalTo(bubbleImageView.snp_height).offset }

看到以下结果就说明已经成功

 view.backgroundColor = UIColor.whiteColor() self.navigationController?.navigationBarHidden = true logo = UIImageView(image: UIImage(named: "logo")) logo.center = CGPoint(x: view.center.x, y: view.center.y - 50) welcomeLabel = UILabel(frame: CGRect(x: view.center.x - 150/2, y: view.center.y + 20, width: 150, height: 50)) welcomeLabel.font = UIFont.systemFontOfSize welcomeLabel.textColor = UIColor(red:0.11, green:0.55, blue:0.86, alpha:1) welcomeLabel.textAlignment = .Center view.addSubview(welcomeLabel) view.addSubview

图片 21iOS Simulator Screen Shot 2015年9月5日.png看!这样就很顺眼了吧?到这里我们的第二部分教程就完成了,第三部分将会实现发送消息、用Alamofire网络请求进行聊天信息的反馈,从Parse服务器接收和保存聊天信息,真正实现和智能机器人聊天!敬请期待!本篇文章源代码放在了百度网盘里:下载地址

 Parse.setApplicationId("CYdFL9mvG8jHqc4ZA5PJsWMInBbMMun0XCoqnHgf", clientKey: "6tGOC1uIKeYp5glvJE6MXZOWG9pmLtMuIUdh2Yzo")
 let insetOld = tableView.contentInset let insetChange = insetNewBottom - insetOld.bottom let overflow = tableView.contentSize.height - (tableView.frame.height-insetOld.top-insetOld.bottom)
 bubbleImageView = UIImageView(image: bubbleImage.incoming, highlightedImage: bubbleImage.incomingHighlighed) bubbleImageView.tag = bubbleTag bubbleImageView.userInteractionEnabled = true // #CopyMesage messageLabel = UILabel(frame: CGRectZero) messageLabel.font = UIFont.systemFontOfSize(messageFontSize) messageLabel.numberOfLines = 0 messageLabel.userInteractionEnabled = false // #CopyMessage

然后刚才导入的数据就会显示出来,并自动新建了一个数据库类:Messages

 override func viewWillAppear(animated: Bool) { if (PFUser.currentUser{ self.welcomeLabel.text = "欢迎 (PFUser.currentUser()!.username!)!" delay(seconds: 2.0, { () -> () in var chatVC = ChatViewController() chatVC.title = "灵灵" var naviVC = UINavigationController(rootViewController: chatVC) self.presentViewController(naviVC, animated: true, completion: nil) }) }else{ self.welcomeLabel.text = "未登录" delay(seconds: 2.0) { () -> () in self.loginVC = LogInViewController() self.loginVC.delegate = self self.signUpVC = SignUpViewController() self.signUpVC.delegate = self self.loginVC.signUpController = self.signUpVC self.presentViewController(self.loginVC, animated: true, completion: nil) } } }

在类外增加一些方法,在文件结尾添加以下代码:

$ gem sources -a https://ruby.taobao.org

修改为以下使用findObjectsInBackgroundWithBlock的版本:

那么它是怎么确定可拉伸区域的呢,这个示意图可以解释一切:

 func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool 

如果溢出大于0,则将tableView当前位置contentOffset向下移动,也就对应着手指向上拖动insetChange的高度,这样可以保证消息和键盘同时向上移动,但是如果滚动之后仍然是负值,且超出insetOld.top的距离,也就是导航栏的高度,就把tableView的当前位置设置成屏幕之上一个导航栏的高度。如果溢出是负值,但是绝对值小于insetChange,则contenOffset.y增加两者的差值。当时长大于0时真正执行我们的动画闭包,否则就即时执行闭包:

图片 22UI结构你可以在这里下载上一篇文章的源代码:上一篇文章源代码

告诉我们的系统我们自定义的输入框可以成为第一响应者,我们也是有身份证的!然后在运行一下,如果没有错误,应该会有以下效果:

2015-09-14 21:16:24.951 TuringChatMachine[820:36384] Warning: A long-running operation is being executed on the main thread. Break on warnBlockingOperationOnMainThread() to debug.

然而这样只是一种聊天气泡,而且没有设置消息内容,我们要根据消息内容和类型对cell进行配置,在这之前我们首先完善我们的消息模型Message,打开Message.swift,在类中添加如下代码:

在里面添加代码:

使WelcomeViewController遵循PFSignUpViewControllerDelegatePFLogInViewControllerDelegate代理:

ok,显示消息发送时间的cell就设置好了。

输入以下命令开始配置第三方库:

还有登录页面,注册页面:

设置气泡视图和消息标签

  • Alamofire,网络请求库,用来调用图灵机器人的api
  • SnapKit,用代码进行autolayout设置
  • Parse ,Parse云服务平台的SDK
  • ParseUI,Parse提供的便捷UI组件在Podfile中,输入以下代码:
 let last18hours = (-18*60*60 < date.timeIntervalSinceNow) let isToday = calendar.isDateInToday let isLast7Days = (calendar.compareDate(NSDate(timeIntervalSinceNow: -7*24*60*60), toDate: date, toUnitGranularity: .CalendarUnitDay) == NSComparisonResult.OrderedAscending)

下面添加以下代码来声明对inputAccessoryView的重写:

同样地,我们保存消息的时候,将当前用户赋值给createdBy属性,修改一下saveMessage()方法:

首先打开我们的项目,你可以找到用于实现该部分的文件:MessageBubbleTableViewCell.swiftMessageSentDateTableViewCell.swift,分别用来实现消息发送时间的cell和聊天气泡的cell首先实现消息发送时间的cell,打开MessageBubbleTableViewCell.swift文件,增加对SnapKit第三方库的引用:

OK,cocoapods顺利安装完毕!2.cocoapods的使用那么cocoapods怎么用呢,当然第一次使用会觉得它非常麻烦,但是呢渐渐地你会发现这是一个非常好用的工具,可以说是iOS开发者必备!首先创建我们的Xcode工程:File/New/Project.../Single View Application起名叫图灵聊天。

仅有7%了!干的漂亮!下面我们来为我们的app增加一个登录的功能,因为没有办法去区分聊天信息,所有人的聊天信息都是共享的,真正的聊天app可不会是这样的。要做到这个,我们要为我们数据库上的聊天消息类增加一个新属性:

 self.tableView.keyboardDismissMode = .Interactive self.tableView.estimatedRowHeight = 44 self.tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom:toolBarMinHeight, right: 0) self.tableView.separatorStyle = .None
tableView.keyboardDismissMode = .Interactive

很难懂?不着急,我们一步一步解释这些代码!首先取出通知的userifno,键盘的所有属性都在这里面,他是一个字典类型的数据:

sentDateLabel.centerX = contentView.centerXsentDateLabel.top = contentView.top + 13sentDateLabel.bottom = contentView.bottom - 4.5

图片 23获取Key红线划掉的那两行就是我们需要的。然后打开项目中的AppDelegate.swift,增加对Parse库的引用:

由于这是异步下载,所以tableView仍然会继续加载cell而不会去管messages里有没有值,这时一定会崩溃,所以为了防止这种情况发生,我们要首先给messages赋一个欢迎消息,在方法开头加上这一行代码:

在override init()方法中添加代码:

设置app启动时显示我们自定义的视图控制器,并设置一下导航栏的外观。ok,第一部分完成。接下来我们来实现一下第三部分:输入框,我们要把最难的第二部分留在最后打开ChatViewController.swift文件:添加一些全局常量,在import下面class的定义之上:

WelcomeViewController.swift增加import模块:

调用父类的构造方法。我们将该cell设置为不可选,因为我们仅仅需要显示时间而已。最后将标签添加到cell的视图

指定下载源,指定平台版本,使用framework进行集成

class WelcomeViewController: UIViewController,PFSignUpViewControllerDelegate,PFLogInViewControllerDelegate{}

上一篇文章swift实现一个与智能机器人聊天的app实现了聊天appUI的输入框部分,接下来我会教大家如何实现聊天窗口部分,也就是下图的第二部分:

1.cocoapods的安装cocoapods的安装是通过ruby,幸运的是Mac电脑都是默认安装ruby的,所以安装ruby的过程就省去了,唯一的前提就是安装Xcode的CommandLineTools。commandLineTools的安装也很简单,只要在终端输入以下命令:

self.signInView?.logo = UIImageView(image: UIImage(named: "logo"))

消息是正常显示出来了,但是消息的发送时间看起来很别扭,所以我们需要对其进行格式化,在类中添加如下方法:

以上用户名只是示例,但是app名称输入TuringChat。注册完毕后,用你刚才注册的用户名登陆,应该会出现以下界面:

增加属性,登录视图控制器和注册视图控制器,还有欢迎界面的logowelcomeLabel用来显示logo和欢迎语:

获取到绘图结果,结束位图绘图上下文并返回绘图结果

我们将要使用图灵机器人的api进行开发:图灵机器人官网

 if !(self.tableView.tracking || self.tableView.decelerating){..........}
 override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { if indexPath.row == 0{ let cellIdentifier = NSStringFromClass(MessageSentDateTableViewCell) var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier,forIndexPath: indexPath) as! MessageSentDateTableViewCell let message = messages[indexPath.section][0] cell.sentDateLabel.text = "(message.sentDate)" return cell }else{ let cellIdentifier = NSStringFromClass(MessageBubbleTableViewCell) var cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as! MessageBubbleTableViewCell! if cell == nil { cell = MessageBubbleTableViewCell(style: .Default, reuseIdentifier: cellIdentifier) } let message = messages[indexPath.section][indexPath.row - 1] cell.configureWithMessage return cell } }
$ sudo gem install cocoapods

Password:(你的管理员密码,这里不会显示出来)Fetching: cocoapods-core-0.38.2.gem Successfully installed cocoapods-core-0.38.2Fetching: claide-0.9.1.gem Successfully installed claide-0.9.1Fetching: xcodeproj-0.26.3.gem Successfully installed xcodeproj-0.26.3Fetching: cocoapods-downloader-0.9.3.gem Successfully installed cocoapods-downloader-0.9.3Fetching: cocoapods-stats-0.5.3.gem Successfully installed cocoapods-stats-0.5.3Fetching: cocoapods-try-0.4.5.gem Successfully installed cocoapods-try-0.4.5Fetching: cocoapods-trunk-0.6.4.gem Successfully installed cocoapods-trunk-0.6.4Fetching: molinillo-0.3.1.gem Successfully installed molinillo-0.3.1Fetching: cocoapods-0.38.2.gem Successfully installed cocoapods-0.38.2Parsing documentation for cocoapods-core-0.38.2Installing ri documentation for cocoapods-core-0.38.2Parsing documentation for claide-0.9.1Installing ri documentation for claide-0.9.1Parsing documentation for xcodeproj-0.26.3Installing ri documentation for xcodeproj-0.26.3Parsing documentation for cocoapods-downloader-0.9.3Installing ri documentation for cocoapods-downloader-0.9.3Parsing documentation for cocoapods-stats-0.5.3Installing ri documentation for cocoapods-stats-0.5.3Parsing documentation for cocoapods-try-0.4.5Installing ri documentation for cocoapods-try-0.4.5Parsing documentation for cocoapods-trunk-0.6.4Installing ri documentation for cocoapods-trunk-0.6.4Parsing documentation for molinillo-0.3.1Installing ri documentation for molinillo-0.3.1Parsing documentation for cocoapods-0.38.2Installing ri documentation for cocoapods-0.38.29 gems installed

第一个方法是执行我们自定义的用户名密码的合法性检查方法;第二个是在登录之后执行,可以通过user参数知道登录的是哪个用户;第三个是如果登录出现错误,错误信息可以在这里找到。同样地,实现注册相应的三个方法:

 let maskIncoming = UIImage(CGImage: maskOutgoing.CGImage, scale: 2, orientation: .UpMirrored)!

新建查询,查询我们刚才所建的Messages类,用findObjectsInBackgroundWithBlock方法取出查询结果,并用一个循环全部打印出来。cmd+R运行一下,如果没有问题会输出类似下面的内容:

其中要注意的是,我们的动画曲线要和键盘弹出动画的曲线相同,所以要用 UIKeyboardAnimationCurveUserInfoKeykey得到曲线信息,这里的类型转换比较麻烦,要进行左移16的位运算,因为没有对应的 as类型转换可用,只能用最底层的方式。为什么要这样呢,其实我也不知道。。我也是查来的= =stackoverflow第二个方法,是用来防止出现底下的白边,原理就是限制显示出的高度,将底部切掉一部分,也就是将contenInset.bottom值变大一些,变大为键盘的高度:

辅助方法写完,下面开始进行cell的配置,在init()方法中添加以下代码:

选择指定的第三方库及其版本

  1. 手指点击tableView,开始滚动,即tracking
  2. 手指抬起,tableView还会有一段减速滚动,也就是decelerating
 if last18hours || isToday { dateFormatter.dateFormat = "a HH:mm" } else if isLast7Days { dateFormatter.dateFormat = "MM月dd日 a HH:mm EEEE" } else { dateFormatter.dateFormat = "YYYY年MM月dd日 a HH:mm" }
  • 用来显示消息发送日期的cell
  • 发送消息气泡的cell
  • 接收消息气泡的cell但其实我们只需要两个,因为后两种cell区别只是是颜色和位置,我们只要判断一下该消息是发送的还是接收的,然后相应进行处理即可!两种cell都是用的以下这个素材:图片 24MessageBubble.png但是,你会问,它为啥是黑色的!怎么让他变成图中的两种颜色呢?还有明明聊天气泡的大小是不定的,这样一张图怎么能满足所有尺寸呢?有疑问很好,因为它可以成为你学习的动力,我们会在接下来向大家解释这是如何实现的!Be patient!3.输入框这里我们要通过重写UIResponder类的inputAccessoryView属性来自定义我们的输入框,这样做的好处是我们的输入框会和系统的键盘结合起来,可以让其成为第一响应者(first responder),一旦它成为第一响应者,我们自定义的输入框会跟随键盘一同弹出和收回,就像真正的短信app那样,这个方法比我有一篇文章所写的实现类似微信的输入框跟随键盘弹出的效果的方法还要更好一些,所以说方法不是绝对的,因为你总是能够找到更好的方法,所以,编程的时候要经常在脑子里想"嗯,一定还有更好的方法"。

图片 25登录.gif到此我们的app已经有一些正式的样子了,下一章还会对其进行功能的扩充和优化!请持续关注!本章完成源代码下载如果我的文章对你有帮助,请点一下喜欢,大家的支持是我继续写作的动力!

 let incoming = coloredImage(maskIncoming, 229/255, 229/255, 234/255, 1).resizableImageWithCapInsets(capInsetsIncoming) let incomingHighlighted = coloredImage(maskIncoming, 206/255, 206/255, 210/255, 1).resizableImageWithCapInsets(capInsetsIncoming) let outgoing = coloredImage(maskOutgoing, 0.05 ,0.47,0.91,1.0).resizableImageWithCapInsets(capInsetsOutgoing) let outgoingHighlighted = coloredImage(maskOutgoing, 32/255, 96/255, 200/255, 1).resizableImageWithCapInsets(capInsetsOutgoing)
textView.setTranslatesAutoresizingMaskIntoConstraints sendButton.setTranslatesAutoresizingMaskIntoConstraints textView.snp_makeConstraints -> Void in make.left.equalTo(self.toolBar.snp_left).offset make.top.equalTo(self.toolBar.snp_top).offset make.right.equalTo(self.sendButton.snp_left).offset make.bottom.equalTo(self.toolBar.snp_bottom).offset sendButton.snp_makeConstraints -> Void in make.right.equalTo(self.toolBar.snp_right) make.bottom.equalTo(self.toolBar.snp_bottom).offset

还有一件事,我们要在读取数据的时候只读取当前登录用户的信息,而不是全部,所以我们要加上一个限制,在query.findObjectsInBackgroundWithBlock执行前加上以下代码:

let maskOutgoing = UIImage(named: "MessageBubble")!

OK,到此我们的项目就配置好了,在我们开始搭建UI之前,先了解一下Parse的使用和一些必要配置

定义这个延时方法,在import下面:

func coloredImage(image: UIImage, red: CGFloat, green: CGFloat, blue: CGFloat, alpha: CGFloat) -> UIImage! { let rect = CGRect(origin: CGPointZero, size: image.size) UIGraphicsBeginImageContextWithOptions(image.size, false, image.scale) let context = UIGraphicsGetCurrentContext() image.drawInRect CGContextSetRGBFillColor(context, red, green, blue, alpha) CGContextSetBlendMode(context, kCGBlendModeSourceAtop) CGContextFillRect(context, rect) let result = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return result}
 var ChatVC:ChatViewController = ChatViewController() ChatVC.title = "灵灵" UINavigationBar.appearance().tintColor = UIColor(red: 0.05, green: 0.47, blue: 0.91, alpha: 1.0) UINavigationBar.appearance().barTintColor = UIColor(red: 0.05, green: 0.47, blue: 0.91, alpha: 1.0) UINavigationBar.appearance().titleTextAttributes = [NSForegroundColorAttributeName: UIColor.whiteColor()] UIApplication.sharedApplication().statusBarStyle = UIStatusBarStyle.LightContent var navigationVC:UINavigationController = UINavigationController(rootViewController: ChatVC) let frame = UIScreen.mainScreen().bounds window = UIWindow(frame: frame) window!.rootViewController = navigationVC window!.makeKeyAndVisible()

insetChange指的是那部分呢?我画出一个图大家就明白了:

新建日期格式化器,设置地区为中国大陆

我们需要搭建的UI只是聊天页面,我们首先来看一看聊天页面的结构:界面主要由以下三个部分组成

import Parseimport ParseUI
 sentDateLabel = UILabel(frame: CGRectZero) sentDateLabel.backgroundColor = UIColor.clearColor() sentDateLabel.font = UIFont.systemFontOfSize sentDateLabel.textAlignment = .Center sentDateLabel.textColor = UIColor(red: 142/255, green: 142/255, blue: 147/255, alpha: 1)
oYtildSAOzfalse你叫什么名字?2015-08-28 06:42:00 +0000LX7kxmmiEptrue我叫灵灵,聪明又可爱的灵灵2015-08-28 06:43:00 +0000p62dmgGIASfalse你爱不爱我?2015-08-28 06:43:00 +0000oWReOM43Nftrue爱你么么哒2015-08-28 06:44:00 +0000mtl2BGt3Mufalse今天北京天气如何?2015-08-29 03:59:00 +0000DikAu5P2Nntrue北京:08/29 周六,20-29° 28° 雷阵雨 微风小于3级;08/30 周日,19-27° 雷阵雨 微风小于3级;08/31 周一,19-27° 雷阵雨 微风小于3级;09/01 周二,20-26° 雷阵雨 微风小于3级;2015-08-29 03:59:01 +0000
 func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { // Override point for customization after application launch. Parse.setApplicationId("CYdFL9mvG8jHqc4ZA5PJsWMInBbMMun0XCoqnHgf", clientKey: "6tGOC1uIKeYp5glvJE6MXZOWG9pmLtMuIUdh2Yzo") var welcomeVC:WelcomeViewController = WelcomeViewController() UINavigationBar.appearance().tintColor = UIColor.whiteColor() UINavigationBar.appearance().barTintColor = UIColor(red: 0.05, green: 0.47, blue: 0.91, alpha: 1.0) UINavigationBar.appearance().titleTextAttributes = [NSForegroundColorAttributeName: UIColor.whiteColor()] UIApplication.sharedApplication().statusBarStyle = UIStatusBarStyle.LightContent let frame = UIScreen.mainScreen().bounds window = UIWindow(frame: frame) window!.rootViewController = welcomeVC window!.makeKeyAndVisible() return true }
 let context = UIGraphicsGetCurrentContext() image.drawInRect CGContextSetRGBFillColor(context, red, green, blue, alpha) CGContextSetBlendMode(context, kCGBlendModeSourceAtop) CGContextFillRect(context, rect)

如果确实没有安装commandLineTools会提示你要安装它,点安装就可以开始下载,然后等待下载完成后安装即可下面开始安装cocoapods,本来只需要简单地在终端输入以下命令即可:

 var loginVC:LogInViewController! var signUpVC:SignUpViewController! var logo:UIImageView! var welcomeLabel:UILabel!
 let rect = CGRect(origin: CGPointZero, size: image.size)
class ViewController: UIViewController,UITextViewDelegate {........}
 messages = [[Message(incoming: true, text: "你好,请叫我灵灵,我是主人的贴身小助手!", sentDate: NSDate]]

然后回到我们的MessageBubbleTableViewCell.swift,添加以下的配置方法:

点一下Pods项目,你会发现所需的framework已经编译好了,只要在使用前import他们就可以了:

至此我们的登录注册功能就集成进我们的app了,当然这只是一个演示,为了演示如何用ParseUI库实现登录功能,并没有太多的自定义,更复杂的应用这里先不进行扩展了。

 func configureWithMessage(message: Message) { //1 messageLabel.text = message.text //2 let constraints: NSArray = contentView.constraints() let indexOfConstraint = constraints.indexOfObjectPassingTest { (var constraint, idx, stop) in return (constraint.firstItem as! UIView).tag == bubbleTag && (constraint.firstAttribute == NSLayoutAttribute.Left || constraint.firstAttribute == NSLayoutAttribute.Right) } contentView.removeConstraint(constraints[indexOfConstraint] as! NSLayoutConstraint) //3 bubbleImageView.snp_makeConstraints -> Void in if message.incoming { tag = incomingTag bubbleImageView.image = bubbleImage.incoming bubbleImageView.highlightedImage = bubbleImage.incomingHighlighed messageLabel.textColor = UIColor.blackColor() make.left.equalTo(contentView.snp_left).offset messageLabel.snp_updateConstraints {  -> Void in make.centerX.equalTo(bubbleImageView.snp_centerX).offset } } else { // outgoing tag = outgoingTag bubbleImageView.image = bubbleImage.outgoing bubbleImageView.highlightedImage = bubbleImage.outgoingHighlighed messageLabel.textColor = UIColor.whiteColor() make.right.equalTo(contentView.snp_right).offset messageLabel.snp_updateConstraints {  -> Void in make.centerX.equalTo(bubbleImageView.snp_centerX).offset } } }) }
import Parse

注册tableViewCell

toolBar用来承载输入框中的组件,之所以用UIToolbar是因为它默认出现在屏幕最下方,就像你的短信输入框那样。textView是我们输入文字的地方,而sendButton则是我们的发送按钮。下面实现我们重写的inputAccessoryView,在这之前先让我们的视图控制器遵循UITextViewDelegate协议:

如果没有错误,cmd+R运行一下,应该能出现下面的效果:

很好,我们的数据库连接没有问题,那么下面开始搭建我们的UI。

 sentDateLabel.setTranslatesAutoresizingMaskIntoConstraints sentDateLabel.snp_makeConstraints {  -> Void in make.centerX.equalTo(contentView.snp_centerX) make.top.equalTo(contentView.snp_top).offset make.bottom.equalTo(contentView.snp_bottom).offset }

但是由于中国的互联网是"自由的"。。咳咳,所以呢,你要改变gem的默认下载源:

设置一些布尔变量用来判断消息发送时间相对于当前时间有多久

$ sudo gem install cocoapods
 let bubbleImageView: UIImageView let messageLabel: UILabel
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool

设置时间标签的背景色、字体,文字居中对齐、文字颜色。

图片 26chat.gif文章本部分源代码好的,第三部分顺利实现!第二部分是我们的重头戏,内容较多,所以我把它放到教程的第二部分中。第二部分教程已经出炉,欢迎围观!swift实现一个与智能机器人聊天的app

let bubbleImage = bubbleImageMake()func bubbleImageMake() -> (incoming: UIImage, incomingHighlighed: UIImage, outgoing: UIImage, outgoingHighlighed: UIImage) { let maskOutgoing = UIImage(named: "MessageBubble")! let maskIncoming = UIImage(CGImage: maskOutgoing.CGImage, scale: 2, orientation: .UpMirrored)! let capInsetsIncoming = UIEdgeInsets(top: 17, left: 26.5, bottom: 17.5, right: 21) let capInsetsOutgoing = UIEdgeInsets(top: 17, left: 21, bottom: 17.5, right: 26.5) let incoming = coloredImage(maskIncoming, 229/255, 229/255, 234/255, 1).resizableImageWithCapInsets(capInsetsIncoming) let incomingHighlighted = coloredImage(maskIncoming, 206/255, 206/255, 210/255, 1).resizableImageWithCapInsets(capInsetsIncoming) let outgoing = coloredImage(maskOutgoing, 0.05 ,0.47,0.91,1.0).resizableImageWithCapInsets(capInsetsOutgoing) let outgoingHighlighted = coloredImage(maskOutgoing, 32/255, 96/255, 200/255, 1).resizableImageWithCapInsets(capInsetsOutgoing) return (incoming, incomingHighlighted, outgoing, outgoingHighlighted)}
名称 类型 含义 备注
objectId String 系统默认键 每一条数据都对应一个独一无二的id
incoming Boolean 用来确定该条信息是发送给我们的还是发送出去的 true就是发送来的反之就是我们发送出去的
sentDate Date 消息发送时间
text String 消息的内容
createdAt Date 系统默认键 数据创建时间
updatedAt Date 系统默认键 数据上一次更新的时间
ACL ACL 系统默认键 数据的读写模式

ok,到目前为止我们已经实现了两种tableViewCell,下面我们来看看如何显示出来这些消息!

然后呢,就可以愉快地安装上cocoapods了!

在类里增加一个UILabel的属性,用来显示时间:

首先打开Parse的官网:点我注册一个新的用户,点击右上角的sign up :

 override func numberOfSectionsInTableView(tableView: UITableView) -> Int { return messages.count } override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return messages[section].count + 1 }

图片 27数据导入成功.png

根据消息新旧来设置日期格式,这些格式由一些占位符和UTF-8字符构成,以下是常用占位符表:

 var query = PFQuery(className: "Messages") query.orderByAscending("sentDate") query.findObjectsInBackgroundWithBlock { (objects,error) -> Void in for object in objects as! [PFObject]{ let incoming:Bool = object["incoming"] as! Bool let text:String = object["text"] as! String let sentDate:NSDate = object["sentDate"] as! NSDate println("\(object.objectId!)\n\\n\\n\") } }
 func formatDate(date: NSDate) -> String { let calendar = NSCalendar.currentCalendar() var dateFormatter = NSDateFormatter() dateFormatter.locale = NSLocale(localeIdentifier: "zh_CN") let last18hours = (-18*60*60 < date.timeIntervalSinceNow) let isToday = calendar.isDateInToday let isLast7Days = (calendar.compareDate(NSDate(timeIntervalSinceNow: -7*24*60*60), toDate: date, toUnitGranularity: .CalendarUnitDay) == NSComparisonResult.OrderedAscending) if last18hours || isToday { dateFormatter.dateFormat = "a HH:mm" } else if isLast7Days { dateFormatter.dateFormat = "MM月dd日 a HH:mm EEEE" } else { dateFormatter.dateFormat = "YYYY年MM月dd日 a HH:mm" } return dateFormatter.stringFromDate }
$ xcode-select --install
 messageLabel.snp_updateConstraints {  -> Void in make.centerX.equalTo(bubbleImageView.snp_centerX).offset }

 messageLabel.snp_updateConstraints {  -> Void in make.centerX.equalTo(bubbleImageView.snp_centerX).offset }

图片 28chat.gif

重写tableView设置cell的代理方法

这里我们将使用假数据,只是为了演示如何实现,我们将在下一篇文章着重介绍怎么将真实的数据显示出来!打开ChatViewController.swift文件,在类里添加如下属性,用于存放我们的聊天数据:

将淘宝的ruby源加入进来,看来淘宝也不光是卖东西哈,也是对开发者做了一些贡献的~删除原来的下载源:

 make.right.equalTo(contentView.snp_right).offset
$ gem sources -r https://rubygems.org/

图片 29MessageBubble.png

  • 如何安装和使用cocoapods来集成第三方库
  • 如何搭建一个类似于iOS短信app的界面,以及使用SnapKit来用代码设置autolayout
  • 如何使用Parse云服务平台存储和同步聊天信息,学习相应地数据库知识
  • 如何使用Parse的远程推送功能
  • 如何使用Alamofire实现与智能机器人聊天功能初始项目下载地址:百度网盘下载地址
 let capInsetsIncoming = UIEdgeInsets(top: 17, left: 26.5, bottom: 17.5, right: 21) let capInsetsOutgoing = UIEdgeInsets(top: 17, left: 21, bottom: 17.5, right: 26.5)

图片 30主界面然后导入我们的示例数据:点我下载点击import按钮:图片 31导入数据

make.left.equalTo(contentView.snp_left).offset

忽略黑洞洞的背景,因为我们还没有添加内容。。。但是你会发现一个问题,键盘怎么回来啊。。不管怎么点都没有反应啊!

self.tableView.contentInset = UIEdgeInsets(top: 0, left: 0, bottom:toolBarMinHeight, right: 0)

 messages = [ [ Message(incoming: true, text: "你叫什么名字?", sentDate: NSDate(timeIntervalSinceNow: -12*60*60*24)), Message(incoming: false, text: "我叫灵灵,聪明又可爱的灵灵", sentDate: NSDate(timeIntervalSinceNow:-12*60*60*24)) ], [ Message(incoming: true, text: "你爱不爱我?", sentDate: NSDate(timeIntervalSinceNow: -6*60*60*24 - 200)), Message(incoming: false, text: "爱你么么哒", sentDate: NSDate(timeIntervalSinceNow: -6*60*60*24 - 100)) ], [ Message(incoming: true, text: "北京今天天气", sentDate: NSDate(timeIntervalSinceNow: -60*60*18)), Message(incoming: false, text: "北京:08/30 周日,19-27° 21° 雷阵雨转小雨-中雨 微风小于3级;08/31 周一,18-26° 中雨 微风小于3级;09/01 周二,18-25° 阵雨 微风小于3级;09/02 周三,20-30° 多云 微风小于3级", sentDate: NSDate(timeIntervalSinceNow: -60*60*18)) ], [ Message(incoming: true, text: "你在干嘛", sentDate: NSDate(timeIntervalSinceNow: -60)), Message(incoming: false, text: "我会逗你开心啊", sentDate: NSDate(timeIntervalSinceNow: -65)) ], ]
class ChatViewController:UITableViewController,UITextViewDelegate {............}
var messages:[[Message]] = [[]]

打开项目,新建一个空文件:File/New/File.../ iOS/Others/Empty起名叫Podfile,这一点非常重要,因为这是cocoapods的配置文件,也就是指定你要使用哪些第三方库!我们要使用以下几个库:

当然这些图片还调用了一个方法coloredImage()进行染色处理,就是下面的这个方法:

 var toolBar: UIToolbar! var textView: UITextView! var sendButton: UIButton!

将标签左右居中,顶部距离cell视图顶部13点,底部距离cell视图底部4.5点。关于SnapKit的使用我在上一篇文章提到了一些,真的十分地好用,上手也很快,只要你想出一个公式,比如上面这段代码可以转化为:

由于swift的特殊性,某些第三方库必须使用framework来集成,但是这样也有一个好处,我也是最近才发现,就是Parse和ParseUI其实是OC编写的库,但是呢却不需要OC-Swift的桥接文件了!可以直接当做swift库来使用!

对应地,消息内容的Label也相应右移或左移3点:

接受消息的聊天气泡距离cell左边缘10点:

本文由永利皇宫463登录发布于编程,转载请注明出处:swift完成二个与智能手机器人聊天的app

关键词:

上一篇:特定图层

下一篇:SWIFT世界中的OBJECTIVE