20200426のSwiftに関する記事は5件です。

Apple Watchのセットアップとアプリ開発(Swift 5.1)

Apple Watchアプリの開発方法

MacでSwiftを使って、AppleWatchアプリを作成しました。
この記事ではアップルウォッチのセットアップ方法と、Xcodeでのプロジェクト作成から、ウォッチにインストール完了するまでの手順を紹介します。
アプリの内容やSwiftのコーディング内容については触れていません。

開発環境

Mac mini (Late 2014) macOS Mojave Ver.10.14.6
Xcode Ver.11.3.1(11C504)
Swift Ver.5.1
Apple Watch Ver.6.1.3(17S811)
iPhone 7 Ver.13.4.1

プロジェクトの作成

Xcodeを起動する。
スクリーンショット 2020-04-01 11.37.15.png
プロジェクトのテンプレートにwatch OS、アプリケーションはWatch Appを選択する。
スクリーンショット 2020-04-01 11.37.42.png
言語はSwift、UIはStoryBoardを選択。その他、必要事項を設定する。
スクリーンショット 2020-04-01 11.38.37.png
ディレクトリを選択して『Create』をクリックするとプロジェクトが作成される。
スクリーンショット 2020-04-01 11.39.14.png
Finderで確認すると、指定した場所にプロジェクトのファイルがたくさん作られたことが分かる。
スクリーンショット 2020-04-01 11.40.13.png

ウォッチのセットアップ

セットアップには、母艦としてiPhoneが必要となる。iPhpneにウォッチアプリをインストールする。
App Storeでキーワード「ウォッチ」で検索するとアプリが見付かる。
キャプチャ.PNG
アプリをインストールして実行すると、以下の画面が起動する。
IMG_0002.png
ウォッチを近付けて「ペアリングを開始」をタップする。カメラが起動するので、ウォッチ上で動いている画像をキャプチャする。認識するとペアリングが行われる。
IMG_20200401_132943.jpg
ペアリングが完了すると、いくつかの設定項目を聞かれる。
IMG_0008.png
それらを入力したら、ハード面の設定は完了。
IMG_0010.png
ウォッチ側では以下の画面が表示される。
IMG_0012.jpeg
デジタルクラウン(時計の竜頭みたいな回せるボタン)を押すと初期画面が表示される。唐突に「もう予定なし」と表示されている。本気で日本語に対応するつもりはないようだ。
IMG_0013.JPG
今回はwebアプリの開発を想定しており、母艦のiPhoneがなくてもウォッチ単体でweb通信を行えるようにしたい。そのための設定を行う。設定→Wi-Fiを開く。ウォッチが直接通信したいWi-Fiネットワーク名が表示されており、「iPhoneが使用できないときは、このWi-Fiを使用する」となっていることを確認。
IMG_20200401_173838.jpg

実行許可設定

アプリの開発を行う前に、実行許可を与える手順が必要となる。プロジェクト側のセキュリティ設定と、ウォッチ側の許可設定を行う。

野良アプリ実行許可

プロジェクトを野良アプリ(AppleStore公式ではないアプリ)として実行可能にする。Info.plistに設定項目を追加する。
スクリーンショット 2020-04-01 15.57.44.png
Information Property Listの直下に「+」をクリックして新規項目「App Transport Security Settings」を追加する。
スクリーンショット 2020-04-01 15.58.17.png
追加した項目の左側の三角をクリックして下向きにし、直下に「Allow Arbitrary Loads」を追加する。
スクリーンショット 2020-04-01 15.59.38.png
初期値は「NO」になっているので、「YES」に変更する。
スクリーンショット 2020-04-01 16.00.03.png

開発チームの設定

Xcode → preferenceでプロジェクトとApple IDをひも付け、開発チームを設定する。
スクリーンショット 2020-04-01 16.13.29.png
ペアリング済の母艦iPhoneをmacとUSB接続し、デバッグを実行する。
スクリーンショット 2020-04-01 15.38.20.png
ウォッチ側にデベロッパに対する信頼確認が表示される。「信頼する」をタップする。
IMG_0059 (2).JPG
ところが、iPhoneとウォッチを眺めていても何も起こらない。Xcode上では、「Finished running」となっているが。。。そこで、ウォッチのアプリ一覧画面を調べてみる。
スクリーンショット 2020-04-15 10.42.14.png
すると、アイコンがたくさん並んでいるところに、デフォルトのクモの巣のようなアイコンが一つ増えている。これがインストールされたアプリで、タップすると実行することができた。
IMG_0062 (2).JPG
以上、Apple Watchアプリの開発手順でした。

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

[Swift5]Realm Swift - サンプル#2

iOS でRealm Swift を 利用し、サンプルを作成してみました。

前回

Realm Swift - クラス#1

サンプル

※ Source: RealmManagerAccess.swift

Ream 定義

RealmManagerAccess.swift
import RealmSwift
class Student: Object, Codable, NSCopying {
    @objc dynamic var studentId = ""
    @objc dynamic var name = ""
    @objc dynamic var age = 0

    override static func primaryKey() -> String? {
        return "studentId"
    }

[JSON]

{
"name": "jane",
"age": 12,
"studentId": "1"
}

レコードの追加

 //guard let data = dataStudentStr.data(using: .utf8) else {...}
 let obj = try JSONDecoder().decode(Student.self, from: data)
 try manager.add(obj: obj)

[結果ログ]
スクリーンショット 2020-04-26 22.23.38.png

レコードの更新

try manager.update(obj: student) {
     student.name = "test"
     student.age = 20
     print("2.update => findFirst updateStudent:" + student.description  )
}

[結果ログ]
スクリーンショット 2020-04-26 22.17.05.png

レコードの削除

//guard let primaryKey = manager.getPrimaryKey() else {...}
try manager.deleteWithQuery(query: "\(primaryKey) == '2' ")

[結果ログ]
スクリーンショット 2020-04-26 22.17.31.png

レコード検索

let student =  manager.findByPrimaryKey(key: "1") 

[結果ログ]
スクリーンショット 2020-04-26 22.17.21.png

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

関数に出てくる『 _ 』って何?

基本的に関数を呼び出すには引数ラベルが必要

_ が用いられるまでの流れを追っていこうと思います。

例として、縦の長さと横の長さとして整数型の2つの引数を持ち、面積として2数の積を出力する関数を考えます。
最も基本的な書き方は

func area(width:Int, height:Int) -> Int {
   return width * height
}

そして呼び出す時には

area(width:3, height:4)

としなければなりません。
ここでarea(3,4)とするとMissing argument labels 'width:height:' in callと怒られてしまいます。

関数名(引数,引数)とするためには

なんとかしてarea(3,4)の形で呼び出したい...

func area(_ width:Int, _ height:Int) -> Int {
    return width * height
}

のように_ を用いることで

area(3,4)

で呼び出せるようになります。

ワイルドカードと呼ばれる_には他にも使い方があるようなので、調べて行こうと思います。

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

【Swift】Enumのassociated Valueがあるときのパターンマッチで単純にif文使ったらエラー出た

Enumのassociated Value、便利なことに気づいたのでよく使うようになったんですが、
この前ちょっと不便だなと思うことがあったので、ストーリー形式で書いてみます。

〜associated Value使う前〜

Twitterライクなアプリの開発に入ってたとして、下記みたいなStructがありました。

import Foundation

struct Followee {

    enum Status {
        case follow
        case blocked
        case mute
    }

    var status: Status = .follow // ホントは別のところでセットするけどわかりやすく

    var canAppearTimeline: Bool {
        if status == .follow {
            return true
        } else {
            return false
        }
    }
}

フォローしてるアカウントの投稿をタイムラインに表示するかどうかを、
canAppearTimelineで判定しています。
現状、フォローしてるアカウントのステータスは、フォロー中・被ブロック・ミュート中の三つで、フォロー中のときだけ表示します。
シンプルですね。

「タイムラインで表示する優先順位づけできひん?」

ここで仕様変更が入って、

「タイムラインで表示する優先順位づけできひん?」

という要望が入りました。

「なんかミュートまでは行かへんけど、この人タイムラインに出過ぎてうっとうしいわ〜ってときあるやろ?」
「嫌いまではいかへんけど、毎日食べると胃もたれする料理みたいな」(?)
「そういうとき、表示頻度を下げる機能あったら、嬉しいよな」

というわけで、仕様変更することになりました。

Statusを追加

とりあえずStatusをひとつ追加することにしました。

    enum Status {
        case follow
        case blocked
        case mute
        case showLess(priority: Int) // New!
    }

ロジック

こう考えました。

「うーん、めんどくさいな……どないしよ……」
「もうええわ。優先順位の初期値1入れて、タイムラインにあらわれるたびに1ずつ増やしてって、10になったら表示にしたろ!」
「とりあえず表示頻度減ったらええやろ」

機能としてはどうかと思いますが、ロジックは単純になりそうですね。

ところが

ところが、Statusを変更した段階で、Protocol 'Equatable' requires that 'Followee.Status' conform to 'Equatable'というエラーが出るようになりました。

    var canAppearTimeline: Bool {
        if status == .follow {
            return true
        } else {
            return false
        }
    }

「な、何もしてないのにif文が通らなくなった!」

associated Valueがあるとなぜifのパターンマッチができないのか

「Equatableに従ってないと怒られるんなら、従わせればええんか……?」
と思って、Statusをこうしてみました。

    enum Status: Equatable {
        case follow
        case blocked
        case mute
        case showLess(priority: Int)
    }

これをやると、showLess以外のケースであればコンパイル通るようになります。
しかし……

    var canAppearTimeline: Bool {
        if status == .showLess(_) { // Missing argument label 'priority:' in call
            // showLessのときのロジックを書く
        }
    }

こんな書き方はできません。

    var canAppearTimeline: Bool {
        if status == .showLess(priority: 1) {
            // showLessのときのロジックを書く
        }
    }

これなら通ります。

「えっ、これ1から10まで書かないとアカンの?!」

    var canAppearTimeline: Bool {
        if status == .showLess(priority: 1) || status == .showLess(priority: 2) ||
           status == .showLess(priority: 3) || status == .showLess(priority: 4) ||
           status == .showLess(priority: 5) || status == .showLess(priority: 6) ||
           status == .showLess(priority: 7) || status == .showLess(priority: 8) ||
           status == .showLess(priority: 9) || status == .showLess(priority: 10) {
            // showLessのときのロジックを書く
        }
    }

「こうやな!」

これなら通りますが、

「すまん!やっぱpriorityもっと細かくしてくれ!1〜100にしよう!」

と言われた瞬間に「アアアアアアアアアアアアアアア」となってしまいますね。

そう。Associated Valueは値を保存してくれるので、マジメにパターン分けすると、
Associated Valueのとりうる値分のパターンがあるんですね〜

対処法

対処法は二つあります。
まずswitch文を使う方法。

    var canAppearTimeline: Bool {
        switch status {
        case .follow:
            // 処理
        case .blocked:
            // 処理
        case .mute:
            // 処理
        case .showLess(priority: let priority):
            // 処理
        }
    }

これはEnum使うときのパターンマッチの王道ですね。

ただ状況によっては、もっとif文ぽく書きたいときもあると思います。
たとえばEnumが10ケースあるけど、ある1ケースに該当するときだけ処理を変えて、あとの9パターンは何もしない、みたいなときとか。
そんなときにSwitch文書くのはちょっと大袈裟ですよね。

そんなときのためにif caseという構文があります。

        if case .showLess(let priority) = status {
            if priority < maxPriority {
                increment()
                return false
            } else {
                reset()
                return true
            }
        }

こんな感じで書けます。
case .xxxのあとに=でつないで、Enumが来るっていうちょっと気持ち悪い構文ですが、
まあ元々Switch文を書かなきゃいけないところの省略形だと思えば多少は……いやそうでもないですか。

完成形

import Foundation

struct Followee {
    enum Status {
        case follow
        case blocked
        case mute
        case showLess(priority: Int)

        mutating func countUp() {
            if case .showLess(let priority) = self { self = .showLess(priority: priority + 1) }
        }

        mutating func resetPriority() {
            if case .showLess(_) = self { self = .showLess(priority: 1) }
        }
    }

    var status: Status = .follow
    let maxPriority = 10

    var canAppearTimeline: Bool {
        if case .follow = status {
            return true
        } else if case .showLess(let priority) = status {
            if priority < maxPriority {
                increment() // 直接countUp()呼んだら怒られたので迂回している
                return false
            } else {
                reset()
                return true
            }
        }
        return false
    }
}

func increment() {
    followee.status.countUp()
}

func reset() {
    followee.status.resetPriority()
}

var followee = Followee()
followee.status = .showLess(priority: 1)
(1...10).forEach { _ in print(followee.canAppearTimeline)}
print(followee.status)
出力
false
false
false
false
false
false
false
false
false
true
showLess(priority: 1)

余談

この記事書くために調べてたら知ったんですが、

switch productBarcode {
case .upc(let numberSystem, let manufacturer, let product, let check):
    print("UPC: \(numberSystem), \(manufacturer), \(product), \(check).")
case .qrCode(let productCode):
    print("QR code: \(productCode).")
}
// Prints "QR code: ABCDEFGHIJKLMNOP."

これを、

switch productBarcode {
case let .upc(numberSystem, manufacturer, product, check):
    print("UPC : \(numberSystem), \(manufacturer), \(product), \(check).")
case let .qrCode(productCode):
    print("QR code: \(productCode).")
}
// Prints "QR code: ABCDEFGHIJKLMNOP."

こんな書き方もできるんですね。へえ〜って感じです。

あとrawValueとassociated Valueは一緒に使えないとか、便利な分色々制約あるんだな〜と思いました。
Enumの持ってる単純性を損なっちゃうデメリットもありますね。

参考

Enumerations
SwiftのEnumをif文で比較できない(Associated Value)

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

【Swift】Main.StoryBoardを消して新しいStoryBoardでスタートさせるときの留意点

シミュレーターがずっと真っ暗

毎度おなじみ初心者応援記事(未来の自分への備忘録)です。
一緒に一つずつクリアしましょう。

今回はxcodeで新規プロジェクトをcreateしたときに、Main.Storyboardを消して、自作のStoryboardを起動してみようと初心者から一歩進んだ方向けかと思います。
まぁ今更感もあるかもしれませんが、探すのに苦労した自分が忘れないようにしたいのです。
アウトプット大事。

iOS12以前ではIs Initial View Controllerという項目にチェックをつけるだけで自作のStoryboardに切替られたはずでした。
スクリーンショット 2020-04-26 1.27.00.png

しかしながらiOS13からiPadで同一アプリを複数画面で起動できるようにしたとかしてないとかっていう頭がいいのか悪いのかわからない使い方ができる(?)ようになった関係で、いろいろと仕様変更があったようです。
詳しくは@omochimetaru様のiOS13のSceneDelegate周りのアプリの起動シーケンスで書かれてます。

まぁ僕のような初心者には何度読み返しても仕様が変わったんだなということしか理解できないのですが、この為に一手間加えないといつまでもシミュレート起動できないことになりました。
いつまでもログに「Failed to instantiate the default view controller for UIMainStoryboardFile 'Main' - perhaps the designated entry point is not set?」と表示されていろいろいじってみても治らず、頭を捻っている方への処方箋となれば幸いです。

なお、今回はStoryboardを使って行きます。コードだけで書かれる方には非対応ですので、ご了承ください。

脱・Main.Storyboard、脱・超初心者

早速行きましょう
さっさとMain.Storyboardを削除します。
↓のような選択が表示されますが、Move To Trashでいいと思います。これは表面上は消えるけど、プロジェクトフォルダには残す?残さない?という問いで、今回はMainに完全に消えてもらいます。
スクリーンショット 2020-04-26 1.44.17.png
消した後に、初期画面は認証画面にしようと思いAuth.StoryboardというStoryboardを作成しました。
スクリーンショット 2020-04-26 2.00.55.png

このまま一度Runさせようとすると、もはや起動しません。
まずはIs Initial View Controllerにチェックを入れてください。
スクリーンショット 2020-04-26 1.27.00.png
(画像は使い回し)

TARGETSの変更

さて、ここで勘の良い方はTARGETS内にMain Interfaceがあることに気づいたはずです。
スクリーンショット 2020-04-26 2.02.22.png
でもこれは空白でも動きました。今回は一応Auth.Storyboardを選択しておきます。詳しいかた教えてください。

でもRunしても起動しません。

真犯人はInfo.plist

犯人はこいつだ!
ということでInfo.plist内の「Main storyboard file base name」といういかにもなプロパティがありました。こいつを直しましょう
スクリーンショット 2020-04-26 2.05.20.png

はい。

起動しません!

なんとApplication Scene Manifestというプロパティの中に真犯人がいました。
スクリーンショット 2020-04-26 2.08.38.png
こいつがMainのままや!

というわけでこいつを「Auth」にしたら起動できるはずです。

スクリーンショット 2020-04-26 2.12.57.png

お疲れ様でした。
この記事が誰かの手助けになれば、恐悦至極にございます。

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