Angular 2 Componentのnode moduleを最小限の構成で作る
モチベーション
Angular2のコンポーネントを共有したい。
具体的には共有したいコンポーネントを@NgModuleで包んでnode moduleにして、いろんなプロジェクトからnpm installして使えるようにしたい。
最低限の構成でこれを実現するnode moduleプロジェクトmydatelibと、mydatelibを利用するプロジェクトconsumerを作った。
構成
root // 作業ディレクトリ ├─mydatelib │ ├─dest │ ├─src │ │ ├─index.ts │ │ ├─mydatelib.module.ts │ │ └─datetime.component.ts │ ├─tsconfig.json │ └─package.json └─ consumer //angular2-seedからゆるく作る。
mydatelib
作業スペースrootを作る。
mkdir root cd root
mydatelibプロジェクトを作る。
mkdir mydatelib cd mydatelib mkdir src
npm initしてAngular2としての最低限のライブラリと、ngcをインストール。
最低限すぎるのでyarnさえ使わない。
npm init -y npm i -S @angular/core rxjs zone.js npm i -D typescript @angular/compiler @angular/compiler-cli
packageとtsのコンパイル設定
package.jsonを編集して、配布ディレクトリを指定。ngcをスクリプトに登録。
"main": "dest/index.js", "files": ["dest"], "scripts": { "ngc": "ngc" },
tsc --initしてtsconfig.jsonを作る。
./node_modules/.bin/tsc --init
tsconfig.jsonを編集。
{ "compilerOptions": { "module": "commonjs", "target": "es5", "noImplicitAny": false, "sourceMap": true, "declaration": true, "outDir": "dest", "experimentalDecorators": true, "moduleResolution": "node", "lib": ["es6", "dom"] } }
コンポーネントとモジュールをかく
時刻表示コンポーネント
datetime.component.ts
import { Component } from '@angular/core'; @Component({ selector: 'datetime', template: '<p>{{date}}</p>' }) export class DateTimeComponent { date: string = new Date().toString(); }
モジュール
mydatelib.module.ts
import { NgModule } from '@angular/core'; import { DateTimeComponent } from './datetime.component'; @NgModule({ declarations: [ DateTimeComponent ], exports: [ DateTimeComponent ] }) export class MyDateLibModule {}
index
index.ts
export * from './mydatelib.module';
ビルドする
npm run ngc
エラーが出なかった完了
mydatelibを利用するconsumer
angular2-seedをベースにrootディレクトリにconsumerプロジェクトを作る。
cd .. // rootに移動 git clone https://github.com/angular/angular2-seed.git consumer cd consumer
angular2-seedデフォルトのnode moduleをインストール
npm install
一旦動かしてみる
npm start
http://localhost:3000 にアクセスするとこんなんが出る。

Angular 2 Seedの下あたりにDateTimeComponentを挿入してみることにする
mydatelibインストール
npm i -S ../mydatelib
ここはgitからインストールするか、プライベートnpmリポジトリからインストールできるように変えたい
DateTimeComponentが表示されるように関連ソースを更新
モジュール
src/app/にあるapp.module.ts
import文を書く。読み込むのはモジュールだけ。
import { MyDateLibModule } from 'mydatelib/dest';
@NgModuleの中にもMyDateLibModuleを書く。
imports: [ BrowserModule, FormsModule, ReactiveFormsModule, HttpModule, RouterModule.forRoot(rootRouterConfig, { useHash: true }), MyDateLibModule // これ! ],
AppComponentのテンプレートを編集
src/appにあるapp.component.html
</h3>と<nav>の間にDateTimeComponentのselectorを書く。
</h3> <datetime></datetime> <nav>
動作確認
npm start

やったね。
国旗の使い方2
これの記事です。12/21日分
前回作った255枚の国旗のPDFをCocoaアプリケーションから使えるようにします。
PDFのロード
適当にiOSアプリケーションプロジェクトを作ります。Single View Applicationで。

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

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

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

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

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

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にするのはだめっぽいので、

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生成してもいいのですが、投稿タイムリミットが迫ってるので普通にコピペします。

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

ViewControllerを書きます。CollectionViewのnumberOfItemsInSectionでCountryKey.length()を返します。
cellForItemAtでCountryKey.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を返すと良しです。

ああ間に合った。
国旗の使い方1
これです
モチベーション
国旗って眺めてるだけで面白くないですか?アルゼンチンの
真ん中のやつとかなかなか良い表情をしてます。

じゃあこれはだーれだ?

はいウルグアイでしたー
じゃあ朴訥な彼は?

え?エクアドルっすよ!
こういう国旗の画像を集めてCocoaとかから使いたい。
収集
国連加盟国だけでも193あるので、一枚にかけるサイズは抑えつつ、国旗に細かく描かれていることを表現してもらいたい。ので、ベクターイメージを集めることにする。WikipediaのFlag_of_国名.svgのファイルを引っ張ってくるんでもいいが、ググったら一発目で

見つけちゃった
これを使うことにします。
CocoaでのSVGの扱い
Cocoa ApplicationでSVGを直に使うことはできません。残念でした。SVGを扱えるようにするライブラリは色々あるのですが、
https://github.com/SVGKit/SVGKit https://github.com/mchoe/SwiftSVG
SVGを使えるようにする程度ならそんなにめんどくさくないので自分で加工します。具体的にはSVGをPDFにしちゃえばいいだけです。PDF変更後のファイルサイズは最悪SVGの2倍くらいになっちゃうのですが、元の国旗のSVGはでかくても数十kbなのであんまり気にしない。
npm install
git cloneでもいいんですが、ここでは後々のためにnpmで管理します。
package.jsonを書いて
{ "dependencies": { "flag-icon-css": "^2.8.0" } }
npm installしましょう
npm install
はい
SVG > PDF
node_modules/flagsの中に1x1と4x3の比率のsvgが転がってます。
国旗の形とかサイズはその国が勝手に規定してるものなんですが、オリンピックとかになると4x3に統一されたりするらしいです。
ネパールとかいう異端児もいますが、
ここでは我慢して長方形の枠の中に収まっていただきたい。
SVGからPDFへの変換は、精度が良かったprawn-svgを使います。rubyです。
package.jsonと同じところにgenerate.rbを作ります。
require "prawn" require "prawn-svg" src = "./node_modules/flag-icon-css/flags/4x3/" dist = "./dist/" Dir.mkdir(dist) unless File.exists?(dist) Dir.entries(src).select { |f| if File.extname(src + f)==".svg" name = File.basename(src + f, ".svg") 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 }
ついでにpackage.jsonにスクリプトも追加して
{
"scripts": {
"gem:install": "gem install prawn-svg",
"pdf": "ruby generate.rb"
},
"dependencies": {
"flag-icon-css": "^2.8.0"
}
}
svgを丸ごとpdfにします。
npm run gem:install npm run pdf
はいできた

ここまでできたらCocoaのAssetを追加してドラッグ&ドロップするだけですが、255ファイルあるんでいちいち追加するのは辛いです。次回は全部の国旗をCocoaからごそっと丸ごと使えるようにします。
Kolodaの使い方
アドベントカレンダー用の投稿
モチベーション
CocoaでTinderみたいなViewを提供するライブラリがある。
これを使いたい。使う。
Carthageの準備
Cocoaライブラリのパッケージマネージャとして、だいたいみんなCocoaPodsかCarthageを使う。 ここではシンプルで軽量なCarthageを使う。
CarthageはHomebrewというパッケージマネージャでインストールできる。 Homebrewが入ってなかったら入れる。入れない理由がない。
$/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
brewが入ったらcarthageを入れる。
$brew install carthage
はいこんにちは
$carthage version
プロジェクトの準備
XcodeでSingle View Applicationプロジェクトを作る。

作ったプロジェクトのルートディレクトリ(*.xcodeprojがあるとこ)にCartfileという名前のファイルを作る。

今回はSwift3+Xcode8.1でiOSターゲットのサンプル作るが、
2016年11月時点でKolodaのmasterブランチはSwift3対応できてないので、Swift3ブランチをCarthageから使う。どんなライブラリを使うか、ライブラリのどのブランチを使うかは、Cartfileで指定できる。
Cartfileの中身はこんなん。
github "Yalantis/Koloda" "swift-3"
書き終わったら、ターゲットをiOSにするオプションをつけてcarthage updateする。
$carthage update --platform iOS
プロジェクトの設定
carthage updateが成功したら、Koloda.frameworkとpop.frameworkがCarthageディレクトリ以下にできてる。

Xcodeを開いて、プロジェクトルートのGeneralタブの一番下Linked Frameworks and Librariesに2つの.frameworkをドロップする。

Build Phasesタブを開いて、左の+ボタン(Add a new build phase)を押し、New Run Script Phaseを選択する。

Run Scriptのコマンドに
/usr/local/bin/carthage copy-frameworks
を書く。さらにInput Filesに
$(SRCROOT)/Carthage/Build/iOS/pop.framework $(SRCROOT)/Carthage/Build/iOS/Koloda.framework
を書く。popがKolodaの前提ライブラリなので、先頭に持ってこないとビルド失敗する。こんな感じになるはず。

なんでこんなスクリプトを設定しなきゃいけないのかというと、App Store のサブミッションのバグの対策らしい。
cmd+bでビルドして、エラーがでなかったらライブラリのロードは完了。
Viewを作る
デフォルトのstoryboardとコントローラをそのまま使う。
Main.storyboardを開く。右下のObject LibraryからViewを選択して、左ツリーのView Controller Scene > View Controller > Viewの下に配置する。
配置したViewをKolodaViewとして扱うために、右上のIdentity InspectorからClassをKolodaViewにする。ModuleをKolodaにする。

配置したKolodaViewをViewの真ん中らへんにレイアウトする。

ControllerとViewを繋げる
Main.storyboardを開きながら、右上の円が二つ重なってるアイコンを押して、ViewController.swiftを表示する。ViewController.swiftにimport Kolodaを書いてKolodaをインポートする。

Main.storyboardのツリーからctrlを押しながらKoloda Viewをクリックして、ViewController.swiftのviewDidLoad()の上らへんにドラッグ&ドロップする。

ドロップしたらNameをkolodaViewにして、Connectを押す。

ViewControllerを書く
KolodaViewには最低限KolodaViewDatasourceを設定する必要がある。Datasourceは表示するカードの情報源となるプロトコル。プロトコルはJavaで言う所のInterfaceとだいたいおなじ。ここではViewControllerをDatasourceにする。
viewDidLoad()はWeb APIでいうところのwindow.onload。ここでkolodaViewのdataSourceをcontroller自身にする。
kolodaNumberOfCardsでカードの枚数を返す。ここでは10枚返すことにする。
koloda:viewForCardAtでカードのViewを返す。ここではコンパイルエラーを取るため、背景がグレーのUIViewを返す。
import UIKit import Koloda class ViewController: UIViewController { @IBOutlet var kolodaView: KolodaView! override func viewDidLoad() { super.viewDidLoad() kolodaView.dataSource = self } } extension ViewController: KolodaViewDataSource { func kolodaNumberOfCards(_ koloda: KolodaView) -> Int { return 10 } func koloda(_ koloda: KolodaView, viewForCardAt index: Int) -> UIView { let view = UIView(frame: koloda.bounds) view.backgroundColor = UIColor.gray return view } }
extension A: BはJavaでいうclass A implements Bだと思っていい。
ここではdataSourceとControllerのライフサイクルは同じだが、dataSourceを別クラスに切り出すような場合は、Controllerより先に勝手にdataSourceのインスタンスが破棄されないように気をつける。インスタンスの破棄はdeinitで追える。
deinit {
print("destroyed")
}
表示されるカードが全部グレーだと寂しいので、ランダムな色の背景を持ったUIViewを返すことにする。UIColorをランダムなFloatのRGBから作れば、ランダムな背景のUIViewが作れる。ランダムなFloatとUIColorを生成するメソッドを作って、koloda:viewForCardAtから呼び出す。
func koloda(_ koloda: KolodaView, viewForCardAt index: Int) -> UIView { let view = UIView(frame: koloda.bounds) view.backgroundColor = randomColor() return view } func randomColor() -> UIColor { return UIColor(colorLiteralRed: randomFloat(), green: randomFloat(), blue: randomFloat(), alpha: 1) } func randomFloat() -> Float { return Float(Float(arc4random()) / Float(UInt32.max)) }
できた

全ソースはGitHubで