国旗の使い方2

これの記事です。12/21日分

www.adventar.org

前回作った255枚の国旗のPDFをCocoaアプリケーションから使えるようにします。

PDFのロード

適当にiOSアプリケーションプロジェクトを作ります。Single View Applicationで。

f:id:chikulla:20161221194720p:plain

使う

全部の.pdfファイルをAssets.xcassetsにドロップします。私のMacBook Airはここで固まります。

f:id:chikulla:20161221195707g:plain

pdfを配置できたので、ImageViewを適当にViewに配置しましょう

f:id:chikulla:20161221200129p:plain

コントローラとImageViewを関連づけます。

f:id:chikulla:20161221200210g:plain

とりあえずセントビンセント及びグレナディーン諸島でも出しておきます。 ViewControllerのviewDidLoad()でImageViewにセントビンセント及びグレナディーン諸島を設定します。 Assetsにあるイメージはコード保管でサムネイル出てきます。素敵ですね。

f:id:chikulla:20161221201111g:plain

ビルドしてSVGからPDFに変えたセントビンセント及びグレナディーン諸島がImageViewでちゃんと表示できるか確認します。 軽いとはいえAssetが255もあるのでビルドにそれなりに時間がかかります。

f:id:chikulla:20161221201352p:plain

はいセントビンセント及びグレナディーン諸島

enumで扱う

大量の画像があるので、全部表示したい!みたいな時はAssetの名前指定ではなく、enumで扱うと楽です。 imageViewにセットする時はAssetの名前をname:パラメータをString指定しなければならないので、enumの要素に対してStringが返ってくると楽です。

Swiftだとこんなふうにかけます

enum CountryKey: String {
    case JP = "Japan"
}
print(CountryKey.JP.rawValue) // "Japan\n"

いちいち文字列割り当てなくても、何も書かなければ要素のキーの文字列が返ってきます。 要素のキーは大文字開始が推奨されてます。

enum CountryKey: String {
    case JP
}
print(CountryKey.JP.rawValue) // "JP\n"

国旗が沢山あるのでこっちの方が楽そうでいいですね。 enumのcase文はPDFを作った時のrubyスクリプトgenerate.rbで作ってもらうことにしましょう。 さらに、swiftのenumは全要素取得(Javaで言う所のvalues)もありません。こんなふうに実装します。

enum CountryKey: String{
    case AD
    //国名全部
    case ZW

    static func fromIndex(at: Int) -> CountryKey  {
        switch at {
        case 0:
            return .AD
        case 1: 
            //全部実装
        default:
            return .ZW
        }
    }

    static func length() -> Int {
        return CountryKey.ZW.hashValue + 1
    }
    
    static func values() -> [CountryKey] {
        var natural: [Int] = []
        for i in 0...CountryKey.length() - 1 {
            natural.append(i)
        }
        var values: [CountryKey] = []
        repeat {
            values.insert(fromIndex(at: natural.popLast()!), at:0)
        } while natural.count > 0
        return values
    }

国旗が255もあるので、fromIndex(at:)の中身もenumの要素のキーcaseと同じようにgenerate.rbに作ってもらうことにします。

ただし、Swiftのenumの要素に-ハイフンは使えません。 また、今生成できているPDFはSVGの元のファイル名に従って小文字の名前を持ってます。enum要素名の推奨は大文字始まりです。 ということで、今のPDFのAssetの名前を直接enumのcaseにするのはだめっぽいので、

f:id:chikulla:20161221203459p:plain

rubyでAssetの名前を大文字、ハイフンをアンダースコアに変えます。 generate.rbをこんな風に変えました。ゲロみたいにネストしてますがAdventCalendarの投稿タイムリミットが迫っているのでご勘弁を

require "prawn"
require "prawn-svg"
src = "./node_modules/flag-icon-css/flags/4x3/"
dist = "./dist/"
swift = "./swift/"

Dir.mkdir(dist) unless File.exists?(dist)
Dir.mkdir(swift) unless File.exists?(swift)

i = 0
open(swift + "enum.swift", "w") { |e|
  open(swift + "switch.swift", "w") { |s|
    Dir.entries(src).select { |f|
      if File.extname(src + f)==".svg"
        name = File.basename(src + f, ".svg").upcase.gsub(/-/,"_")
        e.puts("case " + name)
        s.puts("case " + i.to_s + ":")
        s.puts("    return ." + name) 
        i += 1
        Prawn::Document.generate(dist + name + ".pdf", page_size: [600, 800], page_layout: :landscape) do
          svg IO.read(src + f), position: :center, vposition: :center, width: 800, height: 600
        end
      end
    }
  }
}

綺麗に生成できました。真面目にswiftのenum生成してもいいのですが、投稿タイムリミットが迫ってるので普通にコピペします。

f:id:chikulla:20161221211301p:plain

CollectionViewで見せる

投稿のタイムr ので適当にUICollectionViewをレイアウトします。

f:id:chikulla:20161221222711p:plain

ViewControllerを書きます。CollectionViewのnumberOfItemsInSectionCountryKey.length()を返します。 cellForItemAtCountryKey.fromIndexがいきてきます。

import UIKit

class ViewController: UIViewController {

    @IBOutlet var collectionView: UICollectionView!
    override func viewDidLoad() {
        super.viewDidLoad()
        self.collectionView.dataSource = self
    }
}

extension ViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return CountryKey.length()
    }
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath)
        let imageView = cell.contentView.viewWithTag(1) as! UIImageView
        let image = UIImage(named: CountryKey.fromIndex(at: indexPath.row).rawValue)
        imageView.image = image
        return cell
    }
}

extension ViewController: UICollectionViewDelegateFlowLayout {
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        let width = self.view.frame.width / 2
        let height = width / 4 * 3
        return CGSize(width: width, height: height)
    }
}

collectionViewLayoutで国旗の比率に合わせたCGSizeを返すと良しです。

f:id:chikulla:20161221223744g:plain

ああ間に合った。