- 投稿日:2020-09-09T23:12:42+09:00
Xib で View を生成する時に知っておくといいこと
先日、うっかり
ViewClass
で指定したXib
で作成したカスタムビューのプロパティにアクセスし下記のように Error を出してしまいました。初心者の頃はよく分からずググったものコピペ なりで回避していましたが、そもそも何故これが Error になるのかを今回は紹介したいと思います。override func viewDidLoad() { super.viewDidLoad() hogeView.label.text = "hogehoge" } // Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional valueXib で View を生成する方法
Xib で View を生成する方法は、大きく分けて
View Class
とFile's Owner
を指定する2通りの方法があります。View Class
View Class
は xib上の View に対してクラスを指定することで直接的に xib と参照を作ることができます。この方法は UINib を使用して View をコードから初期化する場合は、全てのオブジェクトが使用できる状態になっていることが担保されますが、Storyboard
・xib
などで直接 View を生成する場合は基本的にはnib-loading
ではない(init(decode: NSCoder)
)ため、全ての View が初期化されていることは担保されません。(これが、View Class
で作成したカスタムビューなどを Xib 上で生成した時に、プロパティへの参照が nil でクラッシュする原因と考えられます)The order in which the nib-loading code calls the awakeFromNib methods of objects is not guaranteed. In OS X, Cocoa tries to call the awakeFromNib method of File’s Owner last but does not guarantee that behavior. If you need to configure the objects in your nib file further at load time, the most appropriate time to do so is after your nib-loading call returns. At that point, all of the objects are created, initialized, and ready for use.
つまり、基本的に
View Class
を指定した View を使用する場合には、コードでインスタンスを生成して使用する必要があります。例えば、UITableViewCell
などは再利用性がありコードから生成するのが適切なので、 Xcode のテンプレートなどでも用意されていると考えられます。File's Owner
File's Owner
は名前の通り Xib 上の View の保持先を指定することで、@IBOutlet
などでクラスに View の参照を付けることができます。つまり、クラスと Xib で作成された View のインスタンスは別なので、nib-loading
を適切なタイミングで呼ぶことで View の全てのオブジェクトが使用可能になっていることが担保されます。これによって、コードから生成される時は、init(frame: CGRect)
で、Storyboard
・Xib
から生成される時はinit?(coder aDecoder: NSCoder)
から View を生成することができるようになります。参考
- 投稿日:2020-09-09T21:21:22+09:00
[アプリ開発]DKImageViewControllerで写真共有アプリ作ってみた
作った経緯
初めて作ったファッションアプリが途中でできなかったので、なんかCollectionView使ってやりたいなーって思いましてインスタみたいな写真共有アプリ作ろうと決心して開発しました!
参考にしたアプリはtabiori
さんですとりあえず作ってみた!
こんな感じでやろうと思ったら、、、ア〜〜〜〜〜〜!!!!
ViewControlller多くなりすぎて訳わからんくなった。。
初心者の僕はパンクしました
っていう感じで何回もプロジェクト作り直しました。インスタの写真一枚だけならスクールの教材で出来るんですが、何せConllectionView使いたかったんでタイムラインの画像を複数表示出来るようにさせたかったんですよね
問題
このアプリ開発するのめっちゃ時間かかりましたこれにスクールの大半の時間消費しましたねw
まずFirebaseですね
ライブラリの画像を選択→CollectionViewで表示→ボタン選択→Firebaseに保存したかったんですがうまくいかず諦めて、
日付とタイトル、画像を別の画面でやろうとするとすごくめんどくさくなったり、、、
いろいろうまくいきませんでした。
でライブラリから写真を複数選択しようとすると標準?のライブラリだとできないしDKImagePickerControllerの極意
http://cocoadocs.org/docsets/DKImagePickerController/3.8.1/ライブラリから選択した画像を画面遷移先にデータを送るとかファイル形式違うものに渡しかたとか、、
どうやってやんの〜〜〜って感じですCollectionViewとTableViewの組み合わせ極意
https://qiita.com/Erica_pon/items/f1c6a06e399723f32549こちらを参考にタイムライン画面でcolleCtionViewとTableView使ってモデル作ってみたけど画像表示できず開発休止しました、、、
結果
リリースはできてません!!
まだ未完成です
諦めませんねー
よかったらみてください!今回作ったリポジトリ
https://github.com/rentamaeda/Travel2感想
なんでもそうですが作る前の計画練るのって楽しいですよねー
なんか途中から気持ち折れてしまって何回も諦めかけましたよ次もしollectioView使うならタスク管理アプリ作りたいなーって思いますw
参考にしたサイト等
taiori様HP
https://tabiori.com/DKImagePickerControllerの極意
http://cocoadocs.org/docsets/DKImagePickerController/3.8.1/CollectionViewとTableViewの組み合わせ極意
https://qiita.com/Erica_pon/items/f1c6a06e399723f32549
- 投稿日:2020-09-09T20:51:08+09:00
【入門】iOS アプリ開発 #8【スペシャルターゲット・得点表示】
はじめに
今回はスペシャルターゲット(くだもの類)、パックマンがそれを食べた時やゴーストを噛み付いた時の得点表示を作成していく。
イメージ図は下記の通り。スペシャルターゲット(サクランボ表示)と、それをパックマンが食べると得点が表示される。
仕様書
まずはスペシャルターゲットの仕様の確認。
表示されている間にパックマンが食べると、得点が2秒だけ表示される(仕様にはないが実機確認)。
続いてパックマンがゴーストを噛み付いた時の得点表示の仕様は以下の通り。
こちらの得点表示は1秒間だけ(同じく仕様にはないが実機確認)。
設計方針
スペシャルターゲットは、パックマンがエサを70個、170個食べた時に、
specialTarget.start(kind: .Cherry)とするとサクランボのスペシャルターゲットが表示され、10秒後に自動的に消えるように設計したい。
表示されている間にパックマンが食べると 100 というように得点表示する。表示の時間は2種類(2秒、1秒)あるため、得点の種類や位置に加えて時間指定も行えるようにする。
ptsManager.start(kind: .pts100, position: specialTarget.position, interval: 2000) // 2000msといった具合にする。
得点表示は、スペシャルターゲットとゴースト4匹を連続で噛みつくとして、最大5つまで同時表示できるように設計しておきたい(実際はゴーストをかみついている間はパックマンの動きが停止するため、実質2つまでとなる)。クラス構造
スペシャルターゲットと得点表示は、CgActorクラスを継承して作成する。
得点表示は CgScorePtsクラスとして、これを最大5つ表示する管理クラス CgScorePtsManager を作成する。
CgSpecialTarget クラス
スペシャルターゲット(くだもの類)を表示する。
class CgSpecialTarget : CgActor { enum EnSpecialTarget: Int { case Cherry case Strawberry case Orange case Apple case Melon case Galaxian case Bell case Key case None func getScorePts() -> CgScorePts.EnScorePts { switch self { case .Cherry: return .pts100 case .Strawberry: return .pts300 case .Orange: return .pts500 case .Apple: return .pts700 case .Melon: return .pts1000 case .Galaxian: return .pts2000 case .Bell: return .pts3000 case .Key: return .pts5000 case .None: return .pts0 } } func getTexture() -> Int { switch self { case .Cherry: return 16*3+2 case .Strawberry: return 16*3+3 case .Orange: return 16*3+4 case .Apple: return 16*3+5 case .Melon: return 16*3+6 case .Galaxian: return 16*3+7 case .Bell: return 16*3+8 case .Key: return 16*3+9 case .None: return 16*3+10 } } } private var kindOfSpecialTarget: EnSpecialTarget = .None private var timer_disappearSpecialTarget: CbTimer! override init(binding object: CgSceneFrame, deligateActor: ActorDeligate) { super.init(binding: object, deligateActor: deligateActor) timer_disappearSpecialTarget = CbTimer(binding: self) actor = .SpecialTarget sprite_number = actor.getSpriteNumber() } // ============================================================ // Core operation methods for actor // - Sequence: reset()->start()->update() called->stop() // ============================================================ /// Reset special target state. override func reset() { super.reset() timer_disappearSpecialTarget.set(interval: 10000) // 10s timer_disappearSpecialTarget.reset() position.set(column: 13, row: 15, dx: 4) } /// Start to draw special target at the specified position. override func start() { super.start() timer_disappearSpecialTarget.start() deligateActor.setTile(column: position.column, row: position.row, value: .Fruit) sprite.draw(sprite_number, x: position.x, y: position.y, texture: kindOfSpecialTarget.getTexture()) } /// Update handler /// - Parameter interval: Interval time(ms) to update override func update(interval: Int) { if timer_disappearSpecialTarget.isEventFired() { stop() } } /// Stop drawing special target. override func stop() { super.stop() timer_disappearSpecialTarget.stop() deligateActor.setTile(column: position.column, row: position.row, value: .Road) sprite.clear(sprite_number) } // ============================================================ // General methods in this class // ============================================================ func start(kind: EnSpecialTarget) { kindOfSpecialTarget = kind self.start() } }コンストラクタで CbTimer を生成して、reset() メソッドで 10秒を設定しておく。
start()メソッドが呼ばれたら、スプライトを表示しタイマーを起動させる。
update()メソッドがフレーム毎に呼ばれる中でタイマーが 0 になったら、内部のstop()メソッドを呼びスプライトを消去する。簡単なコードとなった。
CgScorePts クラス
得点クラスはスペシャルターゲットとほぼ同様のコードとなる。1000点以上はスプライトを2つ使って表示する。
class CgScorePts : CgActor { enum EnScorePts: Int { case pts100 = 0 case pts200 case pts300 case pts400 case pts500 case pts700 case pts800 case pts1000 case pts1600 case pts2000 case pts3000 case pts5000 case pts0 func getScore() -> Int { switch self { case .pts100 : return 100 case .pts200 : return 200 case .pts300 : return 300 case .pts400 : return 400 case .pts500 : return 500 case .pts700 : return 700 case .pts800 : return 800 case .pts1000 : return 1000 case .pts1600 : return 1600 case .pts2000 : return 2000 case .pts3000 : return 3000 case .pts5000 : return 5000 case .pts0 : return 0 } } func get2times() -> EnScorePts { switch self { case .pts100 : return .pts200 case .pts200 : return .pts400 case .pts400 : return .pts800 case .pts800 : return .pts1600 default : return self } } func getTextures() -> (Int, Int) { switch self { case .pts100 : return (16*9 , 0) case .pts200 : return (16*8 , 0) case .pts300 : return (16*9+1 , 0) case .pts400 : return (16*8+1 , 0) case .pts500 : return (16*9+2 , 0) case .pts700 : return (16*9+3 , 0) case .pts800 : return (16*8+2 , 0) case .pts1000 : return (16*9+4 , 16*9+5) case .pts1600 : return (16*8+3 , 0) case .pts2000 : return (16*10+4, 16*10+5) case .pts3000 : return (16*11+4, 16*11+5) case .pts5000 : return (16*12+4, 16*12+5) case .pts0 : return (16*9 , 0) // } } } private var ptsNumber: EnScorePts = .pts0 private var timer_disappearPts: CbTimer! override init(binding object: CgSceneFrame, deligateActor: ActorDeligate) { super.init(binding: object, deligateActor: deligateActor) timer_disappearPts = CbTimer(binding: self) actor = .Pts } // ============================================================ // Core operation methods for actor // - Sequence: reset()->start()->update() called->stop() // ============================================================ /// Reset override func reset() { super.reset() } /// Start override func start() { super.start() timer_disappearPts.start() let textures: (Int,Int) = ptsNumber.getTextures() sprite.draw(sprite_number, x: position.x, y: position.y, texture: textures.0) if textures.1 != 0 { sprite.draw(sprite_number+1, x: position.x+16, y: position.y, texture: textures.1) } } /// Update handler /// - Parameter interval: Interval time(ms) to update override func update(interval: Int) { if timer_disappearPts.isEventFired() { stop() } } /// Stop override func stop() { super.stop() timer_disappearPts.reset() sprite.clear(sprite_number) if ptsNumber.getTextures().1 != 0 { sprite.clear(sprite_number+1) } } // ============================================================ // General methods in this class // ============================================================ func start(kind: EnScorePts, position at: CgPosition, interval time: Int) { ptsNumber = kind timer_disappearPts.set(interval: time) self.position.set(at) start() } }CgScorePtsManager クラス
CgScorePts のオブジェクトを5つ管理して表示の制御を行う。
class CgScorePtsManager: CbContainer { private let firstSpriteNumber: Int = CgActor.EnActor.Pts.getSpriteNumber() private let numberOfActors: Int = 5 private var actors: [CgScorePts] = [] private var doings: [CgScorePts] = [] init(binding object: CgSceneFrame, deligateActor: ActorDeligate) { super.init(binding: object) for i in 0 ..< numberOfActors { let actor: CgScorePts = CgScorePts(binding: object, deligateActor: deligateActor) actor.sprite_number = firstSpriteNumber+i*2 actors.append(actor) } } /// Reset func reset() { for each in actors { each.reset() } self.enabled = false } /// Start to draw Pts /// - Parameters: /// - kind: Kind of pts /// - position: Position to draw /// - time: Time to disappear func start(kind: CgScorePts.EnScorePts, position: CgPosition, interval time: Int) { let actor: CgScorePts if actors.count == 0 { actor = doings.remove(at: 0) actor.stop() } else { actor = actors.remove(at: 0) } actor.start(kind: kind, position: position, interval: time) doings.append(actor) self.enabled = true } /// Update handler /// - Parameter interval: Interval time(ms) to update override func update(interval: Int) { for each in doings { if !each.enabled { actors.append(each) doings.remove(at: 0) } } if doings.count == 0 { enabled = false } } /// Stop func stop() { for each in doings { each.stop() actors.append(each) doings.remove(at: 0) } self.enabled = false } }コンストラクタで CgScorePtsオブジェクトを5つ生成して actors配列に格納しておく。
start()メソッドが呼ばれた時に、actors配列から CgScorePtsオブジェクトを取り出して start()し、doings配列へ移動しておく。enabled = true にすると、フレーム毎に update()が呼ばる。
update()メソッド内では、doings配列にある CgScorePtsオブジェクトが stop(enabled= false) していたら、doings 配列から actors へ戻しておく。start()メソッドが呼ばれた時に5つ全部表示中で actors配列から取り出されなかった場合は、doings から1つ持ってきて使いまわす。
まとめ
スペシャルターゲットと得点表示のクラスを作成した。作成したソースコードは合計300行程度。
必要な役者はそろったので、次回はこれら役者のシーケンス動作を組み合わせて、ゲームとしてプレイできるものを作成していく。
- 投稿日:2020-09-09T20:41:36+09:00
CryptoKit を使って ECDSA を用いた署名を実装する
ECDSAでJWTによる電子署名を作成することがあったのでメモ
ECDSAとは
楕円曲線暗号(だえんきょくせんあんごう、Elliptic Curve Cryptography、ECC)とは、 楕円曲線 上の 離散対数問題 (EC-DLP) の困難性を安全性の根拠とする 暗号。
- 暗号化と復号とで異なる2つの鍵を使用し、暗号化の鍵を公開できるようにした公開鍵暗号
- RSA暗号 と比べて、短いデータ長で処理速度も早いが同レベル安全性が実現できるのがメリット
CryptoKit を使ってやること
CryptoKitを使って以下の手順により署名を作成していきます。
- 鍵の生成
- 電子署名の作成
全体のコードはこちらに上げてあります。
Key Pairの生成
CryptoKitの P256.Signing.PrivateKey を使えば2行で書けます
let privateKey = P256.Signing.PrivateKey() let publicKey = privateKey.publicKey
P256.Signing
はECDSA(P-256)を使用した署名、検証のためのもの
P256.KeyAgreement という鍵交換に使うためのものも用意されている署名の作成
JWT Header
Headerに指定できる各パラメータについての説明は Registered Header Parameter Names を参照
例{ "alg": "ES256", "typ": "JWT" }JWT Claims
Claimsに指定できる各パラメータについての説明は Registered Claim Names を参照
例{ "aud": "my-project", "iat": 1509650801, "exp": 1509654401 }JWT signature
ECDSA P-256 の署名の例が JSON Web Signature (JWS) に書いてあります
Base64urlエンコードした JWT Header と JWT claims を.
でつなげたものを署名し、以下の順序で.
でつないだものがJWTとなります{base64url-encoded header}.{base64url-encoded claim set}.{base64url-encoded signature}署名にはP256.Signing.PrivateKey が持っている signature(for:) を使います
let input = "{base64url-encoded header}.{base64url-encoded claim set}" let data = input.data(using: .utf8)! let signature = try privateKey.signature(for: data)ここで注意が必要なのが署名のフォーマットです。
ECDSA P-256 SHA-256の署名は、符号なし整数のECポイント(各32オクテットのR, S)で表されます。
signature(for:) が返してくる P256.Signing.ECDSASignature は derRepresentation と rawRepresentation の2つをもっていて、それぞれ以下の値が返ってきます。
derRepresentation
ASN.1 DER でフォーマットされた署名rawRepresentation
署名のrawデータECDSA P-256 SHA-256の署名では
rawRepresentation
の方を使用します。
先述の通りJWTはbase64urlエンコードした header と claim set と signature をドットでつなぐのでextension Data { func base64urlEncodedString() -> String { return base64EncodedString() .replacingOccurrences(of: "+", with: "-") .replacingOccurrences(of: "/", with: "_") .replacingOccurrences(of: "=", with: "") } }let raw = signature.rawRepresentation let signedJWT = "\(input).\(raw.base64urlEncodedString())"これでJWTの完成です。
ちなみに、ASN.1 DERフォーマットは以下のような構造になっており、この中からrとsを取り出しつなげたものが
rawRepresentation
に相当します。
0x30|b1|0x02|b2|r|0x02|b3|s
- b1: 0x02以降に続くバイト列の長さ
- b2: rのバイト列の長さ
- b3: sのバイト列の長さ をそれぞれ表す
この仕様に基づいて以下のように
derRepresentation
から raw data に変換してみるとrawRepresentation
と同じ値が得られます。let der = signature.derRepresentation let sequence = der.removeFirst() // 0x30 let b1 = der.removeFirst() let tag1 = der.removeFirst() // 0x02 let b2 = der.removeFirst() var r = der.prefix(Int(b2)) der = der.advanced(by: Int(b2)) let tag2 = der.removeFirst() // 0x02 let b3 = der.removeFirst() var s = der.prefix(Int(b3)) let octetLength = 32 guard r.count <= octetLength + 1, s.count <= octetLength + 1 else { throw SignatureError.invalidLength } r = (r.count == octetLength + 1) ? r.dropFirst() : r s = (s.count == octetLength + 1) ? s.dropFirst() : s return r+sSecurity Frameworkの SecKeyCreateSignature を使う場合、こちらは ASN.1 DERフォーマット で返されるので上記のような変換が必要です。
CryptoKitを使えばsignature.rawRepresentation
だけで取得できるのですごく便利です。まとめ
CryptoKitを使うと鍵の生成や署名が1行くらいでできてしまうのでとても簡単に使えて驚きました。
ただし、CryptoKitで生成した鍵は自分で保存する必要があるのでそれを考えると生成はSecurity Frameworkの SecKeyGeneratePair をつかってもいい気がしました。
CryptoKitで生成した鍵をキーチェーンに保存する方法はAppleがSampleコードを提供しているのでこれを参考に。
Storing CryptoKit Keys in the Keychain
- 投稿日:2020-09-09T19:22:48+09:00
EXC_BAD_ACCESSの対処法【All Exceptionの設定は不要らしい。】
EXC_BAD_ACCESSとは?
通常、エラーメッセージを持たないため、デバッグするのが最も難しい種類のクラッシュ。
でも、Swiftでは非常に稀。要点
All Exception
を使わずとも、EXC_BAD_ACCESSのエラー箇所が出るらしい。
※ Xcode ver 11.7下部にエラー表示。僕の場合は、不適切な
self
が原因でした。
ご覧の通り、Navigator AreaではAll Exception
の設定はしていません。デバッグ方法
どのバージョンから上記の仕様になったか知りませんが、
少なくともXcode 9.4.1ではAll Exceptionの設定が必要だったらしい。↓【Xcode】All Exceptionの設定方法(デバッグ)
バージョンによって、
Exception Breakpoint
とか、Add Exception BreakPoint
とか。
表示は異なれど、やることは同じ。
EXC_BAD_ACCESS対処時には、これらのサイトを参考にしました。
Xcodeでデバッグ実行中にクラッシュした時に捗るブレークポイント設定
おしまい。
- 投稿日:2020-09-09T19:22:48+09:00
EXC_BAD_ACCESSの対処法
EXC_BAD_ACCESSとは?
通常、エラーメッセージを持たないため、デバッグするのが最も難しい種類のクラッシュ。
でも、Swiftでは非常に稀。要点
All Exception
を使わずとも、EXC_BAD_ACCESSのエラー箇所が出るらしい。
※ Xcode ver 11.7下部にエラー表示。僕の場合は、不適切な
self
が原因でした。
ご覧の通り、Navigator AreaではAll Exception
の設定はしていません。デバッグ方法
どのバージョンから上記の仕様になったか知りませんが、
少なくともXcode 9.4.1ではAll Exceptionの設定が必要だったらしい。↓【Xcode】All Exceptionの設定方法(デバッグ)
バージョンによって、
Exception Breakpoint
とか、Add Exception BreakPoint
とか。
表示は異なれど、やることは同じ。
EXC_BAD_ACCESS対処時には、これらのサイトを参考にしました。
Xcodeでデバッグ実行中にクラッシュした時に捗るブレークポイント設定
おしまい。
- 投稿日:2020-09-09T18:53:33+09:00
"No .app bundles found in the package"でハマった話【Xcode / Flutter】
はじめに
Xcodeでアプリを
archive => validate => distribute
と順を追ってデプロイを行ったところ、最後の最後で以下のようなエラーが出ました。ERROR ITMS-90167: "No .app bundles found in the package"対処ストーリー
はじめは、stackoverflowで見かけた記事から、バージョンの問題かと思ったが、少し古い記事だったため、そうではないと判断しました。
私はFlutterで開発をしていたので、とりあえず再びAndroid Studioでビルドをして、シミュレータで動かして見ることにしました。すると、先ほどと何もコードを変えていないのに以下のようなエラーが出てきました。
Launching lib/main.dart on iPad (7th generation) in debug mode... Running pod install... Running Xcode build... Unhandled exception: FileSystemException: writeFrom failed, path = '/var/folders/p3/_58lqlc15y5ckhhzk0l03pgc0000gn/T/flutter_tools.klt2FV/flutter_tool.NhA7Gi/app.dill' (OS Error: No space left on device, errno = 28) #0 _RandomAccessFile.writeFrom.<anonymous closure> (dart:io/file_impl.dart:849:9) #1 _RootZone.runUnary (dart:async/zone.dart:1450:54) #2 _FutureListener.handleValue (dart:async/future_impl.dart:143:18) #3 Future._propagateToListeners.handleValueCallback (dart:async/future_impl.dart:696:45) #4 Future._propagateToListeners (dart:async/future_impl.dart:725:32) #5 Future._completeWithValue (dart:async/future_impl.dart:529:5) #6 Future._asyncCompleteWithValue.<anonymous closure> (dart:async/future_impl.dart:567:7) #7 _microtaskLoop (dart:async/schedule_microtask.dart:41:21) #8 _startMicrotaskLoop (dart:async/schedule_microtask.dart:50:5) #9 _runPendingImmediateCallback (dart:isolate-patch/isolate_patch.dart:118:13) #10 _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:169:5) Xcode build done. 4.9s the Dart compiler exited unexpectedly. the Dart compiler exited unexpectedly.何かがおかしいと思い、コードをよく読んでみると、このような一文がありました。
OS Error: No space left on device
ストレージがいっぱいだったようです、、、。
ストレージの容量を空けて、再びdistributeを行ったところ、成功しました!終わりに
はじめのエラー文から想像できないエラーだったため、まとめさせていただきました。
この技術記事が誰かのお役にたてば何よりです。
- 投稿日:2020-09-09T18:32:54+09:00
Swift基礎文法書に付箋を貼ってみた
Swiftの基礎文法書の復習
Swiftの基礎が学べる良書である「絶対に挫折しない iPhoneアプリ開発「超」入門 第8版 【Xcode 11 & iOS 13】 完全対応」を読み返しながら、開発していて注意すべきところや知らなかったところに付箋を貼ってみました。
for文
知らなかったのですが、forの後に続くのは定数らしいです。
以下、コード例です。
for 定数 in 範囲 { 処理 }一見、範囲の値の回数だけ値が変わっているように見えるので変数ではないかと思っていたのですがどうやら違うみたいです。
for文で使われている定数nは、for文が一度実行されるたびに寿命が切れて、メモリ上から消去され、そして次の文を実行する際に再度同名の定数nが新たに宣言されて・・・・・
プロックとスコープ
以下のコードで{}で囲われたところをブロックと呼びます。
プロックvar count = 0 for n in 1...5 { count += 1 } print(count) // => 0 countのスコープはプロックの中だけ知ってはいるんですけど、開発中についつい忘れてしまいます。
Swiftのswitch文にbreakいらない
以下、コード例です。
case文switch 式 { case 定数A: 処理A case 定数B: 処理B ... default: 処理X //breakいらない }なぜかついついbreakを書いてしまいます。
ちなみにSwiftでは一つのcase内の処理がされるとswitch文は終了します。forと配列
以下、コード例です。
配列を使用した繰り返し処理for 定数 in 配列 { 処理 }配列に入った各要素に対して処理を加えた時に使います。配列のキャストなどで使えます。また、配列のキャストではmap使ったりもします。
プロパティの種類
プロパティにもいろいろな種類があります。
1.ストアドプロパティ
構造体を定義する際にプロパティを定義しますが、ここで値を保持するためのプロパティをストアドプロパティと言います。
2.コンピューテッドプロパティ
そして、値を計算するためのプロパティをコンピューテッドプロパティと言います。
3.タイププロパティ
プロパティを分ける際、基準が値の持ち方による分け方か呼び出し元による分け方でわかれます。
値の持ち方による分け方
・ストアドプロパティ
or
・コンピューテッドプロパティ呼び出し元による分け方
・インスタンスプロパティ //インスタンスから呼び出すプロパティ つまり、ストアドorコンピューテッドプロパティのこと
or
・タイププロパティそして、このタイププロパティというのは構造体から直接呼び出すプロパティのことです。
プロパティの種類 コード例
以下、コード例です。
ストアドプロパティとコンピューテッドプロパティstruct Square { var length = 3 //一辺の長さ var area:Int { //面積 let result = length * length return result } } let square = Square() print(square.area) //92.コンピューテッドプロパティの特徴
上記のlengthがストアドプロパティで、areaがコンピューテッドプロパティです。
ストアドプロパティの方は馴染みがありますが、コンピューテッドプロパティは見たことがあるくらいです。
コンピューテッドプロパティには以下の特徴があります。
■固定の値は保持しない //つまり定数は指定できない
■呼び出されたタイミングで処理を行い、処理結果を戻す
■型名の省略が不可能
■文末に{}があるコンピューテッドプロパティはメソッドと似ていますね、同じ計算式を繰り返す時に使えそうです。
3.タイププロパティコード例
宣言時の式struct 構造体名 { static var タイププロパティ名 = 初期値 }タイ焼きに背びれがあるかどうかを確認するタイププロパティを以下に記述してみます。
タイ焼きに背びれがあるかどうかstruct Taiyaki { static var hasFin = true } Taiyaki.hasFin //trueになる初期値が決まっているものを指定して呼び出す際に安全です。(初期値として何が格納されているのかわかっているため。)
タイプメソッド
タイププロパティと考え方が全く一緒なので、載せておきます。実はメソッドも「インスタンスメソッド」もしくは「タイプメソッド」に分類できます。
インスタンスから呼びだすメソッド => 「インスタンスメソッド」
構造体から呼びだすメソッド => 「タイプメソッド」宣言(タイ焼きの例と合わせる)struct Taiyaki { static func hasFin() -> bool { return true } } Taiyaki.hasFin() //trueが返るイニシャライザのself
イニシャライザの引数と構造体の{ }内のプロパティ名が同じならselfで区別します。
構造体の{ }内でselfをつけるとそのselfは構造体から作成されたインスタンスを指します。以下、コード例です。
例struct Car { var forwardWheel: Int var backWheel: Int init(forwardWheel: Int, backWheel: Int) { self.forwardWheel = forwardWheel //self.forwardWheelが構造体のプロパティ self.backWheel = backWheel //backWheelはinitの引数 } }つまり、
■selfをつけた変数が「プロパティ」
■selfを付けない変数が「引数」ですデフォルトイニシャライザ
インスタンスの生成時に構造体()のようにコードを書けて、引数が1つもないinit()というイニシャライザが自動的に呼び出されます。このイニシャライザをデフォルトイニシャライザと言います。
構造体に含まれるすぺてのプロパティに初期値が設定されている場合に、デフォルトイニシャライザが自動的に呼び出されます。
イニシャライザを記述しない構造体class hoge { var fuga = "Hi!" var piyo = 123 }ここで重要なのはイニシャライザが必要ないというわけではないということです。
構造体のデフォルトイニシャライザclass hoge { var fuga = "Hi!" var piyo = 123 init() { } }クロージャ
正直、今までテキトーに理解していたので復習しました。
クロージャは言ってしまえば、名前のない関数みたいなもので文の中に埋め込めます。Firebaseの匿名ログインのコードAuth.auth().signInAnonymously() { (authResult, error) in // ... 引数"authResult"もしくは"error"に値が入った後に、ここの処理が行われる }さらに具体的に実装してみます。
Firebaseの匿名ログインのコード2Auth.auth().signInAnonymously() { (authResult, error) in if error != nil{ //errorに値が入った場合の処理 print("Auth error: \(error)") } else { print("success: \(authResult)") } }Firebaseの匿名ログイン実装時につかったので、公式ドキュメントを参考にしました。
参考:iOS で Firebase 匿名認証を行うなお、クロージャからプロパティにアクセスする場合selfがつきます。よくエラーメッセージでますよね笑
列挙型
enumとうやつです。理解するのにかなり時間かかりました。これ。
enumとは簡単に言えば、新しい型を独自に作成するようなものです。Week型の宣言enum Result { //Result型 case true //値名 case false case other } //インスタンス作成時 var result = Result.true //値の result = .false //このように仮にResultという型を作りたいとして、値がtrueとfalseとotherしか値に取れなくなり、意図しない値が入るのを防げます。私は具体的にはMVCを学習している時にJSONの変換を通して学んだので、別記事にしてまとめたいです。
型の入れ子の書式でCodingKeyを使いました。型の入れ子とCodingKeysstruct 構造体 { private enum CodingKeys:String CodingKey { case title case url ... } }2回CodingKeyが記述されてるように見えますが、これはCodingKeysという名でString型のCodingKeyを宣言しているという意味になります。
オプショナル
オプショナルについては別で記事を書きました。
オプショナルへの予想を上回る気遣い構造体は「値型」、クラスが「参照型」
構造体とclassは同じものに見えて、微妙に違います。
これに関してはかなりわかりやすい記事を見つけたので、載せておきます。
【swift】イラストで分かる!classとstructの違いについて【初心者向け】プロトコル
MVCの設計を学習する時やtable周りの学習で目にしていると思います。
イメージは構造体で定義したメソッドを渡してあげるものという感じです。構造体とプロトコルの宣言//Model protocol HogeDelegate { func fuga(with someData: Data) //メソッド名のみ } struct HogeModel { weak var delegate: HogeDelegate? //弱参照で記述(弱参照についての説明は省きます) func fuga() { 処理 } } //ViewController private let hogeModel = HogeModel() //privateの説明も省きます class piyoViewController: UIViewController, HogeDelegate{ //extensionさせて記述できるとより見やすくなります。 override func viewDidLoad() { super.viewDidLoad() hogeModel.delegate = self hogeModel.fuga() } // func fuga(with someData: Data) { //with someDataに対する処理 } } /*extensionで記述した場合 extension ItemsViewController: HogeDelegate { func fuga(with someData: Data) { //with someDataに対する処理 } } */上記は、fuga()メソッドがviewDidLoad()で呼び出され、Modelのfuga()メソッドが処理されます。その処理結果をHogeDelegateに渡し、ViewControllerに通知(渡す)してあげています。
プロトコルのストアドプロパティ
またまた、ストアドプロパティとコンピューテッドプロパティがでてきました。プロトコルでのストアドプロパティは少し特殊な宣言をします。
プロトコルでのストアドプロパティprotcol HogeDelegate { var ストアドプロパティ: 型 { get set } //getは読み込み、setは書き込みの意味。読み込みだけさせたいのなら、{ get }にする }{ get set }がプロトコルで、でてきたらストアドプロパティだ!と思い出せれば大丈夫ですね。
プロトコルでのコンピューテッドプロパティprotcol HogeDelegate { var コンピューテッドプロパティ: 型 { get } //型指定必須 }タプル
例var person = (name: "山田太郎", age: 27) //変数名 = (要素名(key):値(value), 要素名:値...) print(person.name) //変数.要素名で取り出しあまり使わないかもしれませんが、一応記述します。
配列や辞書のようにタプルは複数の値をまとめて格納することができます。異なる点は、タプルには異なる型の要素を格納できること。また、要素の追加・削除が行えないことです。これから
特に以下の文法に関するキーワードは別途記事を書いて思考を再度整理していこうと思います。
- classとstruct
- Guard let文
- enum(列挙型)
- varとlet
- publicとかprivateのアクセス修飾子
- final
- 弱参照、強参照
- Any型について他にもSwiftの理解を深めるために、「[改訂新版]Swift実践入門 ── 直感的な文法と安全性を兼ね備えた言語 (WEB+DB PRESS plus)」を読んでいこうと思います。
- 投稿日:2020-09-09T15:41:47+09:00
【IOU】 Object Detection の性能指標【I/U】
ユニオン交差 Intersection Over Union (IOU) が一般的なようです。
IOU = 正解boxと予測boxの重なりあう領域 / 正解boxと予測boxの重なり合っている部分と重なり合っていない部分の和
つまり、IOUが100%であれば、完璧に正解と予測が一致しているということです。
CreateMLの正解率は、このIOUが50%を越えているもの(50%重なっているもの)を正解とみなして%表示しています。
I/U 50%
87%というのは50%領域が重なっているケースが87%あったということです。
もう一つの指標、varied I/Uというのは、閾値を50%〜95%まで変化させた時の平均正解率。
これが低いと、重なっている部分はあるものの、重なる領域は狭い、ということになります(もちろん50%閾値の正解率以下になります)。
Core MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。
- 投稿日:2020-09-09T11:47:07+09:00
[Swift]ColorをRGBで指定する
はじめに
SwiftでcolorをRGBで指定する方法を説明します。
Swiftでのcolor指定
まず通常の指定方法は
ViewController.swiftsampleView.backgroundColor = .blue sampleView.backgroundColor = UIColor(red: 0.1, green: 0.5, blue: 1.0, alpha: 1.0) sampleView.backgroundColor = UIColor(red: 30/255, green: 144/255, blue: 255/255, alpha: 1.0)下に載せている記事が参考になるかと思いますが、SwiftでRGB指定しようとすると全ての値を255で割って分数表記にしないといけないため、若干めんどくさいです。
@shu26 さんのQiita
https://qiita.com/shu26/items/bc0a8a06019b24d799d4そこで、RGBの値をそのまま利用するためのextensionを作成します。
255で割るためのextensionを作成する
UIColorとかの名前をつけたSwiftファイルに、extensionを作成します。
UIColor.swiftimport UIKit extension UIColor { static func rgb(red: CGFloat, green: CGFloat, blue: CGFloat) -> UIColor{ return self.init(red: red / 255, green: green / 255, blue: blue / 255, alpha: 1) } }Viewの色を変更してみる
作成したextensionを使ってViewの色を変更してみます。
ViewController.swiftsampleView.backgroundColor = UIColor.rgb(red: 121, green: 162, blue: 255)
- 投稿日:2020-09-09T11:35:57+09:00
Colab で TuriCreate ObjectDetection -> CoreML
はじめに
Google Colab で TuriCreate を使って Object Detection のトレーニングを行い、iOS 向けの CoreML Model(.mlmodeol) を出力するまで行った時のメモです
事前作業
画像データのラベリング
学習に使用する画像に対するラベリング作業は別途必要となります。
今回は、simple_image_annotator を使用して annotatioon を用意しました。Google Colab
ランタイムタイプの変更
今回は、GPU を使用するため、Google Colab のランタイムタイプを変更します
Google Colab のメニューから [ランタイム] → [ランタイムのタイプを変更] を選択
ハードウェアアクセラレータを 「None」 から 「GPU」 に変更して 「保存」 をクリックしてください。
初期設定
Turi Create のインストール
Turi Create のインストールを行います
!pip install -U turicreateCuda 8 のインストール
Cuda 8 を利用するため、デフォルトでインストールされている mxnet をアンインストールして、Cuda 8 のインストールを行います。
!pip uninstall -y mxnet !pip install mxnet-cu100==1.4.0.post0 !export LD_LIBRARY_PATH=/usr/local/cuda/lib64:$LD_LIBRARY_PATHTensorFlow GPU のインストール
TensorFlow で GPU を利用するため、デフォルトでインストールされている tensorflow をアンインストールして、tensorflow-gpu をインストールします
!pip uninstall -y tensorflow !pip install tensorflow-gpuTuri Create の初期化
import mxnet as mx import turicreate as tc # Use all GPUs(default) tc.config.set_num_gpus(-1)Google Drive のマウント
今回は、学習に使用する画像データを Google Drive に配置して動かすため Google Drive をマウントします
from google.colab import drive drive.mount('/content/gdrive')SFrame の生成
事前作業のラベリングで生成された annotation ファイルを利用します。
annotation ファイルを指定して、Google Drive 上にアップロードしますfrom google.colab import files import os uploaded = files.upload() for fn in uploaded.keys(): os.rename(fn, 'out.csv')今回は、学習に使用する画像は Google Drive 上の images フォルダを想定して IMAGE_DIR にしていますが、適宜変更してください
# 学習に使用する画像のディレクトリ IMAGE_DIR = '/content/gdrive/My Drive/images' # annotation file csv_path = 'out.csv' csv_sf = tc.SFrame.read_csv(csv_path) def row_to_bbox_coordinates(row): """ Takes a row and returns a dictionary representing bounding box coordinates: (center_x, center_y, width, height) e.g. {'x': 100, 'y': 120, 'width': 80, 'height': 120} """ return {'x': row['xMin'] + (row['xMax'] - row['xMin'])/2, 'width': row['xMax'] - row['xMin'], 'y': row['yMin'] + (row['yMax'] - row['yMin'])/2, 'height': (row['yMax'] - row['yMin'])} csv_sf['coordinates'] = csv_sf.apply(row_to_bbox_coordinates) # delete no longer needed columns del csv_sf['id'], csv_sf['xMin'], csv_sf['xMax'], csv_sf['yMin'], csv_sf['yMax'] # rename columns csv_sf = csv_sf.rename({'name': 'label', 'image': 'name'}) # Load all images in random order sf_images = tc.image_analysis.load_images(IMAGE_DIR, recursive=True, random_order=True) # Split path to get filename info = sf_images['path'].apply(lambda path: os.path.basename(path).split('/')[:1]) # Rename columns to 'name' info = info.unpack().rename({'X.0': 'name'}) # Add to our main SFrame sf_images = sf_images.add_columns(info) # Original path no longer needed del sf_images['path'] # Combine label and coordinates into a bounding box dictionary csv_sf = csv_sf.pack_columns(['label', 'coordinates'], new_column_name='bbox', dtype=dict) # Combine bounding boxes of the same 'name' into lists sf_annotations = csv_sf.groupby('name', {'annotations': tc.aggregate.CONCAT('bbox')}) # Join annotations with the images. Note, some images do not have annotations, # but we still want to keep them in the dataset. This is why it is important to # a LEFT join. sf = sf_images.join(sf_annotations, on='name', how='left') # The LEFT join fills missing matches with None, so we replace these with empty # lists instead using fillna. sf['annotations'] = sf['annotations'].fillna([]) # Save SFrame sf.save('lifull.sframe')学習作業
SFrame からデータを読み込んでトレーニングを開始します。
BATCH_SIZE, MAX_ITERATIONS は適宜変更してから実行してくださいBATCH_SIZE=32 MAX_ITERATIONS=1000 import turicreate as tc # Load the data data = tc.SFrame('lifull.sframe') # Make a train-test split train_data, test_data = data.random_split(0.8) # Create a model model = tc.object_detector.create(train_data, batch_size=BATCH_SIZE, max_iterations=MAX_ITERATIONS) # Save predictions to an SArray predictions = model.predict(test_data) # Evalute the model and save the results into a dictionary metrics = model.evaluate(test_data) print(metrics) # Save the model for later use in Turi Create model.save('lifull.model') # Export for use in Core ML model.export_coreml('LifullObjectDetector.mlmodel')動作確認
出力された CoreML Model(.mlmodel) を Google Drive へコピーします
!mv LifullObjectDetector.mlmodel /content/gdrive/My\ Drive/images/コピーされた CoreML Model(.mlmodel) を Apple のサンプル に組み込んでお試しください
- 投稿日:2020-09-09T09:52:25+09:00
360度、デバイスの向きを測定する時の【Tips】
Core MotionのDevice MotionかARKitでデバイスの向きはとれます。
一周は6.28318531ラジアンです。
開始原点が0ラジアン。
デバイスのY軸を中心に回転させた場合、
右回りだとラジアンが加算。
左回りだとラジアンが減算されます。ただし、半周で+-が逆転します。
右回りの場合、3.14ラジアンまで加算すると、次は-3.14ラジアンになります。← -3.13 ← -3.14 ← 分水嶺 ← 3.14 ← 3.13
という具合です。
条件式で右回りを判別する場合、加算しているかにくわえて、分水嶺の部分も判別しておく必要があります。
if rotation.y > recentRotation.y || (recentRotation.y > 3 && rotation.y < 0) { // 現在のラジアンが以前のラジアンより大きい、または、以前のラジアンが3以上で現在のラジアンがマイナス値の場合、右回り print("turning right!") }分水嶺を越えてしまえば、以降右回りは加算、左回りは減算なのは同じです。
Core MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。
- 投稿日:2020-09-09T00:16:43+09:00
【Swift5】FSCalendarのカレンダーに点マークを表示する
はじめに
カレンダー付きToDoアプリを制作する際にFSCalendarというライブラリを使用しましたので備忘録として投稿します。
初学者ですので訂正点ございましたら、ご指摘よろしくお願いします。概要
FSCalendarではカレンダーの日付の下に任意の条件で点マークを表示することができます。
制作したアプリの用途に絡めると「ToDoの登録がある日付に点マークを表示」になります。今回はRealmSwiftを組み合わせての実装になりますのでRealmSwiftに関しましてはこちら参照ください。
また、FSCalendarの導入に関しましてはこちらを参照ください。実行環境
【Xcode】 Version 11.7
【Swift】 version 5.2.4
【CocoaPods】version 1.9.3
【RealmSwift】 version 5.3.2
【FSCalendar】version 2.8.1実装コード
全体のコードになります。
サンプルコードではなく、自作アプリのコードになりますので関連箇所を抜粋しております。
また、FSCalendarのDelegateとDataSourceはstoryboard上で追加しております。Todo.swiftimport Foundation import RealmSwift class Todo: Object { # ・・・省略・・・ @objc dynamic var dateString: String! # ・・・省略・・・ }MainViewController.swiftclass MainViewController: UIViewController { @IBOutlet weak var calendar: FSCalendar! # ・・・省略・・・ var datesWithEvents: Set<String> = [] # ・・・省略・・・ override func viewDidLoad() { super.viewDidLoad() # ・・・省略・・・ } } extension MainViewController: FSCalendarDelegate, FSCalendarDataSource, FSCalendarDelegateAppearance { // 任意の日付に点マークをつける func calendar(_ calendar: FSCalendar, numberOfEventsFor date: Date) -> Int{ let formatter = DateFormatter() formatter.dateFormat = "yyyy/MM/dd" formatter.calendar = Calendar(identifier: .gregorian) formatter.timeZone = TimeZone.current formatter.locale = Locale.current let calendarDay = formatter.string(from: date) // Realmオブジェクトの生成 let realm = try! Realm() // 参照(全データを取得) let todos = realm.objects(Todo.self) if todos.count > 0 { for i in 0..<todos.count { if i == 0 { datesWithEvents = [todos[i].dateString] } else { datesWithEvents.insert(todos[i].dateString) } } } else { datesWithEvents = [] } return datesWithEvents.contains(calendarDay) ? 1 : 0 } }実装方法
以下のメソッドを用いることで点マークがつけられるようになります。
func calendar(_ calendar: FSCalendar, numberOfEventsFor date: Date) -> Int{ return 1 //ここに入る数字によって点の数が変わる }画面に表示されている月の日付(Date型)がループで投げられます。自動的にFor-in構文のようにdateの数だけこのメソッドが呼ばれるイメージです。
しかしこのまま用いても全ての日付の下に点マークが付くようになるだけになります。
これをRealmSwiftと組み合わせることで、ToDoの登録がある日付にだけ点マークを表示するようにしました。1.上記メソッドで呼ばれる日付(Date型)と照合させるためのデータ(dateString)を用意し、RealmSwiftに登録
- RealmSwiftに登録するTodoクラスに下記変数を用意します。
@objc dynamic var dateString: String!formatter.dateFormat = "yyyy/MM/dd" let calendarDay = formatter.string(from: date)2.RealmSwiftからデータを取得
// Realmオブジェクトの生成 let realm = try! Realm() // 参照(全データを取得) let todos = realm.objects(Todo.self)3.取得したデータの中から必要なデータ(dateString)をピックアップして変数に格納
- RealmSwiftに登録データがある場合は、変数「datesWithEvents」に「dateString」の内容を格納。登録データがない場合は、変数「datesWithEvents」には何も入れません。
if todos.count > 0 { for i in 0..<todos.count { if i == 0 { datesWithEvents = [todos[i].dateString] } else { datesWithEvents.insert(todos[i].dateString) } } } else { datesWithEvents = [] }
- 変数「datesWithEvents」は配列のSetクラス(集合型)になります。Arrayクラスと異なる箇所としましてはインデックス番号が存在せず、重複が許されない型ということ違いがあります。今回RealmSwiftから取得するデータが重複する必要がなかったためSetクラス(集合型)にしております。詳細はこちら参照ください。
var datesWithEvents: Set<String> = []4.日付と照合させ、Int型を返す
- 三項演算子を用いて、任意の文字列(calendarDay)が含まれているか判断し、含まれている場合は「1」を、含まれていない場合は「0」を返します。
- 三項演算子の気をつける点としまして、半角スペースを置かずに「?」をつけると「オプショナル型の宣言」と解釈されエラーになりますのでご注意下さい。
return datesWithEvents.contains(calendarDay) ? 1 : 05.実装後の画面
参考
おわりに
自作アプリを制作している際にFSCalendarについての記事はいくつかあり、重宝させて頂きましたが、カレンダーに点マークを表示させるメソッドの紹介記事等で終わっているものが多く、実装に絡めた記事が少なかったので初学者の私からするとイメージがつきにくいなと感じておりました。コードの簡略化等を踏まえると未熟な内容になってしまいますが、誰かのお力になれれば幸いです。