20210607のSwiftに関する記事は8件です。

[Swift]UserDefaultsでstructを保存する方法

はじめに この記事ではUserDefaultsに自作のstruct、structの配列を保存する方法をしまします。この方法のほかにもっと良い方法があれば教えてください。よろしくお願いします。 概要 自作のstructのインスタンスをencodeしてData型に変換することでUserDefaultsに保存します。 UserDefaultsからデータを取得する際はdecodeすることでもとに戻し、使えるようにします。 前半では自作のstructを保存、取得する方法を紹介します。 後半では自作のstructを配列にして保存する方法を紹介します。 structを作る まずは任意のstructを作成します。 今回は以下のように作成しました。 自作のstrucrにはCodableを準拠させます。 struct Pirate: Codable { let name: String let age: Int let siblings: [String]? } インスタンス化 今回は以下のようにインスタンスを作成しました。 let rufy = Pirate(name: "ルフィ", age: 19, siblings: ["エース","サボ"]) encode UserDefaultsに保存するためにData型に変換します。 let encoder = JSONEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase guard let crew = try? encoder.encode(mugiwaraFamily) else { return } 保存 UserDefaultsに保存します。 forkeyはString型で自分の好きなkeyを設定してください。 保存したデータを取得するときにkeyを使います。 これで保存は完了です。 UserDefaults.standard.set(crew, forKey: key) 取得 decodeしてData型から変換します。 forkeyは保存したときに設定したkeyを指定します。 これで、定数「rufy」の中に保存していた値を取得することができます。 let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase guard let data = UserDefaults.standard.data(forKey: key), let rufy = try? decoder.decode(Pirate.self, from: data) else { return } 自作structの配列を保存する 以下のように配列を作成します。保存の方法は同じなので省略します。 let mugiwaraFamily: [Pirate] = [ Pirate(name: "ルフィ", age: 19, siblings: ["エース","サボ"]), Pirate(name: "ゾロ", age: 21, siblings: nil ), Pirate(name: "ナミ", age: 20, siblings: ["ノジコ"]), Pirate(name: "ウソップ", age: 19, siblings: nil ), Pirate(name: "サンジ", age: 21, siblings: ["イチジ","ニジ","ヨンジ","レイジュ"]) ] 取得 取得方法もほとんど変わらないのですが、4行目を[Pirete]とすることで、自作structの配列を取得できます。 let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase guard let data = UserDefaults.standard.data(forKey: key), let crew = try? decoder.decode([Pirate].self, from: data) else { return } おわりに いかがだったでしょうか?何か間違っているところや、もっと良い方法があればご指導ください。よろしくお願いします。 参考リンク
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

protocolについて

備忘録 はじめに ふわっとしか理解していないのでQiitaに書こうと思いました。 protocol 日本語に訳すと規定や約束事という意味合いになります。 Swiftにおいては、プロパティやメソッドを定義することができて クラスや構造体に準拠されること前提で使われます。 protocol HogeProtocol { var hoge: String { get } func doHoge() } ・使い方 protocolは冒頭でも述べたようにクラスや構造体などに 準拠されること前提で使われます。 なので使用する場合は準拠することから始まります。 そして、protocolで定義したものは必ず実装しなければなりません。 class Hoge: HogeProtocol { // 実装しなければエラーが発生する // var hoge: String = "" func doHoge() { print("doHoge") } } ・複数準拠できる クラスの場合、複数の継承はエラーが発生してしまいます。 ですが、protocolでは複数準拠が可能です。 protocol FugaProtocol { var fuga: String { get } func doFuga() } class HogeFuga: HogeProtocol, FugaProtocol { var hoge: String = "" var fuga: String = "" func doHoge() { print("doHoge") } func doFuga() { print("doFuga") } } ・protocolもprotocolを準拠できる タイトル通り、protocol自身もprotocolを準拠できます。 // 先ほど定義したProtocol達をまとめる protocol HogeFugaProtocol: HogeProtocol, FugaProtocol { var hogeFuga: String { get } } class HogeFuga: HogeFugaProtocol { var hogeFuga: String = "" var hoge: String = "" var fuga: String = "" func doHoge() { print("doHoge") } func doFuga() { print("doFuga") } } ・protocol extension 基本的にprotocolはデフォルト実装ができません。 なので準拠したクラスなどで具体的な処理内容を記述しなければいけません。 ですが例外があり、extentionによってデフォルト実装が可能になります。 // デフォルト実装を追加する extension HogeProtocol { func doHoge() { print("doHoge") } } // デフォルト実装しているので実装しなくてもエラーが発生しない class HogeFuga: HogeFugaProtocol { var hogeFuga: String = "" var hoge: String = "" var fuga: String = "" // func doHoge() { // print("doHoge") // } func doFuga() { print("doFuga") } } let hogeFuga = HogeFuga() hogeFuga.doHoge() // "doHoge" protocolのメリット protocolについてよく分かったけど 結局何がいいの?どこで使うの??って感じですよね。 protocolのメリットを備忘録を含めて つらつらと書いていこうと思います。 ・共通化できる 例えば、各動物の走ったり泳いだりなどの速さを出力したいとします。 ◯protocolを使わない場合 まずは大元のAnimalクラスを作ります。 // 動物クラス class Animal { var runningSpeed: Int? var swimmingSpeed: Int? func run() { guard let runnningSpeed = runnningSpeed else { return } print("走るスピードは\(String(describing: runnningSpeed))km/h です") } func swim() { guard let swimmingSpeed = swimmingSpeed else { return } print("泳ぐスピードは\(String(describing: swimmingSpeed))km/h です") } } そして、Humanクラスを作って Animalクラスを継承します。 // 人間クラス class Human: Animal { override init() { super.init() runningSpeed = 36 swimmingSpeed = 8 } } では他にも、色んな動物クラスを追加していきましょう。 // 鳥クラス class Birds: Animal { } // 犬クラス class Dog: Animal { } 新たに作ったBirdsクラスでは飛ぶ速さを出力したいと思います。 ということは飛ぶ速さを出力するメソッド等を 大元のAnimalクラスに追加しないといけませんね。 ついでにBirdsクラス・Dogクラスで速さが出力できるようにしていきましょう。 // 動物クラス class Animal { var runningSpeed: Int? var swimmingSpeed: Int? var flyingSpeed: Int? func run() { guard let runnningSpeed = runnningSpeed else { return } print("走るスピードは\(String(describing: runnningSpeed))km/h です") } func swim() { guard let swimmingSpeed = swimmingSpeed else { return } print("泳ぐスピードは\(String(describing: swimmingSpeed))km/h です") } func fly() { guard let flyingSpeed = flyingSpeed else { return } print("飛ぶスピードは\(String(describing: flyingSpeed))km/h です") } } // 鳥クラス class Birds: Animal { override init() { super.init() flyingSpeed = 340 } } // 犬クラス class Dog: Animal { override init() { super.init() runningSpeed = 50 swimmingSpeed = 4 } } これで出力できそうですね。 ただこれらのコードだと、いくつか欠点があります。 本来、呼び出す必要がないメソッド等が 外部で呼び出せてしまいます。 let human = Human() let birds = Birds() let dog = Dog() human.run() // 走るスピードは36km/h です human.fly() // 何も出力されない birds.fly() // 飛ぶスピードは340km/h です dog.fly() // 何も出力されない 後はAnimalクラスがどんどん肥大化してしまうという点です。 様々なクラスに対応させるために大元のクラスに色々、詰め込んでしまっては膨大なコード量になってしまって 可読性も良くない、あるクラスでは必要ないプロパティやメソッドなどが大量生産されてしまいます。 ◯protocolを使った場合 では、protocolで実装していきます。 protocol RunProtocol { var runningSpeed: Int { get set } func run() } // 同じ処理の部分はextentionでデフォルト実装していく extension RunProtocol { func run() { print("走るスピードは\(String(describing: runningSpeed))km/h です") } } protocol SwimProtocol { var swimmingSpeed: Int { get set } func swim() } extension SwimProtocol { func swim() { print("走るスピードは\(String(describing: swimmingSpeed))km/h です") } } protocol FlyProtocol { var flySpeed: Int { get set } func fly() } extension FlyProtocol { func fly() { print("走るスピードは\(String(describing: flySpeed))km/h です") } } そして、先ほどのクラス達に準拠していきます。 // 動物クラス class Animal { } // 人間クラス class Human: Animal, RunProtocol, SwimProtocol { var runningSpeed: Int = 36 var swimmingSpeed: Int = 8 } // 鳥クラス class Birds: Animal, FlyProtocol { var flySpeed: Int = 340 } // 犬クラス class Dog: Animal, RunProtocol, SwimProtocol { var runningSpeed: Int = 50 var swimmingSpeed: Int = 4 } let human = Human() let birds = Birds() let dog = Dog() human.run() // 走るスピードは36km/h です human.fly() // エラー birds.fly() // 走るスピードは340km/h です dog.fly() // エラー 見て分かるようにprotocolに定義されていないものはエラーとなります。 そして、protocolでは定義したものは必ず実装しなければいけないルールなので どのプロパティに初期化が必要なのかを考慮しなくてもよくなります。 これで必要ないものが外部で呼び出されることはなくなりました。 このように部分的に処理を共通化させる事によって 必要な処理のみを実装できて可読性が向上します。 ・ファイル間の依存関係を薄くできる 例えば、TableViewCellをタップした時に そのCellに付随する値を違うVCに受け渡し、そのVCに画面遷移するとします。 ◯protocolを使わない場合 単純に実装すれば以下のようになると思います。 class SecondViewController: UIViewController { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { // 受け渡す値 let model = models[indexPath.row] let firstVC = FirstViewController() // ここで他のVCに値を受け渡す firstVC.text = model.text // 画面遷移 self.navigationController?.popViewController(animated: true) } } かなり雑ですが、こんな感じで他のVCに値を受け渡すことが出来ます。 ただこのコードだとSecondViewControllerは FirstViewControllerが存在していないとエラーになります。 いわゆる密結合になっている状態です。 つまり、VC間の依存関係が濃い訳ですね。 ◯protocolを使った場合 ここでprotocolを使ったDelegateで依存関係を薄くしていきます。 protocol ToPassDataDelegate: AnyObject { func didSelectData(model: Model) } class SecondViewController: UIViewController { func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let model = models[indexPath.row] // cellをタップした時に値を渡して、それ以降の処理は任せる delegate?.didSelectData(model: model) self.navigationController?.popViewController(animated: true) } } どこにもFirstViewControllerが記述されていないことが分かりますね。 もちろん、存在していなくてもエラーになることはありません。 このように依存関係を薄くすることによって たとえ違うVCに変更したとしても影響がなく、改修しやすいものとなります。 これがいわゆる疎結合ですね。 先ほど出てきたUITableViewクラスも内部でprotocolを使ったDelegateを持っています。 依存関係が薄いからこそ、私たちは様々な処理ができるようになっている訳ですね。 ・構造体や列挙型に準拠できる 構造体や列挙型はクラスのように継承が出来ません。 ですが、protocolを使うことで 構造体や列挙型でも同じようなことができます。 protocol HogeProtocol { var hoge: String { get } } struct Fuga: HogeProtocol { var fuga: String var hoge: String } おわりに Swiftは唯一の「Protocol指向」のプログラミング言語だそうです。 何か間違っている部分や気になる部分があればコメントして下さると有り難いです。 参考資料 mixi-inc/iOSTraining Swiftのプロトコルについて iOSアプリの基本設計を考える:疎結合の概念から構造化、MVVM、RxSwiftまで Swiftのプロトコルとは?役割・使いどころ・存在意義やメリット。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【iOS】縦画面と横画面でレイアウト切替えが必要な時に使えるTips

はじめに 今回は、アプリを回転したときに「縦レイアウト」と「横レイアウト」を動的に別のレイアウトにしたいという要件を満たす必要があるときに、僕がよく実装で使っているやり方を記事にしてみました。 おそらく、王道のやり方としては、回転した時に制約を変更して、レイアウトを切り替えるのだと思うのですが、それをやるのが不便な時によく使っている方法になります。 こんなときに使える 今回記事の中で実装している、例を上げると以下の画像のような感じです。 縦のときは、縦1列に並んでいるレイアウトを採用していますが、横のときは、左側に大きく表示するViewと右側に2つ並んでいるViewに分かれています。 縦レイアウト 横レイアウト 今回は、ViewにaddSubViewする対象が、①〜③のラベルですが、TableViewや自分で作成したカスタムViewをaddSubViewすることが多いと思います。 実装方法 StoryBoardの設定 縦レイアウト 横レイアウト 表示したいView(今回はLabel)を乗せるための親Viewを、縦と横で別々で用意します。これらの親Viewは、ソースコードと@IBOutlet接続する必要があります。 StoryBoardを編集する時、編集しない親Viewについては、表示されていると邪魔であるため、「installed」のチェックを外すのがおすすめです。 ※ビルドするときはチェックを付けてください。 ソースコード ViewRotationViewController.swift import UIKit class ViewRotationViewController: UIViewController { @IBOutlet weak private var viewFirst: UIView! @IBOutlet weak private var viewSecond: UIView! @IBOutlet weak private var viewThird: UIView! @IBOutlet weak var viewFirstHorizon: UIView! @IBOutlet weak var viewSecondHorizon: UIView! @IBOutlet weak var viewThirdHorizon: UIView! @IBOutlet weak var viewVertical: UIView! @IBOutlet weak var viewHorizon: UIView! // 子Viewとして表示するラベル(実際にアプリを作るとなると、カスタムViewやTableViewになることが多い) private let labelNo1 = UILabel() private let labelNo2 = UILabel() private let labelNo3 = UILabel() private var isPortrait: Bool { return UIApplication.shared.windows.first?.windowScene?.interfaceOrientation.isPortrait ?? true } override func viewDidLoad() { initLabel() setScreen() } private func initLabel() { labelNo1.text = "①" labelNo2.text = "②" labelNo3.text = "③" labelNo1.textAlignment = .center labelNo2.textAlignment = .center labelNo3.textAlignment = .center labelNo1.font = .systemFont(ofSize: 60) labelNo2.font = .systemFont(ofSize: 60) labelNo3.font = .systemFont(ofSize: 60) } private func setScreen() { labelNo1.removeFromSuperview() labelNo2.removeFromSuperview() labelNo3.removeFromSuperview() if isPortrait { viewFirst.addSubViewFill(labelNo1) viewSecond.addSubViewFill(labelNo2) viewThird.addSubViewFill(labelNo3) } else { viewFirstHorizon.addSubViewFill(labelNo1) viewSecondHorizon.addSubViewFill(labelNo2) viewThirdHorizon.addSubViewFill(labelNo3) } // 縦Viewと横Viewの表示・非表示を切り替える viewVertical.isHidden = !isPortrait viewHorizon.isHidden = isPortrait } override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { super.viewWillTransition(to: size, with: coordinator) coordinator.animate { [weak self] _ in guard let `self` = self else { return } // 回転時に、レイアウトの変更を行う self.setScreen() } } } addSubViewFillというメソッドについては、画面いっぱいに子Viewを貼り付けるために、UIViewをExtensionしました。 詳しくはこちらの記事を見てもらえればと思います。 https://qiita.com/lemonade_dot_log/items/5304c0fc3fb82adcf1e5 UIView+Extension.swift extension UIView { func addSubViewFill(_ childView: UIView) { self.addSubview(childView) childView.translatesAutoresizingMaskIntoConstraints = false childView.topAnchor.constraint(equalTo: self.topAnchor, constant: 0).isActive = true childView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant: 0).isActive = true childView.leadingAnchor.constraint(equalTo: self.leadingAnchor, constant: 0).isActive = true childView.trailingAnchor.constraint(equalTo: self.trailingAnchor, constant: 0).isActive = true } } 解説 viewDidLoad()(初回表示時)、viewWillTransition()(回転した時)で現在のアプリの画面向きを判断して、それによってレイアウトを切り替える方法を採用しています。(setScreen()を呼び出す。) ポイントとしては、setScreen()で、画面の向きによって、表示する子Viewを乗せる親Viewを変更している点です。 回転時にsetScreen()を呼び出すことで、子Viewの親Viewを変更し、レイアウトを変更することが可能になっています。 流れとしては、以下のような流れです。 「アプリが回転」→「子Viewを親Viweから切り離す」→「縦か横かを判断」→「適切な親Viewに子ViewをaddSubView()する」 こうすることで、親ViewのAutoLayout制約を切り替える方法ではなく、子Viewの切り離し&貼り付けによって、レイアウト変更を実現しているという感じです。 イメージとしては、のりで1度貼った紙を、剥がしてから別の台紙に貼り付けるといった感じでしょうか。 おわりに レイアウト切り替えで他におすすめの方法があったら、是非コメント欄で教えていただけると嬉しいです。 あとは、SizeClassにレイアウトを完全に任せる方法があると思いますが、iPadは縦と横のSizeClassが同じなので、今回は採用しませんでした。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】一意のIDを作成したい時 NSUUID

使用用途 何かを一意に識別する時などに使う。 DBに保存するオブジェクトのプライマリーキーなど、値がユニークである必要がある時など。 使用方法 下記のような形で使うと定数idに16進数で32桁のIDが作成されます。 let id = NSUUID().uuidString ただ必ずユニークな値が保証されているわけではないようです。 しかし、確率としては340,282,366,920,938,463,463,374,607,431,768,211,456分の 1とかみたいなので、よっぽど大丈夫。(笑) 趣味開発でRealmを使用した際、こんなふうに使ってidを作成してました。 class Plan: Object { @objc dynamic var id: String = NSUUID().uuidString @objc dynamic var title: String = "" @objc dynamic var content: String = "" @objc dynamic var date: Date = Date() @objc dynamic var createdAt: Date = Date() override static func primaryKey() -> String? { return "id" } } 終わりに 素人なので参考までに。。。 他にいい方法がありましたら教えていただきたいです。 参考資料
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

イニシャライザの基本

はじめに class DataEntity { let id: Int let name: String let age: Int init(id: Int, name: String, age: Int){ self.id = id self.name = name self.age = age } } こんなのよく目にしますよね。これは一体何をしてるのかっていうことを簡単に説明します。 気持ち的には、インスタンス毎に固有の値を初期値として持たせたい時に使う感じだと思います。classの雛形に則ってインスタンスを生成し、その中身(プロパティ)の値はイニシャライザで与えます。 let data1 = DataEntity(id: 1, name: "Taro", age: 20) let data2 = DataEntity(id: 2, name: "Hanako", age: 20) こんな感じです。 要すると、イニシャライザとはインスタンス生成時にプロパティに値を与えるための関数です(って言って差し支えないと思います)。 こんな流れで解説していきます。 イニシャライザ 上のサンプルコードで使い方はざっくりわかったと思うので、もう少し細かく見てみましょう。 init(id: Int, name: String, age: Int){ self.id = id self.name = name self.age = age } 初見だとなんか書き方がよくわからないですね。ですが分かってしまえば大したことはないです。(多分) selfキーワードはインスタンスに型の内部からアクセスする時に使います。 なので、やってることは self.id = id という式は data.id = 1 ってしてるようなものです つまり、インスタンスのプロパティにinitの引数を代入しているということです。 文字通り初期値を入れて初期化してるわけです。 大分しつこく解説してしまいましたがやってることはシンプルなのです。 終わり
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

iOSでアルファチャンネル付きで動画を作成する

iOS でアルファチャンネル付きで動画を作成する AVAssetWriter を使うと、iOS で透過情報付きの動画を作成する事が可能です iOS の写真アプリは透過付きの動画を再生可能です ツールバーの表示/非表示で背景色が変わったり、プルダウンすることで後ろの画像が見えるので、動画の背景が透過されている事がわかります iOSでアルファチャンネル付きの動画を作成して保存できた✌️MTLTexture にレンダリングして hevc に保存してます pic.twitter.com/hWrjQBGxJ6— ふじき (@fzkqi) June 7, 2021 サンプルアプリ リポジトリはこちらです Examples/NativeExamples/NativeExamples.xcodeproj の CrossMetal iOS ターゲットです Xcode の Metal の Game アプリをベースにしています 透過情報付きで保存するポイント AVAssetWriterInput の生成時の settings で、hevcWithAlpha を指定します let videoConfigs: [String: Any] = [AVVideoCodecKey : AVVideoCodecType.hevcWithAlpha, AVVideoWidthKey : 1920, AVVideoHeightKey :1080] let videoAssetWriterInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoConfigs) 書き込む CMSampleBuffer の CVPixelBuffer のフォーマットとして kCVPixelFormatType_32BGRA を選択します let options = [kCVPixelBufferIOSurfacePropertiesKey: [:]] as [String : Any] let status = CVPixelBufferCreate(nil, width, height, kCVPixelFormatType_32BGRA, options as CFDictionary, &pixelBuffer) 透過情報付きの画像データを生成し、AVAssetWriterInput に書き込むことで、アルファチャンネル付きの動画を取得可能です 実装 録画開始 ファイルタイプは mov を指定しました hevcWithAlpha が保存できるコンテナであれば良いと思います let assetWriter = try! AVAssetWriter(outputURL: url, fileType: AVFileType.mov) let videoConfigs: [String: Any] = [AVVideoCodecKey : AVVideoCodecType.hevcWithAlpha, AVVideoWidthKey : 1920, AVVideoHeightKey : 1080] let videoAssetWriterInput = AVAssetWriterInput(mediaType: .video, outputSettings: videoConfigs) videoAssetWriterInput.expectsMediaDataInRealTime = true assetWriter.add(videoAssetWriterInput) 元の画像を Metal でレンダリングしているので、MTLTexture と描画時刻を受け取ります MTLTexture -> CIImage -> CVPixelBuffer -> CMSampleBuffer の順番に変換します func make(mtlTexture: MTLTexture, time: CMTime) -> CMSampleBuffer? { let ci = CIImage(mtlTexture: mtlTexture, options: nil)! CVPixelBufferLockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0)) context.render(ci, to: pixelBuffer) var res: CMSampleBuffer? = nil var sampleTiming = CMSampleTimingInfo() sampleTiming.presentationTimeStamp = time let _ = CMSampleBufferCreateForImageBuffer(allocator: kCFAllocatorDefault, imageBuffer: pixelBuffer, dataReady: true, makeDataReadyCallback: nil, refcon: nil, formatDescription: formatDescription, sampleTiming: &sampleTiming, sampleBufferOut: &res) CVPixelBufferUnlockBaseAddress(pixelBuffer, CVPixelBufferLockFlags(rawValue: 0)) return res } 作成した CMSampleBuffer を videoAssetWriterInput に書き込みます videoAssetWriterInput.append(sample) 録画終了 finishWriting することで mov ファイルが作成されています assetWriter.finishWriting(completionHandler: { }) 保存 指定した url に動画が保存されているので、必要に応じて保存します ALAssetsLibrary は deprecated なので Photos framework を使った方が良さそうです ALAssetsLibrary() .writeVideoAtPath(toSavedPhotosAlbum: url) { (url: URL?, error: Error?) in print("url: \(String(describing: url)), error: \(String(describing: error))") } おわりに アルファチャンネル付きの動画を保存可能になると、汎用性が高そうです 例えば、LiDAR のデプスをアルファチャンネルに入れて保存するなど活用できそうです Unity でも R8G8B8A8_UNorm など、alpha チャンネルがあるテクスチャを渡すことで、透過情報付きの動作を作成する事が可能です
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Unity]Swift だけで作ったNative PluginでUnityにSendMessage

Swift だけで作った Native Plugin で Unity に SendMessage Swift だけで、Unity の iOS の Native Plugin を作る方法を紹介しました Swift から Unity へコールバックしたい場合がよくあります Unity の Native Plugin のドキュメントには、UnitySendMessage を使う方法が紹介されています UnitySendMessage を使うと、とても容易にコールバックを実装可能です ゲームオブジェクト名、スクリプト名、呼び出されたメソッドに渡す文字列を指定します UnitySendMessage("GameObjectName1", "MethodName1", "Message to send"); これを Swift だけで作った Native Plugin から呼び出す話です 今回のサンプル リポジトリはこちらです Swift だけで、Unity の iOS の Native Plugin を作る の内容を前提知識にしています 処理の流れ Unity で実行されるゲームオブジェクトとスクリプトを用意する == ここまで C# ここから Swift === Swift で、UnityFramework を読み込む UnityFramework を使って、UnitySendMessage を実行する Unity の実装 最終目標 "Cube" オブジェクトの "Sent" 関数に任意の文字を渡して実行することを目標とします public class Cube : MonoBehaviour { public void Sent(string message) { Debug.Log("Sent message -> " + message); } } これで UnitySendMessage からメッセージを受け取る事が可能です Swift の実装 やること UnityFramework.framework には、UnityFramework クラスが実装されています UnityFramework のシングルトンのインスタンスを利用します sendMessageToGO 関数を呼ぶと UnitySendMessage が実行されます let objectName = "Cube" as NSString let scriptName = "Sent" as NSString let message = "Hello" as NSString UnityFramework.getInstance() .sendMessageToGO(withName: objectName.utf8String, functionName: scriptName.utf8String, message: message.utf8String) 実装計画 Framework を事前にビルドする形だと、ビルド時に UnityFramework とリンクできません そのため、事前にビルドした Framework から名前を指定して実行します UnityFramework のシングルトンのインスタンスを取得する UnityFramework.framework の PrincipalClass として UnityFramework クラスが設定されているので利用します getInstance の値を名前を指定して取得します let bundlePath = Bundle.main.bundlePath.appending("/Frameworks/UnityFramework.framework") let bundle = Bundle(path: bundlePath)! let principalClass = bundle.principalClass as! NSObject.Type let unityFramework: NSObject = principalClass.value(forKey: "getInstance") as! NSObject これで、UnityFramework.getInstance() に相当する処理を実装できました sendMessageToGOWithName:functionName:message を実行する やりたいこと 上で取得した unityFramework インスタンスで sendMessageToGOWithName の関数を実行したいです この関数を名前を指定して実行します unityFramework.sendMessageToGO(withName: objectName.utf8String, functionName: scriptName.utf8String, message: message.utf8String) 文字列から関数を実行する class_getMethodImplementation で IMP を取得し、unsafeBitCast を使って、methodType にキャストして実行します sendMessageToGOWithName は引数として、char* を受け取るので、Swift で同等の UnsafePointer<CChar> として実行します private func callInstanceMethod<T: NSObject>(targetInstance: T, selector: Selector, argCStr1: UnsafePointer<CChar>, argCStr2: UnsafePointer<CChar>, argCStr3: UnsafePointer<CChar>) { typealias methodType = @convention(c) ( Any, Selector, // args UnsafePointer<CChar>, UnsafePointer<CChar>, UnsafePointer<CChar> ) -> Void let methodImplementation: IMP = class_getMethodImplementation(type(of: targetInstance), selector)! let methodInvocation = unsafeBitCast(methodImplementation, to: methodType.self) methodInvocation(targetInstance, selector, argCStr1, argCStr2, argCStr3) } 実行 sendMessageToGOWithName 関数のセレクタの sendMessageToGOWithNameSelector を作成します unityFramework のインスタンスで、sendMessageToGOWithNameSelector を実行します let sendMessageToGOWithNameSelector: Selector = NSSelectorFromString("sendMessageToGOWithName:functionName:message:") callInstanceMethod(targetInstance: unityFramework, selector: sendMessageToGOWithNameSelector, argCStr1: ("Cube" as NSString).utf8String!, argCStr2: ("Sent" as NSString).utf8String!, argCStr3: ("from send message" as NSString).utf8String!) Framework の出力と設定 こちらの記事の Framework でビルドする の章以降と同じ手順で実行ビルド可能です callInstanceMethod を実行したときに、Cube.Sent が指定した引数で実行されていることを確認します おわりに UnitySendMessage は Unity で複雑な実装をせずに、Swift から Unity へコールバックする事が可能です 1frame 遅れたり、文字列1つしか送れない、などが問題ない場合は十分に利用する事が可能だと思います Swift から Unity コールバックするのは、応用の幅が広いので、活用して行きたいです
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

guard文による分岐処理

guard文とは guard文とは条件が不成立の場合に処理を抜けるための条件分岐文。 条件が不成立の場合は、else節内の処理が実行されguard文を含むスコープから抜けないとコンパイルエラーとなる。 let str = "abc" func exampleFunction() { guard str == "abc" else { return // スコープを抜ける } print(str) } guard-let文 guard文もif-let文と同様、guard-let文を利用してオプショナル変数のアンラップが行える。 guard文は条件式が成立しなかった場合にはスコープから抜けるため、guard-let文で宣言された変数や定数はguard-let文以降でも利用可能。 let int: Int? = 123 func exampleFunction() { guard let a = int else { return } // guard-let文で宣言した定数aを{}の外からも利用できる print(a) // 123 } if文との使い分け 条件を満たさない場合に処理を早期退出したいときは、if文に比べてguard文の方がシンプルに実装できるため適していると言える。 また、guard文は条件を満たさない場合に必ずスコープを抜けなくてはならないので、想定外の状況においても処理を抜ける実装の漏れを防ぐことができる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む