バツイチとインケンのエンジニアブログ
プログラムやプログラムじゃないこと

iOS初心者がSwiftでグノシーみたいなタブ型RSSリーダーを作ってみた

2015-05-19
書いた人 : インケン
カテゴリ : Swift | タグ : iOS, Xcode

こんにちわインケンです。

先月からswiftを始めました。Obj-Cもまともに触ったことないし、全くのiOS初心者です。

「はじめてのアプリ」みたいな初心者本はいくつか読んだのですが、もうちょっと突っ込んだ情報が欲しいなーと作りながら思っていたので、同じような境遇にいる初心者の方の参考になれば。

Xcodeのバージョンは6.3.1です。

完成はこんな感じ

rss

ソースはこちら https://github.com/inkenkun/RssReader

ストーリーボードは使わず、カスタムセルのxibとコードのみで作りました。

タブというかメニュー型のカテゴリがあってスワイプすると、そのカテゴリの記事の一覧に切り替わります。

一覧の記事をタップすると右の詳細ページへ。詳細ページはWEBブラウザになっています。

使用するライブラリ

使用したライブラリはこれ

Alamofire

https://github.com/Alamofire/Alamofire

通信部分をいい感じに書きやすくしてくれるライブラリです。

RSSのAPIを叩いて結果を取得する部分を担っています

SwiftyJSON

https://github.com/SwiftyJSON/SwiftyJSON

JSONを簡単に扱うためのライブラリです。

RSSはXMLなのですがJSONの方が汎用性があるので、今回はGoogleのRSSをJSONにしてくれるAPIを通してから取得しています。

PageMenu

https://github.com/uacaps/PageMenu

メニューによってページの切り替えをいい感じにやってくれるライブラリです。

画面上部の「コンピューター」「海外」「地域」のメニュー切り替えを行っています。

HTMLReader

https://github.com/nolanw/HTMLReader

HTMLパーサーです。

一覧でのサムネイルの取得と、タイトルの下の内容文の取得で使っています。

SVProgressHUD

https://github.com/TransitApp/SVProgressHUD

ローディングのくるくるを超簡単に付けられるライブラリです。

TOWebViewController

https://github.com/TimOliver/TOWebViewController

ブラウザライブラリです。

標準のWebViewでもいいかなーと思ったのですが、なんとなく

PullToRefreshSwift

https://github.com/dekatotoro/PullToRefreshSwift

PullToRefreshを簡単に実装できるライブラリです。

 

あと、サムネイル画像がなかった時のNoPhotoアイコンはicon8のを使用しています。

Picture

1.プロジェクトを作る

まずはプロジェクトを作ります。

news1

今回は記事一覧のtableviewはコードで書くので、Single View Applicationで作ります。

news2

RssReaderなのにnewsってつけてしまいました、、、LanguageはもちろんSwift

で、作成されたらライブラリを入れるためプロジェクトを一旦閉じます。

2.ライブラリを入れる

ライブラリは Cocoa Pods ってので管理します。NodeJSのnpmみたいなもんですね。

Cocoa Podsの入れ方などはこの辺を参考に。

Pods入れたら、コンソールからプロジェクトのディレクトリで

$ pod init

をやるとPodfileが作られます。

Podfileの中身を以下のように変更します。

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

target 'news' do
 pod 'Alamofire'
 pod 'SwiftyJSON'
 pod 'SVProgressHUD'
 pod 'TOWebViewController'
 pod 'PageMenu'
 pod "HTMLReader"
end

target 'newsTests' do

end

そして、

$ pod install

をすると、ライブラリがインストールされます。

ライブラリがインストールされると、news.xcworkspace ってファイルも作成されるので、news.xcodeproj の代わりにnews.xcworkspace を開いて開発していくことになります。

news.xcworkspace を開いて一旦ビルド(command + b)してみましょう。

pagemenuのCAPSPageMenu.swiftがエラーを大量に吐き出しました。執筆時点ではCocoaPodsに登録されているpagemenuのライブラリのバージョンが低いため、Xcode6.3.1だとエラーになります。

そこで、pagemenuのgitからCAPSPageMenu.swiftのソースをまるまるコピーしてきて上書きします。

これでエラーはなくなりました。

CocoaPodsに対応していないライブラリを入れる

残念ながらこれ書いてる時点では、PullToRefreshSwiftがCocoaPodsに対応していないので、これだけは手動で入れる必要があります。

と言ってもファイルをドラッグ&ドロップするだけです。

news3

news4

Copy Item if neededにして、必要であればgroupでまとめちゃいましょう

Objective-Cのライブラリをswiftで使えるようにする

今回使ったライブラリの中で、SVProgressHUDとTOWebViewControllerの2つがObj-Cで書かれているので、そのままでは使えずブリッジヘッダファイルを用意しなければなりません。

手動で作ることもできるみたいですが、Obj-Cファイルを作成すると「ブリッジングヘッダファイル作る?」と聞かれて勝手に作ってくれるのでこれを利用します

news5news6

このファイルは消すので、適当な名前つけときます。

Nextを押すと以下のようにbridging heder作る?って聞かれます

news7

Yesを押すと、自動的に news-Bridging-Header.h というファイルが作られ、

news8

Build Setting にも自動的に追加してくれます。

news9

ただ、Podsのパスは自動的に追加してくれないので、自分で追加します。

Build Settings の SearchPaths の User Header Search Pathsに以下の設定をします。

news_add

今作ったObj-Cのダミーファイルは削除しちゃいましょう。

 

news-Bridging-Header.h に以下を追加します。

#import "SVProgressHUD/SVProgressHUD.h"
#import "TOWebViewController/TOWebViewController.h"

ついでにno photoの画像もimages.xcassetsにドラッグ&ドロップして追加しておきます

news11

3.カスタムセルを作る

ライブラリが整ったところで早速コードと行きたいところですが、まずは記事一覧の一つ一つのセルのパーツを作ります。

この記事一覧の中身はコードで書くより画面で配置したほうがわかりやすいですしね

新規で Cocoa Touch Classを作ります。

news12 news13

Subclass of を UITableViewCell にして Also create XIB file にチェック入れます

これで CustomCell.swiftとCustomCell.xib ファイルが作られるので、CustomCell.xibを開いてパーツを配置していきます。

news_img2

パーツを配置する前に、Show the Attributes InspectorのIdentifierを「Cell」にします。

news_cell

セル全体の高さは140としておきます。

news15

パーツは今回はUIImageViewとUILabelを使います

UIImageは以下のように設定します。

news_imgnews16

 

Imageには登録したno photoの画像を指定します。

Clip Subviewをチェックしないと画像がはみ出ます。

AutoLayoutのPinで右上のように設定します。画像のサイズは100 x 100に。

タイトルを表示するラベルも配置して設定していきます。

news17 news18

コンテンツはこんな感じ

news19 news20

パーツとコードを紐付ける

パーツを配置したらそれをコードと紐付けます。

news21

マウスでびーって、こんな感じに。

news22

これでカスタムセルの完成です。

4.必要なswiftファイルを作成

必要なライブラリ、パーツが揃ったので、コードを書いていくためのファイルを作成します。

まずは記事一覧ページの FeedTableViewController

news23

Cocoa Touch Class で Subclass of : に UITableViewController を指定します。

次に一覧をタップした先の詳細ページの DetailViewController

news24

Cocoa Touch Class で Subclass of : に UIViewController を指定します。

次にJSONのパースやスクレイピングなどの機能を別ファイルにするための parseFeed.swift (なんでこれだけ小文字始まりにしたんだろ、、)

news25

Swift Fileで作ります。

5.パース関数を作る

parseFeed.swiftに、RSSのJSONをパースする関数と、URL先の画像と文章を取得する関数を作りました。

https://github.com/inkenkun/RssReader/blob/master/news/parseFeed.swift

/*
    RSSのJSONをパースする
*/
func parse (url: String, completion: (([JSON]?, NSError?) -> Void)){

    var url = NSURL(string: url)

    Alamofire.request(.GET, url!, parameters: nil, encoding: .JSON)

        .responseJSON { (request, response, data, error) in

            let json = JSON(data!)
            let entries = json["responseData"]["feed"]["entries"].array

            completion(entries, error)
    }
}
/*
    URL先の文章と画像を取得
*/
func getContents (url: String, completion: ((AnyObject, NSError?) -> Void)){

    var url = NSURL(string: url)
    var ret : Dictionary<String, String!> = [:]

    Alamofire.request(.GET, url!, parameters: nil)

        .responseString { (request, response, data, error) in

            var content = ""
            let html = HTMLDocument(string: data)

            if let ogTags = html.nodesMatchingSelector("meta[property=\"og:description\"]") {
                for tag in ogTags {
                    content = (tag.attributes?["content"] as? String)!
                }
            }

            var image = ""
            if let imgTags = html.nodesMatchingSelector("img") {
                for img in imgTags {
                    if(img.attributes?["data-src"] != nil){
                       image = (img.attributes?["data-src"] as? String)!
                    }
                }
            }

           ret = [ "content": content , "image" : image ]
           completion(ret, error)
    }
}

あとから言われて気づいたけど、これクラスにする必要がなかったみたい。

6.メインのViewControllerでPageMenuを設定する

最初から作られている ViewController.swift にはPageMenuライブラリの設定をしていきます。

https://github.com/inkenkun/RssReader/blob/master/news/ViewController.swift

PageMenuはいくつかのビューコントローラーを配列にして渡してやると、勝手にタブっぽくメニュー化してくれます。

var feedArray: [ Dictionary<String, String!> ] =
    [
        [
           "link" : "http://ajax.googleapis.com/ajax/services/feed/load?v=1.0&q=http://rss.dailynews.yahoo.co.jp/fc/computer/rss.xml&num=10" ,
            "title" : "コンピュータ"
        ],
        [
            "link" : "http://ajax.googleapis.com/ajax/services/feed/load?v=1.0&q=http://rss.dailynews.yahoo.co.jp/fc/world/rss.xml&num=10" ,
            "title" : "海外"
        ],
        [
            "link" : "http://ajax.googleapis.com/ajax/services/feed/load?v=1.0&q=http://rss.dailynews.yahoo.co.jp/fc/local/rss.xml&num=10" ,
            "title" : "地域"
        ]
    ]

for feed in feedArray {

    var feedController = FeedTableViewController()
    feedController.link = feed["link"]!
    feedController.parent = self
    feedController.title = feed["title"]!
    controllerArray.append(feedController)

}

feedArrayにURLとタイトルを設定してます。URLはRSSのURLをgoogleのAPIに通したものを設定してます。これでRSSをJSONで取得できます。

その下のforで、URLとタイトルをこの後いじる TableViewController に渡しています。

あとはPageMenuの設定と、メニューを表示させる位置をナビゲーションバーの高さを考慮して設定してます。

7.記事の一覧ページ

FeedTableViewController.swift をいじっていきましょう。

https://github.com/inkenkun/RssReader/blob/master/news/FeedTableViewController.swift

TableViewControllerなので、よくあるTableViewControllerの記法に則って記載していきます

本とかには、以下のように記載されていることが多いと思うのですが、

class FeedTableViewController: UITableViewController, UITableViewDataSource, UITableViewDelegate {

   ....

これを今回はextensionを使って書いています。

class FeedTableViewController: UITableViewController {
    ...
}

extension FeedTableViewController : UITableViewDataSource {
    ...
}

extension FeedTableViewController : UITableViewDelegate {
    ...
}

内容は一緒なのですが、extension使って書くことによって、可読性が上がっていいらしいです。

override func viewDidLoad() {
    super.viewDidLoad()
    SVProgressHUD.show()

    var nib:UINib = UINib(nibName: "CustomCell", bundle: nil)
    self.tableView.registerNib(nib, forCellReuseIdentifier: "Cell")    

    parse.parse(self.link, completion: {(data,error) in

        self.entries = data!
        self.tableView.reloadData()
        SVProgressHUD.dismiss()
    })

    self.tableView.addPullToRefresh({ [weak self] in

        self?.tableView.reloadData()
        self?.tableView.stopPullToRefresh()
    })
}

SVProgressHUD.show() でローディングのクルクルを出して、SVProgressHUD.dismiss() でクルクルを止めてます

self.tableView.addPullToRefresh で PullToRefreshの設定をしてます。

あとはカスタムセルの指定をしてます。

extension FeedTableViewController : UITableViewDataSource {

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

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

        let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! CustomCell

        cell.title.text = self.entries[indexPath.row]["title"].string

        var contents = ""
        var image = ""
        cell.contents.text = ""
        cell.img.image = UIImage(named:"Picture")!

        parse.getContents(self.entries[indexPath.row]["link"].string!, completion: { (data, error) in

            contents = data["content"] as! String
            cell.contents.text = contents

            image = data["image"] as! String

            if(image != ""){

                self.dispatch_async_global {
                    let url = NSURL(string: image)
                    var err: NSError?;
                    var imageData :NSData = NSData(contentsOfURL: url!, options: NSDataReadingOptions.DataReadingMappedIfSafe, error: &err)!

                    self.dispatch_async_main {
                        cell.img.image = UIImage(data:imageData)!
                        cell.layoutSubviews()
                    }
                }

            }else{
                cell.img.image = UIImage(named:"Picture")!
            }
        })

        return cell

    }

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

    func dispatch_async_main(block: () -> ()) {
        dispatch_async(dispatch_get_main_queue(), block)
    }

    func dispatch_async_global(block: () -> ()) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), block)
    }
}

ここでは取得してきたJSONのデータをカスタムセルにセットしています。

画像のサムネイルを別スレッドで非同期で取得してきています。マルチスレッドはこの辺を参考にしました。

override func tableView(tableView: UITableView, heightForRowAtIndexPath … の部分はセルの高さですね。

extension FeedTableViewController : UITableViewDelegate {

    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {

        let detailViewController = DetailViewController()
        detailViewController.entry = self.entries[indexPath.row].dictionary!
        parent.navigationController!.pushViewController(detailViewController , animated: true)

    }

}

セルをタップした先の詳細画面の設定です。

ナビゲーションコントローラを使って画面遷移してます。

8.記事の詳細ページ、WebView

DetailViewController.swift では、WebViewの設定をしています。

https://github.com/inkenkun/RssReader/blob/master/news/DetailViewController.swift

TOWebViewController の使い方が最初良くわからなかったのですが、どうやらこんな感じで継承させれば良さそうです。

class DetailViewController: TOWebViewController {

 9.ナビゲーションコントローラーの設定

このままだと一覧から詳細に遷移できないので、AppDelegate.swiftでナビゲーションコントローラーの設定をします。

https://github.com/inkenkun/RssReader/blob/master/news/AppDelegate.swift

追加したのは以下の部分

var myNavigationController: UINavigationController?

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

    myNavigationController = UINavigationController(rootViewController: ViewController())
    self.window = UIWindow(frame: UIScreen.mainScreen().bounds)
    self.window?.rootViewController = myNavigationController
    self.window?.makeKeyAndVisible()

    return true

}

10.ビルド

以上で完成です。

初めてなので型でハマりまくったり、ライブラリの使い方がわからなかったりといろいろありましたが、なんとか動くものが出来ました。

もし、こういう書き方のがいいよとか、これ便利だよとかあったら教えて下さい。

 

【追記】2015-5-26

「Objective-Cのライブラリをswiftで使えるようにする」の部分を一部修正しました

 

この記事の動画もあるよ

 

このエントリーをはてなブックマークに追加
Tweet

← あなたのPythonを爆速にする7つの方法
続・あなたのPythonを爆速にする7つの方法 →

 

最近書いた記事

  • Ryzen7 3800XT でmini ITXオープンフレームPCを作る
  • Pythonで機械学習入門 競馬予測
  • HP ENVY 15 クリエイターモデルレビューとRAID0解除
  • JRA-VAN データラボを使って、競馬データを収集する
  • Surface Pro 3 にubuntu18.04を入れる

カテゴリー

  • Android
  • Apache Flink
  • API
  • AWS
  • bazel
  • BigQuery
  • Cassandra
  • Docker
  • Druid
  • Elasticsearch
  • Git
  • Golang
  • gradle
  • HDFS
  • JavaScript
  • jvm
  • Linux
  • MongoDB
  • MySQL
  • Nginx
  • Nodejs
  • PaaS
  • PHP
  • Python
  • RabbitMQ
  • Raspberry Pi
  • React Native
  • Redis
  • Riak
  • rust
  • scala
  • Scheme
  • SEO
  • solr
  • Spark
  • spray
  • Sublime Text
  • Swift
  • Tableau
  • Unity
  • WebIDE
  • Wordpress
  • Youtube
  • ひとこと
  • カンファレンス
  • スケジューラ
  • マイクロマウス
  • 広告
  • 技術じゃないやつ
  • 株
  • 機械学習
  • 競馬
  • 自作キーボード
  • 自然言語処理

アーカイブ

  • 2021年4月
  • 2021年2月
  • 2021年1月
  • 2020年3月
  • 2020年2月
  • 2020年1月
  • 2019年10月
  • 2019年9月
  • 2019年8月
  • 2019年7月
  • 2019年6月
  • 2019年5月
  • 2019年4月
  • 2019年2月
  • 2019年1月
  • 2018年12月
  • 2018年11月
  • 2018年9月
  • 2018年5月
  • 2018年3月
  • 2018年2月
  • 2017年9月
  • 2017年8月
  • 2017年6月
  • 2017年4月
  • 2017年3月
  • 2017年1月
  • 2016年10月
  • 2016年9月
  • 2016年8月
  • 2016年6月
  • 2016年5月
  • 2016年4月
  • 2016年3月
  • 2016年2月
  • 2016年1月
  • 2015年12月
  • 2015年11月
  • 2015年10月
  • 2015年9月
  • 2015年8月
  • 2015年6月
  • 2015年5月
  • 2015年2月
  • 2015年1月
  • 2014年12月
  • 2014年11月
  • 2014年9月
  • 2014年6月
  • 2014年5月
  • 2014年3月
  • 2014年2月
  • 2014年1月
  • 2013年12月
  • 2013年11月
  • 2013年10月
  • 2013年9月
  • 2013年8月

書いた人

  • バツイチちゃん
  • インケンくん

このブログについて

エンジニアとしての考え方が間逆な2人がしょーもないこと書いてます。

バツイチ

アイコン

IT業界で働くエンジニアです。名前の通りバツイチです。
理論や抽象的概念が好きだけど人に説明するのが下手。

インケン

アイコン

バツイチちゃんと同じ業界で働いています。
理論とか開発手法とかは正直どうでもよくて、
生活する上で役に立つことに使いたい

Copyright 2025 バツイチとインケンのエンジニアブログ