20210607のiOSに関する記事は5件です。

【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で続きを読む

イニシャライザの基本

はじめに 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で続きを読む