Dotter version 1.0.3 release

Dotterの新バージョンがAppStoreで販売開始されました。

2年半ぶりのバージョンアップでしたが、無事にリリース出来ました。

審査が開始されるまでの時間は約8時間、審査時間は約2時間でした。早い!

ちなみに、2年半前のバージョンアップ時は審査開始まで7日と16時間、審査時間は約30分!でした。

昔に比べて人員に余裕ができたのかしら・・・

Dotter version 1.0.3 coming soon

本日、Appleにドット絵エディタ「Dotter」新バージョンの審査依頼を行いました。また、AppStore用の紹介動画や画像を作成しました。連休中に終わって良かった・・・

今回のバージョンアップの目玉機能は「カーソル」です!

1.カーソルを1つ目の指で「ドラッグ」します。
2.ドットを打ちたい位置にカーソルが合ったら、キャンバス上(どこでもOK)に2つ目の指でタップすると、カーソルの位置にドットが打てます。

打ち間違いをかなり減らせ、格段に使い易くなっていると思います!!

AppStore用の動画作りました。地味です。無音です。音楽つければ良かった!!!

「カーソル」機能は我ながらよく出来たと思います。この機能のおかげで、全世界で1億本は売れるのではないでしょうか。

240円 X 1億 = 240億

いやごめんウソだ。1本でも売れてコーヒー1杯分でも浮けば十分です。

新作アプリ「My Tiny Planet」作り出す
→ ドット絵を作る必要が出てきた
→ 自分で過去に作ったドット絵エディタ「Dotter」を使う
→ なんか使いにくいので改良した ←今ココ
→ 新作アプリ作りに戻る

さあ、新作アプリ制作がんばるぞ〜〜〜

最速でAppStore用の動画を作成する方法

前提

・ffmpegインストール済み
・実機あり(iPhone/iPad)
・macOS Sierra バージョン10.12.6

手順1

iPhone/iPadをmacに接続し、QuickTime Playerを立ち上げ、「ファイル」「新規ムービー収録」を選択。録画ボタンの右のメニューからカメラやマイクをiPhone/iPadに切り替える。

手順2

録画ボタンを押し、作成したアプリを動作させて動画を保存する。
(30秒以内。30秒を超えると、30秒以下になるよう動画を編集する必要あり)

手順3

fpsを30に落とすため、ターミナルで以下を実行する。fpsを落とした動画を、iTunesConnectでアップロードすれば終わり。

ffmpeg -i src.mov -crf 20 -r 30 dest.mov

-iオプションで、手順3で作成した動画を指定します。
-crfオプションで画質を指定します。
-rオプションでfpsを指定します。

5.5インチ向けにサイズ変更する場合

ffmpeg -i src.mov -crf 20 -r 30 -s 1080×1920 dest.mov

4.7インチ向けにサイズ変更する場合

ffmpeg -i src.mov -crf 20 -r 30 -s 750×1334 dest.mov

4インチ向けにサイズ変更する場合

ffmpeg -i src.mov -crf 20 -r 30 -s 640×1136 dest.mov

アプリ「Dotter」カーソル機能その他追加!リリースまでもうちょっと待ってね

Swiftで作ったアプリ「Dotter」を、1年半ぶりにバージョンアップします。

先日、ようやく1年半の間のSwift仕様変更に追従し、新しい開発環境で「Dotter」のソースコードが無事動作するようになりました。

そして、以下の機能を追加しました!

・「カーソルモード」追加!

狙った位置にドットが超打ちやすくなる目玉機能です。

キャンバスに「カーソル」が表示されます。

1.カーソルを1つ目の指で「ドラッグ」します。
2.ドットを打ちたい位置にカーソルが合ったら、キャンバス上(どこでもOK)に2つ目の指でタップすると、カーソルの位置にドットが打てます。

2つ目の指を置いたまま1つ目の指でカーソルをドラッグすると、連続でドットが打てたりもします。
従来通りの直接打ちをしたい場合は、「SETS」の「Direct Mode」をOnにして下さい。

カーソルはドラッグで位置が動きますが、この際、各ドットに”吸い付く”ように滑らかに動きます。ですので、打ち間違いをかなり減らせると思います。

カーソル色は、ペンだと緑色、消しゴムだと青色、ペイントだと桃色、スポイトだと水色になります。

・ドットの大きさの直接切り替え

ドットの大きさを変更する際、現バージョンでは「SETS」から大きさを指定し直す必要がありましたが、新バージョンでは、キャンバスを表示しながら、対応する太さのペンや消しゴムを選択することで、素早く切り替えられるようになりました。

・グリッドの直接切り替え

グリッドの表示/非表示を切り替える際、現バージョンでは「SETS」から設定を変更する必要がありましたが、新バージョンではキャンバスを表示しながら素早く切り替えられるようになりました。

また、グリッド線の描画方法を見直し、グリッド線が分かり易くなりました。

・キャンバスクリア

今までは、キャンバスを全てクリアするには消しゴムで頑張って消す必要がありました。

あまり多用しないかとは思いますが、キャンバスを初期化する機能を付けました。

実録! 2年半ぶりのSwiftアプリバージョンアップ 2日目 その2

続きです。今日中に終わるかなぁ・・・

【エラーメッセージ】
‘utf16Count’ is unavailable: Take the count of a UTF-16 view instead, i.e. str.utf16.count
【修正前】
if 0 < newXStr.utf16Count && 0 < newYStr.utf16Count && Util.checkNum(newXStr) && Util.checkNum(newYStr) {
【修正後】
if 0 < newXStr.utf16.count && 0 < newYStr.utf16.count && Util.checkNum(newXStr) && Util.checkNum(newYStr) {
【解説】
newYStr.utf16Count を newYStr.utf16.count に変更。

【エラーメッセージ】
Value of type ‘String’ has no member ‘toInt’
【修正前】
var newX : Int = newXStr.toInt()!
【修正後】
var newX : Int = Int(newXStr)!
【解説】
StringからIntへの変換は、Intイニシャライザを利用するようになった(Swift2で)そうです。2箇所修正。

【エラーメッセージ】
Type ‘String’ does not conform to protocol ‘Sequence’
【修正前】
for c in str
【修正後】
for c in str.characters
【解説】
「String型はSequenceプロトコルに準拠していません」とのことです。文字列を1文字ずつ取り出してforループで処理したい場合は、charactersを使うと良いみたいです。
2箇所修正。

【エラーメッセージ】
Cannot call value of non-function type ‘UIColor’
【修正前】
UIColor.lightGrayColor()
【修正後】
UIColor.lightGray
【解説】
lightGrayColor() がなくなって、lightGrayというconvenience methodで呼べるようになりました。

これでエラー全部直りました。さあ、実行してみます。

・・・

・・

Thread 1: EXC_BAD_INSTRUCTION code=EXC_I386_INVOP, subcode=0x0)

おやすみなさい。
さようなら2017年7月16日。

さようなら土日。

・・・続く

実録! 2年半ぶりのSwiftアプリバージョンアップ 2日目 その1

おはようございます。続きです。

【エラーメッセージ】
C-style for statement has been removed in Swift 3
【修正前】
for var y = newYStart; y <= newYEnd; y += 1 {
【修正後】
for y in newYStart … newYEnd { // 3.1
【解説】
Swift 3.0で for ループの構文が変わりました。影響はとても大きいですが、記述が簡潔になりました。英断でしたね。個人的にはそのままにして欲しかった・・・

【エラーメッセージ】
C-style for statement has been removed in Swift 3
【修正前】
for var xi = 1; xi <= xNum; xi += 1 {
【修正後】
for xi in 1 … xNum { // 3.1
【解説】
同じ

【エラーメッセージ】
C-style for statement has been removed in Swift 3
【修正前】
for var yi = 1; yi <= yNum; yi += 1 {
【修正後】
for yi in 1 … yNum { // 3.1
【解説】
同じ

【エラーメッセージ】
C-style for statement has been removed in Swift 3
【修正前】
for var i = 0; i < gs.m_touches.count; i++ {
【修正後】
for i in 0 ..< gs.m_touches.count {
【解説】
“以下”の場合は … で、”未満”の場合は ..< です。 【エラーメッセージ】
C-style for statement has been removed in Swift 3
【修正前】
for var xi = gs.m_glass.enable() ? 8 – gs.m_glass.xV % 8 : 0; xi <= xNum; xi += 8 {
【修正後】
for xi in stride(from: (gs.m_glass.enable() ? 8 – gs.m_glass.xV % 8 : 0), through: xNum, by: 8) { // 3.1
【解説】
飛び石パターンです。throughが”以下”、toが”未満”です。このパターンはもう1箇所ありました。

【エラーメッセージ】
C-style for statement has been removed in Swift 3
【修正前】
for var x:CGFloat = (bPlusX ? start.x + unitX : start.x – unitX);
(bPlusX ? x < end.x : end.x < x);
x += (bPlusX ? unitX : -unitX) {
// 処理
}
【修正後】
if (bPlusX) {
for x : CGFloat in stride(from: start.x + unitX, to: end.x, by: unitX) {
// 処理
}
} else {
for x : CGFloat in stride(from: end.x, to: start.x – unitX, by: unitX).reversed() {
// 処理
}
}
【解説】
時間かかりました・・・
事前に条件分岐して2つのforループに分けるしかない・・・よね?全体として記述が長くなってしまったパターンです。複雑なforループは書くべきじゃないということか・・・このパターンはもう1箇所ありました。

【エラーメッセージ】
Use of unresolved identifier ‘countElements’
【修正前】
countElements(target)
【修正後】
target.characters.count
【解説】
変数targetはString型です。文字の長さ取得だけで、結構手間ががかります。charactersは、見た目の文字数を取得します。

【エラーメッセージ】
‘Default’ has been renamed to ‘default’
【修正前】
UIAlertActionStyle.Default
【修正後】
UIAlertActionStyle.default
【解説】
Defaultの先頭大文字が小文字に変わっただけ。合計3箇所ありました。

【エラーメッセージ】
Cannot convert value of type ‘String’ to expected argument type ‘UnsafeMutableRawPointer’
【修正前】
if let path = Bundle.main.path(forResource: file as String, ofType: “sks”) {
var sceneData = Data(bytesNoCopy: path, count: .DataReadingMappedIfSafe, deallocator: nil)!
【修正後】
if let path = Bundle.main.path(forResource: file as String, ofType: “sks”) {
let pathURL : URL = URL(fileURLWithPath: path)
var sceneData : Data
do {
sceneData = try Data(contentsOf: pathURL, options: .mappedIfSafe)
} catch {
print(“unarchiveFromFile error.”)
}
【解説】
結構変わるなあ・・・

【エラーメッセージ】
‘init(forReadingWithData:)’ has been renamed to ‘init(forReadingWith:)’
【修正前】
var archiver = NSKeyedUnarchiver(forReadingWithData: sceneData)
【修正後】
var archiver = NSKeyedUnarchiver(forReadingWith: sceneData)
【解説】
ラベル名が変わっただけ。

【エラーメッセージ】
‘Any?’ is not convertible to ‘GameScene’; did you mean to use ‘as!’ to force downcast?
【修正前】
let scene = archiver.decodeObjectForKey(NSKeyedArchiveRootObjectKey) as GameScene
【修正後】
let scene = archiver.decodeObject(forKey: NSKeyedArchiveRootObjectKey) as! GameScene
【解説】
ダウンキャストは必ず成功する想定なので、as! で強制的にキャストして対処します、が、いつからこのエラー出るようになったんですかね?

【エラーメッセージ】
Argument labels ‘(data:)’ do not match any available overloads
【修正前】
var data: Data = Data(data:UIImagePNGRepresentation(image))
【修正後】
var data: Data = UIImagePNGRepresentation(image)!
【解説】
UIImagePNGRepresentationから直接Dataを取得可能になったようです。いつ頃変わったんだろう・・・

・・・続く

実録! 2年半ぶりのSwiftアプリバージョンアップ 1日目 その4

続きです。

【エラーメッセージ】
Extra argument ‘error’ in call
【修正前】
fileManager.removeItemAtPath(pngPath, error: nil)
【修正後】
do { // 3.1
try fileManager.removeItem(atPath: pngPath) // 3.1
} catch { // 3.1
print(“removeItem error”) // 3.1
} // 3.1
【解説】
エラー処理が変わりました。今後はtry catchで統一するんですね。また、removeItemAtPathはremoveItemに名称変更となり、第一引数のラベル名atPathが必須になりました。

【エラーメッセージ】
‘++’ is unavailable: it has been removed in Swift 3
【修正前】
if MAX_HISTORY <= ++gs.m_hisIdx {
【修正後】
gs.m_hisIdx += 1 // 3.1
if MAX_HISTORY <= gs.m_hisIdx { // 3.1
【解説】
++や–の演算子は廃止だそうです。わかりにくくなるからかしら・・・
変数の前の++は該当行より前で、変数の後の++は該当行より後でインクリメントします。デクリメントも同様です。

【エラーメッセージ】
‘bytes’ is unavailable: use withUnsafeBytes instead
【修正前】
var buffer : UnsafePointer = CFDataGetBytePtr(dataRef)
memcpy(&gs.m_buffer, Data(bytes: UnsafePointer(buffer), count : newX * newY * 4).bytes, newX * newY * 4)
【修正後】
var buffer : UnsafePointer = CFDataGetBytePtr(dataRef)
memcpy(&gs.m_buffer, buffer, newX * newY * 4)
【解説】
エラーの直接の解説ではないです。withUnsafeBytesを使う以前に、なんで一旦Data型に変換してたんだろう・・・
直接 UnsafePointer をmemcpyの引数に指定することでエラーを回避。動くかなぁ・・・

【エラーメッセージ】
NSString’ is not implicitly convertible to ‘String’; did you mean to use ‘as’ to explicitly convert?
【修正前】
let path = paths1[0].stringByAppendingPathComponent(NSString(format:”slot%02d.png”,index))
【修正後】
let path = paths1[0].stringByAppendingPathComponent(String(format:”slot%02d.png”,index)) // 3.1
【解説】
エラーの意味は「NSStringは暗黙的にStringへ変換できないよ?’as’使って明示的に変換したら?」です。stringByAppendingPathComponentの引数はStringですが、そこにNSStringを指定したのでこのエラーが発生しています。Stringにもformatがあるのでそれを使うようにし、NSStringを使わないように対処しました。

【エラーメッセージ】
GameViewController.swift:109:19: Method does not override any method from its superclass
【修正前】
override func supportedInterfaceOrientations() -> Int {
if UIDevice.current.userInterfaceIdiom == .phone {
return Int(UIInterfaceOrientationMask.allButUpsideDown.rawValue)
} else {
return Int(UIInterfaceOrientationMask.all.rawValue)
}
}
【修正後】
override var supportedInterfaceOrientations : UIInterfaceOrientationMask {
if UIDevice.current.userInterfaceIdiom == .phone {
return UIInterfaceOrientationMask.allButUpsideDown
} else {
return UIInterfaceOrientationMask.all
}
}
【解説】
サポートしている画面の向きを返すメソッド supportedInterfaceOrientations の定義が変わったようです。戻り値も変わってるので、それに合わせてreturnの内容も調整しました。

【エラーメッセージ】
‘CGContextSetLineDash’ is unavailable: Use setLineDash(self:phase:lengths:)
【修正前】
CGContextSetLineDash(UIGraphicsGetCurrentContext(), 0, len, len.count)
【修正後】
UIGraphicsGetCurrentContext()?.setLineDash(phase: 0.0, lengths: len) // 3.1
【解説】
そろそろ疲れてきました ←解説になってない
2年半ぶりのSwiftなので、オプショナル型とかアンラップとかの知識を付け直しました。

今日はもう寝ます。さようなら2017年7月15日。

・・・続く

実録! 2年半ぶりのSwiftアプリバージョンアップ 1日目 その3

コンバートしたらエラーが増えた!とか言っててもエラーが無くなる訳ではないので、諦めてエラー箇所を直していきます。

以下、修正したエラーです。

【エラーメッセージ】
GameScene.swift:218:19: Method does not override any method from its superclass
【修正前】
override func touchesBegan(_ touches: NSSet, with event: UIEvent) {
【修正後】
override func touchesBegan(_ touches: Set, with event: UIEvent?) { // 3.1
【解説】
メソッドの定義が変わったようです。overrideを指示しているにもかかわらず、該当のメソッドがスーパークラスに見つからないので怒られています。最新のメソッド定義に直しました。NSSetがSetになりました。Swift1.2でコレクションクラスSetが追加されたため、Objective-CからあったNSSetを置き換えたのでしょうね。UIEvent?は、オプショナル型というやつですね。nilを許容するようになりましたが、なぜ許容するようにしたんですかね?

【エラーメッセージ】
GameScene.swift:221:19: Method does not override any method from its superclass
【修正前】
override func touchesMoved(_ touches: NSSet, with event: UIEvent) {
【修正後】
override func touchesMoved(_ touches: Set, with event: UIEvent?) { // 3.1
【解説】
同じ

【エラーメッセージ】
GameScene.swift:224:19: Method does not override any method from its superclass
【修正前】
override func touchesEnded(_ touches: NSSet, with event: UIEvent) {
【修正後】
override func touchesEnded(_ touches: Set, with event: UIEvent?) { // 3.1
【解説】
同じ

【エラーメッセージ】
GameScene.swift:227:19: Method does not override any method from its superclass
【修正前】
override func touchesCancelled(_ touches: NSSet!, with event: UIEvent!) {
【修正後】
override func touchesCancelled(_ touches: Set, with event: UIEvent?) { // 3.1
【解説】
同じ

【エラーメッセージ】
Missing argument label ‘rawValue:’ in call
【修正前】
fileprivate let m_bitmapInfo : CGBitmapInfo = CGBitmapInfo(CGImageAlphaInfo.PremultipliedLast.rawValue)
【修正後】
fileprivate let m_bitmapInfo : CGBitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedLast.rawValue) // 3.1
【解説】
Swift3.0から、1つ目の引数にもラベル名が必要になったみたいです。今までは2つめ以降のみラベル名が必要だったので、統一感が出て良いのではないでしょうか。

【エラーメッセージ】
‘PremultipliedLast’ has been renamed to ‘premultipliedLast’
【修正前】
fileprivate let m_bitmapInfo : CGBitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedLast.rawValue)
【修正後】
fileprivate let m_bitmapInfo : CGBitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue) // 3.1
【解説】
‘PremultipliedLast’の先頭が小文字に変更になったとのことです。

【エラーメッセージ】
Value of type ‘String’ has no member ‘stringByAppendingPathComponent’
【エラー箇所】
let paths1 = NSSearchPathForDirectoriesInDomains(
FileManager.SearchPathDirectory.documentDirectory,
FileManager.SearchPathDomainMask.userDomainMask, true)
let path = paths1[0].stringByAppendingPathComponent(“settings.dat”)
【対策】
extension String {
func stringByAppendingPathComponent(_ path: String) -> String {
let nsSt = self as NSString
return nsSt.appendingPathComponent(path)
}
}
【解説】
NSSearchPathForDirectoriesInDomains の戻り値が、NSArray* から [String]に変わってしまったようです。で、stringByAppendingPathComponentはNSStringのメンバなので、呼べないと怒られています。対策として、エクステンションにより既存のStringクラスにstringByAppendingPathComponentを追加し、内部的にNSStringの該当メソッドを呼ぶようにしました。エクステンション素敵。なお、NSStringのメソッドstringByAppendingPathComponentは名前がappendingPathComponentに変更となったようです。

・・・続く

実録! 2年半ぶりのSwiftアプリバージョンアップ 1日目 その2

二度寝から目覚めたら、コンバートが終わっていました!
よかった!

Apple「変換しといたったで。ちゃんとどこ変わったか確認してな」

Apple「ちゃんと見てな。何かトラブっても知らんで」

いや変更箇所多すぎます。いちいち全部見てられないです・・・
「Save」ボタンを押下。

ワーニング2個、エラー51個

エラー増えました。

・・・続く

実録! 2年半ぶりのSwiftアプリバージョンアップ 1日目

2年半前にSwiftで作ったアプリのXcodeプロジェクトを開きます。

上記画像の通り、最終日付は2015年2月18日となっています。
果たして無事に開けるのでしょうか?

気合を入れてもうまく開ける訳ではありません。
無の心境で「xcodeproj」ファイルをダブルクリックします。

出ました!開いた途端にワーニング115個とエラー30個ですっ!
しかしそこはApple様、自動でコンバートしてくれるようです。
コンバートダイアログが出ましたっ!

「Swiftの構文を変換しますか?
ターゲット”Dotter”と”DotterTests”は以前のバージョンのSwiftコードを含みます。
これらのターゲットをSwift3に変換するには”Convert”を選択してください。
この操作は、後でエディットメニューの”Convert to Current Swift Syntax”
から実行できます。」

超気合を入れて”Convert”ボタンを押します!

・・・

・・

8時7分:”Convert”ボタンを押下
8時8分:あれ?これ動いてるのかな?不安になる
8時12分:眠くなってきた
8時19分:二度寝に突入

・・・続く

AI、Automata、Deep Learningなど、興味のあることを題材にスマホのアプリを制作する個人開発者のブログ