【Swift】WordPress製Webサイトをモバイルアプリ化する 〜データ取得編〜

408 view

入社2ヶ月経ちそうです

こんにちは。アベユウタです。

早いもので、もうすぐ入社して2ヶ月が経とうとしています。

慣れないWeb文化(というよりWordPress)に戸惑いの毎日です。

ところで

弊社CunelWork(クーネルワーク)ではWordPressをベースとしたWebサイト構築が大半です。

CunelWorkで運営しているニュースキュレーションメディア新潟永住計画もWordPressで作成されています。

そこで、新潟永住計画のiOSアプリを勝手に作ってしまおうと企んでいます。(※ちゃんと上司にも確認済みです)

しかし、WordPressで作成されたWebサイトの一部もしくは関連付けたモバイルアプリを開発する、となった場合に「どのようにデータをアプリ側で取得するか」が問題になります。

今回はWordpress依存のデータをSwiftで取得するプロトを試しに作ってみたのでその周辺のお話です。

方法その1: WP製APIをコールする

WordPressの標準API(GET: https://ng-life.jp/wp-json)を利用すると以下のようなデータがJSONで取得できます。

{
"name": "新潟永住計画",
"description": "新時代の直感型ローカルWEBマガジン",
"url": "https://ng-life.jp/mag",
"home": "https://ng-life.jp",
"gmt_offset": 9,
"timezone_string": "Asia/Tokyo",
"namespaces": [
"oembed/1.0",
"akismet/v1",
"contact-form-7/v1",
"jetpack/v4",
"yoast/v1",
"wp/v2"
    ],
"authentication": [],
"routes": {
.....

TopページではWordPressが標準で用意してくれているAPIを使用すればある程度の情報がJSONでは取得できるようですが、下層ページでは取得できないようです。

下層ページでも取得したい場合は、WP REST API を使用して独自でエンドポイントを作成する必要がありそうです。

ただ取得結果を眺めてもそれほど情報量は期待できなさそうです。。。

方法その2: 直接DBを無理矢理参照

この方法はあまりおすすめできません。と言いますかおそらくできません。

ちなみにレンタルサーバーだと、DBサーバー接続の時のポート開放やらでつまづく可能性大なので今回は手を出すのを諦めました。

本来ならば、DBをAPIサーバーに放り込んでクライアント側はAPIしか知らない、みたいな形に再構築できれば理想なのですが。。。

方法その3: RSSでどうにかする

この方法が現状では最も簡単に必要な情報が取得できそうです。

基本的なエンドポイントは
https://ng-life.jp/food/17214/?feed=rdf
のようにURI+?feed=rdfで取得できます。

本題: SwiftでXMLParserを使用して取得する方法

ようやく本題です。

上記のRSSではXML形式で取得されます。

Swiftなど最近はJSONが主流となっており、中々XMLParserに関する記事が見当たらないので紹介します。(今時XML?という感じなので、見当たらないのも仕方ない気がします…)


import UIKit

class ConclusionViewController: UIViewController {

    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var descriptionTextView: UITextView!
  
    private var isTitleTag = false
    private var isDescriptionTag = false
    private let strUrl = "https://ng-life.jp"
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.loadXml()
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    private func loadXml() {
        let url = URL(string: self.strUrl+"/?feed=rdf")
        guard let parser = XMLParser(contentsOf: url!) else { return }
        parser.delegate = self
        parser.parse()
    }
}

// MARK: XMLParserDelegate
extension ConclusionViewController: XMLParserDelegate {
 
    // 解析中に要素の開始タグがあったときに実行されるメソッド
    func parser(_ parser: XMLParser, didStartElement elementName: String, namespaceURI: String?, qualifiedName qName: String?, attributes attributeDict: [String : String]) {
        switch elementName {
        case "title":
            self.isTitleTag = true
        case "description":
            self.isDescriptionTag = true
        default:
            break
        }
    }
    
    // 開始タグと終了タグでくくられたデータがあったときに実行されるメソッド
    func parser(_ parser: XMLParser, foundCharacters string: String) {
        if self.isTitleTag {
            self.titleLabel.text = string
            self.isTitleTag = false
        }
        
        if self.isDescriptionTag {
            self.descriptionTextView.text = string
            self.isDescriptionTag = false
        }
    }
}

基本的にXMLParserにURLを渡してparse()するのみです。

しかし、Delegateメソッドには「タグ」と「要素」を検知することはできるのですが、「対象タグの要素」を検知することができません。

…この辺りAppleのJSON推奨(XMLはなるべくやめて感)が伝わってきます。

なので仕方なくフラグで対象のタグを特定して、要素を検知するDelegateメソッドでフラグのステータスで処理を管理している状態です。。。(他にいい方法あったら教えてください)

ちなみにSwift4ではJSONの扱いが楽になりました

実はfeed jsonというWPプラグインを入れると先ほどのエンドポイントfeed=rdfをfeed=jsonに変更するだけでJSONオブジェクトが取得できるようです。

JSONで取得できた場合SwiftでJSONオブジェクトを整形してあげる必要があります。

Swift3.1まではSwiftyJSONを使用してserialize/deserializeする必要がありましたが、Swift4からJSONDecoderClassが標準化され、init()部分のコードが不要となります。

Swift 3.1


struct AricleEntity {
    let title: String
    let url: String
    let category: String
     
    init(json: JSON) {
        title = json["title"].stringValue
        url = json["url"].stringValue
        category = json["category"].stringValue
    }
}

Swift 4


struct AricleEntity: Codable {
    let title: String
    let url: String
    let category: String
}

let aricleEntity = try! JSONDecoder().decode(AricleEntity.self, from: jsonString.data(using: .utf8)!)

7行目のJSONDecoder().decode()だけで、オブジェクトが生成されますのでインフラ層などのコード量がかなり削減できます。

\ SNSでシェア /

WRITER

アベユウタ

プログラマー&エンジニア アベユウタ

2017年11月入社 制作部 所属

新潟市出身です。
iOS/Androidのモバイルアプリ開発が好きです。
好きな言語はswiftです。

休日もパソコンカチカチしているタイプの人です。

Twitterによく出没します→@yutaabe200

TAGS