たろすの技術メモ

Jot Down the Tech

ソフトウェアエンジニアのメモ書き

RSSデータの更新日を取得してTableViewで新しい順に並び替える

※この記事はRSSデータの更新日を取得してTableViewで新しい順に並び替える - Qiitaの記事をエクスポートしたものです。内容が古くなっている可能性があります。

はじめに

 エンジニアブログのまとめアプリを作っている時に、ブログの更新日を取得して順番に並び替える機能を実装しました。その際にハマったところをまとめます。

開発環境

完成形

スクショ8.png

実装

手順は以下です ①各ブログのRSSデータを取得する ②XMLParserで「pubDate」の部分をパースする ③sort(by:)メソッドで並べ替える ④「Sat, 07 Dec 2019 03:17:56 +0000」のような書式で取得されるので「11月7日(土)」のような書式に変換する ⑤TableViewのLabelに表示する

①各ブログのRSSデータを取得する

// ListViewController.swift
import UIKit

class ListViewController1: UITableViewController, XMLParserDelegate {
    
    var parser:XMLParser!
    var items = [Item]()
    var item:Item?
    var currentString = ""
    let formatter = DateFormatter()
    let urls = [
        URL(string: "https://tech.mercari.com/rss"),
        //以下略
    ]

    //①各ブログのRSSデータを取得する
    func startDownload() {
        self.items = []
        for link in urls {
        if let url = link {
            if let parser = XMLParser(contentsOf: url) {
                self.parser = parser
                self.parser.delegate = self
                self.parser.parse()
                }
            }
        }
    }
// Item.swift
import Foundation

class Item {
    var title = ""
    var link = ""
    var pubDate: Date!
    }

 self.items = [ ]で配列の中身を空にします。次にfor in文でurls[ ]のURLを全て指定します。続いてXMLParserのインスタンスを作成しています。引数にはurls[ ]のURLを指定します。  そしてparserのdelegateに「self」を指定し、最後にparse()メソッドを呼び出すことでデータの解析を開始しています。 引用元:絶対に挫折しないiPhoneアプリ開発「超」入門 第7版 p.332

②XMLParserで「pubDate」の部分をパースする,③sort(by:)メソッドで並べ替える

// ListViewController.swift
    func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss ZZZZ"
        //③sort(by:)メソッドで並べ替える
        items.sort(by: { (a, b) -> Bool in
            return a.pubDate > b.pubDate
        })
        switch elementName {
        case "title": self.item?.title = currentString
        case "link": self.item?.link = currentString
        //②XMLParserで「pubDate」の部分をパースする
        case "pubDate": self.item?.pubDate = formatter.date(from: currentString)
        case "item": self.items.append(self.item!)
        default : break
        }
    }
}

②「pubDate」の開始タグが見つかるとその要素をDate型に変換してitemsに追加します。DateFormatter()が使えなくてハマりました。 ③「pubDate」を「en_US_POSIX」の書式のまま並び替えます。sorted(by:)とごっちゃになってハマりました。

④「Sat, 07 Dec 2019 03:17:56 +0000」のような書式で取得されるので「11月7日(土)」のような書式に変換する

// ListViewController.swift
let tempDate = items[indexPath.row].pubDate!
            formatter.locale = Locale(identifier: "ja_JP")
            formatter.dateFormat = "MM月dd日(E)"
let outputDate = formatter.string(from: tempDate)
            cell.detailTextLabel?.text = outputDate

String型で取得した日付情報をDate型に変換し、順番を入れ替えてから今度はString型に変換する部分が個人的にハマったポイントです。

⑤TableViewのLabelに表示する

// ListViewController.swift
cell.detailTextLabel?.text = outputDate

全体のソースコード

// ListViewController.swift
import UIKit

class ListViewController1: UITableViewController, XMLParserDelegate {
    
    var parser:XMLParser!
    var items = [Item]()
    var item:Item?
    var currentString = ""
    let formatter = DateFormatter()
    let urls = [
        URL(string: "https://tech.mercari.com/rss"),
        //以下略
    ]
    
    override func viewDidLoad() {
        //テーブルの高さを自動で調節
        self.tableView.rowHeight = UITableView.automaticDimension
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        startDownload()
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
        cell.textLabel?.text = items[indexPath.row].title
        let tempDate = items[indexPath.row].pubDate!
            formatter.locale = Locale(identifier: "ja_JP")
            formatter.dateFormat = "MM月dd日(E)"
        let outputDate = formatter.string(from: tempDate)
            cell.detailTextLabel?.text = outputDate
        //ラベルの表示行数を無制限にする
        cell.textLabel?.numberOfLines = 0
        return cell
    }

    func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 2
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return items.count
    }
    
    func startDownload() {
        self.items = []
        for link in urls {
        if let url = link {
            if let parser = XMLParser(contentsOf: url) {
                self.parser = parser
                self.parser.delegate = self
                self.parser.parse()
                }
            }
        }
    }
    
    func parser(_ parser: XMLParser, didEndElement elementName: String, namespaceURI: String?, qualifiedName qName: String?) {
        formatter.locale = Locale(identifier: "en_US_POSIX")
        formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss ZZZZ"
        items.sort(by: { (a, b) -> Bool in
            return a.pubDate > b.pubDate
        })
        switch elementName {
        case "title": self.item?.title = currentString
        case "link": self.item?.link = currentString
        case "pubDate": self.item?.pubDate = formatter.date(from: currentString)
        case "item": self.items.append(self.item!)
        default : break
        }
    }
    
    func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String] = [:]) {
        self.currentString = ""
        if elementName == "item" {
            self.item = Item()
        }
    }
    
    func parser(_ parser: XMLParser, foundCharacters string: String) {
        self.currentString += string
    }
    
    func parserDidEndDocument(_ parser: XMLParser) {
        self.tableView.reloadData()
    }

最後に

 他にもっといい方法があるのかもしれませんが、私はこの方法で目的が達成できました。(本題とは関係ありませんが)for in文で配列のRSSデータを取得しているのですが、取得に時間がかかってしまうのでまだまだ改善の余地ありです。

参考