SwiftとiOSアプリ開発の基本を学ぶのに最適なのは、RSSリーダーを作ることです。というのは、iPhoneアプリによく求められるサーバーサイドとの連携を、サーバーの実装無しに練習できるからです。この記事では、そんなRSSリーダーのシンプルな作り方をご紹介します。
最終的にはこんな感じ
ただ、いきなりこれを作ると思うと大変なので、1機能1機能追加して、ステップバイステップで進めるような記事にしています。
なお本記事はつぎの記事を参考にさせて頂いています。
ただ、より初心者の方が取り組みやすいように、難しい部分やコードの量を減らし、シンプルな手順にしています。
また、以下の環境を前提にしています。
- Xcode Version 7.3 (7D175)
- Swift 2.2
メニューバーを作る
ただ記事の一覧が表示されるだけというのも味気ないので、メニューバーを作ります。こんな感じのイメージです。
まずはプロジェクトを新規作成します。
プロジェクトを作る
Xcodeのメニューから、File>New>Project..をクリックし、Single View Applicationを選択します。
以下の3つは自由でOKです。
- Product name
- Organization Name
- Organization Identifier
PageMenuのインストール
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というのができてるのがわかります。
この中に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…を選択します。
次の画面でCocoa Touch Classを選択します。そして下のようにし、swiftファイルとxibファイルを同時に作成します。
これでRunすればこのようなメニューが表示されるはずです。
ここまでのコードは下のリンクで見ることができます。
https://github.com/tarky/RssReaderApp/tree/added-menu
ローカルでみたい方は
git cloneの
-bにタグの名前を指定すればOKです。
1 |
$ git clone https://github.com/tarky/RssReaderApp.git -b added-menu |
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
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で記事一覧が表示されるようになります。
この状態のコードは下で見られます。
https://github.com/tarky/RssReaderApp/tree/show-table-view
記事を表示する
記事の一覧から見たい記事をタップすることで、記事の内容を表示するようにします。こんな感じのイメージです。
スポンサーリンク
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を作成します。
内容は下のようにしてください。
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) } |
Navigation Barを機能させるため、ViewControllerをNavigationControllerに埋め込見ます。
Main.storyboardを開きます。
下のようにViewControllerの上の三つのアイコンのうち一番左のものをクリックした状態で、Editorメニュー → Embed in → Navigation Controllerをクリックします。
そうするとこんな感じで、NavigationControllerとViewControllerが繋がります。
そして、NavigationControllerの上の三つのアイコンのうち一番左のものをクリックした状態で、右側のセクションでTop Barにて Opaque Navigation 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
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が学べるプログラミングスクールで学んでみてもいいかもしれません。
下の記事がよくまとまっています。