Goalist Developers Blog

HRエキスポに行ってきました。

f:id:bbbbbbbbb9:20170817163537j:plain

導入

こんにちは。
四月に新卒で入社して、開発部でエンジニアをしています。バンナイです。

2017/7/28にHRエキスポというものに参加してきました。

www.hr-expo.jp

その目的は

  • 人材業界または人事向けのサービスのチェック。どのようなサービスが存在しているのか知る。

  • 人材業界または人事の抱える問題をシステムで解決することに関する知見を深める。

  • 人材業界または人事の仕事のこれからの行く末を見極める。

です。

何か具体的な目的があるわけではなく、人材業界に関わっているエンジニアとして漠然と知見を深めるということが目的でした。

実際に見たサービス

Google Recruiting with AdWords

Recruiting with AdWordsとは

  • スマホは求職者が最も活用しているツールだが1回あたりの起動時間は1分11秒と短いため、スマホを多用する求職者に対して短時間で効率的にアプローチすることが求められる。
  • 短時間で会社の魅力を伝えるには動画が有効

という二つの前提を踏まえて、Googleが提案するソリューションだそうです。

一言で言うと『ターゲットとする求職者に狙いを定め、行動を喚起するYouTube動画広告』。

Googleの高度なターゲティング技術を使うと、年齢、趣味趣向、アクセス位置、使用しているデバイス等の情報を利用して動画広告を見てもらいたい層に集中的に動画広告を見せることができるという話を聞いて、Googleのターゲティング技術と求人サービスとの相性の良さに気づかされました。

また実際に明光義塾の動画広告を観させてもらったのですが、生徒と先生の関わり合いが丹念に描かれていてとても感情に訴えかけてくるものだなと思いました。

以上の理由からRecruiting with AdWordsはこれからの求人市場においてどんどん影響力を増していくのではないかという可能性を感じました。

ゴーリストは求人媒体のクローリングを行っているのですが、今後動画による求人が活性化してきたら動画のクローリングとかもできると面白いかもなと思いました。どうやればできるのか分からないけれど…

www.google.co.jp

リファラル採用を活性化するクラウドサービス リフカム

リファラル採用 = 企業に所属する社員・アルバイトから友人を紹介・推薦してもらう採用方法

リファラル採用におけるコミュニケーションコストの削減、リファラル採用の社内での定着を狙いとしたサービス。

  • 社員に紹介を依頼する。
  • 社員の紹介の手間を減らす。
  • リファラル採用の効果測定と応募者管理。

ということができるとのことでした。
リファラル採用というとどうしても属人的になりがちというイメージがあったので、リファラル採用をシステムを運用できるのだったら興味深いサービスだなと思いました。

実際に使用画面を見せてもらったが、誰がいつ何人友達を紹介してくれたとかが、視覚的にとても分かりやすかった。

手間やコミュニケーションコストがボトルネックになって今まで出来なかったことをできるようにするというのはシステムが目指すべき一つの形だと思います。

refcome.com

採用HP作成ツール[エンゲージ]

誰でもカンタンに無料で採用HP作成ができるサービス。

実際にこのサービスでつくられたサービスをいくつか見せてもらったがかなり見た目よくリッチな感じでした。

綺麗なホームページが誰でも簡単に作れてしまうということで個人的には衝撃を受けました。 「どうして無料なんですか?」と質問してみました。

「このサービスはエンジャパンという求人媒体も持っている会社が提供しており各企業のホームページがリッチになればそれだけ人と企業の良い出会いがもっと生まれそれはエンジャパンにとってもいいこと」だそうです。
興味深い考え方だと思いました。

簡単にリッチなものが作れるというわけでまさにシステムが解決すべき問題であったと思いました。

en-gage.net

総括

最新のサービスに触れてサービスや業界の動向について学ぶことは重要だと思いました。

ゴーリスト開発部では個々人が皆プロダクトマネージャーになることが求められます。 またゴーリスト開発部では技術は手段であり本来の目的 = プロダクト、提供される価値等、から逸れないことも求められます。

今後も定期的にこのような取り組みをしていきたいです。

angular-google-mapsとカスタムオーバーレイでDOMをマーカーっぽく使う

こんにちは。ゴーリスト開発のイイオです。

先輩エンジニアモリツグさんの投稿で覚えもめでたい、angular-google-mapsでカスタムオーバーレイの続編です。

developers.goalist.co.jp

今回はカスタムオーバーレイでDOMをマーカーっぽく描画する必要に迫られた場合の解決方法を記しておきます。

やったことまとめ

  1. カスタムオーバーレイを描画する
  2. カスタムオーバーレイを緯度経度指定で描画してマーカーっぽく見せる
  3. カスタムオーバーレイにクリックイベントをつける

とりあえずできたものはこんな感じに動かせます。

f:id:y-iio:20170808153347g:plain

マーカーを表示して
ズームインしたらうまいこと位置調整が入って
マーカークリックイベント拾ってアラート表示
マーカーを非表示にする
みたいな流れです。

ここからが解説だ

何は無くともangular-google-mapsを入れます。
npm i --save @agm/core

app.module.tsにインポート文追加します。
APIキーはご自分のをドウゾ。

@NgModule({
  ・・・
  imports: [
    AgmCoreModule.forRoot({
      apiKey: 'YOUR_API_KEY'
    })
  ],
  ・・・
})

コンパイル時に google is not defined で怒られるので型定義も入れておきます。
npm i --save @types/googlemaps

使うところでインポート文を入れます。
import {} from '@types/googlemaps';

angular-google-maps公式のGetting startedを見ながら地図を表示させる。
いや決してめんどくさくなったわけではオボボボボ
CSSで高さ指定しないと表示できないので注意です。

で、先人の知恵を見ながらこんな感じに書いてみた。
任意のタイミングでマーカー表示/非表示したいのでちょっと変えてます。

app.component.ts

import { Component } from '@angular/core';
import {} from '@types/googlemaps';
import { GoogleMap } from '@agm/core/services/google-maps-types';

const cont: object = {
  label: '株式会社ゴーリスト',
  lat: 35.695568,
  lng: 139.771055
};

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss']
})
export class AppComponent {
  public lat: number = 35.695568;
  public lng: number = 139.771055;
  public zoom: number = 15;
  private nativeMap: GoogleMap;
  private overlays: any[] = [];

  public onMapReady(map: GoogleMap): void {
    this.nativeMap = map;
  }

  public toggleShowMarker(): void {
    if (this.overlays.length > 0) {
      this.removeMarker();
    } else {
      this.drawMarker();
    }
  }

  private drawMarker(): void {

    class CustomOverlay extends google.maps.OverlayView {
      map_;
      content_;
      div_;

      constructor(map, content, self) {
        super();

        this.map_ = map;
        this.content_ = content
        this.div_ = null;

        this.setMap(map)

        self.overlays.push(this);
      }
      /**
       * onAdd is called when the map's panes are ready and the overlay has been
       * added to the map.
       */
      onAdd() {
        const div = document.createElement('div');
        div.style.borderStyle = 'none';
        div.style.borderWidth = '0px';
        div.style.position = 'absolute';
        div.innerHTML = '<div class="marker">' + this.content_.label + '</div>';

        this.div_ = div;

        // Add the element to the "overlayLayer" pane.
        const panes = this.getPanes();
        panes.overlayLayer.appendChild(div);

        // Add click event
        panes.overlayMouseTarget.appendChild(div);
        google.maps.event.addDomListener(div, 'click', () => {
          self.onMarkerClicked();
        });
        google.maps.event.trigger(this, 'ready');
      };

      draw() {
        const overlayProjection = this.getProjection();
        const pixel = overlayProjection.fromLatLngToDivPixel(
          new google.maps.LatLng(this.content_.lat, this.content_.lng)
        );

        // Resize the div to fit the indicated dimensions.
        const el = this.div_;
        const content = el.children[0];
        const content_height = content.clientHeight;
        const content_width = content.clientWidth;
        el.style.top = pixel.y - (content_height + 7) + 'px';
        el.style.left = pixel.x - (content_width / 2) + 'px';
      };

      // The onRemove() method will be called automatically from the API if
      // we ever set the overlay's map property to 'null'.
      onRemove() {
        this.div_.parentNode.removeChild(this.div_);
        this.div_ = null;
      };

    }

    const self = this;
    if (cont) {
      new CustomOverlay(this.nativeMap, cont, self);
    }
  }

  private removeMarker(): void {
    this.overlays.forEach((item) => {
      item.setMap(null);
    });
    this.overlays = [];
  }

  private onMarkerClicked(): void {
    alert('marker clicked');
  }
}

onMapReadyでネイティブな Google Mapオブジェクトを変数に特攻ブッコむ。

drawMarkerのインナークラスでカスタムオーバレイを上書き、
newしたカスタムオーバーレイを配列に入れて保持。
(もともと複数マーカー表示していたのでこんなんなっている)

クリックイベントはDOM作ってからgoogle.maps.event.addDomListener()のところで指定。

描画時の位置は OverlayView.getProjection().fromLatLngToDivPixel()で緯度経度から特定。
吹き出しの矢印がちょうど緯度経度の位置に刺さるようにtopとleftのスタイル調整。
地図リサイズ時にdraw()がよびだされるのでその度に表示位置は変わる。

removeMarkerで作ったカスタムオーバーレイを破棄する。
OverlayView.setMap(null);のところ)

みたいなかんじです。

他の部分のソースはこちら

マーカーのCSSはapp.component.scssに書いてもかからなかったのだけど
src/styles.scssのほうでグローバル指定したらいけました。

app.component.html

<agm-map [latitude]="lat" [longitude]="lng" [zoom]="zoom" (mapReady)="onMapReady($event)"></agm-map>
<button (click)="toggleShowMarker()">マーカー表示</button>

app.component.scss

agm-map {
  height: 500px;
  width: 500px;
}

src/styles.scss

/* You can add global styles to this file, and also import other style files */
.marker {
  padding: 5px 6px;
  border-radius: 3px;
  width: auto;
  background: tomato;
  color: #fff;
  opacity: 0.9;
  cursor: pointer;

  &:after {
    top: 100%;
    left: 50%;
    border: solid transparent;
    content: " ";
    height: 0;
    width: 0;
    position: absolute;
    pointer-events: none;
    border-color: rgba(255, 53, 31, 0);
    border-top-color: tomato;
    border-width: 12px 6px 0 6px;
    margin-left: -6px;
  }
}

まとめ

angular-google-mapsの日本語記事なさすぎて嘆きます。
そしてほんとうに
コンポーネントをカスタムオーバーレイに突っ込めるような修正がangular-google-mapsに入ることを期待しています!!!

angular-google-mapsでカスタムオーバーレイする

こんにちわ、 ゴーリスト開発のモリツグです。

AdobeがFlashのサポートを2020年に終了することを発表しましたね!
全国に50人はいるであろう私を含むFlex愛好家はついにこの時が来たかという気持ちだとおもいます。
Angularはそんな悲しみに暮れるFlex愛好家にはピッタリなのでお勧めです。

前回はAngular4でangular-google-mapsを使うという内容を書いたのですが、更にカスタムオーバーレイを行う必要に迫られた場合の解決方法を残しておこうと思います。

はじめに

カスタムオーバーレイとはこんな感じにGoogleMap上に画像などの任意のDOM要素を配置できる機能です。 JavaScriptで使う方法は公式説明に詳しくあるのですが、angular-google-mapsで使うにはどうしたらいいのか。そもそも出来るのか、というのが今回の内容になります。

手順

とりあえず先人の知識を探りますこのリンクがものすごく参考になりました。というかほぼこれで解決します。
前述のリンクは純粋にTypeScriptでカスタムオーバーレイする場合です。
私が調べた限りではangular-google-mapsはカスタムオーバーレイを何とかしてくれる方法を提供してくれていません。
したがって今回はイメージ的にはangular-google-mapsのAgmMapクラスが保持しているであろうネイティブなGoogleMapのインスタンスを引っ張りだし、先人の知識を利用する形になります。(正確にはGoogleMapsAPIWrapperというServiceが保持している)
このような方法をとるのでAngularのComponentをそのまま突っ込むようなことはできず、ネイティブなDOMの追加になります。
AgmMapにはmapReadyというイベントが用意されており、AgmMapの初期化が完了するとネイティブなGoogleMapのインスタンスをイベントの引数としてemitしてくれます。この飛んできたGoogleMapに対して先人の知識を適用すれば完了です。

ソースコードと注意点

plunkerのデモはこちらになります。
なんか緑っぽい拡大縮小されない異質な地図が表示されていると思います。 drawメソッドの中で計算すれば拡大縮小時にオーバーレイされた画像も良い感じに描画されます。
htmlとTypeScriptの関係個所だけを念のため貼っておきます。

<agm-map (省略) (mapReady)="onMapReady($event)">(省略)</agm-map>
  public onMapReady(nativeMap:GoogleMap):void {

    class USGSOverlay extends google.maps.OverlayView  { // user inner class to avoid error of 'google is not defined'
      //export class USGSOverlay extends google.maps.OverlayView { // exportするとここのgoogleがgoogle is not definedにつかまる
      image_;
      map_;
      div_;

      constructor(map) {
        super(); //  we need to call super

        this.image_  = 'https://developers.google.com/maps/documentation/' +
          'javascript/examples/full/images/talkeetna.png';
        this.map_ = map;

        // Define a property to hold the image's div. We'll
        // actually create this div upon receipt of the onAdd()
        // method so we'll leave it null for now.
        this.div_ = null;

        // Explicitly call setMap on this overlay.
        this.setMap(map);
      }
      /**
       * onAdd is called when the map's panes are ready and the overlay has been
       * added to the map.
       */
      onAdd(){
        const div = document.createElement('div');
        div.style.borderStyle = 'none';
        div.style.borderWidth = '0px';
        div.style.position = 'absolute';

        // Create the img element and attach it to the div.
        const img = document.createElement('img');
        img.src = this.image_;
        img.style.width = '100%';
        img.style.height = '100%';
        img.style.position = 'absolute';
        div.appendChild(img);

        this.div_ = div;

        // Add the element to the "overlayLayer" pane.
        const panes = this.getPanes();
        panes.overlayLayer.appendChild(div);
      };

      draw(){ // 今回は緯度経度を位置に変換することはせずいったん直接指定で地図を表示する
        // We use the south-west and north-east
        // coordinates of the overlay to peg it to the correct position and size.
        // To do this, we need to retrieve the projection from the overlay.
        const overlayProjection = this.getProjection();

        // Retrieve the south-west and north-east coordinates of this overlay
        // in LatLngs and convert them to pixel coordinates.
        // We'll use these coordinates to resize the div.
        //  const sw = overlayProjection.fromLatLngToDivPixel(this.bounds_.getSouthWest());
        //  const ne = overlayProjection.fromLatLngToDivPixel(this.bounds_.getNorthEast());
        const sw = {x:100, y:200};
        const ne = {x:200, y:100};


        // Resize the image's div to fit the indicated dimensions.
        const div = this.div_;
        div.style.left = sw.x + 'px';
        div.style.top = ne.y + 'px';
        div.style.width = (ne.x - sw.x) + 'px';
        div.style.height = (sw.y - ne.y) + 'px';
      };

      // The onRemove() method will be called automatically from the API if
      // we ever set the overlay's map property to 'null'.
      onRemove(){
        this.div_.parentNode.removeChild(this.div_);
        this.div_ = null;
      };
    }
    this.overlay = new USGSOverlay(nativeMap);
  }

以下のクラスはonMapReadyメソッドでやっているようにインナークラスにしないと動きません。

class USGSOverlay extends google.maps.OverlayView {
  //省略
}

多分タイミング的な問題だと思うのですが、exportして使おうとすると実行時にgoogle is not definedといわれて怒られます。 色々やってみましたが全然解決できなかったので凄腕の方が解決してくれるのを待つことにします。
また今回はとりあえずカスタムオーバーレイを使うまでを目的としたので緯度経度から描画位置への変換はせず決め打ちです。参考リンクのソースではその辺の変換もやっているようです。

まとめ

angular-google-mapsでカスタムオーバーレイする場合の手順を解説してみました。
ほとんどstackoverflowのコピーじゃないかという意見もありますが、全くもってその通りです。私が紹介したのは「AgmMapのmapReadyがネイティブなGoogleMapのインスタンスを引数にemitされる」という部分だけです。
他力本願でComponentをカスタムオーバーレイに突っ込めるような修正がangular-google-mapsに入ることを期待しています。

バックエンドエンジニアがフロントエンドを書く② 〜storybookをカスタマイズ〜

現プロジェクトでバックエンドを実装しているJPです。
前回の記事の続きで、storybookの基本的な部分をカスタマイズしていきたいと思います。

筆者の経験:CLIツールなどもあまり触ったことがない、フロントエンドの初心者
環境   :macOS Sierra
前回の記事:

developers.goalist.co.jp

今回やることは以下の通りです。

前回の作業のおさらい

storybookの起動

前回作成したプロジェクトにおいて、storybookを起動します。

$ cd my-app
$ yarn run storybook

そして、http://localhost:9009/にアクセスすると、以下のような画面が表示されます。

f:id:j-itoh:20170615200240p:plain

自動生成されたコンポーネント

storybook画面左の「Buttons」を押してみます。

f:id:j-itoh:20170727111832p:plain

「Buttons」というカテゴリーには「with text」と「with some emoji」の2つのコンポーネントが自動で生成されています。

コンポーネントの追加処理をカテゴリーごとに分ける

どういうこと?

作成したコンポーネントをstorybookに表示させるには、そのコンポーネントをどのカテゴリーに表示させるかをstorybookに読み込ませる必要があります。
デフォルトでは表示情報の定義が1つのファイルにまとめて書いてありますが、それをカテゴリーごとに分割したいと思います。

なぜ?

コンポーネントの表示情報の定義の読み込み先を探します。

以下の「my-app/.storybook/config.js」に書いてある模様。

f:id:j-itoh:20170726182259p:plain

my-app/.storybook/config.js

import { configure } from '@storybook/react';

function loadStories() {
  require('../src/stories');
}

configure(loadStories, module);

どうやらloadStories()で読み込んでいる様子。
パスは「my-app/src/stories」フォルダを指しています。
そのフォルダを確認すると、以下のような「my-app/src/stories/index.js」ファイルがありました。

my-app/src/stories/index.js

import React from 'react';

import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { linkTo } from '@storybook/addon-links';

import Button from './Button';
import Welcome from './Welcome';

storiesOf('Welcome', module).add('to Storybook', () => <Welcome showApp={linkTo('Button')} />);

storiesOf('Button', module)
  .add('with text', () => <Button onClick={action('clicked')}>Hello Button</Button>)
  .add('with some emoji', () => <Button onClick={action('clicked')}>😀 😎 👍 💯</Button>);

上記ではstoriesOf()で「Welcome」と「Button」というカテゴリーを追加し、add()でそれぞれのカテゴリーにコンポーネントを追加しています。

ですが、コンポーネントの数が増えたら、以下のようなデメリットが発生します。

  • 「my-app/src/stories/index.js」のコード量が増える
  • コンポーネントに複雑なプロパティーを渡す場合、プロパティーオブジェクトの記述も「my-app/src/stories/index.js」に書かなければならない
  • 「my-app/src/stories」以下にコンポーネントのjsファイルが溢れる

これらの理由により、この追加処理を1つのファイルにまとめて書くのではなく、カテゴリーごとに分けて記述するように変更しようと思います。

手順

フォルダ構成を以下のように変更する

f:id:j-itoh:20170727121614p:plain

src以下のフォルダやファイルの説明は以下の通りです。

  • components:カテゴリーを配置するフォルダ
  • Buttons:ボタンカテゴリーを表すフォルダ
  • Button.js:ボタンのコンポーネント(「my-app/src/stories/Button.js」と同じファイル)
  • HogeButton.js:他のボタンコンポーネント
  • stories.js:ボタンカテゴリーのコンポーネントの追加処理を書くファイル(「my-app/src/stories/index.js」と同じファイル)

「my-app/.storybook/config.js」を編集する

「my-app/.storybook/config.js」はカテゴリーやコンポーネントの追加処理が書いてあるファイルを指定するところでした。
そのため、新しく作成した「components/{カテゴリー}/stories.js」から読み込むように内容を変更します。

import { configure } from "@storybook/react";

const req = require.context("../src/components", true, /stories.js$/); // ①

function loadStories() {
  req.keys().forEach(filename => req(filename)); // ②
}

configure(loadStories, module);

loadStories()の中で対象ファイルをrequire()する必要がありますが、カテゴリーの数だけrequire()するのは大変なので、正規表現で「components/{カテゴリー}」以下の「stories.js」を取得し(①)、ループでrequire()しています(②)。

これで完了です。 これまでと同じようにstorybookにカテゴリーとコンポーネントが表示されていると思います。
(「Welcome」カテゴリーは今回移植していません。)

まとめ

storybook上の表示情報がカテゴリーごとに分けて記述できると、見やすくなるし、複数人での開発もやりやすいと感じました。
今回はここまでしか書けませんでしたが、どんどんstorybookをカスタマイズしていく予定です。

新卒デザイナーのプチ誕生日会

こんちは、渡部です。
突然ですが今日はちょっと社内のことを紹介します。
エンジニアブログだけど気にしないことにします。

f:id:watabe1028:20170720150114j:plain

先日新卒レタスデザイナーの誕生日でした。
その日たまたま一緒にランチをしたメンバーで
帰りにケーキを買ってサプライズすることに。
 
なんとケーキのスポンジが苦手というので選ぶのも一苦労。
優柔不断なサイレントデザイナーにチョイスを任せるも
やっぱり決まらず。。。
 
結局ベーシックなチョイスになりました
(ミルフィーユ、ベイクドチーズ、モンブラン、白桃ゼリー)
ミルフィーユは他のメンバーが食べたかっただけですが。。。

 
打ち合わせを装って呼び出し
ケーキでプチお祝いしました。

f:id:watabe1028:20170720150158j:plain

ランチ後だったので食べきれず
途中見かけたダンサーエンジニアに押し付けおすそ分けしました。
彼も大喜びだったことでしょう。

レタスデザイナーの喜びの日報を見てほっこりしました。

f:id:watabe1028:20170720150231p:plain

他にもゴーリストでは誕生日会もやってます。
こんな感じ↓
f:id:watabe1028:20170720150317j:plain
一番奥の新卒エンジニアがブレていますが
ゴーリスト開発部ではブレ芸が流行って?ます。

今回はもてなす側がケーキを食べたかっただけかもしれません。
こんな感じで自然体で一緒に働く仲間を募集しています。
気になる方はぜひこちらからお問い合わせください。

recruit.goalist.co.jp

Twitterのような動くスプラッシュ画面をふろぐんで作ってみた

こんちは。渡部です。
コメダ珈琲のコーヒーと一緒に出てくる豆が好きです。

Twitterとか海外のアプリでちょいちょい見かけるスプラッシュ画面が動くやつ。
こういうのさりげなく実装できてるとかっこいいなーと思ったんで作ってみました。
こういうの↓
f:id:watabe1028:20170719161417g:plain
引用:twitter / animation / splash screen | UI design | Pinterest | スプラッシュ画面、アニメ、スクリーン

やり方

・LaunchScreenに画像を設定
・LaunchScreenと同じviewを起動時に表示
・viewをアニメーションさせる

LaunchScreenに画像を設定

普通にLaunchScreenにImageViewを追加します。
画像には弊社サービスを牛耳るふろぐんを使用しています。

わかりやすいように画面中央に設定します。
f:id:watabe1028:20170719171044p:plain

LaunchScreenと同じviewを起動時に表示

初期起動時に読み込まれるViewControllerにLaunchScreenと同様のImageViewを追加します。

ここではわかりやすいようにStoryboardを使わずコードで実装します。
viewDidLoad内でこんな感じで

class ViewController: UIViewController {
    
    var imageView: UIImageView!

    override func viewDidLoad() {
        super.viewDidLoad()
                
        //imageView作成
        self.imageView = UIImageView(frame: CGRectMake(0, 0, 200, 200))
        //中央寄せ
        self.imageView.center = self.view.center
        //画像を設定
        self.imageView.image = UIImage(named: "hrog")
        //viewに追加
        self.view.addSubview(self.imageView)
    }

未だにCGRectMakeじゃないとしっくりこないのでCGRectMakeをwrapするメソッドを書いてます。
みんなCGRectMakeなしでどうやっているんでしょうか?

    // CGRectMakeをwrap
    func CGRectMake(_ x: CGFloat, _ y: CGFloat, _ width: CGFloat, _ height: CGFloat) -> CGRect {
        return CGRect(x: x, y: y, width: width, height: height)
    }

viewをアニメーションさせる

アニメーションさせて、ImageViewを消す処理を追加します。
処理はviewDidAppearに書きます。

Twitterでは少しバウンドしてる感じなので
・標準よりちょっと小さくする
・そこから拡大していく
・消える
アニメーションを追加します。

override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        //80%まで縮小させて・・・
        UIView.animate(withDuration: 0.3,
                       delay: 1.0,
                       options: UIViewAnimationOptions.curveEaseOut,
                       animations: { () in
                        self.imageView.transform = CGAffineTransform(scaleX: 0.8, y: 0.8)
        }, completion: { (Bool) in
            
        })
        
        //8倍まで拡大!
        UIView.animate(withDuration: 0.2,
                       delay: 1.3,
                       options: UIViewAnimationOptions.curveEaseOut,
                       animations: { () in
                        self.imageView.transform = CGAffineTransform(scaleX: 8.0, y: 8.0)
                        self.imageView.alpha = 0
        }, completion: { (Bool) in
            //で、アニメーションが終わったらimageViewを消す
            self.imageView.removeFromSuperview()
        })
    }

するとこんな感じになります。

f:id:watabe1028:20170719161647g:plain

お、意外と簡単!
動きがカクカクしてるのはgifのせいです。
うまく作れない・・・

ちなみに・・・

たまにスプラッシュ画面が動かない、画像を更新しても反映されない、なんてことがあります。
そんな時は以下を試してください。

・プロジェクトのクリーン
 「command」+「shift」+「k」でクリーンされます。

・実機の場合はアプリを入れ直す
 一回消して入れ直すだけです。
 時間がたつと反映されるとの噂もあります。

・シュミレータの場合はキャッシュを消す
 Simulatorを選択して、「Reset Content and Settings…」でキャッシュを消します。
 その後ビルドすれば反映されます。
f:id:watabe1028:20170719171221p:plain

まとめ

意外にやってみると結構簡単でした。
アニメーションの実装次第で色々な見せ方が出来て良いかもしれません。

これからもいかに手間を取らずにそれっぽいアプリに見せるテクニックを磨いていきたいと思います。

UX勉強会に行ってみた

来たる7月6日、UXBridgeさんの勉強会に参加したのでメモと感想です。

uxbridge.connpass.com

「BtoB/BtoBtoCサービスにおけるUX勉強会」ということで
仕事内容との親和性高いし行っておいでよ〜ということで

社内の綺麗なお姉さん方と一緒に参加してきました。
ご期待に添えずもうしわけないのですが今回写真は無いです。

f:id:y-iio:20170714195400j:plain

聞いたことの3行まとめと所感

1. UX評価指標とUX改善

  • UXの評価指標は「パフォーマンス」と「使い勝手の満足度」が両輪
  • 使い勝手の満足度はSUS(System Usability Scale)で計測
  • SUSは少人数の回答でも割と正確な数値が出る

自分の持ってるサービスでも最近アンケート取ったばかりだったのですが
回答が少なくてどう参考にしたらいいのか途方にくれていたところだったので
個人的にとってもタイムリーな話題でした。
次回のアンケートではぜひSUSでスコアリングしてみたいです。

スライドはこちら

UX評価指標とUX改善 // Speaker Deck

SUS

ウェブサイトユーザビリティ評価のためのSUS(System Usability Scale)

2. 失敗から学ぶUX

  • プロダクトとしての機能が整った状態で使われないと改善案は出ない
  • ので、設計段階での作り込みはしなくてOK。最低限の不具合を確実に潰そう
  • UXの本番は運用開始後の改善

今日イチ興味深い発表でした。
あと頭の回転の速い人が話すの聞くの楽しいです。

チームメイキングのためにしたこととして雑談が上がっていました。
雑談から入る→愚痴が出る→悪いところがわかる
ということだそうです。ゴーリスト開発部も雑談増加を推進しています。

3. ユーザ規模の変化に応じた最適なUX設計

  • サービスの規模が大きくなればユーザー層が変わる
  • 捨てる判断も大事

あんまり内容メモして無かったので2行まとめになってしまいました。
いやあの名刺管理サービスのUIUXごちゃっとしててあんまり好きじゃな…ゲフンゲフン
自分がミニマル大好きだからというのは大いにあります。

4. 新規サービス立ち上げ時のUX設計

  • ユーザーエクスペリエンスの「ユーザー」について、一番身近なユーザーはプロジェクトのメンバー
  • スタッフのUX向上のためにデザインのコンセプトを見える化。自分の仕事に誇りを持とう
  • 初期ユーザーのUX向上のためにはゴールの共有が重要

スライドの文字がちっっっっちゃい!というのがずっと気になっていました(失礼)
でも司会のおねいさんはスライドのデザインが綺麗ですね〜と喜んでいた…
彼女にとってのUXはよかったのだ…UXって…

まとめ

勉強会って初めて行きました。
おもしろ〜ともこんなもんか〜とも思いました。
もうちょい色々行ってみたい気持ちはあります!とても