20201011のiOSに関する記事は12件です。

iOS 14 WidgetKit からアプリが起動されたかどうか知る

動作環境

  • Xcode 12.0 (12A7208)
  • iOS 14.0

やりかた

いくつかありますが、今回は application(_:continue:restorationHandler:) で判別する方法を紹介します。

本体アプリの AppDelegate.swift で次のように判定できます。

func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool {
    if userActivity.activityType == "WidgetExtension" {
        print("Launched from WidgetKit!")
    }
    return true
}

WidgetExtension のところはアプリによって異なるかも。テストできていないので、ご存じの方がいましたら教えてください

また、起動元ウィジェットの情報は userInfo の WidgetCenter.UserInfoKey で取り出せます。

Optional("[AnyHashable(\"WGWidgetUserInfoKeyKind\"): WidgetExtension, AnyHashable(\"WGWidgetUserInfoKeyFamily\"): systemMedium]")

ウィジェットのサイズ(小中大)なんかも取れます。

参考リンク

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

[iOS] Fabric SDKを最新のFirebase Crashlytics SDKにアップデートする

これはなに?

[Action Required] Update your apps to use latest Firebase Crashlytics SDKs before November 15, 2020

というメールが届いたiOSアプリ開発者向けの記事です。

  • Firebase の設定は完了していることを前提としています。

何が起きているのか

Fabric Crashlytics SDK が2020年3月に deprecated になりました。2020/11/15 以降はレポーティング機能が使えなくなります。

何が必要なのか

下記、公式ドキュメントがわかりやすいです。

この中で、私が実施した内容だけピックアップしてみます。

Podfile を更新

Podfile から

pod 'Fabric'
pod 'Crashlytics'

を削除して、

pod 'Firebase/Crashlytics'

に書き換えます。バージョン 4.0.0 以降を追加するようにしてください。

その後、pod install しましょう。

sharedInstance を crashlytics に修正

Podfile を更新しただけではビルドに失敗します。

これは、他のFirebase SDK との整合性を取るため、sharedInstancecrashlytics に変更になったためです。

Crashlytics.sharedInstance().xxx

となっている箇所を

Crashlytics.crashlytics().xxx

に書き換えましょう。

実行スクリプト名を修正

実行スクリプト名も Fabric から FirebaseCrashlytics に変更されています。

Builds Phase 等、スクリプトを実行している箇所を

${PODS_ROOT}/Fabric/run

から

${PODS_ROOT}/FirebaseCrashlytics/run

に書き換えましょう。

まとめ

私のプロジェクトではこの対応のみでビルドできるようになりました。しかし、setUserIdentifier など、他のメソッドを使用している場合は様々なエラーが出ると予想されます。その場合は、下記公式ドキュメントを参考に、一つ一つソースコードを書き換えていく必要があります。

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

SwiftUIでNavigationBarのBackgroundColorなどを変更する方法

やりたいこと

SwiftUIでNavigationBarの背景色を変える
UIAppearanceは使いたくない(強い意志)

結論

  • UINavigationBar.appearance()を使わずにNavigationBarのBackgroundColorを変更
  • UIViewControllerRepresentableでNavigationBarのStyleを変えたいViewを生成
  • NavigationBarを使うViewの.background()に上記のViewを設定

準備

import SwiftUI
import UIKit

struct NavigationConfigurator: UIViewControllerRepresentable {
    var configure: (UINavigationController) -> Void = { _ in }

    func makeUIViewController(context: UIViewControllerRepresentableContext<NavigationConfigurator>) -> UIViewController {
        UIViewController()
    }
    func updateUIViewController(_ uiViewController: UIViewController, context: UIViewControllerRepresentableContext<NavigationConfigurator>) {
        if let nc = uiViewController.navigationController {
            self.configure(nc)
        }
    }

}

実際の使用例

import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView {
            Text("Don't use .appearance()!")
            .navigationBarTitle("Try it!", displayMode: .inline)
            .background(NavigationConfigurator { nc in
                // UIKitでNavigationBarのスタイルを変更するのと同じ方法でOK
                nc.navigationBar.barTintColor = .blue
                nc.navigationBar.titleTextAttributes = [.foregroundColor : UIColor.white]
            })
        }
    }
}

プレビュー

ss20201011

環境

2020年10月11日時点
Xcode 12.0.1
Swift 5.3
これ以外の環境については特に検証してないのでわからないですが、
SwiftUIが動く環境であれば可能だと思い込んでます

NavigationBarの背景色を変えたい

とりあえず、さくっとググってみるといくつかの記事で、コンストラクタとかの初期化処理の際にUINavigationBar.appearance()でしかできないとかかんとか、、、:sob:

たしかに、UINavigationBar.appearance()でも変えれることは変えれるが、
画面ごとに色を変えるのが困難(ってかこれだけなら無理なのでは、、、)

Stack Overflowありがとう!

SwiftUI update navigation bar title color
ちゃんと、いい感じに変更する方法を教えてくださってる方がいらっしゃりました:smile:

この方法のデメリット

プレビューをライブモードにしないとスタイルが反映されないです
ライブモードにすればいいだけなので、どうでもよいですね

ってことで、 UIViewControllerRepresentable を使っていい感じに背景色を変更できました
まだまだ、SwiftUIは成長途中なので、この子を使いこなさないといけなさそう

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

[App groups] UserDefaults データにアクセスできないときに確認したこと

App groupsを使用した、UserDefaultsの使用で、データにアクセスできない問題が発生した。
私の場合は、Build Settings の Signing 設定のCode Signing Entitlement 設定がなかったことが問題だった。
通常、app groupsを有効にしgroupを指定したら自動的にできあがる?とおもうのだが
私の環境では(Xcode 12.0.1) では、自動的に設定されなかったので、他の正常に動いているTargetを参考にこの問題を確認し解決できた。
メモまでに。

Code Signing Entitlementを設定

まず、XXXXXX.entitlementsファイルを作成する。
XXXXXX.entitlements (XXXXXXは任意のファイル名) ファイルを作成。
内容は以下のplistデータで、group.my.appgroup.id の部分は、使用するapp group名を記述する。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.security.application-groups</key>
    <array>
        <string>group.my.appgroup.id</string>
    </array>
</dict>
</plist>

作成したentitlementsファイルを、プロジェクトに追加する。

次に、設定を行う。

app groupが使用できない問題となっているTARGETを選択し、
Build Settings の Signing の Code Signing Entitlement に、ファイル名を設定する。

image.png

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

【Swift】共用型の列挙型

1.はじめに


前回に列挙型について解説しましたが、今回は事前にお伝えした通り、共用型の列挙型について解説をしていこうと思います。

前回の記事と非常に深い関わりがあるので、まだご覧になっていない方はこちらの記事もご覧ください。
(https://qiita.com/0901_yasyun/items/a87c49438db68241778b)

2.共用型の列挙型の概要について


共用型の列挙型は、実体型を指定しないシンプルな列挙型と、複数の異なるタプルの構造を併せ持つことができる型です。
この型にはいく通りかの異なるデータを、共通の概念でまとめて扱えるようにする狙いがあります。

まず、共用型の列挙型の定義の概要を次に示します。
シンプルな列挙型のケース名後ろにタプルの宣言を付けた形になっています。

enum 型名 {
    case ケース名 タプル型宣言
    case ケース名 タプル型宣言
    ... ...

次に具体的な定義例を示します。
この例では、Webページなどの作成などで使用する色の指定は、文字列の色名、16進数によるカラーコードなどで行われますが、どの形式でも色を指定するという目的は同じなので、これを1つのデータ型で表すことを考えます。

enum WebColor {
    case name(String)       // 色の名前
    case code(String)       // 16進カラーコード
    case white, black, red  // よく使う色
}

この定義を使って、様々なインスタンスを生成できます。

let background = WebColor.name("indigo")   // インディゴブルー
let turquoise: WebColor = .code("#40E0D0") // ターコイズ
let textColor = WebColor = .black          // 黒

タプル型の構造に記述される情報を以下では付加情報、またはペイロードと呼びます。
共用型の定義には、実体型を記述することはできません。
値型共用型の列挙型は別のものとして記述しなくてはなりません。

前回のシンプルな列挙型値型の列挙型では要素同士を比較することができましたが、共用型の列挙型の場合、自分で==演算子を定義するか、プロトコルEqutableを採用しない限り、相互に比較はできません

if turquoise == WebColor.code("#40E0D0")  // エラー。比較できない

もう少し複雑な例を示します。
次の例では、市営地下鉄で販売されている数種類の切符やプリペイドカードのどれもが、自動改札機で処理できるとし、切符やカードの種類を列挙型で定義しました。

enum Ticket {
    case 切符(Int, Bool, 回数券:Bool)  // 普通券:価格、小人、回数券かどうか
    case カード(Int, Bool)             // プリペイドカード:残高、小人
    case 敬老バス                      // 敬老バス
}

caseに記述する括弧の中はタプルで、項目にキーワードを付けることもできます。
このような列挙型に対する処理を書き分けるために、switch文を使います。
caseで定数や変数へ値を代入でき、付加情報を使わない場合には、下の「.カード」のように括弧以降を記述しないこともできます

以下では、caseの後に置かれる構造に関する条件の記述caseパターンと呼びます。
また、where以下に条件を書くことができるのはタプルを扱う場合と同じです。
次の例を確認してください。

switch t {
case let .切符(fare, flag, _):
    print("普通券: \(fare) " + (flag ? "小人" : "大人"))
case .敬老バス:
    print("敬老バス")
case .カード(let r, true) where r < 110:  // 小人の最低運賃
    print("カード: 残高不足")
case .カード(let r, false) where r < 230: // 大人の最低運賃
    print("カード: 残高不足")
case .カード:
    print("カード")

タプルをswitch文で使う場合と同様、付加情報を定数に代入するにはletを()の中に置く方法と外側に置く方法が使えます。

case let .切符(fare, flag, _):     // または
case .切符(let fare, let flag, _):

キーワード付きのタプルの場合、caseパターン内にはラベルを書かなくて大丈夫ですが、書くこともできます。
letとともに使う場合、letを書く場所に注意が必要です。

case let .切符(fare, _, 回数券: flag):     // または
case .切符(let fare, _, 回数券: let flag):

3.if-case文について


与えられたタプルが、複数の条件のどれに一致するかを調べるには、switch文が便利に利用できました。
しかし、特定の1つの条件のみに一致するかどうか調べたいという場合もあります。

例えば、先ほどの例で用いたTicket型の変数pが、プリペイドカードかどうかだけ調べたいとします。しかし、if文で次のようには記述できません。

if p == .カード {              // エラー。この記述はできない
    print("プリペイドカード")
}

switch文なら次のように記述できますが、使わないとわかっているdefault節も書かなければならず、少々面倒です。

switch p {
case .カード: print("プリペイドカード")
default: break
}

そこで、こういった場合でも容易に記述できる構文が用意されています。
上の例は次のように記述できます。この構文をif-case文と呼ぶことがあります。

if case .カード = p {
    print("プリペイドカード")

条件はまずcaseパターンを記述し、「=」と対象となる式を置きます。
caseパターンと式が一致するかどうかは、switch文の場合と同様に処理されます。
switch文の場合と異なり、付加的な条件はwhere節ではなく、カンマ「,」で区切って後ろに続けます。

この記法はif文、while文、およびguard文の条件の書き方で説明したものと同じで、カンマで区切って、一般の式、オプショナル束縛構文などを記述可能です。

if caseパターン = 式, 条件 {.....}

次の例では、変数tの内容が残額が1200円以上のプリペイドカードかどうかを調べます。

if case .カード(let y, _) = t, y >= 1200 { .....

少し複雑な例として、辞書のインスタンスからTicket型の要素を取り出し、その情報がパターンに一致しているかを調べるという条件を記述してみましょう。

let tickets:[String:Ticket] = [ "志倉" : .切符(260, false, 回数券: true), 
               "佐々木" : .切符(220, false, 回数券:false)]
let name = "佐々木"
if let t = tickets[name], case .切符(220, _, _) = t {
    print("220円券")
}

この例の場合、辞書から得た情報がオプショナル型なので、まずオプショナル束縛構文で定数tにTicket型のインスタンスを得ます。
nilの場合はそこでifの条件が不成立となりますが、nilでなければ、次に定数tが220円の切符かどうか調べます。

なお、上のif文は「?」を使うと次のように記述することもできます。

if case .切符(220, _, _)? = tickets[name] {
    print("220円券")
}

4.for-in文でcaseパターンを使う


for-in文でも、caseパターンを利用できます。
これまでに説明したfor-in文と同様に、配列などのSequenceプロトコルに適合した式をinの次に置き、インスタンスを次々に取り出します。

この書き方では、取り出されたインスタンスとcaseの次のパターンが一致した場合にだけ、コードブロックを実行します。
実行のための条件をさらに記述する必要があれば、where節を追加することもできます。

for case パターン in 式 where 式 {
    文... ...
}

たとえば、Ticket型の要素を持つ配列passesから次々にインスタンスを取り出して、220円より高い切符の情報を表示するには次のようにします。

for case let .切符(fare. child, coupon) in passes where fare > 220 {
    var k = coupon ? "回数券" : "普通券"
    if child { k += "(小人)" }
    print(k, "\(fare)円")
}

この文は次のように記述するのと同じです。

for t in passes {
    switch t {
    case let .切符(fare, child, coupon):
        if fare > 220 {
            var k = coupon ? "回数券" : "普通券"
            if child { k += "(小人)" }
            print(k, "\(fare)円")
        }
    default: break
    }
}

この構文でオプショナル型に当てはまる「?」を利用すると、オプショナル型が含まれるデータ列をうまく扱うことができます。
また、letではなく、varを使うこともできます。

5.再帰的な列挙型


共用型の列挙型で、タプルの内部に自分自身を要素として指定したい場合がありえます。
例えば次のような場合を考えてみます。

enum メッセージ {
    case 文書(String, String)     // 差出人、文書
    case データ(String, [Int8])   // 差出人、データ列
    case 転送(String, メッセージ)  // 差出人、メッセージ
}

このデータの宣言は間違っていないようにも見えますが、Swiftでは扱うことができません。

Swiftでは列挙型値型のデータですので、直感的に説明するならば、負荷情報は列挙型のデータを表すメモリ位置に直接並べて格納されています。
上記のように自分自身を含む、つまり再帰的なデータ構造はデータを格納するために必要なメモリ容量がコンパイル時に決定することができません

これに対して参照型のデータは、メモリ上のどこかに存在する実体を間接的に参照するためのための情報なので、メモリの問題はありません。

しかし、再帰的な列挙型が使えると便利な場面も多いので、Swiftでは列挙型自分自身を付加情報に含む場合、該当するcaseの前にindirectというキーワードを記述します
これを間接指定と呼びます。indirect、つまり間接であるとは、その部分だけポインタのように間接参照を行うことを意味します。

今の話を以下の例に示します。
CustomStringConvertibleプロトコルを採用し、printで表示できるようにしています。

enum メッセージ : CustomStringConvertible {
    case 文書(String, String)              // 差出人、文書
    case データ(String, [Int8])            // 差出人、データ列
    indirect case 転送(String, メッセージ)  // 差出人、メッセージ

    ver description: String {
        switch self {
        case let .文書(from, str): return from + "(" + str + ")"
        case let .データ(from, _): return from + "[データ]"
        case let .転送(from, msg): return from + "←\(msg)"
        }
    }
}

実行例を示します。

let m1 = メッセージ.文書("伊藤", "休みます")
let m2 = メッセージ.転送("白石", m1)
let m3 = メッセージ.転送("山田", m2)
print(m3)   // "山田←白石←伊藤(休みます)"を出力

再帰的なcaseパターンがいくつかある場合には、enumの前にindirectを置いて全体に間接指定を行うことができます。

6.おわりに


今回は共用型の列挙型についての記事を書きましたが、プロトコルやオプショナル型の扱いについては、記事が少し長くなってしまうので今回は解説していません。
来週以降はクラスなどについての解説をしていこうと思います。
ここまで読んでくれた方、ありがとうございました。

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

VisionFrameworkとCoreMLで画像解析をするときの流れを図にしてみた

VisionFrameworkとCoreMLを使った画像の解析は、そんなに手順は多くないので、過去のコードを見ればなんとなく思い出せたりしますが、手元に図があると良いかもということで描いてみました。

画像解析と言ってもいろんなパターンがありますが、今回はAppleの公式サンプルであるRecognizing Objects in Live Captureの流れを起こしたものになります。

あと、こちらの記事はVisionFrameworkとCoreMLを使った画像の解析のコードを端的にまとめられていて参照するのにちょうどよいです。
VisionFrameworkとCoreMLを使用した画像解析


image.png

iOS開発について発信しています。
NoteではiOS開発について定期的に発信していますので、フォローしていただけますと幸いです。
https://note.com/tokyoyoshida

Twitterでは簡単なtipsを発信しています。
https://twitter.com/jugemjugemjugem

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

[2020年最新版]Xcodeで肥大したストレージ容量を抹殺する方法

はじめに

Xcodeを使用している方なら誰もが遭遇するであろうストレージ容量の肥大化による容量不足。今回はそんな悩みを解決する方法について投稿します。

環境

MacBook Air (Retina, 13-inch, 2019)
macOS Catalina 10.15.6
Xcode 12.0

削除対象のディレクトリ

DerivedDataディレクトリ

~/Library/Developer/Xcode/DerivedData

iOS DeviceSupportディレクトリ

~/Library/Developer/Xcode/iOS DeviceSupport

CoreSimulatorディレクトリ

~/Library/Developer/CoreSimulator

Cachesディレクトリ

~/Library/Caches

iCloudへ保存

Xcodeで作成したプロジェクトやデスクトップに配置しているフォルダや書類などをiCloudにて保存することで空き容量を確保できる。

最後に

個人差はあると思いますが、私は35G容量を確保することができました!
参考にしてください!

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

ARKitやSceneKitの図形をMetalシェーダーで変形させる方法

ARKitやSceneKitの図形を自由に変形させる方法を紹介します。

なお、この手法を使って最終的にはARで空中に水を浮かべるといった処理も作りましたので、こちらの記事もご覧下さい。

ARKit + Metalで空中に水を浮かべる方法

やり方

今回はこんな感じの球体を変形させていきます。
※下の画像は球体にライトを当てていないのでのっぺりしていますが球体です。

SceneKitのマテリアルをMetalシェーダーで描画させる方法

球体のノードを作り、SCNProgramを使ってMetalのシェーダーの関数を設定すると、シェーダーに描画させることができます。

GameViewController.swift
// 球体をノードに追加する
let sphereNode = SCNNode()
sphereNode.geometry = SCNSphere(radius: 2)
sphereNode.position.y += Float(0.05)
sphereNode.name = "my_node"

// Metalのシェーダーを指定する
let program = SCNProgram()
program.vertexFunctionName = "vertexShader"
program.fragmentFunctionName = "fragmentShader"
sphereNode.geometry?.firstMaterial?.program = program
// 経過時間の情報をシェーダーに渡す
let time = Float(Date().timeIntervalSince(startDate))
globalData.time = time
let uniformsData = Data(bytes: &globalData, count: MemoryLayout<GlobalData2>.size)
sphereNode.geometry?.firstMaterial?.setValue(uniformsData, forKey: "globalData")

このあたりを詳しく知りたい方はこちらを読むと良いと思います。

Metal入門

Vertexシェーダーで変形してみる

まずは、簡単な変形をしてみます。
x座標にy座標分を足してみます。

Shader.metal
vertex ColorInOut vertexShader(VertexInput2          in       [[ stage_in ]],
                               constant SCNSceneBuffer& scn_frame [[buffer(0)]],
                               constant NodeBuffer& scn_node [[ buffer(1) ]],
                               device GlobalData2 &globalData [[buffer(2)]])
{
    // 出力用変数
    ColorInOut out;
    // 頂点の座標情報
    float3 pos = in.position;
    // 頂点のxにyを足している。ここが変形処理の実体
    pos.x += pos.y;
    // SceneKit用のMVP変換をする
    float4 transformed = scn_node.modelViewProjectionTransform * float4(pos, 1.0);
    // 変換後の座標を出力変数に入れる
    out.position = transformed;
    // テクスチャー座標は何もせずに出力変数に入れる
    out.texCoords = in.texCoords;

    // 出力する
    return out;
}

このようになります。

y座標の値が大きくなるほど(y座標は下から上に大きくなる)、x座標が大きくなっている(左から右に大きくなる)のがわかります。

なお、シェーダーの宣言の方法や、modelViewProjectionTransformについては、先程紹介したMetal入門に詳しくありますので、そちらをご覧ください。

cosを与えて変形させてみる。

さきほど、pos.x += pos.y;としていたところを、次のように変更してみます。

Shader.metal
    pos.x += cos(pos.y);

このようになります。

面白いですね。cosを与えると-1〜1の範囲で波打つようになります。

こうした数式の考え方はこちらの本にわかりやすく書いてありました。

Unityでわかる!ゲーム数学

経過時間を与えてアニメーションさせてみる

さきほど、pos.x += cos(pos.y);としていたところを、次のように変更してみます。

Shader.metal
    pos.x += cos(pos.y + globalData.time);

こんな感じで面白い動きをします。
qiita用動画.gif

最初に紹介した空中に水を浮かべる方法では、x, y, zのそれぞれについてこのような処理をすることで水のような感じを出しています。

NoteではiOS開発について定期的に発信していますので、フォローしていただけますと幸いです。
https://note.com/tokyoyoshida

Twitterでは簡単なtipsを発信しています。
https://twitter.com/jugemjugemjugem

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

DeepLab V3+ をCore MLに変換する

segmentation.gif
凍結グラフから変換します。

DeepLabをトレーニングする方法、凍結グラフとしてエクスポートする方法は下記の記事を参照していただければ。
DeepLabv3+を自前のデータセットで学習させる

手順

1、必要なライブラリを用意

TensorFlow1をインストールしていない場合はインストール。

pip install tensorflow==1.x

Colabの場合。TensorFlow1を選択。

%tensorflow_version 1.x

Core ML Toolsをインストール。

pip install coremltools==4.0

2、変換

import coremltools as ct

input = ct.ImageType(shape=(1, 513, 513, 3))
mlmodel = ct.convert('./frozen_graph.pb',inputs=[input])
mlmodel.save('./deeplabv3')

?


お仕事のご相談こちらまで
rockyshikoku@gmail.com

Core MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。

Twitter
Medium

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

if文について理解しよう!

今回は、ifについて学習したので、アウトプットしていきます
※以下の内容は、学習内容のアウトプット用のため、誤りがある場合があります。予めご了承ください

if文とは?

ifを一言でいうと、条件の成否に応じて実行する構文のことです。
基本的な書き方は以下の通りです

qiita.rbvar
if 条件式 { 条件式がtrueの場合に実行される文 }

次は簡単な例を見ていきましょう。

qiita.rbvar
1.let value = 5
2.if value <= 10 {

print("valueは10以下です")

}

実行結果valueは10以下です

このコードを日本語に直してみると

qiita.rbvar
もし[valueの値が10以下だったら{}の中のメソッド実行してくださいね]という意味になります

非常に簡単で、わかりやすいですよね!
ちなみに、if文の条件式はBool型を返す必要があります!

では、さらにif文について深堀していきましょう!

else節とは?

else節は、条件が成立しなかった場合に実行する文のことを言います。
基本的な構文を見ていきましょう!

qiita.rbvar
if 条件式 { 条件式がtrueの場合に実行される文

 }else{
条件式がfalseの場合に実行される文
}

次は簡単な例を見ていきましょう!

qiita.rbvar
1.let value = 26
2.if value <= 10 {

print("valueは10以下です")

}else{

print("valueは10より大きいです")

}
//実行結果:valueは10より大きいです

上記と同様に、このコードを日本語に直してみると

qiita.rbvar
もし[valueの値が10以下だったら{}の中のメソッド実行してくださいね
そうでないならelse{}の中のメソッドを実行してくださいという意味になります

また、else節にはif文をつなげて書くことができます。

qiita.rbvar
if 条件式1 {
 条件式1がtrueの場合に実行される文

 }else if 条件式2{
条件式1がfalseかつ条件式2がtrueの場合に実行される

}else{
条件式1条件式2の両方がfalseの場合に実行される
}

if-let文とは?

if-let文は、optional型の値の有無に応じて、分岐を行い値が存在する場合は、値の取り出しも同時に行える文のことです。

Optional型とは? Optional型を深堀したい人はこちら!
Optional(Wrapped)型を理解しよう!

では、基本的な書き方を見ていきましょう!

qiita.rbvar
if let 定数名 = Optional型の値{
値が存在する場合に実行される文

}else{
値が存在しない場合に実行される文

}

次は、簡単な例を見ていきましょう。

qiita.rbvar
let optionalA =Optional("G")

if let X = optionalA {

print("値は\(X)")

}else{
print("値が存在しません")

実行結果:値はGです

最後に

今回は、if文についてアウトプットしていきました。
プログラミング初心者でも、理解しやすく、アプリ開発でも多用される構文なので、しっかり基礎をマスターしていきたいです。

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

UITextContentType の rawValue 一覧。

UITextContentType.init(rawValue: ) を使いたかったんですけど、rawValue が若干違っていたので自分用にリスト化しておきます。

UITextContentType rawValue
.addressCity address-level2
.addressCityAndState address-level1+2
.addressState address-level1
.countryName country-name
.creditCardNumber cc-number
.emailAddress email
.familyName family-name
.fullStreetAddress street-address
.givenName given-name
.jobTitle organization-title
.location location
.middleName additional-name
.name name
.namePrefix honorifix-prefix
.nameSuffix honorifix-suffix
.newPassword new-password
.nickname nickname
.oneTimeCode one-time-code
.organizationName organization
.password password
.postalCode postal-code
.streetAddressLine1 address-line1
.streetAddressLine2 address-line2
.sublocality address-level3
.telephoneNumber tel
.URL url
.username username

たぶん二度と使う気はしないですが、ざっと検索した限りだと見つけられなかったのでまとめておきます。

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

【入門】iOS アプリ開発 #11【ゲームのアレンジ(加速度センサーを使用した操作など)】

はじめに

公開されている仕様書をもとに作成したパックマンに、アレンジを加えてみたい。パックマンををスワイプだけでなくタッチや加速度センサーで操作できるようにし、オリジナルの迷路も追加してみる。ソースコードは GitHub に公開しているので参照してほしい。

Image2.png

コンフィグレーション・メニュー

操作や迷路を切り替えるためのコンフィグレーション・メニューを追加する。
追加する迷路は開発途中の下記、仕様書を参考にした。

Image3.png

ソースコード

CgSceneCreditMode にメニューを追加する。クレジットを入れて暫くすると case 1 でメニューの入り口が表示される。そこをタッチするとメニューに入れるようにした。

CgSceneFrameクラスの構造は、以前の【入門】iOS アプリ開発 #5【シーケンスの設計】を参照してほしい。

/// Credit Mode
class CgSceneCreditMode : CgSceneFrame {

    enum EnEvent: Int {
        case EnterConfig = 3
        case Operation = 5
        case ExtraMode = 6
        case DebugMode = 7
        case Language = 8
        case ResetSetting = 9
        case ExitConfig = 10
        case None
    }

    private let table_enterConfiguration: [(Int,Int,Int,Int,EnEvent)] = [
        (  26, 34, 28, 36, .EnterConfig)
    ]

    private let table_setConfiguration: [(Int,Int,Int,Int,EnEvent)] = [
        (  26, 34, 28, 36, .ExitConfig),
        (   4, 25, 28, 27, .Operation),
        (   4, 20, 28, 22, .ExtraMode),
        (   4, 15, 28, 17, .DebugMode),
        (   4, 10, 28, 12, .Language),
        (   4,  5, 28,  7, .ResetSetting)
    ]

    private var table_search: [(column0:Int,row0:Int,column1:Int,row1:Int,event:EnEvent)] = []
    private var configMode: Bool = false

    /// Event handler
    /// - Parameters:
    ///   - sender: Message sender
    ///   - id: Message ID
    ///   - values: Parameters of message
    override func handleEvent(sender: CbObject, message: EnMessage, parameter values: [Int]) {
        if message == .Touch {
            let position = CgPosition.init(x: CGFloat(values[0]), y: CGFloat(values[1]))
            let event = search(column: position.column, row: position.row)
            if event == .None {
                if !configMode {
                    stopSequence()
                }
            } else {
                goToNextSequence(event.rawValue)
            }
        }
    }

    /// Handle sequence
    /// To override in a derived class.
    /// - Parameter sequence: Sequence number
    /// - Returns: If true, continue the sequence, if not, end the sequence.
    override func handleSequence(sequence: Int) -> Bool {
        switch sequence {
            case  0:
                configMode = false
                table_search = []
                clear()
                printFrame()
                printPlayerScore()
                printHighScore()
                printCredit()
                printRounds()
                background.print(0, color: .Orange, column: 6, row: 19, string: "PUSH START BUTTON")
                background.print(0, color: .Cyan, column: 8, row: 15, string: "1 PLAYER ONLY")

                if context.language == .English {
                    background.print(0, color: .Pink, column: 1, row: 11, string: "BONUS PAC-MAN FOR 20000 ]^_")
                    background.print(0, color: .Purple, column:4, row: 4, string: "@ 2020 HIWAY.KIKUTADA")
                } else {
                    background.print(0, color: .Pink, column: 1, row: 11, string: "BONUS PAC-MAN FOR 10000 ]^_")
                    background.print(0, color: .Purple, column: 7, row: 7, string: "@ #$%&'()*  2020")   // NAMACO
                }
                goToNextSequence(after: 60*16*2)

            case 1:
                table_search = table_enterConfiguration
                background.print(0, color: .White, column:  27, row: 35, string: "$")
                goToNextSequence()

            case 2:
                // wait for event
                break;

            case 3:
                configMode = true
                table_search = table_setConfiguration
                background.fill(0, texture: 0)
                printFrame()
                printPlayerScore()
                printHighScore()
                printCredit()
                background.print(0, color: .White, column: 27, row: 35, string: "#")
                background.print(0, color: .Red,   column:  8, row: 31, string: "CONFIGURATION")
                background.print(0, color: .White, column:  4, row: 26, string: "OPERATION")
                background.print(0, color: .White, column:  4, row: 21, string: "EXTRA MODE")
                background.print(0, color: .White, column:  4, row: 16, string: "DEBUG MODE")
                background.print(0, color: .White, column:  4, row: 11, string: "LANGUAGE")
                background.print(0, color: .White, column:  4, row:  6, string: "SETTING")
                print_operation(color: .Pink)
                print_extraMode(color: .Pink)
                print_debugMode(color: .Pink)
                print_language(color: .Pink)
                print_resetSetting(color: .Pink)
                goToNextSequence()

            case 4:
                // wait for event
                break;

            case 5: // operation
                context.operationMode = context.operationMode.getNext()
                print_operation(color: .Yellow)
                goToNextSequence(4)

            case 6: // ExtraMode/
                context.extraMode = context.extraMode.getNext()
                print_extraMode(color: .Yellow)
                goToNextSequence(4)

            case 7: // DebugMode
                context.debugMode = context.debugMode.getNext()
                print_debugMode(color: .Yellow)
                goToNextSequence(4)

            case 8: // Language
                context.language = context.language.getNext()
                print_language(color: .Yellow)
                goToNextSequence(4)

            case 9: // ResetSetting
                context.resetSetting = context.resetSetting.getNext()
                print_resetSetting(color: .Yellow)
                goToNextSequence(4)

            case 10:
                context.saveConfiguration()
                goToNextSequence(0)

            default:
                clear()
                // Stop and exit running sequence.
                return false
        }

        return true
    }

    func clear() {
        background.fill(0, texture: 0)
    }

    func print_operation(color: CgCustomBackgroundManager.EnBgColor) {
        let str = context.operationMode.getString()
        background.print(0, color: color, column: 16, row: 26, string: str)
    }

    func print_extraMode(color: CgCustomBackgroundManager.EnBgColor) {
        let str = context.extraMode.getString()
        background.print(0, color: color, column: 16, row: 21, string: str)
    }

    func print_debugMode(color: CgCustomBackgroundManager.EnBgColor) {
        let str = context.debugMode.getString()
        background.print(0, color: color, column: 16, row: 16, string: str)
    }

    func print_language(color: CgCustomBackgroundManager.EnBgColor) {
        let str = context.language.getString()
        background.print(0, color: color, column: 16, row: 11, string: str)
    }

    func print_resetSetting(color: CgCustomBackgroundManager.EnBgColor) {
        let str = context.resetSetting.getString()
        background.print(0, color: color, column: 16, row: 6, string: str)
    }

    func search(column: Int, row: Int) -> EnEvent {
        var event: EnEvent = .None
        for i in 0 ..< table_search.count {
            let t = table_search[i]
            if (t.column0 <= column && t.row0 <= row) && (t.column1 >= column && t.row1 >= row) {
                event = t.event
                break
            }
        }
        return event
    }

}

handelEventメソッドで Touch のイベントを受信したら、search関数で押された範囲をチェックして該当していれば、そのシーケンスの case を実行する。

設定値は CgContextクラスで定義しておく。

class CgContext {

    enum EnOperationMode: Int {
        case Swipe = 0, Touch, Accel

        func getString() -> String {
            switch self {
                case .Swipe: return "(SWIPE)"
                case .Touch: return "(TOUCH)"
                case .Accel: return "(ACCEL)"
            }
        }

        func getNext() -> EnOperationMode {
            switch self {
                case .Swipe: return .Touch
                case .Touch: return .Accel
                case .Accel: return .Swipe
            }
        }
    }

    enum EnLanguage: Int {
        case English = 0, Japanese

        func getString() -> String {
            switch self {
                case .English:  return "(ENGLISH) "
                case .Japanese: return "(JAPANESE)"
            }
        }

        func getNext() -> EnLanguage {
            switch self {
                case .English: return .Japanese
                case .Japanese: return .English
            }
        }
    }

    enum EnOnOff: Int {
        case Off = 0, On

        func getString() -> String {
            switch self {
                case .On:  return "(ON) "
                case .Off: return "(OFF)"
            }
        }

        func getNext() -> EnOnOff {
            switch self {
                case .On: return .Off
                case .Off: return .On
            }
        }
    }

    enum EnSetting: Int {
        case Clear = 0, Keep

        func getString() -> String {
            switch self {
                case .Clear: return "(CLEAR)"
                case .Keep:  return "(KEEP) "
            }
        }

        func getNext() -> EnSetting {
            switch self {
                case .Clear: return .Keep
                case .Keep: return .Clear
            }
        }
    }

    var operationMode: EnOperationMode = .Swipe
    var extraMode: EnOnOff = .Off
    var debugMode: EnOnOff = .Off
    var resetSetting: EnSetting = .Clear
    var language: EnLanguage = .Japanese

    // 以下、省略

加速度センサーによる操作

class GameScene: SKScene {

    /// Main object with main game sequence
    private var gameMain: CgGameMain!

    /// Points for Swipe operation
    private var startPoint: CGPoint = CGPoint.init()
    private var endPoint: CGPoint = CGPoint.init()

    // MotionManager for accel
    private var motionManager: CMMotionManager!

    override func didMove(to view: SKView) {

        //  Create and start game sequence.
        gameMain  = CgGameMain(skscene: self)
        gameMain.startSequence()

        // Create motion manager
        motionManager = CMMotionManager()
        motionManager.accelerometerUpdateInterval = 0.05

        motionManager.startAccelerometerUpdates(
            to: OperationQueue.current!, withHandler: {
                (accelData: CMAccelerometerData?, errorOC: Error?) in self.sendAccelEvent(acceleration: accelData!.acceleration)
            }
        )

    }

    func sendAccelEvent(acceleration: CMAcceleration){
        let x_diff: Int = Int(acceleration.x * 100)
        let y_diff: Int = Int(acceleration.y * 100)

        if abs(x_diff) > abs(y_diff) {
            gameMain.sendEvent(message: .Accel, parameter: [Int(x_diff > 0 ? EnDirection.Right.rawValue : EnDirection.Left.rawValue)])
        } else {
            gameMain.sendEvent(message: .Accel, parameter: [Int(y_diff > 0 ? EnDirection.Up.rawValue : EnDirection.Down.rawValue)])
        }
    }

GameSceneクラスで CMMotionManager を生成し、値を取得する周期を accelerometerUpdateIntervalメンバに設定、
コールバック・メソッドに sendAccelEvent を設定しておく。

sendAccelEvent メソッドは 0.05s周期で呼ばれ、この中で加速度センサーの傾きから方向を算出し、スワイプ操作と同様に gameMain.sendEvent でオブジェクトにイベント送信する。

class CgPlayer : CgActor {

    /// Event handler
    /// - Parameters:
    ///   - sender: Message sender
    ///   - id: Message ID
    ///   - values: Parameters of message
    override func handleEvent(sender: CbObject, message: EnMessage, parameter values: [Int]) {
        guard !deligateActor.isDemoMode() else { return }
        switch message {
            case .Accel where deligateActor.isOperationMode(mode: CgContext.EnOperationMode.Accel): fallthrough
            case .Swipe where deligateActor.isOperationMode(mode: CgContext.EnOperationMode.Swipe):
                if let direction = EnDirection(rawValue: values[0]) {
                    targetDirecition = direction
                }

            case .Touch where deligateActor.isOperationMode(mode: CgContext.EnOperationMode.Touch):
                setTargetPosition(x: values[0], y: values[1])
                targetDirecition = decideDirectionByTarget(forcedDirectionChange: true)
                position.amountMoved = 0

            default:
                break
        }
    }

CgPlayerクラスの handleEvent の中で、コンフィグレーション・メニューで設定した操作が有効ならば(deligateActor.isOperationMode(mode: CgContext.EnOperationMode.Accel))、加速度センサーのイベントを受け付けるようにする。

こちらは以前の、【入門】iOS アプリ開発 #6【キャラクタの操作】を参照してほしい。

オリジナル迷路の追加

CgSceneMazeクラスの getMazeSourceメソッドを、メニューの値で迷路データを切り替えるようにする。この迷路データの作りは古典的だが意外と簡単。

    func getMazeSource() -> [String] {

        let mazeSource: [String] = [
            "aggggggggggggjiggggggggggggb",
            "e111111111111EF111111111111f",
            "e1AGGB1AGGGB1EF1AGGGB1AGGB1f",
            "e3E  F1E   F1EF1E   F1E  F3f",
            "e1CHHD1CHHHD1CD1CHHHD1CHHD1f",
            "e11111111111111111111111111f",
            "e1AGGB1AB1AGGGGGGB1AB1AGGB1f",
            "e1CHHD1EF1CHHJIHHD1EF1CHHD1f",
            "e111111EF1111EF1111EF111111f",
            "chhhhB1EKGGB1EF1AGGLF1Ahhhhd",
            "     e1EIHHD2CD2CHHJF1f     ",
            "     e1EF          EF1f     ",
            "     e1EF QhUWWVhR EF1f     ",
            "gggggD1CD f      e CD1Cggggg",
            "____  1   f      e   1  ____" ,
            "hhhhhB1AB f      e AB1Ahhhhh",
            "     e1EF SggggggT EF1f     ",
            "     e1EF          EF1f     ",
            "     e1EF AGGGGGGB EF1f     ",
            "aggggD1CD1CHHJIHHD1CD1Cggggb",
            "e111111111111EF111111111111f",
            "e1AGGB1AGGGB1EF1AGGGB1AGGB1f",
            "e1CHJF1CHHHD2CD2CHHHD1EIHD1f",
            "e311EF1111111  1111111EF113f",
            "kGB1EF1AB1AGGGGGGB1AB1EF1AGl",
            "YHD1CD1EF1CHHJIHHD1EF1CD1CHZ",
            "e111111EF1111EF1111EF111111f",
            "e1AGGGGLKGGB1EF1AGGLKGGGGB1f",
            "e1CHHHHHHHHD1CD1CHHHHHHHHD1f",
            "e11111111111111111111111111f",
            "chhhhhhhhhhhhhhhhhhhhhhhhhhd"
        ]

        let mazeSourceExtra1: [String] = [
                "aggggjiggggggjiggggggjiggggb",
                "e1111EF111111EF111111EF1111f",
                "e1AB1EF1AGGB1CD1AGGB1EF1AB1f",
                "e3EF1EF1E  F1111E  F1EF1EF3f",
                "e1CD1CD1CHHD1AB1CHHD1CD1CD1f",
                "e111111111111EF111111111111f",
                "kGGB1AGGB1AGGLKGGB1AGGB1AGGl",
                "YHJF1EIHD1CHHJIHHD1CHJF1EIHZ",
                "e1EF1EF111111EF111111EF1EF1f",
                "e1EF1EKGGGGB1EF1AGGGGLF1EF1f",
                "e1CD1CHHHHHD2CD2CHHHHHD1CD1f",
                "e11111111          11111111f",
                "e1AB1AGGB QhUWWVhR AGGB1AB1f",
                "e1EF1CHHD f      e CHHD1EF1f",
                "e1EF11111 f      e 11111EF1f",
                "kGLF1AGGB f      e AGGB1EKGl",
                "YHHD1EIHD SggggggT CHJF1CHHZ",
                "e1111EF11          11EF1111f",
                "e1AGGLF1AGGGGGGGGGGB1EKGGB1f",
                "e1CHHJF1CHHHHJIHHHHD1EIHHD1f",
                "e1111EF111111EF111111EF1111f",
                "kGGB1EKGGGGB1EF1AGGGGLF1AGGl",
                "YHHD1CHHHHHD2CD2CHHHHHD1CHHZ",
                "e111111111111  111111111111f",
                "e1AGGGB1AGGGGGGGGGGB1AGGGB1f",
                "e1CHHJF1CHHHHJIHHHHD1EIHHD1f",
                "e3111EF111111EF111111EF1113f",
                "kGGB1EF1AB1AGLKGB1AB1EF1AGGl",
                "YHHD1CD1EF1CHHHHD1EF1CD1CHHZ",
                "e1111111EF11111111EF1111111f",
                "chhhhhhhnmhhhhhhhhnmhhhhhhhd"
        ]

        return context.extraMode == CgContext.EnOnOff.Off ? mazeSource : mazeSourceExtra1
    }

まとめ

今回パックマン・ゲームのアレンジとして以下を追加した。
 - コンフィグレーション・メニュー
 - 加速度センサー、タッチ操作
 - オリジナル迷路

最後に

パックマンのゲームを題材にして、SwiftによるiOSプログラミングを勉強してきた。
ソースコードはコメント含む 5000行程度で、アーケードゲームに相当するクオリティとアレンジを簡単に実現することができた。

コロナ禍のどこに行けない夏休みから始まったが、また一つプログラミングの面白さを味わう良い機会ともなった。

全11回のiOSアプリ開発入門は、これにて終了。

<読んでいただいた方、ありがとうございました〜>

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