20200909のSwiftに関する記事は13件です。

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 value

Xib で View を生成する方法

Xib で View を生成する方法は、大きく分けて View ClassFile's Owner を指定する2通りの方法があります。

View Class

image.png

View Class は xib上の View に対してクラスを指定することで直接的に xib と参照を作ることができます。この方法は UINib を使用して View をコードから初期化する場合は、全てのオブジェクトが使用できる状態になっていることが担保されますが、Storyboardxib などで直接 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.

https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/LoadingResources/CocoaNibs/CocoaNibs.html#//apple_ref/doc/uid/10000051i-CH4-SW24

つまり、基本的に View Class を指定した View を使用する場合には、コードでインスタンスを生成して使用する必要があります。例えば、UITableViewCell などは再利用性がありコードから生成するのが適切なので、 Xcode のテンプレートなどでも用意されていると考えられます。

File's Owner

image.png

File's Owner は名前の通り Xib 上の View の保持先を指定することで、@IBOutlet などでクラスに View の参照を付けることができます。つまり、クラスと Xib で作成された View のインスタンスは別なので、nib-loading を適切なタイミングで呼ぶことで View の全てのオブジェクトが使用可能になっていることが担保されます。これによって、コードから生成される時は、init(frame: CGRect) で、StoryboardXib から生成される時は init?(coder aDecoder: NSCoder) から View を生成することができるようになります。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[アプリ開発]DKImageViewControllerで写真共有アプリ作ってみた

5568440d5934344ad6f605910ee9f3e1.gif
464b0ad207bdce4da56f3c8461957761.gif

作った経緯

初めて作ったファッションアプリが途中でできなかったので、なんかCollectionView使ってやりたいなーって思いましてインスタみたいな写真共有アプリ作ろうと決心して開発しました!
参考にしたアプリはtabioriさんです

https://tabiori.com/

とりあえず作ってみた!
スクリーンショット 2020-08-23 14.58.10.png
こんな感じでやろうと思ったら、、、ア〜〜〜〜〜〜!!!!
スクリーンショット 2020-09-09 20.34.11.png
ViewControlller多くなりすぎて訳わからんくなった。。
初心者の僕はパンクしました
っていう感じで何回もプロジェクト作り直しました。

インスタの写真一枚だけならスクールの教材で出来るんですが、何せConllectionView使いたかったんでタイムラインの画像を複数表示出来るようにさせたかったんですよね

問題

このアプリ開発するのめっちゃ時間かかりましたこれにスクールの大半の時間消費しましたねw
まずFirebaseですね
ライブラリの画像を選択→CollectionViewで表示→ボタン選択→Firebaseに保存したかったんですがうまくいかず諦めて、
日付とタイトル、画像を別の画面でやろうとするとすごくめんどくさくなったり、、、
いろいろうまくいきませんでした。
でライブラリから写真を複数選択しようとすると標準?のライブラリだとできないし

DKImagePickerControllerの極意
http://cocoadocs.org/docsets/DKImagePickerController/3.8.1/

ライブラリから選択した画像を画面遷移先にデータを送るとかファイル形式違うものに渡しかたとか、、
どうやってやんの〜〜〜って感じです:joy_cat:

CollectionViewとTableViewの組み合わせ極意
https://qiita.com/Erica_pon/items/f1c6a06e399723f32549

こちらを参考にタイムライン画面でcolleCtionViewとTableView使ってモデル作ってみたけど画像表示できず開発休止しました、、、

結果

リリースはできてません!!
picture_pc_fb4f1c662c0d1ac2606e5093ff74ddec.png
まだ未完成です
諦めませんねー
よかったらみてください!

今回作ったリポジトリ
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

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【入門】iOS アプリ開発 #8【スペシャルターゲット・得点表示】

はじめに

今回はスペシャルターゲット(くだもの類)、パックマンがそれを食べた時やゴーストを噛み付いた時の得点表示を作成していく。

イメージ図は下記の通り。スペシャルターゲット(サクランボ表示)と、それをパックマンが食べると得点が表示される。

Image7.png

仕様書

まずはスペシャルターゲットの仕様の確認。

Image5.png

表示されている間にパックマンが食べると、得点が2秒だけ表示される(仕様にはないが実機確認)。

続いてパックマンがゴーストを噛み付いた時の得点表示の仕様は以下の通り。

Image51.png

こちらの得点表示は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クラスを継承して作成する。

Image62.png

得点表示は 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行程度。

必要な役者はそろったので、次回はこれら役者のシーケンス動作を組み合わせて、ゲームとしてプレイできるものを作成していく。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

CryptoKit を使って ECDSA を用いた署名を実装する

ECDSAでJWTによる電子署名を作成することがあったのでメモ :pencil:

ECDSAとは

楕円曲線暗号(だえんきょくせんあんごう、Elliptic Curve Cryptography、ECC)とは、 楕円曲線 上の 離散対数問題 (EC-DLP) の困難性を安全性の根拠とする 暗号

  • 暗号化と復号とで異なる2つの鍵を使用し、暗号化の鍵を公開できるようにした公開鍵暗号
  • RSA暗号 と比べて、短いデータ長で処理速度も早いが同レベル安全性が実現できるのがメリット

CryptoKit を使ってやること

CryptoKitを使って以下の手順により署名を作成していきます。

  1. 鍵の生成
  2. 電子署名の作成

全体のコードはこちらに上げてあります。

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.ECDSASignaturederRepresentationrawRepresentation の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+s

Security Frameworkの SecKeyCreateSignature を使う場合、こちらは ASN.1 DERフォーマット で返されるので上記のような変換が必要です。
CryptoKitを使えば signature.rawRepresentation だけで取得できるのですごく便利です。

まとめ

CryptoKitを使うと鍵の生成や署名が1行くらいでできてしまうのでとても簡単に使えて驚きました。
ただし、CryptoKitで生成した鍵は自分で保存する必要があるのでそれを考えると生成はSecurity Frameworkの SecKeyGeneratePair をつかってもいい気がしました。
CryptoKitで生成した鍵をキーチェーンに保存する方法はAppleがSampleコードを提供しているのでこれを参考に。
Storing CryptoKit Keys in the Keychain

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EXC_BAD_ACCESSの対処法【All Exceptionの設定は不要らしい。】

スクリーンショット 2020-09-09 19.12.58.png

 EXC_BAD_ACCESSとは?

通常、エラーメッセージを持たないため、デバッグするのが最も難しい種類のクラッシュ。
でも、Swiftでは非常に稀。

 要点

All Exceptionを使わずとも、EXC_BAD_ACCESSのエラー箇所が出るらしい。
※ Xcode ver 11.7

下部にエラー表示。僕の場合は、不適切なselfが原因でした。
ご覧の通り、Navigator AreaではAll Exceptionの設定はしていません。

スクリーンショット 2020-09-09 18.51.02.png

 デバッグ方法

どのバージョンから上記の仕様になったか知りませんが、
少なくともXcode 9.4.1ではAll Exceptionの設定が必要だったらしい。↓

【Xcode】All Exceptionの設定方法(デバッグ)

バージョンによって、
Exception Breakpointとか、Add Exception BreakPointとか。
表示は異なれど、やることは同じ。


EXC_BAD_ACCESS対処時には、これらのサイトを参考にしました。

Xcodeでデバッグ実行中にクラッシュした時に捗るブレークポイント設定

おしまい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EXC_BAD_ACCESSの対処法

スクリーンショット 2020-09-09 19.12.58.png

 EXC_BAD_ACCESSとは?

通常、エラーメッセージを持たないため、デバッグするのが最も難しい種類のクラッシュ。
でも、Swiftでは非常に稀。

 要点

All Exceptionを使わずとも、EXC_BAD_ACCESSのエラー箇所が出るらしい。
※ Xcode ver 11.7

下部にエラー表示。僕の場合は、不適切なselfが原因でした。
ご覧の通り、Navigator AreaではAll Exceptionの設定はしていません。

スクリーンショット 2020-09-09 18.51.02.png

 デバッグ方法

どのバージョンから上記の仕様になったか知りませんが、
少なくともXcode 9.4.1ではAll Exceptionの設定が必要だったらしい。↓

【Xcode】All Exceptionの設定方法(デバッグ)

バージョンによって、
Exception Breakpointとか、Add Exception BreakPointとか。
表示は異なれど、やることは同じ。


EXC_BAD_ACCESS対処時には、これらのサイトを参考にしました。

Xcodeでデバッグ実行中にクラッシュした時に捗るブレークポイント設定

おしまい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

"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を行ったところ、成功しました!

success.png

終わりに

はじめのエラー文から想像できないエラーだったため、まとめさせていただきました。
この技術記事が誰かのお役にたてば何よりです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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)  //9

2.コンピューテッドプロパティの特徴

上記の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の匿名ログインのコード2
Auth.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を使いました。

型の入れ子とCodingKeys
struct 構造体 {
    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)」を読んでいこうと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【IOU】 Object Detection の性能指標【I/U】

ユニオン交差 Intersection Over Union (IOU) が一般的なようです。
iou.png

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を使ったアプリを作っています。
機械学習関連の情報を発信しています。

Twitter
MLBoysチャンネル
Medium

相棒
note

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Swift]ColorをRGBで指定する

はじめに

SwiftでcolorをRGBで指定する方法を説明します。

Swiftでのcolor指定

まず通常の指定方法は

ViewController.swift
sampleView.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.swift
import 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.swift
sampleView.backgroundColor = UIColor.rgb(red: 121, green: 162, blue: 255)

rgbsample.png
これでRGBでcolorを指定することができるようになります!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Colab で TuriCreate ObjectDetection -> CoreML

はじめに

Google ColabTuriCreate を使って Object Detection のトレーニングを行い、iOS 向けの CoreML Model(.mlmodeol) を出力するまで行った時のメモです

事前作業

画像データのラベリング

学習に使用する画像に対するラベリング作業は別途必要となります。
今回は、simple_image_annotator を使用して annotatioon を用意しました。

Google Colab

ランタイムタイプの変更

今回は、GPU を使用するため、Google Colab のランタイムタイプを変更します

Google Colab のメニューから [ランタイム][ランタイムのタイプを変更] を選択
スクリーンショット 2020-09-09 10.57.43.png

ハードウェアアクセラレータ「None」 から 「GPU」 に変更して 「保存」 をクリックしてください。
スクリーンショット 2020-09-09 10.57.50.png

初期設定

Turi Create のインストール

Turi Create のインストールを行います

!pip install -U turicreate

Cuda 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_PATH

TensorFlow GPU のインストール

TensorFlowGPU を利用するため、デフォルトでインストールされている tensorflow をアンインストールして、tensorflow-gpu をインストールします

!pip uninstall -y tensorflow
!pip install tensorflow-gpu

Turi 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 のサンプル に組み込んでお試しください

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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を使ったアプリを作っています。
機械学習関連の情報を発信しています。

Twitter
MLBoysチャンネル
Medium

相棒
note

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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.swift
import Foundation
import RealmSwift

class Todo: Object {
    # ・・・省略・・・
    @objc dynamic var dateString: String!
    # ・・・省略・・・
}
MainViewController.swift
class 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!
  • Realm Studioで確認すると赤枠の箇所になります。
    スクリーンショット 2020-09-08 22.17.10.png

  • また、メソッドで呼ばれる日付(Date型)をRealmSwiftに保存したデータと同じフォーマットに変換します。

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 : 0 

5.実装後の画面

スクリーンショット 2020-09-08 23.33.18.png

参考

おわりに

自作アプリを制作している際にFSCalendarについての記事はいくつかあり、重宝させて頂きましたが、カレンダーに点マークを表示させるメソッドの紹介記事等で終わっているものが多く、実装に絡めた記事が少なかったので初学者の私からするとイメージがつきにくいなと感じておりました。コードの簡略化等を踏まえると未熟な内容になってしまいますが、誰かのお力になれれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む