今日のう〜のん3

f:id:project-unknown:20180531202718p:plain

ふかふかのチュンチュンに挟まれて温もり200%。
幸せなお昼寝のとろけるう〜のんでした。

デザイン担当のぽぽたです。
ちょっと聞いてください、使い始めたペイントツールのCLIP STUDIOがとてもすごいんです。
今回描いた絵の葉っぱも雲も筆でスッと撫でただけでこのクオリティなんです!
感動メーターが振り切れました、楽しすぎてやみつきになりそうです。

written by ぽぽた

う〜のん語録

うまいらる : おいしいもの、おやつ、食べること


その他のデザイン・イラスト関連の記事

その他のう〜のんに関する記事は、以下のページから見れますよ!

www.project-unknown.jp

う〜のんがこれまで登場した作品

ぱたぱたう〜のん

f:id:project-unknown:20180510010406p:plain:w300

www.project-unknown.jp

written by ぽぽた

Blender はじめの一歩 vol 1 - オブジェクトの選択・削除と球体をつくる

はじめに

今後のアプリ開発に向けて3D技術の勉強をぼちぼちと。
デザイン担当のぽぽたです。

無料の3DCGソフト、Blenderを一から始めてみようと思います。
文字通り一からですので、少しでもこの勉強が、これから始めてみようという方の
手引きになれたらなあという気持ちで。
そして追い抜いて、私を導いてください!

使用環境はMacで操作しています。

オブジェクトを画面上で移動させる

次の画像はBlender起動時の全体画面です。

f:id:project-unknown:20180523132156p:plain

わあもう難しい。
今すぐに思考を停止してBlenderを終了してPCをたたんで布団に潜り込みたいですね。

...でもそうも言ってられないので私の心が折れないようにゆっくり覚えていきます。

f:id:project-unknown:20180523132737p:plain

中心に鎮座しているのが3D空間の主、オブジェクトですね。
そして3Dカーソル、3D空間内をクリックした場所に現れます。

これらをああやったりこうやったりしていくのでしょう。
まだ何もわかっていません。これから少しずつ覚えていくんです!

f:id:project-unknown:20180523133249p:plain

オブジェクトの中心から生えている3本の矢印は各座標を示していると同時に、
矢印の先をクリックしつつ引っ張ると、その座標の軸に沿って動かす事ができます。

球体を作る

初めての3D...ここはやっぱりう〜のんを作ってみたいですよね!
とりあえずデフォルトの四角形は端っこに追いやって、球体を作ってみます。

f:id:project-unknown:20180523133858p:plain

画面左上に、縦に並んでいるタブを作成に切り替えます。
するとUV球というボタンがありますので押してみましょう。

う〜のんの基盤になりそうな球体ができました!

オブジェクトは3Dカーソルが示していた場所に現れるようです。
せっかくなので、中心に来てもらいましょう。

f:id:project-unknown:20180523134556p:plain

shift+Sからカーソル→原点を選ぶと、3Dカーソルが原点に移動します。

f:id:project-unknown:20180523135218p:plain

四角形に代わり、球体が中心にきました。

オブジェクトを削除する

端っこから覗いている四角形が邪魔なので消してしまいましょう。

f:id:project-unknown:20180523140608p:plain

削除したい四角形の上で右クリックすることによって四角形が選択されます。
選択されたら、キーボードの「x」を押すと削除の確認画面が出るので、削除を決定します。
これで...

f:id:project-unknown:20180523141120p:plain

画面上に球体だけになりました!
とりあえずここまでできた自分を褒めて次に繋げようね...よくやった!
こんな感じで、少しずつ進んでいきたいと思います。

最後の最後に、保存はこちら。

f:id:project-unknown:20180523143136p:plain

written by ぽぽた

走る - GIFアニメ

f:id:project-unknown:20180521183654g:plain

はじめに

今回は人のアニメーションに挑戦してみました。
デザイン担当のぽぽたです。

描き始める前のイメージは抜群の疾走感だったのに、どことなく感じが怒りのノシノシ感に。
「こんなはずでは」というのは絵描きあるあるなので仕方ないです。...ですよね...?
でももったいないので性懲りもなく解説していきます。

解説

次の画像は全体の動作です。

f:id:project-unknown:20180521191843g:plain f:id:project-unknown:20180521183715p:plain

今回は8枚になりました、再生時間は全て0.1秒です。

走る動画を延々リピート再生していて気づいたのですが、
意外と頭の位置は激しく上下しないようですね。
手と足の位置関係なども勉強できました。

せっかくなので右から左へ走り抜けるGIFアニメも作っていたのですが、
上手く表現できませんでした...。
うーん修行してまた出直してきます!

written by ぽぽた

リズミカル - う〜のんGIFアニメ

f:id:project-unknown:20180518123057g:plain

はじめに

最近は、1年前に買ってサボっていた温めていたCLIP STUDIOに引っ越し中の
デザイン担当のぽぽたです。

CLIP STUDIOのサポート機能はとてもすごいですね、本当に万能すぎて...私いらないのでは…。
なんだか悔しいので、練習のつもりでCLIP STUDIOでリズミカルな動きをするう〜のんを作ってみました。

動きの解説

今回作ったGIFアニメは、あまり位置が動かないので、
どう作ったのかが分かりやすいように遅めのGIFアニメも合わせて作りました。

f:id:project-unknown:20180519011124g:plain

それぞれの再生時間は次のようにしています。

  • 4〜5枚目と9〜10枚目
    • 0.2秒
  • 残りの画像
    • 0.1秒

なんだかGIFアニメを作っていると、0.1秒の差って意外と大きいんだなと気づかされました。
1秒の1/10なのに侮れない…。

う〜のんに限りませんが、いつか音楽と合わせてアニメのオープニングやエンディング
のように動画を作るのが夢のひとつです^^

written by ぽぽた


その他のデザイン・イラスト関連の記事

その他のう〜のんに関する記事は、以下のページから見れますよ!

www.project-unknown.jp

う〜のんがこれまで登場した作品

ぱたぱたう〜のん

f:id:project-unknown:20180510010406p:plain:w300

www.project-unknown.jp

UIViewからはみ出したViewを見れなくする方法

はじめに

UIViewは、初期設定だと画面からViewがはみ出してもそのまま表示されてしまいます。

例えば以下のように高さ230pxのUITableViewCellがあるとして、

f:id:project-unknown:20180516230227p:plain:w400

UITableViewで表示する際に、最初隠したい為に、44pxとした場合、以下のように崩れてしまいます。

f:id:project-unknown:20180516230328p:plain:w400

今回はその解決法です。

解決方法 clipsToBounds を使う

今回の事例では、clipsToBoundsを使うと簡単に解決出来ます。
clipsToBoundsと言えば、UIImageViewでimageがViewの外に出ないようにする時によく使われますが、UIViewに対しても適用されます。

今回は、UITableViewCellのUIViewの範囲を出てほしくないので、Cell本体にこの設定を入れます。

class LogDetailDateTableViewCell: UITableViewCell {
    override func awakeFromNib() {
        super.awakeFromNib()
        self.clipsToBounds = true
    }
}

これでUITableViewCellの描画範囲を超えた描画は行われません。

f:id:project-unknown:20180516230908p:plain:w400

必要な時に、今回の例で言うとDatePickerを載せたCellを足すと言うのもありますが、今回の例では、日付を管理するLabelとDatePickerを同一Cellとして暑かったほうがリソース管理の意味では良いので、高さ改変に耐えれる方法のご紹介でした。

UINavigationControllerに複数ボタンを並べる方法

はじめに

アプリによっては、ナビゲーションの部分にボタンを複数設置しているものもあります。

提供中のKeyHolder もトップ画面のナビゲーションに複数配置してます。

f:id:project-unknown:20180515221532p:plain:w300

この記事では、このやり方について紹介します。

Storyboard上、Xib上で配置

UINavigation上に、Bar Button Itemを複数配置するだけで、実現可能です。

f:id:project-unknown:20180515221137p:plain

(昔はこれ出来なかった気がするのですが、いつのまにか出来るようになってました。記憶違いでしたら申し訳ありません。)

後は、他のパーツ同様にOutlet接続すればコード上からも操作できます。

コード上で配置する

コード上で配置するには、以下のコードの様に、配列にNavigationItemを入れて、登録する方法です。

func setupNavigation() {
    let button1: UIBarButtonItem = UIBarButtonItem.init(
        image: <画像>,
        style: UIBarButtonItemStyle.plain,
        target: self,
        action: #selector(button1Proc))
        
    let button2 = UIBarButtonItem.init(
        image: <画像>,
        style: UIBarButtonItemStyle.plain,
        target: self,
        action: #selector(button2Proc))
    categoryButton.tintColor = tintColor
        
    self.navigationItem.rightBarButtonItems = [button1, button2]
    }

最後に

この記事はStoryboardでも出来るようになってるすげー!で感動したのが原因で書いてます。 どちらも一長一短ですが、私的にはコードがスッキリするというのもそうですが、Storyboardが提供しているのならそちらに任せてしまって、コードは必要な時のみに書く方が責任範囲の分割が分かりやすいので、Storyboardを利用していきそうです。

Quickを使ってSwiftコードのユニットテストを行う - Carthageからの利用

はじめに

iOSのユニットテストを行うのに、標準ですとXCTestがありますが、
最近Quickと呼ばれるテストフレームワークがよく使われ出しました。
XCTestも非常に強力なテストフレームワークですが、初見であったり、他の人が記載したテストコードは中々読みづらかったりします。

QuickはXCTestと比べて見かけ上自然なので、後でメンテがやりやすいです。 実際に記事本文にSampleを載せていますので、是非体感してみてください。

今日はそんなQuickの触り部分を紹介します。

導入

CarthageからFrameworkを導入

Cartfileに以下を入力

github "Quick/Quick"
github "Quick/Nimble"

Carthageのビルド

carthage update --platform iOS

f:id:project-unknown:20180410232224p:plain

同じくBuildPhaseの画面上部の「+」でNew Copy File Phaseを選択。

f:id:project-unknown:20180410232256p:plain

Frameworksを選択して、「Nible.framework」と「Quick.framework」を選択する

f:id:project-unknown:20180410232335p:plain

ProjectファイルのDefine Moduleを設定する

Puroject設定の Build SettingsのDefine ModuleをYESにします。
(図では、今回のQuickの為に設定するものなので、Debugのみにしています)

f:id:project-unknown:20180411210636p:plain

テストコードを書く

今回は、UtilitySpec.swiftというファイルをTestターゲットに追加しました。

f:id:project-unknown:20180411221840p:plain

次に、実際にSampleのQuickテストを書いていきます。

import Quick
import Nimble

@testable import <プロジェクト名>

class UtilitySpec: QuickSpec {
    override func spec() {
        describe("データのInsertテスト") {
            beforeSuite {
                // 全てのテストが始まる前に実行する処理
            }
            beforeEach {
                // テスト(it)が始まる前に毎回実行する処理 
            }
            afterEach {
                // テスト(it)が終わった後に毎回実行する処理 
            }
            afterSuite {
                // 全てのテストが終わった後に実行する処理
            }
            context("空のDBに新規Insert") {
                it("正常系") {
                    expect(<処理等>).to(equal(<比較したい値>))
                }
            }
        }
    }
}

<プロジェクト名>や<処理等>と記載された箇所は適切な物を埋め込んでください。
では、実際に上述のコードのそれぞれの意味を紹介します。

QuickSpec

このコードはQuickの基本形で、クラスはQuickSpecを継承して利用しspec()メソッドの中にテストケースを記載します。

describe, context, it

  • descrribe("")
    • 何のテストを行うのか?(クラスやメソッドの何をテストするのか等)を記載します。
  • context("")
    • どの条件でテストを行うのか?を記載します。
  • it("")
    • 結果はどうなるのが正しいのか?を記載します。

※describe, context, itは省略してもOKです。

beforeSuite, beforeEach, afterEach, afterSuite

  • beforeSuite
    • 全てのテストが始まる前に行う処理を記載
  • beforeEach
    • それぞれのテストが始まる前(itが始まる前)に行う処理を記載
  • afterEach
    • それぞれのテストが終わった後(itの後)に行う処理を記載
  • afterSuite
    • 全てのテストが終わった後に処理を記載

expect

expect(<処理等>).to(equal(<比較したい値>))

expectの引数にテスト対象の処理(メソッド呼び出し)や、変数を記載し、
それの結果がtoの引数の中のものになるか?と言う意味になります。
この場合は、equal(<比較したい値>)としていますので、比較したい値と同じかを見ています。

例えば、具体的に、calc()メソッドを呼び出し、その戻り値が2だった場合の書き方です。

expect(calc()).to(equal(2))

テストの実行

テストを実行します。
実行するには、 ⌘ + U で実行するか、以下のようにクラスの先頭のマークをクリックします。

f:id:project-unknown:20180512012305p:plain

以下は上記のテストケースの実行結果です。

Test Case '-[<プロジェクト名>.UtilitySpec データのInsertテスト__空のDBに新規Insert__正常系]' started.
Test Case '-[<プロジェクト名>.UtilitySpec データのInsertテスト__空のDBに新規Insert__正常系]' passed (0.042 seconds).

最後に

今開発中のアプリが仕上げ段階に入り、ユニットテストを本格的に書き始めたので、Quickでテストコードを記載したのですが、やはり書いた後のコードが見やすくて良いですね。
XCTestで慣れている場合は、最初書き方の独特さに躓くかもしれませんが、実際にコードを書いて管理しだすと、非常にメンテがし易いのでおすすめです。

written by ゆう@あんのうん

エンドレスジャンピング - う〜のんGIFアニメ

f:id:project-unknown:20180511192206g:plain

気づいたら描いていました...。
デザイン担当のぽぽたです。

少ない枚数をコピー・リピートしただけの単純なものなんですが、これだけでも午前を丸々潰すくらいには時間がかかったので...GIF職人さんは本当にすごいですね。
自分の手でキャラクターが動くというのは感動しますね。

f:id:project-unknown:20180511194909p:plain

全体の動作。
これを左右にコピーしただけ...。
1画像の滞在時間は0.1秒。
0.2秒にすると動きが少なすぎてカタカタしてしまいました。

プロジェクトメンバーとの話題で、う〜のんを主人公にしたゲームアプリなんかでショートアニメを入れたいよね〜などの妄想も出てくるので...うーん勉強、頑張ろうと思います!

written by ぽぽた


その他のデザイン・イラスト関連の記事

その他のう〜のんに関する記事は、以下のページから見れますよ!

www.project-unknown.jp

う〜のんがこれまで登場した作品

ぱたぱたう〜のん

f:id:project-unknown:20180510010406p:plain:w300

www.project-unknown.jp

今日のう〜のん2

f:id:project-unknown:20180510115014p:plain

水たまり、どう楽しもう?
王道のチャプチャプもいいけど、水たまり浴も捨てがたい。

お日様を浴びてキラキラの大海原(水たまり)を
真剣に楽しむう〜のんでした。

デザイン担当のぽぽたです。
何を考えているのか何も考えていないのか、よくわからないUnknown...。
Project.Unknownのデザインを担当させてもらっていますが、
いつでも笑顔のう〜のんを描くと、いつでも前向きになれます^^

f:id:project-unknown:20180510123134j:plain

written by ぽぽた

う〜のん語録

うまいらる : おいしいもの、おやつ、食べること


その他のデザイン・イラスト関連の記事

その他のう〜のんに関する記事は、以下のページから見れますよ!

www.project-unknown.jp

う〜のんがこれまで登場した作品

ぱたぱたう〜のん

f:id:project-unknown:20180510010406p:plain:w300

www.project-unknown.jp

written by ぽぽた

今日のう〜のん1

f:id:project-unknown:20180509224536p:plain

落としたアメちゃんはもううまいらるできない...
でも雨に溶かされていくのも悲しい...

地面から溶かされてゆくアメちゃんにそっと傘をさしつつ
哀愁に包まれるう〜のんでした。

デザイン担当のぽぽたです。
のんびりと、Project.Unknownマスコットキャラクターである、う〜のんのイラストなどを描いていこうかなと思います!
マイペースでちょっと変わり者のう〜のん共々、どうぞよろしくお願い致します^^

written by ぽぽた

う〜のん語録

うまいらる : おいしいもの、おやつ、食べること


その他のデザイン・イラスト関連の記事

その他のう〜のんに関する記事は、以下のページから見れますよ!

www.project-unknown.jp

う〜のんがこれまで登場した作品

ぱたぱたう〜のん

f:id:project-unknown:20180510010406p:plain:w300

www.project-unknown.jp

KeyHolder Version 2.5.2 パフォーマンスを向上させました

いつもKeyHolderをご利用戴き、ありがとうございます。

前回のアップデートで、一部パフォーマンス向上の施策を打ったのですが、今回新たにパフォーマンスを向上させる施策を打ちました。

具体的には、パスワードの数が多く登録している時に、アプリを立ち上げた際に若干固まる(重い)状態となっているのが改善しています。

もし、他にこの不具合を直して欲しい、こんな機能をつけて欲しい等ありましたら、

  • AppStoreのレビュー
  • このブログ
  • Twitter
  • project.unknown.cs@gmail.com

にご連絡いただけますと幸いです。
全てにお答えすることは難しいですが、少しでも皆様の要望を実現していきます。

引き続き、KeyHolderのご愛用のほど宜しくお願い致します!

Swift 構造体の使い方 - 基本定義, イニシャライザ, メソッドを扱う

はじめに

構造体とは値型のデータを構造的に持つものです。
特にSwiftの構造体は、その中でメソッドを作成出来たりと、クラスと何が違うねん!と思いますが、

構造体はクラスと違って代入や関数呼び出しの際、データの実態がコピーされ、新しいインスタンスが渡されるのが大きな特徴です。
つまり、構造体のデータの実態は、常にプログラム内のどこか1箇所から参照されているだけとなります。

この特性上、冒頭でかいた通り、構造体はデータという考え方で設計すると考えやすいです。

逆に、複数の箇所から同じ実体を参照したい場合はクラスを用いると良いでしょう。

今回は、構造体の使い方について取り上げます。

構造体を定義する

以下は構造体の定義の例です。

/// プレイヤーデータ
struct Player {
    let name: String  // 名前
    var hp: Int       // ヒットポイント
    var mp: Int       // マジックポイント
    var atk: Int      // 攻撃力
}

上記のように構造体は「struct」を使って表現します。
また、上記例で言う「name」などはプロパティと呼ばれ、変数「var」の場合は変更可能ですし、「let」の場合は一度値がセットされたら変更不可です。
ここは、通常の変数と定数と同じ扱いですね。

構造体をインスタンス化する

上記で記載した構造体を実際にインスタンス化して活用する例です。

/// プレイヤーデータ
struct Player {
    let name: String
    var hp: Int
    var mp: Int
    var atk: Int
}

let hero = Player(name: "勇者", hp: 100, mp: 20, atk: 30)
let priest = Player(name: "僧侶", hp: 60, mp: 50, atk: 10)

print(hero.name) // 「勇者」が出力
print(priest.name) // 「僧侶」が出力

インスタンス化は上述の通り

定数 or 変数 = 構造体名(イニシャライザで指定する引数)

で指定します。
ここで注意が必要なのは、先程構造体のプロパティで定数「let」が指定されていたら、1度セットした値は書き換えられなく、変数「var」が指定されていたら、後で書き換えることが可能とかきましたが、

インスタンス化するときに、定数で構造体を宣言すると、例え構造体のプロパティが変数であっても外部から書き換えはできません。

/// プレイヤーデータ
struct Player {
    let name: String
    var hp: Int
    var mp: Int
    var atk: Int
}

let hero = Player(name: "勇者", hp: 100, mp: 20, atk: 30)
let priest = Player(name: "僧侶", hp: 60, mp: 50, atk: 10)

hero.atk = 100 // ここでエラー

書き換えが発生する場合は、変数でインスタンス化をしておきましょう。

var hero = Player(name: "勇者", hp: 100, mp: 20, atk: 30)
hero.ark = 100 // これは問題なし

構造体の初期化 (イニシャライザ)

インスタンス生成のタイミングで値を全てセットする

構造体をインスタンス化するで記載した例だと、イニシャライザである「init」がありません。

この場合、構造体をインスタンス化する際に構造体のプロパティ名に対して値を渡してセットする事ができます。
例で言うと、

let hero = Player(name: "勇者", hp: 100, mp: 20, atk: 30)

上記の「勇者」「100」「20」「30」の部分がそうです。

構造体に予め値をセットしておく

構造体のプロパティに予め値をセットすることも可能で、以下がその例です。

struct Monk {
    let name: String = "モンク"
    var hp: Int  = 200
    var mp: Int  = 0
    var atk: Int  = 40
}

let monk = Monk()
print(monk.name)  // モンクが出力

データの初期値が定まっている場合は、このように初めから値をセットしておくと便利ですね。
また上述しましたが構造体のプロパティは、変数で定義しているものは後から変更が可能なので、必要に応じて書き換えれます。

monk.hp = 100
print(monk.hp) // 100が出力

構造体のプロパティに予め値をセットしていても、インスタンス化のタイミングでプロパティに値を別にセットすることも可能です。

struct Monk {
    let name: String = "モンク"
    var hp: Int  = 200
    var mp: Int  = 0
    var atk: Int  = 40
}

let monk = Monk(hp: 100, mp: 20, atk: 30)
print(monk.hp)  // 100が出力

上記例では、nameは定数で宣言しているので、変数部分を設定しています。

イニシャライザの定義 (init)

これまでは、予め値をセットするや、インスタンス化のタイミングで全項目の値をセットする形でしたが、
initを使うことで柔軟に初期値設定ができます。

以下の例では、initでキャラクターの設定を行っていますが、hp, mp, atkはランダムな値を加算するようにしています。
(ゲームでよくあるキャラクリエイトのイメージ

struct Player {
    let name: String
    var hp: Int
    var mp: Int
    var atk: Int
    
    init() {
        name = "勇者"
        hp = 100 + Int(arc4random_uniform(10))
        mp = 30  + Int(arc4random_uniform(10))
        atk = 30 + Int(arc4random_uniform(10))
    }
}

このように「init」を用いる事で、初期化のタイミングで色々とやれることが増えます。

また、initで他のメソッドと同じように値を受け付けることも可能です。

struct Player {
    let name: String
    var hp: Int
    var mp: Int
    var atk: Int
    
    init(name: String) {
        self.name = name  // selfは不要だが、引数と混合するのでつけてます
        hp = 100 + Int(arc4random_uniform(10))
        mp = 30  + Int(arc4random_uniform(10))
        atk = 30 + Int(arc4random_uniform(10))
    }
}

var hero = Player(name: "勇者")

ただ注意が必要なのは、「init」を用いると構造体をインスタンス化するの例に出した様に、

let hero = Player(name: "勇者", hp: 100, mp: 20, atk: 30)

この書き方をしたい場合は、自分でinitを用意しないといけません。
なので、イニシャライザのオーバーロードを行って、全てのプロパティの値を受け付けるようにすれば、これまでの書き方ができます。
以下はイニシャライザのオーバーロードを行った例です。

struct Player {
    let name: String
    var hp: Int
    var mp: Int
    var atk: Int
    
    init(name: String) {
        self.name = name  // selfは不要だが、引数と混合するのでつけてます
        hp = 100 + Int(arc4random_uniform(10))
        mp = 30  + Int(arc4random_uniform(10))
        atk = 30 + Int(arc4random_uniform(10))
    }

    init(name: String, hp: Int, mp: Int, atk: Int) {
        self.name = name
        self.hp  = hp
        self.mp = mp
        self.atk = atk
    }
}

var hero = Player(name: "勇者")
var priest = Player(name: "僧侶", hp: 80, mp: 60, atk: 30)

またイニシャライザの処理の書き方にはルールがあり、すべてのプロパティの初期設定が完了するまで、構造体内のメソッドを使うことはできません。
(クラスと同様のルールですね。)

なので、以下はエラーになります。

struct Player {
    let name: String
    var hp: Int
    var mp: Int
    var atk: Int
    
    init(name: String) {
        self.name = name 
        hp = 100 + Int(arc4random_uniform(10))
        mp = 30  + Int(arc4random_uniform(10))
        calAtk() // ここでエラー
        atk = 30 + Int(arc4random_uniform(10))
    }

    init(name: String, hp: Int, mp: Int, atk: Int) {
        self.name = name
        self.hp  = hp
        self.mp = mp
        self.atk = atk
    }

    /// 実際の攻撃力. 攻撃する際にある程度の乱数を発生させてダメージを算出する
    func calAtk() -> Int {
        return self.atk + Int(arc4random_uniform(10))
    }
}

var hero = Player(name: "勇者")
var priest = Player(name: "僧侶", hp: 80, mp: 60, atk: 30)

上記は、init(name:) でatkの設定が終わっていないのに、calAtk()を呼び出そうとしているのでエラーとなります。
この場合は、以下のように、calAtk()を全ての設定が終わった後の場所に移すだけでエラーは消えます。

struct Player {
    let name: String
    var hp: Int
    var mp: Int
    var atk: Int
    
    init(name: String) {
        self.name = name 
        hp = 100 + Int(arc4random_uniform(10))
        mp = 30  + Int(arc4random_uniform(10))
        atk = 30 + Int(arc4random_uniform(10))
        calAtk()
    }

    init(name: String, hp: Int, mp: Int, atk: Int) {
        self.name = name
        self.hp  = hp
        self.mp = mp
        self.atk = atk
    }

    /// 実際の攻撃力. 攻撃する際にある程度の乱数を発生させてダメージを算出する
    func calAtk() -> Int {
        return self.atk + Int(arc4random_uniform(10))
    }
}

var hero = Player(name: "勇者")
var priest = Player(name: "僧侶", hp: 80, mp: 60, atk: 30)

構造体でメソッドを使う

すでにこれまでの例で登場してきていますが、構造体にメソッドを定義することも可能です。

struct Player {
    let name: String
    var hp: Int
    var mp: Int
    var atk: Int
    
    init(name: String) {
        self.name = name 
        hp = 100 + Int(arc4random_uniform(10))
        mp = 30  + Int(arc4random_uniform(10))
        atk = 30 + Int(arc4random_uniform(10))
        calAtk()
    }

    init(name: String, hp: Int, mp: Int, atk: Int) {
        self.name = name
        self.hp  = hp
        self.mp = mp
        self.atk = atk
    }

    /// ここがメソッド
    /// 実際の攻撃力. 攻撃する際にある程度の乱数を発生させてダメージを算出する
    func calAtk() -> Int {
        return self.atk + Int(arc4random_uniform(10))
    }
}

上記のcalAtk()が構造体内のメソッドです。
ここで注意が必要なのは、構造体のメソッドでは基本はプロパティの値を変更できません。
なので以下のようにかくとエラーが発生します。

struct Player {
    let name: String
    var hp: Int
    var mp: Int
    var atk: Int
    
    init(name: String) {
        self.name = name 
        hp = 100 + Int(arc4random_uniform(10))
        mp = 30  + Int(arc4random_uniform(10))
        atk = 30 + Int(arc4random_uniform(10))
        calAtk()
    }

    init(name: String, hp: Int, mp: Int, atk: Int) {
        self.name = name
        self.hp  = hp
        self.mp = mp
        self.atk = atk
    }

    /// ここがメソッド
    /// 実際の攻撃力. 攻撃する際にある程度の乱数を発生させてダメージを算出する
    func calAtk() -> Int {
        return self.atk + Int(arc4random_uniform(10))
    }

    /// ダメージを受けた時の処理
    func damage(num: Int) {
        self.hp -= num   // ここでエラー
    }
}

上記では、self.hp -= num の所でエラーが発生します。
これは大本の構造体の考え方が、データを格納するものだと言う所からきているのかもしれませんね。
ただし、swiftではメソッドに「mutating」のキーワードを付与することで、構造体のプロパティを変更することができます。

struct Player {
    let name: String
    var hp: Int
    var mp: Int
    var atk: Int
    
    init(name: String) {
        self.name = name 
        hp = 100 + Int(arc4random_uniform(10))
        mp = 30  + Int(arc4random_uniform(10))
        atk = 30 + Int(arc4random_uniform(10))
    }

    init(name: String, hp: Int, mp: Int, atk: Int) {
        self.name = name
        self.hp  = hp
        self.mp = mp
        self.atk = atk
    }

    /// ここがメソッド
    /// 実際の攻撃力. 攻撃する際にある程度の乱数を発生させてダメージを算出する
    func calAtk() -> Int {
        return self.atk + Int(arc4random_uniform(10))
    }

    /// ダメージを受けた時の処理
    mutating func damage(num: Int) {
        self.hp -= num   // ここでエラー
    }
}

構造体でGetter / Setterを使う

構造体でもGetter / Setterを使うことができます。

Getter / Setterを使って、これまでのPlayer構造体に歩数管理の「walk」プロパティを追加します。

struct Player {
    let name: String
    var hp: Int
    var mp: Int
    var atk: Int
    var walk: Int {
        get {
            return self.walk
        }
        set {
            walk += newValue
        }
    }
    
    init(name: String) {
        self.name = name
        hp = 100 + Int(arc4random_uniform(10))
        mp = 30  + Int(arc4random_uniform(10))
        atk = 30 + Int(arc4random_uniform(10))
        walk = 0
    }
    
    init(name: String, hp: Int, mp: Int, atk: Int) {
        self.name = name
        self.hp  = hp
        self.mp = mp
        self.atk = atk
    }
    
    /// ここがメソッド
    /// 実際の攻撃力. 攻撃する際にある程度の乱数を発生させてダメージを算出する
    func calAtk() -> Int {
        return self.atk + Int(arc4random_uniform(10))
    }
    
    /// ダメージを受けた時の処理
    mutating func damage(num: Int) {
        self.hp -= num   // ここでエラー
    }
}

var hero = Player(name: "勇者")
hero.walk = 10
print(hero.walk) // 10が出力
hero.walk = 10
print(hero.walk) // 20が出力

上記のように

プロパティ名 {
    get {
        // 参照される時の処理
    }
    set {
        // 値がセットされる時の処理
    }
}

と記載する事で、プロパティの値をいじる時に専用の処理を記述することが可能です。
今回の処理はGetter / Setterを記載したかったのでかきましたが、このような計算を行う場合は、後述の通りプロパティオブザーバに書いたほうが良いでしょう。

構造体プロパティのwillSet / didSet (プロパティオブザーバ)

プロパティの値が変更される直前「willSet」、直後「didSet」に処理を呼ぶことができます。
先程のSetterの処理をwillSetに、didSetでは歩いていて、毒状態であればhpを減らすようにします。

struct Player {
    let name: String
    var hp: Int
    var mp: Int
    var atk: Int
    var walk: Int {
        willSet {
            self.walk += newValue
        }
        didSet {
            // 歩いた分だけダメージ
            self.hp -= walk - oldValue
        }
    }
    
    init(name: String) {
        self.name = name
        hp = 100 + Int(arc4random_uniform(10))
        mp = 30  + Int(arc4random_uniform(10))
        atk = 30 + Int(arc4random_uniform(10))
        walk = 0
        calAtk()
    }
    
    init(name: String, hp: Int, mp: Int, atk: Int) {
        self.name = name
        self.hp  = hp
        self.mp = mp
        self.atk = atk
        self.walk = 0
    }
    
    /// ここがメソッド
    /// 実際の攻撃力. 攻撃する際にある程度の乱数を発生させてダメージを算出する
    func calAtk() -> Int {
        return self.atk + Int(arc4random_uniform(10))
    }
    
    /// ダメージを受けた時の処理
    mutating func damage(num: Int) {
        self.hp -= num   // ここでエラー
    }
}


var hero = Player(name: "勇者")
hero.walk = 10
print(hero.walk)

これで歩く時の歩数と、歩いた際に毒のダメージを受ける処理が記載できました。
また上記例の様に、

  • willSetでは
    • 新しくセットされる値はnewValue
    • 既存の値は、walk
  • didSetでは
    • 新しくセットされた値はwalk
    • 既存の値は、oldValue

で表現できます。
ちょっとnewやらoldやらややこしいですが、will / didの言葉の意味で推測できますね。

さいごに

冒頭でも記載しましたが、構造体は一見クラスとほぼ同程度の機能が扱えるので、クラスを使うのか構造体を使うのかの判断がしにくい所があり、
わたし自身混乱してきたので、構造体はデータを取り扱うのであるを主眼にまとめてみました。
これまで記載した例のように、データを主眼に構造体を用いると、物凄く使いやすくプログラム自体がとてもスッキリするので是非マスターしたい所ですね。

written by ゆう@あんのうん

Module compiled with Swift 4.0.3 cannot be imported in Swift 4.1: x86_64.swiftmodule エラーの対応

はじめに

XCodeのVersionを挙げたら、標題の通りのエラーが発生するようになりました。
詳細には

Module compiled with Swift 4.0.3 cannot be imported in Swift 4.1: RxSwift.framework/Modules/RxSwift.swiftmodule/x86_64.swiftmodule

Carthage経由で入れたパッケージがエラーを出すようになった感じです。

原因

Carthageは毎回のbuildを早くする為に、予めライブラリをコンパイルします。
今回の場合は、XCodeのVersionが上がることでSwiftのVersionも上がった為、過去の4.0.3でコンパイルされたものが4.1では利用出来ないのでエラーになった模様。

解決方法

再度コンパイルします。

carthage update --platform iOS --no-use-binaries

今回は再度コンパイルするだけで十分なので、 「--no-use-binaries」 オプションを付けてコンパイルするで十分です。

KeyHolder Version 2.5.1 軽微な不具合を修正しました

いつもKeyHolderをご利用戴き、ありがとうございます。

今回のVersion2.5.1で一部操作でクラッシュする不具合の修正を行いました。

ご利用の皆様にはご不便をおかけして大変申し訳ありませんでした。

また、今回のVersionにて、パスワードが大量に登録された際に、アプリが重くなる事象がありましたので、合わせてパフォーマンスチューニングを行いました。

もし、他にこの不具合を直して欲しい、こんな機能をつけて欲しい等ありましたら、

  • AppStoreのレビュー
  • このブログ
  • Twitter
  • project.unknown.cs@gmail.com

にご連絡いただけますと幸いです。
全てにお答えすることは難しいですが、少しでも皆様の要望を実現していきます。

引き続き、KeyHolderのご愛用のほど宜しくお願い致します!

GoogleHome の喋らせ方 - 勝手に喋らせる方法

はじめに

Google HomeはIFTTT連携で声で命令して色んなことをさせることが出来ますが、まだ何かのトリガーをきっかけに自動で喋ってくれるという機能までは提供されていません。

今日は、GoogleHomeを自動的に喋らせる事が出来たので、そのやり方とハマった所の備忘録を紹介します。

最終的には、この記事の様に雨が振りそうになったら自動的に教えてくれるような事ができるようになります。

www.project-unknown.jp

今回は、その前準備として、Raspberry pi上でイベントを実行したらGoogle Homeが喋ってくれるようになる所をまでをご紹介します。

実行結果

まず、簡単に実行結果から、以下のTwitterにあるように好きなコトバを喋らせています。

他のサイトでも取り上げられている、google-home-notifierを用いて実現しております。

環境

以前買ったraspberry pi3をローカル環境でWebサーバとして、nodejsを突っ込んでます。

以下は簡単な全体構成 (nodejsでコマンドを受け付けて喋らせるだけなので、非常にシンプル)

f:id:project-unknown:20171221024930p:plain

Node.jsとnpmのinstall

$ sudo apt-get update
$ sudo apt-get install -y nodejs npm
$ sudo npm cache clean
$ sudo npm install npm n -g
$ sudo n stable

バージョン確認

$ node -v
v7.3.0
$ npm -v 
4.0.5

google-home-notifierのinstall

まずは下準備

curl -sL https://deb.nodesource.com/setup_7.x | sudo -E bash -

nodejsを突っ込む

sudo apt-get install nodejs

git, libnss-mdns, libavahi-compat-libdnssd-devを突っ込む

sudo apt-get install git-core libnss-mdns libavahi-compat-libdnssd-dev

libavahi-compat-libdnssd-devがinstallできない

私がハマったのですが、libavahi-compat-libdnssd-devを入れようとすると、以下な感じになってハマりました

$sudo apt-get install libavahi-compat-libdnssd-dev
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています                
状態情報を読み取っています... 完了
インストールすることができないパッケージがありました。おそらく、あり得
ない状況を要求したか、(不安定版ディストリビューションを使用しているの
であれば) 必要なパッケージがまだ作成されていなかったり Incoming から移
動されていないことが考えられます。
以下の情報がこの問題を解決するために役立つかもしれません:

以下のパッケージには満たせない依存関係があります:
 libavahi-compat-libdnssd-dev : 依存: libavahi-client-dev しかし、インストールされようとしていません
E: 問題を解決することができません。壊れた変更禁止パッケージがあります。

う〜ん、絶妙な日本語。

どうやら、依存パッケージのlibavahi-client-devが入らないからinstall出来ないっぽいです。

では、実際にlibavahi-client-devが何故入らないのか、直接installを試みて確認してみます。

$ sudo apt-get install libavahi-client-dev
パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています                
状態情報を読み取っています... 完了
インストールすることができないパッケージがありました。おそらく、あり得
ない状況を要求したか、(不安定版ディストリビューションを使用しているの
であれば) 必要なパッケージがまだ作成されていなかったり Incoming から移
動されていないことが考えられます。
以下の情報がこの問題を解決するために役立つかもしれません:

以下のパッケージには満たせない依存関係があります:
 libavahi-client-dev : 依存: libavahi-client3 (= 0.6.31-5) しかし、0.6.32-2 はインストールされようとしています
                       依存: libavahi-common-dev しかし、インストールされようとしていません
                       依存: libdbus-1-dev (>= 0.60) しかし、インストールされようとしていません
E: 問題を解決することができません。壊れた変更禁止パッケージがあります。

どうやら、更に依存パッケージを入れようとして失敗しているぽいですね。
この依存パッケージは既に最新のverが入っており、ダウングレードになるので依存関係で勝手にダウングレードにならないようにエラーで止めてるみたいです。

今手元のrasspberry piは他に利用用途も無いので、ダウングレードで対応します。
(apt-getのダウングレードは、バージョン直指定で下げれます)

他何回か躓いたのですが、基本同様の判断で適切なパッケージをアテてます

$ sudo apt-get install libavahi-client3=0.6.31-5
$ sudo apt-get install libavahi-common3=0.6.31-5
$ sudo apt-get install libavahi-common-dev
$ sudo apt-get install libdbus-1-3=1.8.22-0+deb8u1
$ sudo apt-get install libdbus-1-dev=1.8.22-0+deb8u1
$ sudo apt-get install libavahi-client-dev

これで依存関係の悪さがなくなったはずなので、以下のコマンドを進めます。

$ sudo apt-get install libavahi-compat-libdnssd-dev

上記でしれっと、バージョンを書いていますが、警告が出ていれば警告通りのバージョン番号を、よく分からなければ、以下のコマンドで最新のバージョンを見て突っ込んでます。

$ apt-cache policy libdbus-1-dev

gitからgoogle-home-notifierを取得

$ git clone https://github.com/noelportugal/google-home-notifier

npm install

$ cd google-home-notifier
$ npm install

これで必要なパッケージが全て入ったはずです。

Google Home に喋らせて見る

コード準備

google-home-notifierには、Gitからcloneしたタイミングで、example.jsが入っており、必要な設定さえすればいきなり喋らせる事が出来ます。

以下を修正してみてください。(以下はexample.jsの中身

(以下に出て来るGoogleHomeのIPは、GoogleHomeアプリのデバイスから調べることが出来ます

...省略...
var app = express();
const serverPort = 8091; // ポート番号、好きな番号で後でGoogle Homeに命令を送る特に指定
...省略...

...省略...
var deviceName = 'Google Home Room'; <- google homeの名前
var ip = '192.168.1.10'; // <- google homeのIPを記載
....省略...

...省略...
  var language = 'ja'; // <- jaに変更
  if (req.query.language) {
    language;
  }

  googlehome.ip(ip, language);
  googlehome.device(deviceName,language) // <-ここを追加
...省略...

...省略...
if (req.query.ip) {
     ip = req.query.ip;
  }

  var language = 'ja'; // <-ここをjaに
  if (req.query.language) {
    language;
  }

  googlehome.ip(ip, language);
  googlehome.device(deviceName,language) // <- ここを追加
...省略...

google-home-notifierの起動

ここまで出来たら、以下のコマンドで立ち上げます。

node example.js

以下の様に出てくると思いますので、

$ node example.js
Endpoints:
    http://〜〜〜/google-home-notifier
    https://〜〜〜/google-home-notifier
GET example:
curl -X GET https://〜〜〜/google-home-notifier?text=Hello+Google+Home
POST example:
curl -X POST -d "text=〜〜〜" https://〜〜〜/google-home-notifier

別途ターミナルを立ち上げて、以下のように入力します。

curl -X POST -d "text=明日会社行きたくない" http://Raspberry PiのIP:コードで指定したポート番号/google-home-notifier

これで以下のような結果になります。

Twitterにも書きましたが、これでGoogleHomeでできなかった、自動で喋らせると言う所が可能になりました。
(一番はIFTTTのThat部分を早くGoogle Assistantが対応してくれることですが)
指定時間になったらのリマインダや、雨雲が近づいている時に洗濯物大丈夫?の注意喚起をGoogle Homeが行ってくれることになるので、生活が一層楽になりそうですね。

written by ゆう@あんのうん