2、swift中本地文件的体系化和容错处理,上一篇作品源代码

截图

上一篇作品swift实现一个与智能机器人聊天的app(一)兑现了聊天appUI的输入框部分,接下去我会教我们咋样贯彻聊天窗口部分,也就是下图的第二片段:

前言

您在本体系作品校官会学到

  • 什么设置和运用cocoapods来集成第三方库
  • 如何搭建一个近乎于iOS短信app的界面,以及接纳SnapKit来用代码设置autolayout
  • 什么样利用Parse云服务平台存储和共同聊天音信,学习相应地数据库知识
  • 何以运用Parse的中距离推送效用
  • 怎么着利用Alamofire实现与智能机器人聊天效用
    始于项目下载地址:
    百度网盘下载地址

UI结构

首先天我们把品种怎么托管到github中,今日重中之重记录基本框架的搭建,和简易的UI实现。遭遇的题材如下

1、swift中哪些动态加载控制
2、swift中本地文件的体系化和容错处理
3、swift按钮事件的监听写法
4、swift中cocopods使用SnapKit
5、怎么样自定义View和xib定义view以及swift中类方法的写法
6、swift中协商的写法
7、swift中怎么样用闭包来代表协议

末段简短的兑现效益如下图

图片 1

QQ20160820-0@2x.png

布置起始项目

1.cocoapods的安装
cocoapods的装置是经过ruby,幸运的是Mac电脑都是默认安装ruby的,所以安装ruby的历程就节约了,唯一的前提就是设置Xcode的CommandLineTools。
commandLineTools的装置也很简短,只要在极端输入以下命令:

$ xcode-select --install

假若确实尚未设置commandLineTools会指示您要安装它,点设置就足以起初下载,然后等待下载完成后安装即可
上面初阶安装cocoapods,本来只需要简单地在终点输入以下命令即可:

$ sudo gem install cocoapods

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

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

看到以下结果

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

将天猫的ruby源插足进来,看来Tmall也不光是卖东西哈,也是对开发者做了有的贡献的~
除去原来的下载源:

$ gem sources -r https://rubygems.org/

见到以下结果就证实已经成功

https://rubygems.org/ removed from sources

然后呢,就可以称心快意地设置上cocoapods了!

$ sudo gem install cocoapods

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

OK,cocoapods顺利安装收尾!
2.cocoapods的使用
这就是说cocoapods怎么用吧,当然首先次接纳会以为它特别劳苦,不过呢渐渐地你会意识这是一个那些好用的工具,能够说是iOS开发者必要!
先是创造大家的Xcode工程:File/New/Project…/Single View Application
起名叫图灵聊天。

俺们即将采纳图灵机器人的api举办支付:
图灵机器人官网

开辟项目,新建一个空文件:File/New/File…/ iOS/Others/Empty
起名叫Podfile,这点非凡紧要,因为这是cocoapods的配备文件,也就是指定你要拔取什么第三方库!
大家要利用以下多少个库:

  • Alamofire,网络请求库,用来调用图灵机器人的api
  • Snap基特,用代码进行autolayout设置
  • Parse ,Parse云服务平台的SDK
  • ParseUI,Parse提供的便捷UI组件
    在Podfile中,输入以下代码:

source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '8.4'
use_frameworks!

指定下载源,指定平台版本,使用framework举办合并

是因为swift的特殊性,某些第三方库必须运用framework来集成,可是如此也有一个功利,我也是近期才发现,就是Parse和ParseUI其实是OC编写的库,不过呢却不需要OC-斯维夫特(Swift)(Swift)的桥接文件了!可以一贯作为swift库来利用!

pod 'Alamofire', '~> 1.3'
pod 'SnapKit', '~> 0.12.0'
pod 'Parse','~>1.7.1'
pod 'ParseUI','~>1.1.3'

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

开班设置第三方库,打开终端,将当前目录转到Podfile所在目录:

$ cd <Podfile所在目录>

输入以下命令起始部署第三方库:

$ pod install

Analyzing dependencies
Downloading dependencies
Using Alamofire (1.3.1)
Using Bolts (1.2.1)
Using Parse (1.7.5.3)
Using ParseUI (1.1.4)
Using SnapKit (0.12.0)
Generating Pods project
Integrating client project
Sending stats

接下来等待几分钟,如若一切正常,没有出现错误的话,打开项目文件后您会见到workspace的文本,未来都要动用那多少个文件来打开项目。

图1

开拓项目,看一下类另外协会:

项目结构.png

点一下Pods项目,你会意识所需的framework已经编译好了,只要在采用前import她俩就足以了:

Pods

OK,到此我们的门类就配备好了,在大家伊始搭建UI往日,先掌握一下Parse的施用和部分必需配置

你可以在此间下载上一篇著作的源代码:
上一篇著作源代码

1、动态加载控制

先是继承一个UITabBarController的子类

class YJBaseTabBarViewController: UITabBarController

在该决定中添加大家需要添加的子控制器,子决定需要包装一层导航控制器。一般自己不直接采取系统的控制器,所以还写了下边三个类

class YJBaseNaviViewController: UINavigationController
class YJBaseTableViewController: UITableViewController

大家的控制器都无冕于 YJBaseTableViewController

在YJBaseTabBarViewController中viewDidLoad中添加子控制,定义一个主意,用来添加子控制器

   // MARK: 添加子控器
   private func  addChildViewController(childController: UIViewController ,title:String, imageName:String) {

    let main = childController

    main.title = title;
    main.tabBarItem.image = UIImage(named: imageName)
    main.tabBarItem.selectedImage = UIImage(named: imageName + "_highlighted")

    //创建一个nav
    let navi = YJBaseNaviViewController()
    navi.addChildViewController(main)
    addChildViewController(navi)

}

俺们还足以依据服务器重返的类名来动态加载我们的子控制器,于是自己又定义了下边的主意

// MARK: 通过字符串动态加载控制

private func addChildViewController(childControllerName: String,title:String, imageName:String) {


    //在swift中有动态命名空间的概念,默认未工程名字
    let prodouctName = NSBundle.mainBundle().infoDictionary!["CFBundleExecutable"] as! String

    //获取类名
    let className:AnyClass? = NSClassFromString(prodouctName + "." + childControllerName)

    // 将类名转化为指定的类型
    let vcCls = className as! UIViewController.Type

    // 调用初始化方法
    let childVC = vcCls.init()


    addChildViewController(childVC, title: title, imageName: imageName)


}

此地最重要遭逢的题目swift中怎样通过字符串来赢得类名,然后再经过类来创建需要的控制器。下边的代码我想已经写的够详细了。

配置Parse

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

Parse注册.png

如上用户名只是示例,然而app名称输入TuringChat。
登记截止后,用你刚才注册的用户名登陆,应该会现出以下界面:

主界面

接下来导入我们的演示数据:
点我下载
点击import按钮:

导入数据

选料刚刚下载的公文:

数量导入成功.png

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

数据.png

俺们来看一眼Messages类里都有什么样:

名称 类型 含义 备注
objectId String 系统默认键 每一条数据都对应一个独一无二的id
incoming Boolean 用来确定该条信息是发送给我们的还是发送出去的 true就是发送来的反之就是我们发送出去的
sentDate Date 消息发送时间
text String 消息的内容
createdAt Date 系统默认键 数据创建时间
updatedAt Date 系统默认键 数据上一次更新的时间
ACL ACL 系统默认键 数据的读写模式

接下去我们来测试一下是否读取到这一个多少,首先要拿到该app的application
ID和Client Key:

获取Key

红线划掉的这两行就是我们需要的。
然后打开项目中的AppDelegate.swift,增添对Parse库的引用:

import Parse

找到以下措施

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

在内部添加代码:

    Parse.setApplicationId("CYdFL9mvG8jHqc4ZA5PJsWMInBbMMun0XCoqnHgf", clientKey: "6tGOC1uIKeYp5glvJE6MXZOWG9pmLtMuIUdh2Yzo")

连日来Parse的服务器

        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\\(incoming)\\n\\(text)\\n\\(sentDate)")
        }
     }

新建查询,查询我们刚刚所建的Messages类,用findObjectsInBackgroundWithBlock方法取出查询结果,并用一个循环往复全体打印出来。
cmd+R运行一下,假若没有问题会输出接近下边的始末:

oYtildSAOz
false
你叫什么名字?
2015-08-28 06:42:00 +0000
LX7kxmmiEp
true
我叫灵灵,聪明又可爱的灵灵
2015-08-28 06:43:00 +0000
p62dmgGIAS
false
你爱不爱我?
2015-08-28 06:43:00 +0000
oWReOM43Nf
true
爱你么么哒
2015-08-28 06:44:00 +0000
mtl2BGt3Mu
false
今天北京天气如何?
2015-08-29 03:59:00 +0000
DikAu5P2Nn
true
北京: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

很好,我们的数据库连接没有问题,那么上边起先搭建我们的UI。

第一打开我们的档次,你可以找到用于落实该部分的文件:
MessageBubbleTableViewCell.swiftMessageSentDateTableViewCell.swift,分别用来贯彻信息发送时间的cell和拉扯气泡的cell
率先实现新闻发送时间的cell,打开MessageBubbleTableViewCell.swift文件,扩大对Snap基特第三方库的引用:

2、本地文件的体系化处理,使用do和catch来拍卖体系化容错

是因为我们并未网络重回的多少,我们就径直模拟动态加载,在地面添加一个json文件,然后我们解析json文件,然后加载我们的控制器
主意如下:

// 解析本地json
    let jsonPath = NSBundle.mainBundle().pathForResource("localSettings.json", ofType: nil)

    if let resultPath = jsonPath {

        print(resultPath)

         //转为data
        let fileData = NSData.init(contentsOfFile: resultPath)

        if let realData = fileData  {

            //在这里序列化,序列化可能会出现,抛异常

            do {

                // 这里正常解析
                let jsonArr = try NSJSONSerialization.JSONObjectWithData(realData, options: .MutableContainers)

                //遍历数组,这里需要注意因为json返回的是Anyobject对象,需要将其转为数组
                for dict in jsonArr as![[String:String]] {

                    addChildViewController(dict["vcName"]!, title: dict["title"]!, imageName: dict["imageName"]!)
                }

                print("解析成功")


            }
            catch {

                print(error)
                // 异常处理,如果解析json失败,就自己创建
                //创建首页
                addChildViewController("YJMainViewController", title: "首页", imageName:"tabbar_home")

                //消息
                addChildViewController("YJMessageViewController", title: "消息", imageName:"tabbar_message_center")

                //发现
                addChildViewController("YJAttentionViewController", title: "发现", imageName:"tabbar_discover")

                //我的
                addChildViewController("YJMineViewController", title: "我的", imageName:"tabbar_profile")
            }


        }

    }

搭建UI

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

UI结构

这就是说这三局部怎样去落实吗,我先向大家做一些粗略的牵线:
1.导航栏
这一有的实现相比较简单,只要把视图控制器嵌套在一个导航控制器(UINavigationController)中即可,然后对其外观举行部分定制化操作。
2.拉扯窗口
这一有的用UITableView来构建。仔细考察你会意识此处一起有两种UITableViewCell:

  • 用来显示音信发送日期的cell
  • 发送新闻气泡的cell
  • 接到新闻气泡的cell
    但骨子里大家只需要多少个,因为后二种cell区别只是是颜色和地方,大家只要判断一下该音信是殡葬的依旧收下的,然后相应举行处理即可!
    两种cell都是用的以下这些材料:

    MessageBubble.png

但是,你会问,它为啥是黑色的!怎么让他变成图中的两种颜色呢?还有明明聊天气泡的大小是不定的,这样一张图怎么能满足所有尺寸呢?  
有疑问很好,因为它可以成为你学习的动力,我们会在接下来向大家解释这是如何实现的!Be
patient!  
**3.输入框**  
这里我们要通过重写`UIResponder`类的`inputAccessoryView`属性来自定义我们的输入框,这样做的好处是我们的输入框会和系统的键盘结合起来,可以让其成为第一响应者(first
responder),一旦它成为第一响应者,我们自定义的输入框会跟随键盘一同弹出和收回,就像真正的短信app那样,这个方法比我有一篇文章所写的[实现类似微信的输入框跟随键盘弹出的效果](https://www.jianshu.com/p/4e755fe09df7)的方法还要更好一些,所以说方法不是绝对的,因为你总是能够找到更好的方法,所以,编程的时候要经常在脑子里想"嗯,一定还有更好的方法"。

哦好嘞,废话不多说,上面我们就来一步一步地相继实现它们!

率先从最简便易行的做起,实现自定义导航栏:
开辟起先项目你会看出模板文件已经全副建好:
找到AppDelegate.swift文本中的以下方法:

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

在其间添加如下代码:

        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()

设置app启动时彰显大家自定义的视图控制器,并设置一下导航栏的外观。
ok,第一有的成功。
接下去我们来兑现一下第三有的:输入框,我们要把最难的第二局部留在最后( ⊙ o
⊙ )
打开ChatViewController.swift文件:
增长一些大局常量,在import下边class的概念之上:

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

先是个是消息所用的字体大小,第二个是大家输入框的中度。
加上一些构成输入框的零部件:

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

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

class ViewController: UIViewController,UITextViewDelegate {
....
....
}

下边添加以下代码来声称对inputAccessoryView的重写:

 override var inputAccessoryView: UIView! {

}

用get的艺术将输入框的零件举行布置:
在大括号内部添加代码:

        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(textView)

                sendButton = UIButton.buttonWithType(.System) as! UIButton
                sendButton.enabled = false
                sendButton.titleLabel?.font = UIFont.boldSystemFontOfSize(17)
                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(false)
                sendButton.setTranslatesAutoresizingMaskIntoConstraints(false)

                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
        }

您会意识有一个荒唐,这是因为大家的InputTextView是一个单身定义的类,它还不曾定义,我们在后来会对他做一些操作,最近先不用管它,不过我们先把它定义出来,在视图控制器类之外定义该类:

class InputTextView: UITextView {



}

还有一个题材,用系统默认的代码实现autolayout看起来很难了解,所以这边可以用第三方库Snap基特来实现,把下面设置autolayout的代码替换成以下代码:

textView.setTranslatesAutoresizingMaskIntoConstraints(false)             
sendButton.setTranslatesAutoresizingMaskIntoConstraints(false)

 textView.snp_makeConstraints({ (make) -> Void in

                    make.left.equalTo(self.toolBar.snp_left).offset(8)
                    make.top.equalTo(self.toolBar.snp_top).offset(7.5)
                    make.right.equalTo(self.sendButton.snp_left).offset(-2)
                   make.bottom.equalTo(self.toolBar.snp_bottom).offset(-8)


                })
                sendButton.snp_makeConstraints({ (make) -> Void in
                    make.right.equalTo(self.toolBar.snp_right)
                     make.bottom.equalTo(self.toolBar.snp_bottom).offset(-4.5)

                })

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

autoLaout

sendButton的有些也是如此:
make.right.equalTo(self.toolBar.snp_right)代表发送按钮左边直接贴输入框的左边,没有位移
make.bottom.equalTo(self.toolBar.snp_bottom).offset(-4.5)出殡按钮底部距离输入框底部4.5点
这样是不是让autoLayout变得简单很多了?前边的档次我们就平昔采纳它来举行autoLayout设置了!

import SnapKit

3、按钮点击事件的写法。

上图中间的按钮是我们自己加上一个的按钮,添加点击写法真心蛋筒,需要专注的是响应事件措施也无法用private来修饰
往日添加点击事件的可以如此写:

  btn.addTarget(self, action: "composeClick", forControlEvents: .TouchUpInside)

近来下边的写法已经弃用改用下边的写法,然则蛋痛是写的时候根本没有指示。

  btn.addTarget(self, action: #selector(YJBaseTabBarViewController.composeClick), forControlEvents: .TouchUpInside)

今昔从不不当了,cmd+R运行一下,啊哦,为什么是空白!作者你骗人!( ⊙ o ⊙ )

=好吧,我们还差一步,记得呢,它要改成第一响应者才能弹出键盘哦,大家要重写一个格局它才能立竿见影!在视图控制器类中加进以下方法:

   override func canBecomeFirstResponder() -> Bool {
        return true
    }

报告我们的系列大家自定义的输入框能够成为第一响应者,大家也是有身份证的!
下一场在运转一下,倘使没有错误,应该会有以下职能:

chat.gif

疏忽黑洞洞的背景,因为我们还尚未增长内容。。。
而是你会意识一个问题,键盘怎么回来呀。。不管怎么点都未曾影响啊!

好啊,下面我们来用一个全优的不二法门来缓解它。由于聊天页面是一个UITableView,所以我们得以接纳UITableViewContoller来取代大家的UIViewContoller,这样大家的页面中就默认有了一个UITableView,然后它有一个不行实用的性能—keyboardDismissMode,我们把它设置为.Interactive也就是键盘的弹出和撤回状态可以依照你对tableView的拖拽举办变更,也就是你的手指头拖到何地你的键盘就到何地,是不是很酷。
更改视图控制器的花色:

class ChatViewController:UITableViewController,UITextViewDelegate {
....
....
....
}

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

tableView.keyboardDismissMode = .Interactive

再也运行,你会意识黑洞洞的背景不见了,取而代之的是空白的TableView!而且键盘也促成了炫酷的听从!

chat.gif

作品本有的源代码
好的,第三有的风调雨顺落实!第二有的是我们的主体,内容较多,所以我把它内置教程的第二部分中。
第二有的科目已经出炉,欢迎围观!
swift实现一个与智能机器人聊天的app(二)

在类里扩大一个UILabel的性能,用来显示时间:

4、cocopods的使用

下面大家着力就能搭建好全部的框架,接下就需要实现未登陆界面,这里有UI布局,我们只要用代码的话,为了适配大家需要用autolayout来布局,我使用第三方库SnapKit,为了方便管理使用Cocopods来安装
a、首先需要设置cocopods,如果没有安装的话。

安装命令:sudo gem install cocoa pod

有可能会遇见安装出错,如下

图片 2

0DA1D724-CEC7-4851-9D96-4C0FC10CDB4A.png

则用下边的安装命令

sudo gem install -n /usr/local/bin cocoa pods

b、cd 到你工程目录

pod init

下一场用Xcode打开编辑

open -a Xcode Podfile

写入上边内容

 use_frameworks!
platform :ios, '9.0'
pod 'SnapKit'

关闭在巅峰中实践

 pod install

等候安装到位

图片 3

5754B61B-3058-4ED3-897B-C50B25A3F84D.png

就足以由此上图这几个打开大家的工程,这样我们就能动用第三方Snap基特

倘诺该随笔对你有救助,请点一下喜欢!您的辅助是本人连续写作的重力!

  let sentDateLabel: UILabel

5、6、7、自定义view和xib自定义view类方法、协议、闭包

首页的未登陆界面,用来代码来自定义view,用Snapkit来布局UI。
代码自定义view必须写下面的不二法门

// 主页的访客视图、

    override init(frame: CGRect) {

    super.init(frame: frame)

    addSubview(rorationView)

    addSubview(hourseView)

    addSubview(introduceLabel)

    addSubview(attentionButton)

}

// 如果是自定义xib view必须实现这个方法,不是的就不需要实现
required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
}

下边是采用Snap基特布局的有些代码如下

  // 在这里写布局相关的代码
    weak var weakSelf = self

    //旋转视图
    rorationView.snp_makeConstraints { (make) in

        make.size.equalTo(CGSizeMake(200, 200))
        make.center.equalTo(weakSelf!)
    }

里头有个关注按钮的点击事件需要传递给控制器,OC最容易想到的采纳代理。swift中代理协议的写法必须继承:NSObjectProtocol协议,首先是宣称协议。

  //申明协议
  protocol YJMainVisitorViewDelegate:NSObjectProtocol {

// 关注按钮点击
    func attentioButtonClick()
}

在该view注明一个代理,紧要需要使用weak关键字来修饰

 // 申明代理
   weak var delegate:YJMainVisitorViewDelegate?

在按钮响应事件中实现

//MARK: 按钮点击事件

    func attentionClick()  {

    // 感觉这个写法要比OC简单
     delegate?.attentioButtonClick()
    }

这样想要监听关注点击事件一旦成为它代理,坚守协议就能监听点击事件了。使用如下
第一,遵从协议

class YJMainViewController: YJBaseTableViewController,YJMainVisitorViewDelegate

然后改成visitorView的代理

   let visitorView = YJMainVisitorView()

        visitorView.delegate = self

最终实现代理方法

//MARK:YJMainVisitorViewDelegate
func attentioButtonClick() {

    print("我点击关注按钮了")
}

地点的代理协议落实过程,走了两个步骤,实在让人蛋痛,于是想到了闭包和OC
的block有点类似,于是就尝试了下。
写法如下
率先在view中表明一个闭包属性

 // 定义一个闭包
var attentClickBlock:(()->())?

然后在响应事件中

 //MARK: 按钮点击事件
func attentionClick()  {

    // 
    attentClickBlock?()
}

最终在控制器中的使用如下

    let visitorView = YJMainVisitorView()

        visitorView.attentClickBlock = {

            print("我闭过包来的")
        }

本身或者习惯用闭包,因为简单啊,我是个怕麻烦的人。

音讯的未登陆页面,我们应用xib关联的法子来实现,必须重写下边的办法

  // 自定义xib重写这个方法
required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)

}

另外实现和OC基本均等自己就不描述了,其它就是swift中类方法的写法,使用
internal和 class五个关键字来声称

   //MARK: 获取messageVisitorView,声明类方法 internal
internal class func  messageVisitorView() ->(YJMessageVisitorView){

    return NSBundle.mainBundle().loadNibNamed("YJMessageVisitorView", owner: nil, options: nil).first as! YJMessageVisitorView
}

先天的始末到此截止,假如有什么样错误的地点,希望指正,毕竟swift我用的不多,不是很熟,谢谢!!!

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

        sentDateLabel = UILabel(frame: CGRectZero)
        sentDateLabel.backgroundColor = UIColor.clearColor()
        sentDateLabel.font = UIFont.systemFontOfSize(10)
        sentDateLabel.textAlignment = .Center
        sentDateLabel.textColor = UIColor(red: 142/255, green: 142/255, blue: 147/255, alpha: 1)

安装时间标签的背景观、字体,文字居中对齐、文字颜色。

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

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

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

将标签左右居中,顶部相差cell视图顶部13点,底部距离cell视图底部4.5点。关于Snap基特的应用自家在上一篇作品提到了部分,真的相当地好用,上手也很快,只要您想出一个公式,比如下面这段代码可以转账为:

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

ok,显示音信发送时间的cell就安装好了。

接下去打开MessageBubbleTableViewCell.swift文件,扩大新的性能:

    let bubbleImageView: UIImageView
    let messageLabel: UILabel

在import下面扩展全局变量,用来标示cell的体系(接受或发送的音信):

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

在类外扩充部分艺术,在文件结尾添加以下代码:

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)
}

回来一个结构体包含4种图片:发送音信气泡的正规和高亮(被点击后)图片,接收消息气泡的正常化和高亮图片,以供调用。

MessageBubble.png

这是图形的原型,不难领会这是发送信息对应的扯淡气泡,所以直接调用即可

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)

这就是说它是怎么规定可拉伸区域的吧,这多少个示意图可以分解一切:

可拉伸区域

事实上那多少个可拉伸区域只有1×1像素,不过也够我们用了,因为这一有的可以极其地横向或纵向拉伸,接收音信气泡和发送音信气泡可拉伸区域唯一的区分就是水平方向上,所以把right和left的值互相互换即可。
接下来经过UIImageresizableImageWithCapInsets()主意,获取可拉伸图片:

 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)

本来这么些图片还调用了一个格局coloredImage()展开染色处理,就是下面的这些措施:

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(rect)
    CGContextSetRGBFillColor(context, red, green, blue, alpha)
    CGContextSetBlendMode(context, kCGBlendModeSourceAtop)
    CGContextFillRect(context, rect)
    let result = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return result
}

拿到图片大小

  let rect = CGRect(origin: CGPointZero, size: image.size)

开创位图绘图上下文

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

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

   let context = UIGraphicsGetCurrentContext()
    image.drawInRect(rect)
    CGContextSetRGBFillColor(context, red, green, blue, alpha)
    CGContextSetBlendMode(context, kCGBlendModeSourceAtop)
    CGContextFillRect(context, rect)

得到到绘图结果,停止位图绘图上下文并回到绘图结果

    let result = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    return result

扶植方法写完,上面先导展开cell的部署,在init()方法中添加以下代码:

        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

设置气泡视图和信息标签

        super.init(style: .Default, reuseIdentifier: reuseIdentifier)
        selectionStyle = .None

        contentView.addSubview(bubbleImageView)
        bubbleImageView.addSubview(messageLabel)

初始化cell

        bubbleImageView.setTranslatesAutoresizingMaskIntoConstraints(false)
        messageLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
        bubbleImageView.snp_makeConstraints { (make) -> Void in
            make.left.equalTo(contentView.snp_left).offset(10)
            make.top.equalTo(contentView.snp_top).offset(4.5)
            make.width.equalTo(messageLabel.snp_width).offset(30)
            make.bottom.equalTo(contentView.snp_bottom).offset(-4.5)


        }
        messageLabel.snp_makeConstraints { (make) -> Void in
            make.centerX.equalTo(bubbleImageView.snp_centerX).offset(3)
            make.centerY.equalTo(bubbleImageView.snp_centerY).offset(-0.5)
            messageLabel.preferredMaxLayoutWidth = 218
            make.height.equalTo(bubbleImageView.snp_height).offset(-15)

        }

进行autolayout设置

只是如此只是一种聊天气泡,而且没有安装新闻内容,我们要依据音信内容和项目对cell举办布置,在这从前大家率先全面我们的音信模型Message,打开Message.swift,在类中添加如下代码:

    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
    }

接下来回来咱们的MessageBubbleTableViewCell.swift,添加以下的配置模式:

    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({ (make) -> 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(10)
                    messageLabel.snp_updateConstraints { (make) -> Void in
                        make.centerX.equalTo(bubbleImageView.snp_centerX).offset(3)
                    }

                } else { // outgoing
                    tag = outgoingTag
                    bubbleImageView.image = bubbleImage.outgoing
                    bubbleImageView.highlightedImage = bubbleImage.outgoingHighlighed
                    messageLabel.textColor = UIColor.whiteColor()
                     make.right.equalTo(contentView.snp_right).offset(-10)
                    messageLabel.snp_updateConstraints { (make) -> Void in
                        make.centerX.equalTo(bubbleImageView.snp_centerX).offset(-3)
                    }


                }
            })


    }

//1
设置信息内容。
//2
删去聊天气泡的left或right约束,以便于遵照音信类型重新展开安装。
//3
依照新闻类型举办相应的安装,包括使用的图样还有约束原则。由于发送音信的聊天气泡是靠右的,而接受信息的聊天气泡是靠左的,所以发送音信的聊天气泡距离cell左侧缘10点:

 make.right.equalTo(contentView.snp_right).offset(-10)

经受音信的聊天气泡距离cell右侧缘10点:

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

对应地,音讯内容的Label也对应右移或左移3点:

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

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

ok,到目前截止大家早已实现了二种tableViewCell,下边大家来看看哪些呈现出来那么些音信!

将聊天内容展现到主界面

此处我们将动用假数据,只是为了演示咋样实现,我们将在下一篇随笔着重介绍怎么将真正的数目展现出来!
打开ChatViewController.swift文本,在类里添加如下属性,用于存放我们的闲话数据:

var messages:[[Message]] = [[]]

这是一个Message类型的数组,数组的元素也是一个Message类型的数组。为啥要这样定义呢,这是为着区别聊天暴发的年华,同一段时光发出的聊天打包到一起构成一个数组元素,当先这一段时间的拉扯放到新开辟的数组元素中,这样做也有益大家的tableView确定分区(section)和行(row),同一段时光的闲话放在同一个section,超过这段时间的扯淡放在下一个section,每一分区(section)中有多少个音信,就有几行(row)。
找到viewDidLoad()方法,在super.viewDidLoad()这行代码下添加如下代码:

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

注册tableViewCell

        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举办一些必备的安装,由于tableView底部有一个输入框,由此会遮挡cell,所以要将tableView的情节inset扩大部分平底位移:

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))
            ],
        ]

填充假的拉扯数据

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

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {

        return messages.count

    }
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

        return messages[section].count + 1
    }

重写tableView设置cell的代办方法

   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(message)




            return cell
        }

    }

假如没有错误,cmd+R运行一下,应该能冒出上边的效应:

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

音讯是健康呈现出来了,可是新闻的出殡时间看起来很别扭,所以我们需要对其举行格式化,在类中添加如下方法:

    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(date)
        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(date)
    }

您会感觉看到了有的出乎意料的事物,所以我来解释一下这多少个代码:

let calendar = NSCalendar.currentCalendar()

收获当前的日历,我们要接纳其中的部分方法

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

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

        let last18hours = (-18*60*60 < date.timeIntervalSinceNow)
        let isToday = calendar.isDateInToday(date)
        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"

        }

据悉新闻新旧来设置日期格式,这么些格式由局部占位符和UTF-8字符构成,以下是常用占位符表:

占位符 含义
YYYY 年份
MM 月份
dd
HH 小时
mm 分钟
ss
a 表示上午、下午等
EEEE 星期几

就此在此间日期就被代表为(以2015年8月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
现在,在给日期赋值前,调用该方法进行格式化,修改下面这一行代码:

cell.sentDateLabel.text = "\(message.sentDate)"

 cell.sentDateLabel.text = formatDate(message.sentDate)

下一场再度运行:

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

看!这样就很美观了呢?
到这里我们的第二有些科目就完了了,第三有些将会落实殡葬音讯、用Alamofire网络请求进行聊天音讯的报告,从Parse服务器收到和封存聊天消息,真正实现和智能机器人聊天!敬请期待!
本篇小说源代码放在了百度网盘里:
下载地址

设若该著作对您有赞助,请点一下欢喜!您的扶助是自我连续写作的引力!

相关文章