使用 Swift 制作一个新闻通知中心插件(1)

随着 iOS 8 的发布,苹果为开发者们开放了很多新的 API,而在这些开放的接口中 通知中心插件 无疑是最显眼的一个。通知中心就不用过多介绍了,相信大家对这个都很清楚了。在以往的 iOS 版本中,我们只能使用 iOS 系统自带的有限的几个 通知中心组件。 这次新开放的这个功能,就相当于为大家提供了一个全新的市场。相信通过大家的智慧创造,一定会出现很多非常流行的应用。

其他就不多说了,现在我们来以一个新闻插件作为例子,来为大家介绍如何来创建一个 通知中心 插件。我们做好之后,大概就是这个样子:

<!-- more -->

准备工作

我们要开发的是一个简单的新闻插件,那么这就需要一个数据源。 我们这里使用 BBC News 的 RSS 订阅接口来作为新闻数据的来源。

http://feeds.bbci.co.uk/news/rss.xml

这是一个标准的 RSS 2.0 接口,它用 XML 格式返回新闻的数据。如果对 RSS 不了解的话可以参看这篇关于它的介绍 http://en.wikipedia.org/wiki/RSS 。

打开这个 RSS 接口后,我们会看到类似这样的 XML 数据:

  1. <rss xmlns:media="http://search.yahoo.com/mrss/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"\>
  2. <channel>
  3. <title>BBC News - Home</title>
  4. <link>http://www.bbc.co.uk/news/#sa-ns_mchannel=rss&amp;ns_source=PublicRSS20-sa</link>
  5. <description>The latest stories from the Home section of the BBC News web site.</description>
  6. <language>en-gb</language>
  7. <lastBuildDate>Tue, 30 Dec 2014 23:52:29 GMT</lastBuildDate>
  8. <copyright>Copyright: (C) British Broadcasting Corporation, see http://news.bbc.co.uk/2/hi/help/rss/4498287.stm for terms and conditions of reuse.</copyright>
  9. <image>
  10. <url>http://news.bbcimg.co.uk/nol/shared/img/bbc_news_120x60.gif</url>
  11. <title>BBC News - Home</title>
  12. <link>http://www.bbc.co.uk/news/#sa-ns_mchannel=rss&amp;ns_source=PublicRSS20-sa</link>
  13. <width>120</width>
  14. <height>60</height>
  15. </image>
  16. <ttl>15</ttl>
  17. <atom:link href="http://feeds.bbci.co.uk/news/rss.xml" rel="self" type="application/rss+xml"/>
  18. <item>
  19. <title>Poppy duo and acting stars honoured</title>
  20. <description>The creators of the World War One ceramic poppy display at the Tower of London join acting grandees Joan Collins and John Hurt on the New Year Honours list.</description>
  21. <link>http://www.bbc.co.uk/news/uk-30633687#sa-ns_mchannel=rss&amp;ns_source=PublicRSS20-sa</link>
  22. <guid isPermaLink="false">http://www.bbc.co.uk/news/uk-30633687</guid>
  23. <pubDate>Tue, 30 Dec 2014 23:31:59 GMT</pubDate>
  24. <media:thumbnail width="66" height="49" url="http://news.bbcimg.co.uk/media/images/79989000/jpg/_79989926_honours2_composite.jpg"/>
  25. <media:thumbnail width="144" height="81" url="http://news.bbcimg.co.uk/media/images/79989000/jpg/_79989927_honours2_composite.jpg"/>
  26. </item>
  27. </channel>
  28. </rss>

注意一下 <item>节点,这个节点里面包含了我们每条新闻的具体数据,比如新闻标题,描述,发布日期等等。

有了这个数据源,我们就可以开始专注于代码的编写啦。首先,我们要创建一个新项目。打开 Xcode,然后进入 File > New Project... > Single View Application.

然后,再接下来的界面中,填写项目的信息:

ProductName: rsswidget Orgnaization Name: theswiftworld Orgnaization Identifier: com.theswiftworld Language: Swift Devices: iPhone

都填写好后,点击 Next 按钮。 在接下来的界面中,选择一个合适的位置来存放项目文件。然后点击 Create 按钮来创建项目。这样,我们的基础项目就创建好了。

到现在位置,大家可能会发现,我们创建的还是一个普通的 App 项目。因为任何 App Extension 都是需要在一个宿主应用上运行的。比如我们现在在制作的新闻扩展插件,也是需要通过一个原生的 App 来安装到设备上。

那么接下来就是开始创建 App Extension 的过程了,

打开项目的设置页面,点击左下角的加号按钮。

在弹出的窗口中,选择 Application Extension 分类,然后选择该分类下的 Today Extension 选项,然后点击 Next 按钮。

在弹出的 Extension 详细信息窗口中,填写好创建信息:

Product Name: extension Organization Name: theswiftworld

到这里,我们的 Extension 就创建完成了,现在可以在模拟器中运行它,来看到最终的效果了。注意将运行的 Target 选择到 extension。

然后再宿主App中选择 Today 应用:

选择好后,我们就可以在模拟器中,看到我们自己的 App Extension 运行在通知中心里了。 一个 Hello World 显示在通知中心里面。到这里我们关于 Extension 的基础结构搭建就完成了。接下来我们就要考虑如何在我们自定义的 Extension 中显示新闻列表了。

新闻数据源

我们先暂时抛开 Extension 一会儿,现在我们将注意力转移到新闻的数据源中。我们在上面已经介绍了,我们的新闻数据源是以 XML 格式返回给我们的,我们需要用到以下这些第三方库:

Alamofire PKHUD AEXML

下面一一对它们进行介绍:

  • Alamofire

Alamofire 是专门为 Swift 打造的网络操作库,对很多系统方法进行了封装,并提供了方便地异步处理方法和JSON等数据格式的支持。如果你以前用过AFNetworking,就会对这个库更加了解啦,它其实就是 AFNetworking 的作者 Matt Thompson 的另外一个作品。并且 AFNetworking 中前两个字母 AF 其实就是 Alamofire 的字头缩写。

  • PKHUD

PKHUD 是用 Swift 写的一个非常简洁健壮的浮出框组件,提供了多种显示方式,我们这里用到它来作为我们的加载提示框。

  • AEXML

AEXML 是用来解析 XML 数据的 Swift 库,我们这里的新闻数据源是 XML 格式的,所以我们用到它来解析我们收到的 XML 数据。

介绍完需要用到的这些库我们就可以继续我们的教程了。

首先,我们需要引入 Alamofire 库,进入 Alamofire 的首页 https://github.com/Alamofire/Alamofire, 然后在右边的菜单中得最下面点击 Download Zip 按钮把它下载下来。将这个 Zip 包解压后的文件夹放到项目的目录中:

然后将 Alamofire 的项目文件拖动到 Xcode 中的项目结构中。

并且将 Alamofire 设置为 宿主应用和Extension的依赖库。进入 rsswidget 和 extension 的 Build Phrases > Target Dependencies,并将 Alamofire添加为依赖库:

选择 Alamafire 并点击 Add,将库添加进来。

为主应用添加完成后,还要记得给 Extension 也添加一遍哦。

最后再将 Alamofire 添加一遍就完成了。

现在我们将 Alamofire 集成到项目中了,接下来我们来集成 AEXML。

我们进入 AEXML 的主页 https://github.com/tadija/AEXML ,然后点击 Download Zip 将这个库的包下载下来。这个库的集成就非常容易了,只需将解压后包中得 AEXML.swift 文件拖放到 Xcode 项目结构中即可:

怎么样,很简单吧。最后,我们再把 PKHUD 集成进来。

首先,进到 PKHUD 的主页,https://github.com/pkluz/PKHUD, 然后点击 Download Zip 下载 PKHUD 的文件包,然后解压出来,并将整个文件夹放到项目目录中:

然后将项 PKHUD 的项目文件拖动到 XCode 工程结构里面:

这样,我们的集成工作就完成了。

读取数据

现在,我们这个项目所必须得资源库都已经配置好了,接下来我们就来写代码吧!

首先,我们需要一个用于和新闻订阅接口交互的类,我们新建一个实体类 NewsItem:

在 XCode 中打开 File > New > File.. 然后选择 Swift File。点击 Next 按钮:

然后将文件名命名为 NewsItem.swift,并且要注意同时选中主应用和Extension两个Target:

文件创建好后,我们来看看这个类的代码:

  1. import Foundation
  2. class NewsItem {
  3. var title:String?
  4. var link:String?
  5. var pubDate:String?
  6. var description:String?
  7. var thumbnail:String?
  8. init(title:String, link:String,pubDate:String,description:String, thumbnail:String){
  9. self.title = title
  10. self.link = link
  11. self.pubDate = pubDate
  12. self.description = description
  13. self.thumbnail = thumbnail
  14. }
  15. }

这个类很简单,就是一个对新闻数据的实体封装,里面定义了5个属性,分辨对应:

  • title 新闻标题
  • link 新闻链接
  • pubDate 新闻发布时间
  • description 新闻描述
  • thumbnail 新闻标题图片

还定义了一个构造方法,使用这5个参数来分别对这几个属性进行初始化,这个代码应该很容易理解吧。这里只有一天需要提醒下大家,就是我们看到每个类属性定义后面都有一个问号 ?,这个是 Swift 中的一个特性,它叫做 Optionals,关于这个特性的介绍,可以参看 浅谈 Swift 中的 Optionals 这篇文章,里面有详细的介绍。

有了实体类后,我们还需要一个方法来读取新闻数据,这里我们还是在这个类中来定义这个读取方法:


  1. class func getNews(completionHandler: (Array<NewsItem>) -> Void) {
  2. //数据结构的URL 地址
  3. var url:String = "http://feeds.bbci.co.uk/news/rss.xml"
  4. //使用 Alamofire 库来请求网络
  5. Alamofire.request(.GET, url).responseString { (_, _, string, err) in
  6. //验证请求结果
  7. if(err != nil){
  8. print(err?.debugDescription)
  9. }
  10. var error: NSError?
  11. //将新闻结构返回的数据转换为 NSData 类型,并准备进行 XML 解析。
  12. if let xmlData:NSData = string?.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true){
  13. if let xmlDoc = AEXMLDocument(xmlData: xmlData, error: &error) {
  14. var resultNewsList:Array<NewsItem> = Array()
  15. for item in xmlDoc.rootElement["channel"]["item"].all {
  16. var newsTitle:String = item["title"].value
  17. var newsLink:String = item["link"].value
  18. var newsPubDate:String = item["pubDate"].value
  19. var newsDescription:String = item["description"].value
  20. var thumbnail:String = item["media:thumbnail"].all[1].attributes["url"] as String
  21. var newsItem:NewsItem = NewsItem(title: newsTitle, link: newsLink, pubDate: newsPubDate, description: newsDescription, thumbnail:thumbnail)
  22. resultNewsList.append(newsItem)
  23. }
  24. completionHandler(resultNewsList)
  25. }
  26. }
  27. }
  28. }

我们来解释一下上面这段代码, 首先我们定义了一个类方法:

  1. class func getNews(completionHandler: (Array<NewsItem>) -> Void) {

这个类方法接受一个 completionHandler 回调函数,用于在任务完成的进行回调通知。

在这个方法里面,我们定义了变量 url 作为数据接口的地址。随后我们用 Alamofire 库来发送网络请求:

Alamofire.request(.GET, url) 第一个参数是请求方式,在这里我们用 .GET 来请求。第二个参数是发送请求的 url 地址。随后我们用一个回调responseString { (_, _, string, err) 来接受请求的返回。这个回调方法里面的 string 变量代表这个接口返回的数据。

在回调方法中,我们先将这个 string 变量转换为 NSData,随后交由 AEXML 来处理。

  1. //将新闻结构返回的数据转换为 NSData 类型,并准备进行 XML 解析。
  2. if let xmlData:NSData = string?.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: true){
  3. if let xmlDoc = AEXMLDocument(xmlData: xmlData, error: &error) {

我们看到 AEXMLDocument 的构造方法接收的是 NSData数据,随后它会返回一个 XML 文档对象,我们这里存放在 xmlDoc 变量中。

接下来我们遍历这个 xmlDoc 对象,并将它里面的数据转换成实体类,保存起来:

  1. var resultNewsList:Array<NewsItem> = Array()
  2. for item in xmlDoc.rootElement["channel"]["item"].all {
  3. var newsTitle:String = item["title"].value
  4. var newsLink:String = item["link"].value
  5. var newsPubDate:String = item["pubDate"].value
  6. var newsDescription:String = item["description"].value
  7. var thumbnail:String = item["media:thumbnail"].all[1].attributes["url"] as String
  8. var newsItem:NewsItem = NewsItem(title: newsTitle, link: newsLink, pubDate: newsPubDate, description: newsDescription, thumbnail:thumbnail)
  9. resultNewsList.append(newsItem)
  10. }

这个转换过程应该很容易理解吧,就不多做解释了。 最后,我们调用作为参数传递进来的回调函数 completionHandler 并将我们封装好的实体类数组传递进去,通知上级代码来处理这个新闻列表(我们后面会用这个回调函数来通知 UITableView 刷新数据)。

  1. completionHandler(resultNewsList)

构造 Extension 的 UI 界面

有了数据源的支持,我们接下来就可以创建我们的前端显示了。

首先,我们将 Extension 自带的 Hello World 标签删除掉,打开 MainInterface.storyboard 文件,然后将 UI 中的 Hello World 删除掉:

然后打开 Extension 中的 TodayViewController.swift 文件。 我们加入两个成员变量的定义:

  1. var newsListTableView:UITableView?
  2. var newsList:Array<NewsItem>?

这两个变量分别代表用于显示新闻数据的 UITableView 和用来存放数据的数组。 然后我们重写这个类的 viewDidLoad() 方法:

  1. override func viewDidLoad() {
  2. super.viewDidLoad()
  3. self.preferredContentSize = CGSizeMake(0, 263)
  4. self.newsListTableView = UITableView(frame: CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height))
  5. self.newsListTableView?.delegate = self
  6. self.newsListTableView?.dataSource = self
  7. self.view.addSubview(self.newsListTableView!)
  8. NewsItem.getNews { (newsList) in
  9. self.newsList = newsList
  10. let table:UITableView? = self.newsListTableView
  11. dispatch_async(dispatch_get_main_queue()){
  12. table!.reloadData()
  13. }
  14. }
  15. }

第一行代码 self.preferredContentSize = CGSizeMake(0, 263) 设置了 Extension 组件在通知中心的高度。

接下来,我们对 UITableView 进行了初始化:

  1. self.newsListTableView = UITableView(frame: CGRectMake(0, 0, self.view.frame.size.width, self.view.frame.size.height))
  2. self.newsListTableView?.delegate = self
  3. self.newsListTableView?.dataSource = self
  4. self.view.addSubview(self.newsListTableView!)

然后用我们前面写的 NewsItem 类的数据读取方法 getNews 来取得新闻数据,并用得到的数据刷新 UITableView 显示。

  1. NewsItem.getNews { (newsList) in
  2. self.newsList = newsList
  3. let table:UITableView? = self.newsListTableView
  4. dispatch_async(dispatch_get_main_queue()){
  5. table!.reloadData()
  6. }
  7. }

我们在 getNews 的回调方法中将得到的新闻列表存储到属性中,并刷新了 UITableView 的数据显示。这样 viewDidload 方法就完成了。

接下来我们添加 UITableView 的代理方法和数据源方法:

  1. 用于确定 UITableView 的 Section 数量,我们的新闻列表只需要一个 Section。
  1. func numberOfSectionsInTableView(tableView: UITableView) -> Int {
  2. return 1
  3. }
  1. 用于确定 UITableView 的显示行数,这里有一个判断,由于我们指定了 extension 的高度为 263,这个高度只可以显示 6 条新闻,所以如果我们的数据源多于 6 条新闻,我们也按 6 条来显示:
  1. func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  2. if(self.newsList != nil){
  3. if(self.newsList!.count > 6){
  4. return 6
  5. }else{
  6. return self.newsList!.count
  7. }
  8. }else{
  9. return 0;
  10. }
  11. }
  1. 处理 UITableView 的选择状态,这个主要是让用户点击完某条新闻后,清空单元格的选中状态。
  1. func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
  2. tableView.deselectRowAtIndexPath(indexPath, animated: false)
  3. }
  1. 构造 UITableView 的每个单元格,用我们保存的 newsList 里面的实体类的来构造每个单元格。

  1. func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  2. var cellIdentifier:String = "cellIdentifier"
  3. var cell:UITableViewCell? = tableView.dequeueReusableCellWithIdentifier(cellIdentifier) as UITableViewCell?
  4. if(cell == nil){
  5. cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: cellIdentifier)
  6. }
  7. cell?.textLabel.textColor = UIColor.lightGrayColor()
  8. cell?.textLabel.text = self.newsList![indexPath.row].title!
  9. return cell!
  10. }

经过一番折腾,我们的 extension 基本完成了,现在我们可以运行它看看效果了。还是将 Target 设置为 extension,并选择一个合适的模拟器来运行,我们会看到这样的效果:

是不是发现有些问题呢,新闻列表整体向右偏移了一块。这是因为 extension 默认的内容有一个左边距,我们需要设置一下才可以正常显示,所以我们还需要在 TodayViewController 中重写一个方法:

  1. func widgetMarginInsetsForProposedMarginInsets(defaultMarginInsets: UIEdgeInsets) -> UIEdgeInsets {
  2. return UIEdgeInsetsZero
  3. }

这个方法会将 Extension 的边距设置为 0,这样我们的新闻列表就显示正常啦。 再次运行 Extension,我们会在模拟器中看到:

这样我们的新闻列表就算是完成了,到这里我们对 Extension 差不多有了一个初步的认识啦,并且我们自己完成了一个 Extension 的制作。但这个Extension 还需要进一步的完善,比如这个列表的排版还很初级,而且点击这个列表里面的新闻后,没有任何的反应,我们还需要一个新闻内容的显示界面来响应从通知中心的点击,等等。

相信读完这篇后,大家对通知中心扩展插件已经有了一个比较具体的认识啦,我们会在下一部分继续介绍这个通知中心扩展的继续完善过程,让大家对它的运用有更深入的了解,同时,希望大家继续支持.

更多文章请访问: www.theswiftworld.com

Swift 制作一个新闻通知中心插件1的更多相关文章

  1. 使用 Swift 制作一个新闻通知中心插件(1)

    input[type="date"].form-control,.input-group-sm>input[type="date"].input-grou ...

  2. 使用 Swift 制作一个新闻通知中心插件(2)

    我们在第一部分的文章中详细讲解了创建一个通知中心插件的整体过程.我们成功的在通知中心里面显示了新闻列表.但是截止到目前,我们还不能从通知中心的列表中查看新闻的详细内容.在这次的教程中,我们就以上次的教 ...

  3. 手把手制作一个简单的IDEA插件(环境搭建Demo篇)

    新建IDEA插件File --> new --> Project--> Intellij PlatForm Plugin-->Next-->填好项目名OK 编写插件新建工 ...

  4. 制作一个简洁的jquery插件

    原文:http://mp.weixin.qq.com/s?__biz=MzAxMzgwNDU3Mg==&mid=401571467&idx=1&sn=08cb00963e6ef ...

  5. Swift - 制作一个在线流媒体音乐播放器(使用StreamingKit库)

    在之前的文章中,我介绍了如何使用 AVPlayer 制作一个简单的音乐播放器(点击查看1.点击查看2).虽然这个播放器也可以播放网络音频,但其实际上是将音频文件下载到本地后再播放的. 本文演示如何使用 ...

  6. Swift - 制作一个录音机(声音的录制与播放)

    1,技术介绍 (1)AVFoundation.framework框架提供了AVAudioRecorder类.它可以实现录音功能. (2)而使用该框架的AVAudioPlayer类,可以实现声音的播放. ...

  7. 用 Swift 制作一个漂亮的汉堡按钮过渡动画

    汉堡按钮在界面设计中已经是老生常谈了,但是当我在dribbble看到这个漂亮的过渡动画时,我决定试试用代码实现它.   这是 CreativeDash team 的原型图: 你可能已经注意到了,汉堡顶 ...

  8. 【前端】制作一个handlebars的jQuery插件

    (function($) { var compiled = {}; $.fn.handlebars = function($srcNode, data) { // 取出模版内容 var src = $ ...

  9. ASP.NET MVC + 百度富文本编辑器 + EasyUi + EntityFrameWork 制作一个添加新闻功能

    本文将交大伙怎么集成ASP.NET MVC + 百度富文本编辑器 + EasyUi + EntityFrameWork来制作一个新闻系统 先上截图: 添加页面如下: 下面来看代码部分 列表页如下: @ ...

随机推荐

  1. Nginx搭建反向代理服务器过程详解(转)

    一.反向代理 我们都知道,80端口是web服务的默认端口,其他主机访问web服务器也是默认和80端口进行web交互,而一台服务器也只有一个80端口,这是约定俗成的标准. 我们来看下面两个场景: 1.服 ...

  2. Android.9图片评论(一个)

    什么是.9图片 至于什么是.9图片这里就简单提一下,即图片后缀名前有.9的图片,如pic.9.png.pic1.9.jgp,诸如此类的图片就称为.9图片. .9图片的作用 ①.9图片的作用是在图片拉伸 ...

  3. HDU 1195 Open the Lock (双宽搜索)

    意甲冠军:给你一个初始4数字和目标4数字,当被问及最初的目标转换为数字后,. 变换规则:每一个数字能够加1(9+1=1)或减1(1-1=9),或交换相邻的数字(最左和最右不是相邻的). 双向广搜:分别 ...

  4. MongoDB(两)mongoDB基本介绍

    MongoDB介绍 MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库其中功能最丰富,最像关系数据库的.他支持的数据结构很的松散,是类似json的bjson格式,因此能够存储比 ...

  5. ENode简介与各种资源汇总

    ENode简介与各种资源汇总 ENode是什么 ENode是一个.NET平台开源的应用开发框架,为开发人员提供了一套完整的基于DDD+CQRS+ES+(in-memory)+EDA架构风格的解决方案. ...

  6. UVALive 5099 Nubulsa Expo 全球最小割 非网络流量 n^3

    主题链接:点击打开链接 意甲冠军: 给定n个点m条无向边 源点S 以下m行给出无向边以及边的容量. 问: 找一个汇点,使得图的最大流最小. 输出最小的流量. 思路: 最大流=最小割. 所以题意就是找全 ...

  7. tomcat如何避免遭遇ClassNotFoundException

    于Tomcat紧接着为什么要创建一个类加载器Thread.currentThread().setContextClassLoader(catalinaLoader)?这里加载失败主要是为了避免以后加载 ...

  8. SettingsProvider 它SettingsCache

    转载请注明出处:http://blog.csdn.net/droyon/article/details/35558437 SettingsCache,如指出,SettingsProvider缓冲.这将 ...

  9. activiti入门2流程引擎API和服务基础设施

    RepositoryService : 管理和控制公布包和流程定义(包括了一个流程每一个环节的结构和行为)的操作 除此之外,服务能够 查询引擎中的公布包和流程定义. 暂停或激活公布包.相应所有和特定流 ...

  10. DevExpress XtraReports 入门六 控件以程序方式创建一个 交叉表 报表

    原文:DevExpress XtraReports 入门六 控件以程序方式创建一个 交叉表 报表 本文只是为了帮助初次接触或是需要DevExpress XtraReports报表的人群使用的,为了帮助 ...