Swiftの勉強(2日目)

引き続きSwift。今日は昨日スキップしたシンタックスについて。

昨日のログ
Swiftの勉強(1日目) - ryotah’s blog

Part 2: Swiftのシンタックス

馴染みやすいです。軽く読んだだけである程度把握できる気がします。

とはいえ、タプル、オプショナル、関数の定義、文字列操作、クロージャの書き方、レイジーコピー、プロパティオブザーバーなどなど、触ってみないことには覚えにくいところは多数あるので、実際にコードをごにょごにょ書いて覚えてました。

今回はIBM Swift Sandboxを利用してみました。Playgroundよりさらに気軽に書けて便利です。

Swiftの勉強(1日目)

3日かけて詳細! Swift 3 iPhoneアプリ開発 入門ノート サポートサイトをやることにしました。
Flasherにはお馴染みと思われる、大重さんの本です。

計画

「Part 1: 概要」「Part 2: Swift シンタックス」「Part 3: 実践入門」という3部構成になっているので、1 -> 3(一部) -> 2 の順番で進めていこうと思います。

Part 2を後回しにしたのは、シンタックスを詳しく把握していなくても他のPartは進められそうだったから。
Part 3を一部だけにしたのは、すぐには使わなそうな機能の説明もけっこう含まれていそうだから。

初日やったこと

Part 1: 概要

何も知らないので、まずはここから。

  • Xcodeについて
    • プロジェクト
    • ワークスペース、各ビューの説明
    • 行番号の表示、Code folding
    • 便利ショートカット
  • シュミレーター操作
  • サンプルつくり
    • ライブラリ
    • 関連づけ
  • Playground
    • グラフ

Part 3: 実践入門

各UIの利用方法を把握しつつ、Xcodeの使い方についても習得できます。 Viewの構造や、シーンの扱い方なども。

Ch 11 オートレイアウト

  • 整列のやり方
    • Align
    • Add New Constraints
  • あとから修正できる
    • Resolve Auto Layout …

Ch 12 アシスタントエディタとUI部品の使い方

アシスタントエディタ

  • 使い方(結び付け方)
  • 結び付けを消す場合は右クリor Connections inspector
  • Connection -> Outlet or Action

UILabel

  • Attributesみればだいたいわかる

UIButton

  • 画像、背景画像
  • コード上での生成方法
    • ボタンタイプ
  • イベントリスナーの設定
  • 背景画像のスライス

UIStepper UISwitch UISegmentedControl UISlider

  • (すっとばし)

UITextField

  • 各種設定内容
    • キーボード種類
    • クリアボタンの表示、など
  • デリゲート処理について
    • UITextFieldDelegate
  • キーボードをさげるために
    • Tap Gesture Recognizer
    • 改行で閉じる

UIPickerView

  • (すっとばし)

UIのクラス継承図

  • Attributesインスペクタでは固有の設定が一番上に表示される
  • カスタム設定について

Ch 13 ビューと画像

ビューの作成と表示

画像

  • UIImageViewUIImage
  • UIImageView
    • UIImageView(frame: CGRect(…))
  • UIImage
    • UIImage(named:)
  • 背景画像にパターン
    • view.backgroundColor = UIColor(patternImage: UIImage)

座標と領域

  • center(CGPoint型) frame(CGRect型) bound(CGRect型)
  • viewDidLayoutSubviews
    • viewDidLoadより前に実行
  • frame
    • origin(CGPoint型), size(CGSize型)
    • minX midX maxX width heightなども利用できる(取得のみ)
  • convert

UIStackView

  • HorizontalとVertical
    • 切り替え可能
  • Stackツールボタン
  • コードでの書き方

UITableView

  • UITableViewDataSourceUITableViewDelegate
  • UITableViewDataSource
    • セクションごとの行数を決める
    • セルを作る
    • セクションのタイトルを決める
    • セクションの個数を決める
  • UITableViewDelegate
    • タップしたら内容を表示

UIScrollView

  • 配置しやすくするために一旦縦長のViewにする
  • 実行時
    • scrollViewのframeを設定(画面サイズを利用)
    • scrollView.contentSizeを設定(コンテンツのboundsを利用)
  • キーボードに合わせて、画面をスクロール
    • 詳細はコード確認を (uiScrollView_keyboard.xcodeproj)

Ch 14 シーンの作成と移動

  • (ざっと読み)

Ch 15 - Ch 19

  • アニメーション(ざっと読み)
  • フィンガーアクション(ざっと読み)
  • 図形の描画(ざっと読み)
    • 矩形、角丸、線、弧
  • データの保存(ざっと読み)
  • バイスの機能(すっとばし)

まとめ

いい本です。ありがたいことやで。

AngularJSコンポーネントのユニットテスト

よく忘れるので整理。
以下のようなFooComponentをテストする場合。

/**
 * 初期化されたらステータスをactiveにし、タイトルを描画する
 * タイトル下部に外部から渡されたテキストを描画する
 */ 
class FooController {

  private active: boolean = false;

  /*@ngInject*/
  constructor(private $element) {
  }

  $onInit() {
    this.active = true;
    this.render();
  }

  render() {
    const elm = this.$element.find('h1');
    elm.text('foo');
  }

  doSomething() {
    return 'Do something';
  }
}

const FooComponent: ng.IComponentOptions = {
  template: '<h1></h1><div>{{ $ctrl.bar }}</div>',
  controller: FooController,
  bindings: {
    bar: '<'
  }
};

export default FooComponent;

$componentControllerを利用

describe('FooComponent', () => {

  let controller;

  beforeEach(angular.mock.module('app'));
  beforeEach(inject(($componentController, $rootScope) => {
    const locals = {
      $scope: $rootScope.$new(),

      /**
       * Componentが$elementを利用している場合、localsに追加する必要がある
       * 
       * https://docs.angularjs.org/api/ngMock/service/$componentController
       *   > If you are using $element or $attrs in the controller,
       *   > make sure to provide them as locals.
       */
      $element: angular.element('<div></div>')
    };
    const bindings = {
      bar: 'bar'
    };
    controller = $componentController('foo', locals, bindings);
  }));

  it('should be active', () => {
    expect(controller.active).toBeFalsy();

    // $onInitは直接実行
    controller.$onInit();

    expect(controller.active).toBeTruthy();
  });

  it('doSomethingを実行', () => {
    expect(controller.doSomething).toBeDefined();
    expect(controller.doSomething()).toBe('Do something');
  });
});

コントローラーのテストのみ可能。
https://docs.angularjs.org/guide/component

$compileを利用

describe('FooComponent', () => {

  let element;
  let parentScope;

  beforeEach(angular.mock.module('app'));
  beforeEach(inject(($compile, $rootScope) => {
    parentScope = $rootScope.$new();

    // element = angular.element('<foo bar="bar"></foo>');
    // element = $compile(element)(parentScope);

    // こっちでも問題ない?
    element = $compile('<foo bar="bar"></foo>')(parentScope);

    parentScope.bar = 'bar';
    parentScope.$digest();
  }));

  it('bindingsされたbarがhtmlに反映される', () => {
    expect(element.find('div').text()).toBe('bar');
  });

  it('$onInit -> render -> タイトルが変更される', () => {

    // 実際のComponent lifecycleと同じで、$onInitは自動実行される
    expect(element.find('h1').text()).toBe('foo');
  });

  it('doSomethingを実行', () => {

    // controllerを取得
    const controller = element.controller('foo');

    expect(controller.doSomething).toBeDefined();
    expect(controller.doSomething()).toBe('Do something');
  });
});

実際の挙動に近い形でコンポーネントのテストが可能。
https://docs.angularjs.org/guide/unit-testing

参考

bundlerとCocoaPodsすら知らない

  • bundler
    • gemを管理するツール
    • 自身もgem
    • Gemfileを使う
    • Gemfileはpackage.jsonのようなもの
  • CocoaPods
    • iOSのパッケージを管理するツール
    • これもgem
    • Podfileを使う
    • Podfileはpackage.jsonのようなもの

このようなプロジェクトファイルが既にある場合、

├── Gemfile
├── Gemfile.lock
├── Podfile
├── Podfile.lock
├── README.md
├── foo
├── foo.xcodeproj
└── foo.xcworkspace

以下のように始める。

# bundlerをインストール
gem install bundler

# bundlerを使い、gemをインストール
bundle install --path vendor/bundle

# CocoaPodsを使い、iOSのパッケージをインストール
bundle exec pod install

HTML Element の幅と高さ、座標。

幅と高さ

Determining the dimensions of elements | MDN

補足

https://www.w3.org/TR/CSS22/box.html#box-dimensions f:id:ryotah:20170124143422p:plain

座標

補足

// https://github.com/oneuijs/You-Dont-Need-jQuery

function getOffset (el) {
  const box = el.getBoundingClientRect();
  
  return {
    top: box.top + window.pageYOffset - document.documentElement.clientTop,
    left: box.left + window.pageXOffset - document.documentElement.clientLeft
  };
}

参考

Promiseの配列を順に処理

reduceを使い新しいpromiseを作成していく。

const tasks = [
  () => $q(resolve => doneAsync(resolve)),
  () => $q(resolve => doneAsync(resolve))
];

function doneAsync(resolve) {
  setTimeout(() => resolve(), 1000);
}

tasks.reduce((promise, task) => {
  return promise.then(task);
}, $q.resolve()) // $q.resolve()で最初のPromiseを生成
.then(() => {
  console.log('done');
});

実行結果を保存するrecord処理を追加。

const tasks = [
  (value) => $q(resolve => doneAsync(resolve, value)),
  (value) => $q(resolve => doneAsync(resolve, value))
];

const record = (results, value) => {
  results.push(value);
  return results;
};

// [] に実行結果を保存する
const _record = record.bind(null, []);

tasks.reduce((promise, task) => { 
  return promise.then(task).then(_record)
}, $q.resolve(0))
.then(value => {
  console.log(value);
  // -> [1, 2]
});

function doneAsync(resolve, value) {
  setTimeout(() => resolve(++value), 1000);
}