SwiftによるシンプルなRSSリーダーの作り方

swift

SwiftとiOSアプリ開発の基本を学ぶのに最適なのは、RSSリーダーを作ることです。というのは、iPhoneアプリによく求められるサーバーサイドとの連携を、サーバーの実装無しに練習できるからです。この記事では、そんなRSSリーダーのシンプルな作り方をご紹介します。

最終的にはこんな感じ

完成品

ただ、いきなりこれを作ると思うと大変なので、1機能1機能追加して、ステップバイステップで進めるような記事にしています。

なお本記事はつぎの記事を参考にさせて頂いています。

ただ、より初心者の方が取り組みやすいように、難しい部分やコードの量を減らし、シンプルな手順にしています。

また、以下の環境を前提にしています。

  • Xcode Version 7.3 (7D175)
  • Swift 2.2

この手順は次の流れで進みます。

  1. メニューバーを作る
  2. APIから情報を取得する
  3. TableViewを表示する
  4. 記事を表示する
  5. Thumbnail画像を表示する

1. メニューバーを作る

ただ記事の一覧が表示されるだけというのも味気ないので、メニューバーを作ります。こんな感じのイメージです。

メニューバーのGIF

まずはプロジェクトを新規作成します。

プロジェクトを作る

Xcodeのメニューから、File>New>Project..をクリックし、Single View Applicationを選択します。

新プロジェクト

以下の3つは自由でOKです。

  • Product name
  • Organization Name
  • Organization Identifier

PageMenuというライブラリを使うため、CocoaPodsというライブラリ管理ツールをインストールしておいてください。インストール方法は次のページがわかりやすいです。

【初心者向け】SwiftでもObj-cでも、CocoaPodsを使ってiOSライブラリを使って開発効率をアップさせる

Macのコーソールで、このプロジェクトのルートディレクトリに移動してください。

1
$ cd /Users/hoge/ios/RssReaderApp

Podfileという名前でファイルを作成します。

1
$ vi Podfile

中身は次のようにします。

1
2
3
4
5
6
7
source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!
platform :ios, '8.0'

target 'RssReaderApp' do
 pod 'PageMenu'
end

RssReaderAppの部分をきちんとあなたが作ったプロジェクト名と同じになっているようにしてください。

コンソールで、下のようにpod installを実行するとPageMenuがインストールされます。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ pod install
Updating local specs repositories

CocoaPods 1.0.0.beta.8 is available.
To update use: `sudo gem install cocoapods --pre`
[!] This is a test version we'd love you to try.

For more information see https://blog.cocoapods.org
and the CHANGELOG for this version http://git.io/BaH8pQ.

Analyzing dependencies
Downloading dependencies
Installing PageMenu (1.2.9)
Generating Pods project
Integrating client project

[!] Please close any current Xcode sessions and use `RssReaderApp.xcworkspace` for this project from now on.
Sending stats
Pod installation complete! There is 1 dependency from the Podfile and 1 total pod installed.

最初のpod install完了後には、Xcodeでこのプロジェクトを開き直す必要があります。まずこのプロジェクトを左上の赤いボタンでOKです。

コンソールでlsを実行すると、RssReaderApp.xcworkspaceというファイルが生成されているはずです。それを開きます。下のようにするとXcodeで開きます。

1
$ open RssReaderApp.xcworkspace

すると下のようにXcodeの左側のProject NavigatorにPodsというのができてるのがわかります。

Podsができてる

この中にCocoaPodsでインストールしたライブラリが入ってきます。

なおPodfileでバージョンを指定しないと、その時々の最新バージョンをインストールします。なので、本記事執筆時から時間が経つにつれて動かなくなってしまうケースが出てくるかもしれません。

その場合は、それぞれのセクションに対応するgithubのタグから、Podfile.lockに書いてある各ライブラリのバージョンを確認し、Podfileの方で指定するようにしてみてください。そうすれば、本記事で使ったバージョンを確実に使うことができます。例えば、下のような形で指定できます。

1
pod 'LibraryName', '1.0'

Podfile.lockとは、pod install実行時に、実際にインストールされたバージョンを具体的に記録したファイルです。

ViewController.swiftの編集

ViewController.swiftを開いてください。

まず、PageMenuをimportするようにし(2行目)、PageMenu用のプロパティを用意します。(5行目)

1
2
3
4
5
import UIKit
import PageMenu

class ViewController: UIViewController {
  var pageMenu : CAPSPageMenu?

viewDidLoad()内を以下のようにします。

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
  override func viewDidLoad() {
    super.viewDidLoad()

    var controllers : [UIViewController] = []
    for i in 1...4 {
      let controller   = TableViewController(
        nibName: "TableViewController",
        bundle:  nil
      )
      controller.title = "MENU" + String(i)
      controllers.append(controller)
    }

    let params: [CAPSPageMenuOption] = [
      .ScrollMenuBackgroundColor(UIColor.blackColor()),
      .ViewBackgroundColor(UIColor.whiteColor()),
      .SelectionIndicatorColor(UIColor.orangeColor()),
      .MenuItemFont(UIFont(name: "HelveticaNeue", size: 15.0)!),
      .MenuHeight(80.0),
      .MenuItemWidth(90.0),
      .CenterMenuItems(true)
    ]

    pageMenu = CAPSPageMenu(
      viewControllers: controllers,
      frame:           CGRectMake(
        0.0, 0.0, self.view.frame.width, self.view.frame.height
      ),
      pageMenuOptions: params
    )

    self.addChildViewController(pageMenu!)
    self.view.addSubview(pageMenu!.view)
    pageMenu!.didMoveToParentViewController(self)
  }

TableViewControllerの作成

TableViewController.swiftとそれに対応するxibファイルを作成します。

まず、Project NavigatorでRssReaderAppを右クリックし、New File…を選択します。

TableViewControllerの作成

次の画面でCocoa Touch Classを選択します。そして下のようにし、swiftファイルとxibファイルを同時に作成します。

xibも同時作成

これでRunすればこのようなメニューが表示されるはずです。

メニューバーのGIF

ここまでのコードは下のリンクで見ることができます。

https://github.com/tarky/RssReaderApp/tree/added-menu

ローカルでみたい方はgit clone-bにタグの名前を指定すればOKです。

1
$ git clone https://github.com/tarky/RssReaderApp.git -b added-menu

2. APIから情報を取得する

TableViewに記事一覧を表示する前に、APIからデータが取得できるかどうかを確認しておきましょう。

ライブラリを追加する

まずPodfileに必要なライブラリを追加しておきます。(7〜8行目)

1
2
3
4
5
6
7
8
9
source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!
platform :ios, '8.0'

target 'RssReaderApp' do
 pod 'PageMenu'
 pod 'Alamofire'
 pod 'SwiftyJSON'
end

忘れずにpod installを実行します。

ViewController.swiftの編集

ViewControllerからそれぞれのメニューのTableViewControllerに対して、記事を取得するURLとタイトルを設定します。

1
2
3
4
5
6
7
8
for i in 1...4 {
  let controller   = TableViewController(
    nibName: "TableViewController",
    bundle:  nil
  )
  controller.title = "MENU" + String(i)
  controllers.append(controller)
}

という部分を、下のコードに置き換えてください。

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
var feeds: [Dictionary<String, String>] =
  [
    [
      "link": "https://ajax.googleapis.com/ajax/services/feed/load?v=1.0&q=http://menthas.com/top/rss",
      "title": "top"
    ],
    [
      "link": "https://ajax.googleapis.com/ajax/services/feed/load?v=1.0&q=http://menthas.com/ruby/rss",
      "title": "ruby"
    ],
    [
      "link": "https://ajax.googleapis.com/ajax/services/feed/load?v=1.0&q=http://menthas.com/ios/rss",
      "title": "ios"
    ],
    [
      "link": "https://ajax.googleapis.com/ajax/services/feed/load?v=1.0&q=http://menthas.com/infrastructure/rss",
      "title": "infrastructure"
    ],
  ]
for feed in feeds
{
  let feedController = TableViewController(nibName: "TableViewController", bundle: nil)
  feedController.parent = self
  feedController.fetchFrom = feed["link"]!
  feedController.title = feed["title"]
  controllers.append(feedController)
}

RSSの取得元はhttp://menthas.com/というサイトです。

1
https://ajax.googleapis.com/ajax/services/feed/load?v=1.0&q=http://menthas.com/top/rss

というURLになっているのは、RSSをGoogleが提供するJson形式に変換するAPIに通しているからです。

こうすることで、XMLではなくJsonとしてデータを取得することができます。

TableViewController.swiftの編集

下のように、2つのライブラリのimportの追加と、2つのプロパティを追加してください。

1
2
3
4
5
6
7
8
import UIKit
import Alamofire
import SwiftyJSON

class TableViewController: UITableViewController {

  var fetchFrom: String?
  var parent: UIViewController?

viewDidLoad()を次のようにします。

1
2
3
4
5
6
7
8
9
10
11
override func viewDidLoad() {
  super.viewDidLoad()
  Alamofire.request(.GET, fetchFrom!).responseJSON { response in
    if let values = response.result.value {
      JSON(values)["responseData"]["feed"]["entries"].forEach {i,value in
        print(value["title"].string!)
        print(value["link"].string!)
      }
    }
  }
}

Runしてみて、下のようにXcodeのコンソールにタイトルとURLが表示されていればデータは取得できていることが確認できました。

ログが表示されている

この状態のコードは下で見れます。

https://github.com/tarky/RssReaderApp/tree/connect-api

3. TableViewを表示する

取得したデータをそれぞれのメニューのTableViewに表示していきます。完成イメージはこんな感じです。

tableView

FeedTableViewCellの作成

名前はなんでもいいのですが、テーブルの一つ一つのセルとなるパーツを作ります。Interface Builderの操作は文字で説明するのが大変なので、動画にしました。

データ保存用のstructを作る

新しいSwift fileを作成し、名前をEntry.swiftとし、中身を下のようにします。

1
2
3
4
5
struct Entry {
  var title : String
  var desc : String
  var link : String
}

TableViewController.swiftの修正

次のように修正してください。

  • 9行目追加
  • 13〜18行目追加
  • 23〜27行目修正
  • 29行目追加
  • 38行目以降、修正&追加
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
47
48
49
50
51
52
53
import UIKit
import Alamofire
import SwiftyJSON

class TableViewController: UITableViewController {

  var fetchFrom: String?
  var parent: UIViewController?
  var entries: [Entry] = []

  override func viewDidLoad() {
    super.viewDidLoad()
    tableView.delegate = self
    tableView.dataSource = self
    self.tableView.registerNib(
      UINib(nibName: "FeedTableViewCell", bundle: nil),
      forCellReuseIdentifier: "FeedTableViewCell"
    )

    Alamofire.request(.GET, fetchFrom!).responseJSON { response in
      if let values = response.result.value {
        JSON(values)["responseData"]["feed"]["entries"].forEach {i,value in
          self.entries.append(Entry(
            title: value["title"].string!,
            desc: value["content"].string!,
            link: value["link"].string!
          ))
        }
        self.tableView.reloadData()
      }
    }
  }

  override func didReceiveMemoryWarning() {
      super.didReceiveMemoryWarning()
  }

  override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return self.entries.count
  }

  override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
    return 90
  }

  override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("FeedTableViewCell", forIndexPath: indexPath) as! FeedTableViewCell
    let entry = self.entries[indexPath.row]
    cell.title.text = entry.title
    cell.desc.text = entry.desc
    return cell
  }
}

この状態でRunすると、下のようにTableViewで記事一覧が表示されるようになります。

tableView

この状態のコードは下で見られます。

https://github.com/tarky/RssReaderApp/tree/show-table-view

4. 記事を表示する

記事の一覧から見たい記事をタップすることで、記事の内容を表示するようにします。こんな感じのイメージです。

記事を表示する

App Transport Security(ATS)を無効化する

アプリから外部通信を行う場合、通常ATSを丁寧に設定する必要があります。ですが、本記事ではその説明は趣旨ではないので、通信できるようこの機能自体を無効化します。実際にリリースするアプリでは必ずきちんと設定してください。

info.plistに以下を追加してください。info.plistを右クリックして、Open As → Source Codeをクリックすると編集できます。

1
2
3
4
5
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

追加する場所は、一番下の</dict>のすぐ上に入れるのがわかりやすいです。

TOWebViewControllerの追加

PodfileにTOWebViewControllerを追します。WebViewをよしなに使えるようにするためのものです。

1
2
3
4
5
6
7
8
9
10
source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!
platform :ios, '8.0'

target 'RssReaderApp' do
 pod 'PageMenu'
 pod 'Alamofire'
 pod 'SwiftyJSON'
 pod 'TOWebViewController'
end

pod installを実行します。

DetailViewControllerの作成

記事の内容を表示するDetailViewControllerを作成します。
下のように新しいCocoa Touch Classを作成します。

DetailViewControllerを作る

内容は下のようにしてください。

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
import UIKit
import SwiftyJSON
import TOWebViewController

class DetailViewController: TOWebViewController {

  var webview: UIWebView = UIWebView()
  var entry:Entry?

  override func viewDidLoad() {

    super.viewDidLoad()

    self.webview.frame = self.view.bounds
    self.webview.delegate = self;
    self.view.addSubview(self.webview)

    let url = NSURL(string: self.entry!.link)
    let request = NSURLRequest(URL: url!)

    self.webview.loadRequest(request)

  }
  override func webViewDidStartLoad(webView: UIWebView) {
    UIApplication.sharedApplication().networkActivityIndicatorVisible = true
  }

  override func webViewDidFinishLoad(webView: UIWebView) {
    UIApplication.sharedApplication().networkActivityIndicatorVisible = false
  }
}

TableViewControllerに以下のメソッドを追加してください。

1
2
3
4
5
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    let detailViewController = DetailViewController()
    detailViewController.entry = self.entries[indexPath.row]
    parent!.navigationController!.pushViewController(detailViewController , animated: true)
}

ViewControllerをNavigationControllerに埋め込む

Navigation Barを機能させるため、ViewControllerをNavigationControllerに埋め込見ます。

Main.storyboardを開きます。

下のようにViewControllerの上の三つのアイコンのうち一番左のものをクリックした状態で、Editorメニュー → Embed in → Navigation Controllerをクリックします。

NavigationControllerに埋め込む

そうするとこんな感じで、NavigationControllerとViewControllerが繋がります。

Controllerがつながる

そして、NavigationControllerの上の三つのアイコンのうち一番左のものをクリックした状態で、右側のセクションでTop BarにてOpaque Navigation Barを選択します。

Top Barの変更

ViewController.swiftの修正

viewDidLoad()内のsuper.viewDidLoad()のすぐ下に以下を挿入してください。navigationBarを設定しています。

1
2
3
4
5
6
7
8
9
10
self.title           = "Dev News"
let navBar           = self.navigationController?.navigationBar
navBar!.barTintColor = UIColor.blackColor()
navBar!.shadowImage  = UIImage()
navBar!.tintColor    = UIColor.whiteColor()
navBar!.titleTextAttributes =
  [NSForegroundColorAttributeName: UIColor.grayColor()]
navBar!.setBackgroundImage(
  UIImage(), forBarMetrics: UIBarMetrics.Default
)

pageMenuのデザインにメリハリをつけるため、paramsを変更します。viewDidLoad()内のparamsを設定する部分を下のように変更します。

1
2
3
4
5
6
7
8
9
let params: [CAPSPageMenuOption] = [
  .ScrollMenuBackgroundColor(UIColor.blueColor()),
  .ViewBackgroundColor(UIColor.whiteColor()),
  .SelectionIndicatorColor(UIColor.orangeColor()),
  .MenuItemFont(UIFont(name: "HelveticaNeue", size: 15.0)!),
  .MenuHeight(30.0),
  .MenuItemWidth(90.0),
  .CenterMenuItems(true)
]

この状態でRunすると下のように記事を表示できるようになります。

記事を表示する

ここまでのコードは下から見られます。

https://github.com/tarky/RssReaderApp/tree/show-article02

5. Thumbnail画像を表示する

TableViewのそれぞれのセルにThumbnail画像を表示します。こんな感じのイメージです。

サムネイルを表示する

SDWebImageの追加

PodfileにSDWebImageを追加します。ネットワーク上の画像の取得をよしなにやってくれるライブラリのようです。

1
2
3
4
5
6
7
8
9
10
11
source 'https://github.com/CocoaPods/Specs.git'
use_frameworks!
platform :ios, '8.0'

target 'RssReaderApp' do
 pod 'PageMenu'
 pod 'Alamofire'
 pod 'SwiftyJSON'
 pod 'TOWebViewController'
 pod 'SDWebImage'
end

pod installを実行します。

ThumbnailとActivity Indicatorを追加する

Interface BuilderからThumbnailと、ロード中に表示するActivity Indicatorを追加します。

まずは、FeedTableViewCell.xibを開きます。こちらも文字で説明するのが大変なため、動画で説明します。

FeedTableViewCell.swiftの修正

FeedTableViewCell.swiftを以下のようにします。

  • importの追加
  • let ogpApiの追加
  • var linkの追加
  • func setThumbnailImageView()の追加
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
import UIKit
import Alamofire
import SDWebImage
import SwiftyJSON

class FeedTableViewCell: UITableViewCell {
  @IBOutlet weak var title: UILabel!
  @IBOutlet weak var desc: UILabel!
  @IBOutlet weak var thumbnailView: UIImageView!
  @IBOutlet weak var indicator: UIActivityIndicatorView!

  override func awakeFromNib() {
      super.awakeFromNib()
      // Initialization code
  }

  let ogpApi = "http://api.hitonobetsu.com/ogp/analysis?url=";

  var link: String! {
    didSet {
      Alamofire.request(.GET, ogpApi + link).responseJSON { response in
        if let imageUrl = response.result.value?["image"] as? String {
          self.setThumbnailImageView(NSURL(string: imageUrl))
        }
      }
    }
  }

  func setThumbnailImageView(imageUrl: NSURL!){
    self.thumbnailView.sd_setImageWithURL(imageUrl){
      (image, error, cacheType, url)->Void in
      UIView.animateWithDuration(0.25){
        self.thumbnailView.alpha = 1
        self.indicator.stopAnimating();
      }
    }
  }

  override func setSelected(selected: Bool, animated: Bool) {
      super.setSelected(selected, animated: animated)

      // Configure the view for the selected state
  }
}

最初にRSSから取得した記事一覧の情報には、サムネイル画像についての情報は入っていないので、OGP(Open Graph Protocol)データ取得APIを利用して、サムネイル画像のURLを1記事1記事取得しています。

URLが取れたら、sd_setImageWithURL()で改めてサムネイル画像自体をとりに行っているということです。

そして、サムネイル画像がダウンロードできたら、アニメーションつきで、thumbnailViewのalphaを1にして、indicatorをstopしています。

TableViewController.swiftの修正

cellのlinkに値を代入する処理を加えます。下のコードでいう6行目を追加します。

1
2
3
4
5
6
7
8
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCellWithIdentifier("FeedTableViewCell", forIndexPath: indexPath) as! FeedTableViewCell
  let entry = self.entries[indexPath.row]
  cell.title.text = entry.title
  cell.desc.text = entry.desc
  cell.link = entry.link
  return cell
}

この状態で、Runすると下のようにサムネイルが表示されるようになっているはずです。

サムネイルを表示する

ここまでのコードは下から見ることができます。

https://github.com/tarky/RssReaderApp/tree/show-thumbnail

さいごに

いかがでしたでしょうか。一気に全機能をつけるのではなく、なるべく一つずつ加えていく手順にしてみました。参考になればさいわいです。

また、リアルでもSwiftの勉強会に参加しています。つぎの記事で感想を書いています。横浜に近い方、一緒に勉強しましょう!

第1回 Swift勉強会 in 横浜に参加しました

羊毛や小麦