- 投稿日:2020-10-11T23:24:17+09:00
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]")ウィジェットのサイズ(小中大)なんかも取れます。
参考リンク
- Detect app launch from WidgetKit widget extension - 他の判別方法も載っています。
- 投稿日:2020-10-11T23:22:59+09:00
[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 との整合性を取るため、
sharedInstance
がcrashlytics
に変更になったためです。Crashlytics.sharedInstance().xxxとなっている箇所を
Crashlytics.crashlytics().xxxに書き換えましょう。
実行スクリプト名を修正
実行スクリプト名も
Fabric
からFirebaseCrashlytics
に変更されています。Builds Phase 等、スクリプトを実行している箇所を
${PODS_ROOT}/Fabric/runから
${PODS_ROOT}/FirebaseCrashlytics/runに書き換えましょう。
まとめ
私のプロジェクトではこの対応のみでビルドできるようになりました。しかし、
setUserIdentifier
など、他のメソッドを使用している場合は様々なエラーが出ると予想されます。その場合は、下記公式ドキュメントを参考に、一つ一つソースコードを書き換えていく必要があります。
- 投稿日:2020-10-11T22:02:51+09:00
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] }) } } }プレビュー
環境
2020年10月11日時点
Xcode 12.0.1
Swift 5.3
これ以外の環境については特に検証してないのでわからないですが、
SwiftUIが動く環境であれば可能だと思い込んでますNavigationBarの背景色を変えたい
とりあえず、さくっとググってみるといくつかの記事で、コンストラクタとかの初期化処理の際に
UINavigationBar.appearance()
でしかできないとかかんとか、、、たしかに、
UINavigationBar.appearance()
でも変えれることは変えれるが、
画面ごとに色を変えるのが困難(ってかこれだけなら無理なのでは、、、)Stack Overflowありがとう!
SwiftUI update navigation bar title color
ちゃんと、いい感じに変更する方法を教えてくださってる方がいらっしゃりましたこの方法のデメリット
プレビューをライブモードにしないとスタイルが反映されないです
ライブモードにすればいいだけなので、どうでもよいですねってことで、
UIViewControllerRepresentable
を使っていい感じに背景色を変更できました
まだまだ、SwiftUIは成長途中なので、この子を使いこなさないといけなさそう
- 投稿日:2020-10-11T21:45:29+09:00
[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 に、ファイル名を設定する。
- 投稿日:2020-10-11T18:50:45+09:00
【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.おわりに
今回は共用型の列挙型についての記事を書きましたが、プロトコルやオプショナル型の扱いについては、記事が少し長くなってしまうので今回は解説していません。
来週以降はクラスなどについての解説をしていこうと思います。
ここまで読んでくれた方、ありがとうございました。
- 投稿日:2020-10-11T16:56:26+09:00
VisionFrameworkとCoreMLで画像解析をするときの流れを図にしてみた
VisionFrameworkとCoreMLを使った画像の解析は、そんなに手順は多くないので、過去のコードを見ればなんとなく思い出せたりしますが、手元に図があると良いかもということで描いてみました。
画像解析と言ってもいろんなパターンがありますが、今回はAppleの公式サンプルであるRecognizing Objects in Live Captureの流れを起こしたものになります。
あと、こちらの記事はVisionFrameworkとCoreMLを使った画像の解析のコードを端的にまとめられていて参照するのにちょうどよいです。
VisionFrameworkとCoreMLを使用した画像解析iOS開発について発信しています。
NoteではiOS開発について定期的に発信していますので、フォローしていただけますと幸いです。
https://note.com/tokyoyoshidaTwitterでは簡単なtipsを発信しています。
https://twitter.com/jugemjugemjugem
- 投稿日:2020-10-11T15:48:30+09:00
[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容量を確保することができました!
参考にしてください!
- 投稿日:2020-10-11T07:58:39+09:00
ARKitやSceneKitの図形をMetalシェーダーで変形させる方法
ARKitやSceneKitの図形を自由に変形させる方法を紹介します。
なお、この手法を使って最終的にはARで空中に水を浮かべるといった処理も作りましたので、こちらの記事もご覧下さい。
やり方
今回はこんな感じの球体を変形させていきます。
※下の画像は球体にライトを当てていないのでのっぺりしていますが球体です。
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")このあたりを詳しく知りたい方はこちらを読むと良いと思います。
Vertexシェーダーで変形してみる
まずは、簡単な変形をしてみます。
x座標にy座標分を足してみます。Shader.metalvertex 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.metalpos.x += cos(pos.y);面白いですね。cosを与えると-1〜1の範囲で波打つようになります。
こうした数式の考え方はこちらの本にわかりやすく書いてありました。
経過時間を与えてアニメーションさせてみる
さきほど、
pos.x += cos(pos.y);
としていたところを、次のように変更してみます。Shader.metalpos.x += cos(pos.y + globalData.time);最初に紹介した空中に水を浮かべる方法では、x, y, zのそれぞれについてこのような処理をすることで水のような感じを出しています。
NoteではiOS開発について定期的に発信していますので、フォローしていただけますと幸いです。
https://note.com/tokyoyoshidaTwitterでは簡単なtipsを発信しています。
https://twitter.com/jugemjugemjugem
- 投稿日:2020-10-11T04:33:36+09:00
DeepLab V3+ をCore MLに変換する
DeepLabをトレーニングする方法、凍結グラフとしてエクスポートする方法は下記の記事を参照していただければ。
DeepLabv3+を自前のデータセットで学習させる手順
1、必要なライブラリを用意
TensorFlow1をインストールしていない場合はインストール。
pip install tensorflow==1.xColabの場合。TensorFlow1を選択。
%tensorflow_version 1.xCore ML Toolsをインストール。
pip install coremltools==4.02、変換
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.comCore MLを使ったアプリを作っています。
機械学習関連の情報を発信しています。
- 投稿日:2020-10-11T01:10:59+09:00
if文について理解しよう!
今回は、ifについて学習したので、アウトプットしていきます
※以下の内容は、学習内容のアウトプット用のため、誤りがある場合があります。予めご了承くださいif文とは?
ifを一言でいうと、条件の成否に応じて実行する構文のことです。
基本的な書き方は以下の通りですqiita.rbvarif 条件式 { 条件式がtrueの場合に実行される文 }次は簡単な例を見ていきましょう。
qiita.rbvar1.let value = 5 2.if value <= 10 { print("valueは10以下です") } 実行結果:valueは10以下ですこのコードを日本語に直してみると
qiita.rbvarもし、[valueの値が10以下だったら{}の中のメソッド実行してくださいね]という意味になります非常に簡単で、わかりやすいですよね!
ちなみに、if文の条件式はBool型を返す必要があります!では、さらにif文について深堀していきましょう!
else節とは?
else節は、条件が成立しなかった場合に実行する文のことを言います。
基本的な構文を見ていきましょう!qiita.rbvarif 条件式 { 条件式がtrueの場合に実行される文 }else{ 条件式がfalseの場合に実行される文 }次は簡単な例を見ていきましょう!
qiita.rbvar1.let value = 26 2.if value <= 10 { print("valueは10以下です") }else{ print("valueは10より大きいです") } //実行結果:valueは10より大きいです上記と同様に、このコードを日本語に直してみると
qiita.rbvarもし、[valueの値が10以下だったら{}の中のメソッド実行してくださいね。 そうでないなら、else{}の中のメソッドを実行してくださいという意味になります。また、else節にはif文をつなげて書くことができます。
qiita.rbvarif 条件式1 { 条件式1がtrueの場合に実行される文 }else if 条件式2{ 条件式1がfalseかつ、条件式2がtrueの場合に実行される }else{ 条件式1、条件式2の両方がfalseの場合に実行される }if-let文とは?
if-let文は、optional型の値の有無に応じて、分岐を行い値が存在する場合は、値の取り出しも同時に行える文のことです。
Optional型とは? Optional型を深堀したい人はこちら!
Optional(Wrapped)型を理解しよう!では、基本的な書き方を見ていきましょう!
qiita.rbvarif let 定数名 = Optional型の値{ 値が存在する場合に実行される文 }else{ 値が存在しない場合に実行される文}
次は、簡単な例を見ていきましょう。
qiita.rbvarlet optionalA =Optional("G") if let X = optionalA { print("値は\(X)") }else{ print("値が存在しません") 実行結果:値はGです最後に
今回は、if文についてアウトプットしていきました。
プログラミング初心者でも、理解しやすく、アプリ開発でも多用される構文なので、しっかり基礎をマスターしていきたいです。
- 投稿日:2020-10-11T01:07:26+09:00
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 .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 たぶん二度と使う気はしないですが、ざっと検索した限りだと見つけられなかったのでまとめておきます。
- 投稿日:2020-10-11T00:01:09+09:00
【入門】iOS アプリ開発 #11【ゲームのアレンジ(加速度センサーを使用した操作など)】
はじめに
公開されている仕様書をもとに作成したパックマンに、アレンジを加えてみたい。パックマンををスワイプだけでなくタッチや加速度センサーで操作できるようにし、オリジナルの迷路も追加してみる。ソースコードは GitHub に公開しているので参照してほしい。
コンフィグレーション・メニュー
操作や迷路を切り替えるためのコンフィグレーション・メニューを追加する。
追加する迷路は開発途中の下記、仕様書を参考にした。ソースコード
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アプリ開発入門は、これにて終了。
<読んでいただいた方、ありがとうございました〜>