【アプリ】IconTimer 1.0 をリリースしました

2015年12月23日水曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加


新アプリ IconTimer をリリースしました。決めた時間をカウントダウンしてアラームを鳴らすだけのシンプルなタイマーアプリです。見た目にも音にもこだわりました。


特徴的なのは時間の選び方。
タップしたボタンの数字を足した合計が計測時間(分)になります。


以下は紹介動画。




時間・アイコン・色を自由にカスタマイズ。




以下は開発メモをつらつらと書きます。

ユーザインターフェイスめも


既存のタイマーアプリを使っていて毎回不満に感じていたのは、時間を選ぶ時に頭の中で「ひっかかり」があるところ。うまく説明できないのだけれど、あえて言えば時間を選ぶ時に注意を払わせるのが苦痛だった(どんだけものぐさと言われそうですが。。)
アプリを使うのがちっとも楽しくない。


例えば電卓式の時間を直接入力するタイプの場合、目的の数字を押してスタートするだけ....なんだけど打ち間違えたらクリアしないといけない。だから無意識のうちに間違えないように注意を払う。それがひっかかる。一方あらかじめ時間がリストで選べるタイプの場合、たくさんの選択肢から目的の時間を注意を払って探さなければならない。これもひっかかる。

そんな気を遣わずにポン・ポン・ポンと選ぶだけで時間が選べる UIはできないのか?それが開発の出発点だった。注意を払わ無くて良い UIって、きっと失敗しても直すのが苦にならないものだろう。試行錯誤がしやすいUI はどんなものか。





最初イメージとしてあったのは、少ないボタンを配置してそこから時間を選んでスタートするデザイン。どうせよく使う時間は決まっているだろうから、あらかじめ好きな時間を割り当てておけばボタンは少なくていいだろう。数が少ないのでひと目で把握できて判断もしやすそうだ。ただ問題なのはボタンが少ないと時間のパターンが物足りないこと。さらに使う前に時間を決めて設定するのはいちいち面倒。もしパターンを多くしようとボタンを増やせば選択方式と同じ罠に陥る。


そんなことを思いつつプロトタイプをひたすら弄ってた。ふとボタンを排他的に扱うではなく「足し算」にしたらどうかと思いつく。タップした時にONだった他のボタンをOFFにするのではなくそのまま残す。その残ったONボタンの数字の合計がタイマーの時間とする。逆にもう一度押すとOFFになり、その分タイマーの時間が減る。つまりタップのON・OFFがちょうど足し算・引き算の意味を持つことになる。これなら間違っても何度でもやり直せる。そして何よりもこの方法だとボタンの数以上の時間パターンを作り出すことができるのに気がついた。

でも、タップ数は他の方式に比べると増えてしまう。これって最初の問題に逆戻りしないだろうか?ところがプロトタイプで試してみると意外にもこれが楽しい。妙な話だけど、3分のタイマーを作るのに1分のボタンをわざわざ3回押したくなる。むしろタップが増えて非効率になってるし。。でもついつい目的の時間を作るのに今あるボタンだけで何を足し引きすれば良いのかと考えてしまう。そうか、タップのON・OFF行為がちょっとしたパズルを解く感覚なんだと気がついた。さらにアイコンを表示して音を鳴らすとこの傾向が顕著になった。意味もなくついついボタンを弄ってしまう。これで行くことに決めた。


開発情報

ツール:Xcode7 / swift 2.0
期間:1ヶ月
要員:1人

アイディアを元にプロトタイプから作り始めた。それを iPhoneに入れて暇があれば弄って改良を加える漸進的なアプローチ。プログラムもデザインも一人でやるのでお気楽。

アイコン




アイコンはいつも使っている icons8 のカラーアイコンを使用。自分の開発ではもはや定番。デザインが良くて種類も豊富だし、SVGもサポートしているなど使い勝手がいい。100pxまでのPNGはフリー。$149 でPNGのすべてのサイズと1年間の更新権。$249ならSVG / PDF / EPS / AI 形式のほかフォント/SVGの生成、カラーリング等々さまざまなオプションが付く。


効果音


500を超える効果音集 $35。aifとwavの2種類で提供される。bepp系から始まり、chime、click、dig、pad、pop、zap などなど。各系統でさらに多くのバリエーションが用意されている。これだけあると効果音を選ぶのも大変だが、それだけに GUIに合う音が鳴らせるとかなり気分がいい。

ライブラリ

Crashlytics, AdMob のみ。その他 UI / 機能に関しては自前。UIは色々なパターンを試作して捨てたものも多数。その分作るのは時間がかかったが楽しい作業でもあった。


多言語対応(アプリ)

開発当初から意識していた。アプリの性格上、文字での説明は要らないと割りきって文字の埋め込みをやめて、その分 UI を誰が見ても理解できて迷わないものにすることを心がけた。これによって翻訳の手間がほぼゼロとなった。一人開発でこれは嬉しい。対応言語はXcodeで選択可能なものをかたっぱしからローカライズした。なお唯一文字があるクレジットは英語表記のみとしてどの言語も統一した。

多言語対応(AppStore)

多言語といえばアプリとは別に AppStore向けの説明がある。こればっかりは翻訳をやらないわけには行かないので各言語用に文面を作った。アプローチは、
 (1) 無駄無く簡潔で短い最低限の文面を日本語で作成
 (2) それをGoogle翻訳の助けを借りて英語へ翻訳
 (3) Google Sheetを使って英語から他の言語へ一気に翻訳

多言語展開の元を英語にしたのは日本語起点よりも精度が良さそうだから。

Google Sheet(Google Driveで提供されるオンラインのスプレッドシートアプリ)にはセルの文字列を Google翻訳サービスへ渡す関数が用意されていて、これを使うと大量の文字列を一気に翻訳することができる。こんな感じ。

あらかじめ言語コード(jaとかen)の列を用意しておき、それを関数へ渡して翻訳をかける。

日本語を含む23言語の翻訳がこの方法であっという間にできた。Googleすごい。


Apple審査

12/04 Ver1.0を申請

12/12 リジェクト:1.0 メタデータでの却下
AppStore用のアプリ名に長い説明を入れたのがまずかったらしい。説明はやめて IconTimer だけにして再申請。バイナリは更新せず。

12/18 リジェクト:1.0 バイナリでの却下
設定画面に分を増減させる+−のボタンがあるのだが、それを押している時にクラッシュするのでダメというもの。自分の手持ちの iPhoneやiPadやシミュレータを総動員して試すも再現しない。再現しない上に、メイン機能ならともかく設定の一部の機能のクラッシュだけで落とすな=3、となんだか頭に来て「再現しない」「もっと詳しい情報を出して」的なコメントを送る。

が、その後繰り返しボタンを押し続けるとクラッシュが再現。簡単には起きないが、確かに使っていると誰でもいつかはクラッシュに遭遇しそう。クラッシュログはこんな感じ。

Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
Exception Subtype: KERN_INVALID_ADDRESS at 0x00000000155d3f40
Triggered by Thread:  0

Oh...わからん。


その後、Xcodeに繋いだiPhoneで問題を再現させるも AppDelegateで停止して上記例外が出てるだけ。調査を重ねるもさっぱりわからない。仕方なくボタンを連打しつつ(それが唯一の条件なので)、一つ一つコードから行を取り除いてクラッシュに関係のある箇所を絞り込んでいった。泣きが入りそうになったころ、ボタンを押した時に鳴らす音の処理で落ちていることが判明。音を鳴らすために Soundというクラスを作り、AVAudioPlayerのインスタンスをクラス変数で管理していたのだが、どうもそこがクラッシュの原因ぽい。こんな感じ。
class Sound: NSObject {

    static var player:AVAudioPlayer?
    static func play(...) {
        
        if let sound = NSDataAsset(name: name) {
            do {
                 :
                try player = AVAudioPlayer(data: sound.data, fileTypeHint: fileTypeHint)

※元ネタはネット上のブログから拾ってきた。いま手元に情報が無いので略

ボタンが続けて押された場合、前の発音が終わらない内にこの play()メソッドが呼び出され、新しい AVAudioPlayerのインスタンスが作られる。この時、直前の発音で使っていた AVAudioPlayer は音が鳴っている最中に参照が切れて廃棄対象になってしまうケースがあるようだ。そして音が鳴り終わった時にこの廃棄対象のインスタンスで後処理を行おうとしてクラッシュ(たぶん)。そこで次の音を鳴らす時には直前の発音を明示的に停止する処理を入れた。
class Sound: NSObject {

    static var player:AVAudioPlayer?
    static func play(...) {
        
        if let _ = player {
            player?.stop()
        }
        if let sound = NSDataAsset(name: name) {
            do {
                 :
                try player = AVAudioPlayer(data: sound.data, fileTypeHint: fileTypeHint)

if文いらないかも。ともかくこれでクラッシュがピタッと無くなった。速攻でバイナリをアップロードして再申請。頭に来て送ったコメントを思い出すとなんだかバツが悪かったので、クラッシュの指摘の(最大限の)お礼と、ついでに急いで審査お願いと付け足しておく。その後なぜか expedited review はできないよ、という連絡メールが来る(expedited reviewは依頼していない)。

12/22 審査通過
日本時間で 0時ごろレビュー開始、6時すぎに通過の連絡。これは嬉しかった。


(おわり)






Asset Catalog にサウンドファイルをおく

2015年11月17日火曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

Asset Catalogには画像以外のデータも置ける。サウンドファイル(.aif)を置いてみた。


取り出すには NSDataAsset を使う。
let sound = NSDataAsset(name: name)
// use sound.data

取り出したサウンドファイルを鳴らす。
import UIKit
import AVFoundation

class Sound: NSObject {

    static var player:AVAudioPlayer?
    static func play(name:String) {
        if let sound = NSDataAsset(name: name) {
            do {
                try! AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
                try! AVAudioSession.sharedInstance().setActive(true)
                try player = AVAudioPlayer(data: sound.data, fileTypeHint: "aif")
                player?.volume = 0.3
                player?.play()
            } catch {
                print("error")
            }
        }
    }
}


上記は下記サイトのコードをクラスメソッドに置き換えただけ。サウンドファイルをアセットに置くのもここで知った。
Accessing audio (and other) files stored in the Xcode asset catalog with Swift

[Autolayout] サンプルケース - 親ビューの下半分に子ビューを配置 (2) アニメーション

2015年6月12日金曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

前回のやつにアニメーションを加えてみる。子ビューが下からシュッと出てくるやつ。

これは制約を使えば簡単。子ビューの上のラインを画面下端から、最終位置(この場合親ビューのCenterY)まで引き上げてやれば良い。
まず子ビューの初期位置を最終下端で作る。
let v1c = NSLayoutConstraint(item: tableView, attribute: .Top, relatedBy: .Equal,
     toItem: parentView, attribute: .CenterY, multiplier: 1.0, constant: parentView.frame.size.height)

一旦 parentView.layoutIfNeeded()で初期描画させた後、アニメーションのコードを走らせる。
v1c.constant = 0.0
        UIView.animateWithDuration(0.5) {
            parentView.layoutIfNeeded()
        }

こうすると子ビューの上の制約の値(constraint)が下端(parentView.frame.size.height)から 0.0へアニメーションしながら変化する。

なおこのままだと制約のワーニングが出る。
2015-06-11 08:10:18.938 LKMenu_Example[89282:6908924] Unable to simultaneously satisfy constraints.
 Probably at least one of the constraints in the following list is one you don't want.
 Try this: (1) look at each constraint and try to figure out which you don't expect; 
(2) find the code that added the unwanted constraint or constraints and fix it.
 (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer
 to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints) 
(
    "",
    "",
    ""
)
  :

これは子ビューの上の制約(v1c)と下の制約(v2c)の値が初期状態では等しくなる(逆転する?)ため。こんな時は優先順位(priority)をつけてやればいい。
let v2c = NSLayoutConstraint(item: tableView, attribute: .Bottom, relatedBy: .Equal,
      toItem: parentView, attribute: .Bottom, multiplier: 1.0, constant: 0.0)
        v2c.priority = 750   // 追加
        parentView.addConstraint(v2c)
親ビューに制約を追加する前につけるのを忘れずに。これでワーニングが消える。priorityは大きい方が優先順位が高い。IBでプロパティ欄を見ると 1000(Required), 750(High), 250(Low)となっている。
デフォルト値は 1000(最大値も 1000)。


最終的なコード。
parentView.addSubview(tableView)

        tableView.setTranslatesAutoresizingMaskIntoConstraints(false)
        
        let h1c = NSLayoutConstraint(item: tableView, attribute: .Left, relatedBy: .Equal,
            toItem: parentView, attribute: .Left, multiplier: 1.0, constant: 0.0)
        parentView.addConstraint(h1c)
        let h2c = NSLayoutConstraint(item: tableView, attribute: .Right, relatedBy: .Equal,
            toItem: parentView, attribute: .Right, multiplier: 1.0, constant: 0.0)
        parentView.addConstraint(h2c)
        
        let v1c = NSLayoutConstraint(item: tableView, attribute: .Top, relatedBy: .Equal,
            toItem: parentView, attribute: .CenterY, multiplier: 1.0, constant: parentView.frame.size.height)
        parentView.addConstraint(v1c)
        let v2c = NSLayoutConstraint(item: tableView, attribute: .Bottom, relatedBy: .Equal,
            toItem: parentView, attribute: .Bottom, multiplier: 1.0, constant: 0.0)
        v2c.priority = 750
        parentView.addConstraint(v2c)
        parentView.layoutIfNeeded()
        
        v1c.constant = 0.0
        UIView.animateWithDuration(0.5) {
            parentView.layoutIfNeeded()
        }

Autolayoutはコツがわかってくると楽しい。


(おまけ)animateWithDuration:usingSpringWithDamping: を使えばアニメーションにバネ効果(バウンス効果っぽい)をつけられる。
UIView.animateWithDuration(0.5, delay: 0.0, usingSpringWithDamping: 0.7,
            initialSpringVelocity: 0.0, options: UIViewAnimationOptions.CurveEaseInOut,
            animations: { () -> Void in
            parentView.layoutIfNeeded()
        }) { (Bool) -> Void in
        }

[Autolayout] サンプルケース - 親ビューの下半分に子ビューを配置

2015年6月11日木曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

メニュー作成中。Autolayoutでこんな感じのビュー配置をしたい。
ランドスケープで。

親ビューが parentView、子ビューが tableViewとするとこんな感じ。
        parentView.addSubview(tableView)

        tableView.setTranslatesAutoresizingMaskIntoConstraints(false)
        
        let h1c = NSLayoutConstraint(item: tableView, attribute: .Left, relatedBy: .Equal, toItem: parentView, attribute: .Left, multiplier: 1.0, constant: 0.0)
        parentView.addConstraint(h1c)
        let h2c = NSLayoutConstraint(item: tableView, attribute: .Right, relatedBy: .Equal, toItem: parentView, attribute: .Right, multiplier: 1.0, constant: 0.0)
        parentView.addConstraint(h2c)
        
        let v1c = NSLayoutConstraint(item: tableView, attribute: .Top, relatedBy: .Equal, toItem: parentView, attribute: .CenterY, multiplier: 1.0, constant: 0.0)
        parentView.addConstraint(v1c)
        let v2c = NSLayoutConstraint(item: tableView, attribute: .Bottom, relatedBy: .Equal, toItem: parentView, attribute: .Bottom, multiplier: 1.0, constant: 0.0)
        parentView.addConstraint(v2c)

        parentView.layoutIfNeeded()

ポイントは v1c のところで、親のCenterYに 子ビューのTOPを紐付けているところ。ここの constantを変更すれば高さを調整することもできる。


[CocoaPods] use_frameworks! で作ったライブラリのバンドル

2015年6月1日月曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

CocoaPodsで自作ライブラリを作っているケースで、swift対応のためにフレームワーク化(use_framework!)するとバンドルはその中に入ってしまう。

Example.app
 ├── LKPostingQueueManager.framework
 │   ├── Info.plist
 │   ├── LKPostingQueueManager
 │   └── Resources.bundle ←ここにくる(以前はもっと上の階層)


プログラムでこれを参照するにはメインバンドル配下では見つからない。
let path = NSBundle.mainBundle().pathForResource("Resource", ofType: "bundle")! → ここで nilでクラッシュ
let bundle = NSBundle(path: path)

最初にフレームワークのバンドル(クラスが含まれるバンドル)を取って、そこから引き出すのがポイント。
let frameworkBundle = NSBundle(forClass: LKPostingQueueManager.self)
let path = frameworkBundle.pathForResource("Resources", ofType: "bundle")!
let bundle = NSBundle(path: path)


関連

Resource Bundle の作り方と CocoaPodsでの配布
※上記はフレームワーク化する前の話。フレークワーク化するとこんな面倒は入らず設定だけで済む。
s.resource_bundles = { 
    'Resources' => ['Pod/Assets/*']
  }
みたいな。

【アプリ】StackOne 1.6.3 アイコンが変わりました

2015年5月15日金曜日 | Published in | 2 コメント

このエントリーをはてなブックマークに追加

StackOneの最新バージョン 1.6.3をリリースしました。このバージョンからアイコンが変わっています。


象がいなくなり重なりの模様だけのシンプルなものに変えました。

これは某マネー系アプリからクレームがあったためです(英語)。確かにそれとは似ていて先方からは、自分たちが先に出したしメジャーだからそっちを変えてほしい、みたいな文面が来た。あと法的な対処も匂わせたりもしていました。これが1番目の理由です。

2番目の理由は、その件とは無関係に最近の私のエバーノート系アプリはどれも象が入っていないものになっているので、それに合わせたというのもあります。以前から漠然と変えようと思ってたところにクレームが来たのがきっかけになったとも言えます。

アイコンは別のデザインも色々と考えたのですが、しっくりくるものがなかなかできず。最終的には以前の重なりの部分だけを残すシンプルなものに落ち着きました。
象が好きだったという方もいらっしゃったようでその方には誠に申し訳ないです。改良は続けていく予定なのでユーザの方々には今後ともどうぞよろしくお願いいたします。

では。


【アプリ】PicsEver 1.0 をリリースしました

2015年5月6日水曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加


新アプリ PicsEver をリリースしました。カメラロールの写真を選択してエバーノートに投稿するアプリです。最大100枚までの写真をまとめて投稿できるのが一番の特徴です。




選択した写真は1つのノートにまとめられて投稿されます。
写真1枚ごとに日付と場所情報のヘッダがつきます(位置情報のある写真のみ)。



ノートタイトルは標準で日付が入りますが設定でカスタマイズ可能。送信時に変更することもできます。


簡易ビューア


設定画面

iPadでも使えます




写真をまとめて投稿する手段がなくて困っていた方、是非ご利用下さい。


【アプリ】PicsPicker 1.0 をリリースしました

2015年4月11日土曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加


新アプリ PicsPicker をリリースしました。カメラロールの写真や動画を選択して他のアプリに渡すという単純なアプリです。


1日ごとにグルーピングしてまとめて選択することができます。


ビューアもついてます(動画の再生も可)


指定サイズに縮小できる。



初の Swiftを使ったアプリで四苦八苦しました。でも実際 Swiftで書いたのは100行ぐらいで写真選択の部分は昨年 Objective-Cでガリガリ書いた自作ライブラリがメイン。Swiftの使い始めとしてはちょうどよかったかも。

このライブラリは githubで公開している。


このライブラリを使うとこのアプリの写真選択機能と同じものが簡単にできる。

Swiftは面白い。が、Objective-Cのゆるゆるさに比べるとチェックが厳しいので慣れるまでが大変。




【アプリ】EverGear 3.0 メジャーアップデートしました

2015年1月28日水曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加



本日 EverGear 3.0 がリリースされました。数年ぶりのメジャーバージョンアップになります。基本機能はあまり変わっていませんがデザインとアイコンをすべて一新しました。さらに iPhone6 / iPhone6Plusに対応、そして iPadでも利用できるようになりました。


新しくなった EverGearを紹介します。

(参考)ランディングページ

特徴

EverGearは エバーノートへの投稿に特化したアプリです。このアプリの一番の特徴は投稿前の下書き機能が充実しているところです。

下書きの一覧


下書きは通常のテキストはもちろん、

写真を添付することができて
ちょっとしたメモも書けます。

カメラ撮影はもちろんのこと、
アルバムから複数の写真を取り込むことができます。


クリップボードからは画像やテキストが取り込めます。

写真とクリップボードは画面全体が上へスライド
した後にその下から現れます。この下から現れるエリアを
「ドロワー」と呼んでいます(アイコンが引き出し
なのはその為です)。


写真は専用タブを用意していてスライドで眺めることができます。


EverGearのもう一つの大きな特徴が「ひな形」。
あらかじめテキストやノートブック、タグを定義しておくと
下書を新規作成する時のひな形(テンプレート)として利用できます。


登録したひな形は、下書きを作る時に
さっと呼び出すことができます。


下書の仕組み

EverGearでもっともわかりづらい(?)下書の仕組みについて図解してみました。


テキストは1行目がタイトルになります。長い文章などはここに書きます。「メモ」は文字と写真を1まとめにしたカードのようなもので、このメモは1つの下書きに複数付けることができます。メモには必ずしも写真をつける必要はなく、文字だけのメモも作ることができます。またメモごとに作成日時と位置情報が自動で記録されるようになっているので、ちょっとしたライフログにも最適です。写真の入ったメモは写真のタブで地図と一緒にスライドで観ることができるようなっています。

下書きをエバーノートへ送るとテキスト、メモ、メモ、... の順でレイアウトされたノートができあがります。
投稿したノートの例

このエバーノート上のレイアウトは4種類から選ぶことができます。

  • サムネイル+通常画像
  • サムネイルのみ
  • 通常画像のみ
  • プレーン(CSSなし)
いろいろ試してみてお好みのレイアウトを探してみてください。



送信の流れ

もう一つ EverGearでわかりづらい送信の流れについて、こちらも解説しておきます。

まず下書を送信するとノートは「送信」タブに
移り、すぐに送信処理が始まります。

オフライン(ネットワークに接続していない)の場合は
ここで待機状態になります。その後オンラインになり
次第送信が始まります。通信エラーなどで一時的に
待機状態になっている場合は、該当するノートを
右フリックしてメニューから「再送信」を選びます。



送信が終わると履歴に移ります。

履歴タブを開くと過去に送信完了したノートの
内容を確認することができます。



エバーノートへ送信したノートであっても
下書に戻して再編集することが可能です。
(フリックメニューから下書きへ戻します)

注意点としては、この時に別の端末で同じノートを
変更してもそれはEverGearには反映されないというところです。
編集後に再送信すると、エバーノート上のノートは上書きされ
るのでご注意ください。



なお、もし送信前に手直ししたい場合は下書に
戻すことができます。※送信中は元に戻せません。



メニュー

最後によく使う「メニュー」について補足しておきます。

一覧形式で表示される画面ではセル(行)ごとに
メニューが用意されています。

メニューはセル(行)の上で指を左または
右へすべらせる(フリック)と現れます。
左メニューだけでなく、右メニューが使える
セルもあるのでいろいろな画面で試してみてください。


サポート

Facebook / Twitter / メールにて受け付けています。質問や感想がありましたらお気軽にどうぞ。

EverGearサポートページ(Facebook)




リリースやバグ情報、開発状況などを発信しています。

@evergear(Twitter)



Lakesoftのサポートページ(Facebook)



メールアドレス






新しくなった EverGear を是非お試しください。




人気の投稿(過去 30日間)