読者です 読者をやめる 読者になる 読者になる

俺、サービス売って家買うんだ

Swift, Vue.js, 統計, GCP / このペースで作ってればいつか2-3億で売れるのがポっと出来るんじゃなかろうか

Swiftで左から右にグラデーションがかかるViewを作る

Swift
f:id:ie-kau:20151120074851p:plain


連日のSwiftメモです。

作りたい物

こんな感じで左から右にグラデーションがかかるボタンを作ります。

f:id:ie-kau:20170323192947p:plain

ソースコード

buttonViewがグラデーションをかける対象のViewとなります。

 //グラデーションの設定
let gradientLayer = CAGradientLayer()
gradientLayer.frame = buttonView.bounds
let color1 = UIColor.hexStr(hexStr: "#a6a1ff", alpha: 1.0).cgColor as CGColor
let color2 = UIColor.hexStr(hexStr: "#f871d0", alpha: 1.0).cgColor as CGColor
gradientLayer.colors = [color1, color2]
gradientLayer.startPoint = CGPoint(x: 0.0, y: 1.0)
gradientLayer.endPoint = CGPoint(x: 1.0, y: 1.0)
buttonView.layer.insertSublayer(gradientLayer,at:0)
buttonView.layer.cornerRadius = 5
buttonView.clipsToBounds = true

これでOK! 16進数のカラーコードを扱うextensionを用意していますが、色はどう指定しても構いません。

Swift で○○秒前みたいにするエクステンション

Swift
f:id:ie-kau:20151120074851p:plain


こんにちは、はやとです。最近はSwiftばっかりやってて楽しいです。 Swift3.0で、SNSでよくある「○○秒前」みたいに表示するエクステンションを書いたので置いておきます。

環境

  • Swift3.0
  • extensionで実装 (Dateクラスを拡張)

コード

Date.swift

import Foundation
import UIKit

extension Date {
    func timeAgoSinceDate(numericDates:Bool) -> String {
        let calendar = NSCalendar.current
        let now = NSDate()
        let earliest = now.earlierDate(self as Date)
        let latest = (earliest == now as Date) ? self : now as Date
        let components = calendar.dateComponents([.minute , .hour , .day , .weekOfYear , .month , .year , .second], from: earliest, to: latest as Date)
        
        if (components.year! >= 2) {
            return "\(components.year!) 年前"
        } else if (components.year! >= 1){
            if (numericDates){
                return "1 年前"
            } else {
                return "去年"
            }
        } else if (components.month! >= 2) {
            return "\(components.month!) ヶ月前"
        } else if (components.month! >= 1){
            if (numericDates){
                return "1 ヶ月前"
            } else {
                return "先月"
            }
        } else if (components.weekOfYear! >= 2) {
            return "\(components.weekOfYear!) 週間前"
        } else if (components.weekOfYear! >= 1){
            if (numericDates){
                return "1 週間前"
            } else {
                return "先週"
            }
        } else if (components.day! >= 2) {
            return "\(components.day!) 日前"
        } else if (components.day! >= 1){
            if (numericDates){
                return "1 日前"
            } else {
                return "昨日"
            }
        } else if (components.hour! >= 2) {
            return "\(components.hour!) 時間前"
        } else if (components.hour! >= 1){
            if (numericDates){
                return "1 時間前"
            } else {
                return "1 時間前"
            }
        } else if (components.minute! >= 2) {
            return "\(components.minute!) 分前"
        } else if (components.minute! >= 1){
            if (numericDates){
                return "1 分前"
            } else {
                return "1 分前"
            }
        } else if (components.second! >= 3) {
            return "\(components.second!) 秒前"
        } else {
            return "ついさっき"
        }
        
    }
    
}

で、ViewControllerとかで

let dateString = Date() //現在時刻
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" //フォーマット合わせる
let posTimeText:String = formatter.date(from: dateString!)!.timeAgoSinceDate(numericDates: true)

ExpressでGoogle Cloud Storageに画像をアップロードする

Node.js GCP
f:id:ie-kau:20170222235628j:plain

やりたきこと

  • Expressで稼働しているWebサービスで画像をGoogle Cloud Storage(以下GCS)にアップロードする
  • サブドメインで画像を閲覧できるようにする

各種バージョン

Node 7.x
Express 4.x

事前準備

以下は完了している

  • ドメインは取得
  • Google Cloud DNSの設定

1. image.[ドメイン].comというサブドメインを設定する

Google Cloud DNSでCNAMEレコードに該当サブドメインを追加します。

f:id:ie-kau:20170222235440p:plain

2. ドメインの所有権の認証をする

ドメイン名を持つバケットを作成するには、GCPにログインしている管理者が対象のドメインの所有者であることを確認しておかなければなりません。

ドメインを持つバケットを作成できるユーザー

ドメインに確認済みのオーナーが複数いる場合、これらのオーナーのみが名前にドメインを使用したバケットを作成することができます。ドメインに確認済のオーナーがいない場合、確認済のウェブサイト オーナーが名前にドメインを使用したバケットを作成することができます。少なくとも 1 人の確認済のオーナーがドメインまたはウェブサイトに含まれていないと、名前にドメインを使用したバケットを作成することはできません。ドメイン所有権はウェブサイト所有権よりも制御レベルが高いため、ドメイン名を持つバケットを作成できるユーザーをサイトで厳しく制御する必要がある場合に役立ちます。

たとえば、「example.com」というサイトの管理を担当する IT スタッフ メンバーが 2 人いるとします。必要な確認(以下を参照)を完了すると、「example.com」、「reports.example.com」、「downloads.example.com」など、ドメイン名を持つバケットを作成できるユーザーはその 2 人のみになります。

確認済のウェブサイトまたはドメイン オーナーは、Search Console を使用してウェブサイトまたはドメイン オーナーを追加することができます。Search Console のダッシュボードで、管理するウェブサイトを検索し、[プロパティの管理] > [ユーザーを追加/削除] を選択します。ドメイン所有者を追加するには、[プロパティ所有者の管理] リンクを選択します。ドメイン所有者は、他のドメイン所有者を追加することができます。

※引用元
Domain-Named Bucket Verification  |  Cloud Storage Documentation  |  Google Cloud Platform

これは引用にあるようにSearch Consoleから対応できます。

3. Google Cloud Platform API に利用する鍵の作成

「IAMと管理」の「サービス アカウント」でGCPのAPIを利用するサービスアカウントと鍵を作成します。

f:id:ie-kau:20170222235332p:plain

ここで作成したjsonの鍵はバケットにファイルをアップロードする時に利用します。

4. GCSのバケットを作る

利用したいサブドメインと同じバケット名でバケットを作ります。 ドメインの所有権が確認されてない場合はここでエラーになります。

f:id:ie-kau:20170222235225p:plain

Expressの設定

利用するモジュール

ソースコード

const express = require('express')
const router = express.Router()
// -- その他色々 -- 
const multer  = require('multer')
const storage = require('@google-cloud/storage')
const gcs = storage({
  projectId: '[projectId]',
  keyFilename: '[/path/to/key.json]' // 事前準備3で作成した鍵
})

const bucket = gcs.bucket('image.[ドメイン].jp')
bucket.acl.default.add({
  entity: "allUsers",  // 外部アクセスを可能にする
  role: storage.acl.READER_ROLE
}, err => {})

const imageUploader = multer({
  storage: multer.memoryStorage(),
  limits: {
    fileSize: 10 * 1024 * 1024  // ファイルサイズ上限
  }
}).single('image')

router.post('/upload', (req, res, next) => {
  imageUploader(req, res, err => {
    if (err) {
      var error = null
      switch(err.code) {
        case 'LIMIT_FILE_SIZE':
          error = {code: 1111, message: 'ファイルが大きすぎます'}
          break
        default:
          error = err
          break
      }
      next(error)
      return
    }

    const mimetype = req.file.mimetype
    const type = [
      { ext: 'gif', mime: 'image/gif' },
      { ext: 'jpg', mime: 'image/jpeg' },
      { ext: 'png', mime: 'image/png' }
    ].find(ext => {
      return ext.mime === mimetype
    })

    if (!type) {
      next({code: 1111, message: "不正なファイル形式です"})
      return
    }

    const blob = bucket.file(`uploads/${req.file.originalname}`)
    const opts = { 
      metadata: { 
        contentType: mimetype,
        cacheControl: "public, max-age=300"
      } 
    }
    const blobStream = blob.createWriteStream(opts)

    blobStream.on('error', err => {
      next(err)
      return
    })

    blobStream.on('finish', () => {
      const publicUrl = `http://${bucket.name}/${blob.name}`
      res.json({ok: 1, imageUrl: publicUrl})
    })

    blobStream.end(req.file.buffer)
  })
})

コードは端折りましたがこれで image.[ドメイン].jp のバケットの uploadsというフォルダにファイルをアップロードすることができました。

課題

  • GCSでサーブした場合のSSL対応はどうすればいいのか?

【Swift】関数にオプショナルのブロックを渡す

Swift
f:id:ie-kau:20151120074851p:plain:w400


括弧が多くて意味不明になったので一旦メモ。

func execute(completion: (() -> Void)?) {
    if let unwraped = completion {
        unwraped()
    }
}

execute { 
    print("hoge") // hoge
}

これだとコンパイルエラー

func execute(completion: () -> Void?) {
    if let unwraped = completion {
        unwraped()
    }
}

error: initializer for conditional binding must have Optional type, not ‘() -> Void?’ if let unwraped = completion {

Moment.jsを使ってタイムゾーンがEtc/GMTの日付と時刻をAsia/Tokyoに変更する

JavaScript Node.js
f:id:ie-kau:20170130023225p:plain:w400


iOSのIn-App Purchase の Auto-Renewing subscription (自動更新型の購読)を利用した開発では購入処理が走った際や、期限切れの際にAppleから得られるレシートの検証をAppleのサーバーにpostして行う必要があります。
当然ながら有効期限を表すexpires_date など時間に関するデータのタイムゾーンは標準時です。Appleの場合Etc/GMT。

現在進めている開発ではDBやサーバーのOSはAisa/Tokyoで動作させているので、データベース追加時や期限切れの確認のときには当然タイムゾーン変更する処理が必要になります。

バックエンドはNodeで作っているので、時間を扱うのに便利なMoment.jsを利用してタイムゾーンを変更していきます。

返ってくるデータの例 : 2017-01-30 16:03:34 Etc/GMT

const datetime = "2017-01-30 16:03:34 Etc/GMT".split(" ")
moment.tz(`${datetime[0]} ${datetime[1]}`, datetime[2]).clone().tz("Asia/Tokyo").format("YYYY-MM-DD HH:mm:ss")
// => 2017-01-31 01:03:34

と言った具合簡単にタイムゾーンの変更ができます。
とはいえ調べる必要があったのでメモメモ。

関係ないけどUTCとGMTの違いがよくわかってなかったので参考。

citizen.jp

  • GMT = 英国のグリニッジ(経度0度)の地方平均時(平均太陽が南中する時を正午とする)のこと
  • UTC = 国際原子時と世界時の誤差が0.9秒以上ならないようにうるう秒で調整をしたのがUTC(協定世界時:Coordinated universal time)

一般的には同義と考えて良い。

Vuexを利用せずに親子関係を持たないコンポーネント同士で通信する

JavaScript Vue.js
f:id:ie-kau:20150818232251p:plain


しばらくぶりにWebのフロントエンドを書いてるので色々復習を込めて。

ReactやVueなどコンポーネント指向で実装できるフレームワークを利用して開発する際に親子関係を持たないコンポーネント同士で通信をしたい時ってありますよね。

とりわけシングルページアプリケーションを作っている際に自分がよく出会うケースとして

  • 共通ヘッダーのタイトルを書き換える
  • ページによって共通ヘッダーに「戻る」ナビゲーションを出したり消したりする
  • バックグラウンドで通信し続けているソケットからデータを受信して画面上にNotificationのバッヂやポップアップを出す

とかがあります。

こういう時にこそアプリケーション全体で一つのstoreを共有してstateを管理する方針のfluxパターンを実現するVuexやReduxを導入したくなります。

ただただ、概念を理解したり動くようになるまでに準備が結構必要だったりと、ちょっとした開発や、アプリケーション全体で親子関係を持たないコンポーネント間通信が数回しか無かったりするとやることが多すぎると感じることが多々あります。

で、EventEmitterを継承したクラスを作ってエイヤッ!ってやることも多かったのですが、実はVue.jsのドキュメントを読み込んでみると

親子間以外の通信

たびたび、互いに親子関係ではない2つのコンポーネントが互いに通信する必要があるかもしれません。簡単なシナリオとして、空の Vue インスタンスを中 心のイベントバスとして使用することができます。

とのことで、空のVueインスタンスを使えば $on, $emitで通信できるイベントバスとして利用できると公式で明言されていました。 (イベントバスと言う言葉は知らなかったのですが。)

なのでどんな形でもいいのでアプリケーション上で一つのイベントバスを作ってそれを経由させてイベントのやり取りをするのが良いでしょう。

例えば、全てのコンポーネントの上にポップアップを出してモーダル状態にするとなるとこんなイメージです。

Vue.js 2.x系
vue-router 2.x系

EventBus.js

import Vue from 'vue'

const EventBus = new Vue()

export default EventBus

App.js プアリケーション全体管理のJS

import EventBus from '../libs/EventBus.js'

export default {
  name: 'App',

  created() {
    EventBus.$on('show-popup', this.showPopup)
    EventBus.$on('hide-popup',  this.hidePopup)
  },

  data() {
    return {
      isPopupShonw: false,
      isOverlayShown: false
    }
  },

  methods: {
    showPopup() {
      this.isPopupShown = true
      this.isOverlayShown = true
    },
    hidePopup() {
      this.isPopupShown = false
      this.isOverlayShown = false
    }
  }
}

コンポーネント

<template>
  <main>
    <header>ヘッダー</header>

    // vue-routerを利用した際のコンテンツ
    <router-view></router-view>

    <div v-show="isPopupShown" class="popup">ポップアップだよ</div>
    <div @click='hidePopup' v-show="isOverlayShown"></div>
  </main>
</template>

てな具合でしょうか。

まとめ

こんな感じでイベントを利用したコンポーネント間通信が簡単にできます。

注意としては、やりすぎるとイベント地獄が待っているのでアプリケーショ大きくなり始めたところで、規約を持たせるためFluxのフレームワーク導入を検討するのが良いでしょう。

iPhoneゲーム、「荻月のゴマビィ」をリリースしました。

Cocos2d-x サービス
f:id:ie-kau:20170111200551p:plain:w400


あけましておめでとうございます! @hazumu です。

前回 Hayato さんがめっちゃ質の高い記事を書いてたので新年一発目から尻込みしちゃいますね。

さてさてさてさて、一昨年の9月に途中まで作って放置していたゲームを去年の12月に作り込んで12末のAppleの審査がお休みの時期前に申請にだして、この年始に(多分1月2日ぐらい)に、なんとか初回リリースにこぎつけました。

今回はその紹介記事です。

ゲームの名は。

ゲームの名前は「荻月のゴマビィ」と言います。
よくわからんタイトルですが古典的な「コイン落とし + キャラ集めゲーム」です。

最初はキャラクターが黄色くて丸くて仙台名物萩の月に似ていたので「萩の月のゴマビィ」にしていたのですが、流石に商標とかヤバそうでよくわからないから適当に字をもじって意味不明な名前にしました。

ゴマビィというキャラクターは、今回デザインを全部やってくれた @kemkem_ さんが小学校時代にノートの片隅に連載していた、その名も「合体」という漫画の主人公を流用したものです。およそ20年の時を経て色と命を吹き込みました。何を合体させたかはご想像におまかせします!

iOS版ダウンロードはこちら。↓↓

荻月のゴマビィ

荻月のゴマビィ

  • Hazumu Jo
  • Juegos
  • Gratis

何はともあれ概要。

西暦2200年。

エネルギー問題に直面していた人類は月のエネルギーを利用することによりついに永久機関を開発することに成功した。

しかし、月のエネルギーを浴び続けた生物はどうしても他者との異存なしに生きられなくなってしまうという特性を持っていた。そう、融合である。

そして、いよいよその特性を悪用しようとする者が出てくる。 その一人が「ゴマ・パロアルト博士」。彼は、いわばマッドサイエンティストである。

2つの生命体に月のエネルギーを浴びせ、お互いの生物の強依存を利用して2つの生物の融合を促進する機械を開発してしまったのだ。。

最初に創りだされた生物の名前は「ゴマビィ」。 ゴマビィは、「みんなが大好きな仙台銘菓のあのお菓子」と「ゴマフアザラシ」の融合生物(キメラ)である。しかも量産型。

しばらくすると博士の研究所は合成生物で溢れかえっていた。 そんななか偶然生まれた知性をもった一匹の「ゴマビィ」。彼は月にある研究所の頭上に青く光る星を見ながら思った。

「生命として生まれてきたのに、こんな自由のない暮らしをしていて良いのだろうか?僕たちは自由を得るために戦うべきではないのだろうか。」

(後に彼は「民衆を率いる自由のゴマビィ」と呼ばれることになるが、それはもう少し後の話しである。)

そして、あの日、星が降った日。それはまるで、 まるで、夢の景色のように、 美しいながめだった日、知性をもったゴマビィはついに立ち上がった。 数多くのゴマビィやその他の合成生物(キメラ)を率いて博士の研究所内で大暴動を起こしたのだ!

「量産型のゴマビィはどうでもいい!少しでも、少しでもレアな合成生物(キメラ)は捕まえなければ。ワシの資産が!」 合成生物(キメラ)たちのの暴動の中で博士は、UFOに乗り出し逃げ出そうとする合成生物を捕まえる作戦を始めたのだった!!

■追伸 ちなみにこのゲーム内においてあなたの立ち位置は博士であることを述べておこう。

コイン落としです!

説明

ダウンロードしたくなる説明をいざ、転載!!

f:id:ie-kau:20170112191756j:plain

f:id:ie-kau:20170112191809j:plain

f:id:ie-kau:20170112191820j:plain

こだわりポイント

1. 思わず抱きしめたくなる愛くるしいキャラクター

f:id:ie-kau:20170111203459p:plain

f:id:ie-kau:20170111203506p:plain

こんな感じのキャラクターがなんと全32種類!!!

新しいキャラクターを捕まえると、そのキャラクターのなきごえせつめいを見ることができます。 時代にそぐったあんなキャラクターやあんな人のパロディーも出現するかも。

全てコンプリートして、ゴマ博士を目指しましょう。

2. 剽軽な楽曲

無料素材です!
が、、素材が素晴らしいのでどんなに落ち込んだときだってイヤフォンをつけてプレイすれば気持ちも剽軽になります!

クレジット

スタッフ

音楽素材提供

展望

正直言って出すことが目的になってたのでやるかわからないのですが、余力があればこの辺をやってみたいですね。

  • 手ゴマ(体力)回復のインターステシャル広告入れてみたいよね
    • その上でadsを打ってリクープするか見てみたい
  • キャラを増やす
  • キャラクターが弾丸っぽいので弾幕シューティングゲームに作り変える

さてと!

今年も、公私にわたってたくさん作るぞ!!リリースするぞ!!
٩( 'ω' )و オラッオラッ