- 投稿日:2021-04-05T23:51:35+09:00
0 からアプリをリリースしてみよう!〜第一話〜
0からアプリをリリースしてみよう! はじめに みなさんこんにちは。paulownia friendsです。みなさんは、iosアプリを作りて〜けどどうやってやればいいのか分からね〜とか、始めたはいいもののつまづくポイントが多すぎて挫折しそう〜といった経験をしたことはないでしょうか?私たちも、ついこの間まで、同じような状態でした。そこで、同じような悩みを抱える方のために、2本のiosアプリをリリースした経験を元にして、「決断メーカー」の制作過程をベースにiosアプリの計画からリリース、広告の貼り付けまでの一連の流れを大公開したいと思います!この記事が何かの役に立てば幸いです。 1.企画 アプリを作ろう!となっても、何も計画を持たないで始めてしまうと、当然初手でつまづいてしまいます。第1話でこけたシンジくんのエヴァみたいですね。なので、どのようなアイデアをどのようなアプリに落とし込み、どのようにして収益化を図ろうとするのか、どのような機能を持たせるのか、どのようなデザインにするのかなどの検討をじっくり重ねた上で作り始めます。 今回、私たちは検討の末、自分に変わって決断を下してくれるアプリが欲しい、と思ったので、そのようなアプリを作ることにしました。 以下企画案↓ ・コンセプト:決断を下してくれるアプリ ・収益化:admobの広告(インタースティシャル広告) ・機能:決断を下してくれる、選択肢の数を増減させることができる ・デザイン:とてもシンプルなものにする ・予定作成期間:一週間 2.作成の準備 さて、いざ作るぞーとなったとしても、環境が整っていないと先には進めません。ここでは、開発の環境を整えていきます。 まず、iosアプリを作る際は、基本的にXcodeというmac用のアプリを使います。(app storeからダウンロードできます)環境としては、Xcodeがインストールされたmacと、やる気があれば大丈夫です。わざわざタイトルをつけたのに、これだけかよ、と思う方もいるかもしれません。でも実際これだけなのです。この手軽さも魅力の一つではないでしょうか。 3.作成開始 それでは、いよいよ、作成を始めていきましょう。まず、Xcodeを開くと、このような画面が出てきます。ここで、「Create a new Xcode project」を押してください。 すると、このような画面になります。様々なアプリの形式がありますが、ここでは「App」を選択します。 すると、このような画面が出てきます。ここで、product Nameを入力してください。写真では日本語で入力していますが、エラー回避のために、アルファベットで打つようにしましょう。Identifireは、ダブりが生じないようなものにしてください。ダブらなければ何でも良いですが、エンジニアの方々の間では、メアドをひっくり返して入力するのが主流だそうです。つまり、paulowniaFriends@mail.comというメアドを持っていたとしたら、Identifireはcom.mail@paulowniaFriendsにする、といった感じです。 すると、次にこのような画面になります。ここでは、作成したプロジェクトの保管場所を定めることができます。場所を決めたら、「Create」ボタンを押します。 そうしたら、このような画面が出てきます。このような画面が出てきたら、いよいよコーディング開始です!がんばっていきましょう!! ここから先は、少し長い作業になるので、第一話はこの辺りで締めたいと思います。このくらいはまだ簡単すぎる、もっと最後の部分を知りたい、という方も多いとは思いますが、続編が出るまで待っていただけると幸いです。近日中にはアップロードします。 次回予告 コーディングをスタートしたものの、あちらこちらからエラーが出てしまい、どこをどうしたら良いのかも分からない、バグへの対処方法が分からない、などといった悩みを抱える方も多いと思います。そこで、次回は、バグに対処していく方法をお伝えする回にしたいと思います。ただし、ここで紹介するのは、個別具体的なエラーへの対処ではなく、バグ対処の一般的なコツのようなものになります。それでは、次回記事にて! 自分たちのアプリの宣伝 私たちは、自作のリマインダーアプリ「タスクリマインダー -TaskReminder 課題管理-」、決断代行アプリ「決断メーカー」をAppStoreにて公開しています。 ~ダウンロードはこちらから~ タスクリマインダー -TaskReminder 課題管理- 決断メーカー
- 投稿日:2021-04-05T23:29:52+09:00
Unity iOS TwitterKitを使用するとFirebase DynamicLinkが機能しなかった
以下のエラーがでる。(Analyticsを使用している場合) [Firebase/Analytics][I-ACS023001] Deep Link does not contain valid required params. URL params: { TwitterKitを使用しない場合は上手くいった。なぜ?
- 投稿日:2021-04-05T23:21:24+09:00
複数の QuickSpec での beforeSuite と it の実行順序
担当しているプロジェクトのUIテストコードに Quick/Nimble(テスト用フレームワーク) を導入しようとして若干ハマったのでメモします。 何に詰まったのか 最初、Spec1とSpec2のように複数のQuickSpecを用意して、それぞれ初期化処理をしてから実際のテストを走らせようとしていました。 Spec1 beforeSuite1 - (A) it1 - (B) Spec2 beforeSuite2 - (C) it2 - (D) 構造としては上記にようにして、私は以下の実行順序を期待します。 Spec1について、beforeSuite -> it Spec2について、beforeSuite -> it しかし実際にテストを実行してみると、Spec2のbeforeSuiteの処理が効いているように見えません。。 なぜ...? 簡単なプロジェクトを作ってチェック そこで、簡単なサンプルプロジェクトを作ってログを仕込み、実行順序を見てみることにしました。 私が用意したプロジェクトはこちらです。 こんな感じに Spec1 と Spec2 を用意して実行します。 Spec1.swift class Spec1: QuickSpec{ override func spec() { beforeSuite { print("===============================") print("> This is a beforeSuite on Spec1.") print("===============================") let app = XCUIApplication() app.launch() } describe("description") { context("context") { it("it1") { print("-------------------------------") print("> This is a testing on Spec1.") print("-------------------------------") sleep(5) expect(true).to(beTrue()) } } } } } Spec2.swift class Spec2: QuickSpec{ override func spec() { beforeSuite { print("===============================") print("> This is a beforeSuite on Spec2.") print("===============================") let app = XCUIApplication() app.launch() } describe("description") { context("context") { it("it2") { print("-------------------------------") print("> This is a testing on Spec2.") print("-------------------------------") sleep(5) expect(true).to(beTrue()) } } } } } 実行してみた結果、このようにログ出力されました。 (間に入ったログは省略) =============================== > This is a beforeSuite on Spec1. =============================== ... =============================== > This is a beforeSuite on Spec2. =============================== ... ------------------------------- > This is a testing on Spec1. ------------------------------- ... ------------------------------- > This is a testing on Spec2. ------------------------------- 結論 Spec1 beforeSuite1 - (A) it1 - (B) Spec2 beforeSuite2 - (C) it2 - (D) 上記の場合、実行順序はこのようになるということでした。 (A) -> (C) -> (B) -> (D) UIテストで beforeSuite 上にアプリの起動処理を書いて初期化しようとしていましたが、起動を2回してからテストを実行していたということですね。 結局、UIテストでは beforeSuite に起動処理を書かずに it1, it2 内に書くことで対処しました。
- 投稿日:2021-04-05T22:41:42+09:00
【Swift】XIBファイルを初期起動画面にする
はじめに XIBで初期起動する方法を紹介します。 手順 1.Main.storyboardを消去する Main.storyboard起動をやめて使わなくなるので、消去しましょう。(move to trash) 2.XIBを作成 Also create XIB fileをクリックしましょう TopViewController.swiftを作らずにViewからXIBを追加した場合は、以下のようにfile's ownerとviewを接続しなければいけません。 わかりやすく背景色を赤とかにしておきましょう。 3.SceneDelegateを編集 SceneDelegateを以下のようにはじめに起動したいControllerをrootViewControllerにします。 SceneDelegate import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { guard let scene = (scene as? UIWindowScene) else { return } self.window = UIWindow(windowScene: scene) self.window?.rootViewController = TopViewController() self.window?.makeKeyAndVisible() } } 4.Info.plistを編集 以下のように、Storyboard NameとMain storyboard file base nameがMainになっているので、二つとも消去します。 5.ビルドする これでビルドしてみてください。赤い画面(TopViewController.xib)が表示されるはずです。 おわりに おわりです。
- 投稿日:2021-04-05T22:39:58+09:00
スマートバナーでスマホWebからアプリへの導線をつくってみた - iOS, Android 両対応-
はじめに WEBアプリのモバイル化に成功した!...なのになぜだ!?全然インストールされない!! 最近まさにそんな経験をしたんですが、ユーザの立場でよくよく考えてみると、アプリのインストールに至るにはいくつか障壁があるということに気が付きました。 リリースされたアプリの存在にそもそも気が付かない アプリのインストールがめんどくさい(ストアいってアプリ名検索して...など) なるべく障壁を低くしてユーザにアプリをインストールしてもらえる方法なんてあるのかよ...と頭を抱えたのですが、少しググったら?な解決策をみつけてしまいました。 それがスマートバナーです。 これ↓ 誰でも1回はみたことあると思います。 WEB版のアプリをスマホブラウザで開くと上部に表示されるアレですね。 というわけで今回はiOS, Androidでスマートバナーを出す方法をまとめました。 「WEB版からアプリへの導線を簡単につくりたい」 「iOSとAndroidの両方で導線をつくりたい」 という方に役立つ記事になると思います。 Safari(iOS) Safari(iOS)版のスマートバナーは、なんとAppleが公式で機能をだしています。 しかも使い方は超簡単。 以下のmetaタグをスマートバナーを表示したい画面のheadタグに埋め込むだけです。 <!-- Safari SmartBanner --> <meta name="apple-itunes-app" content="app-id=[app-id]"> appleのdeveloperアカウントで、自分のアプリの9-10桁のapp-idを確認することができるので、そこからコピペして利用します。 Apple公式スマートバナーのメリットとデメリットは以下になります。 メリット アプリのインストール有無によって表示が変わる(入手 - App Store or インストール済) 表示によってタッチしたときの導線が変わる(インストール済の場合はアプリを開く) app-idを指定するだけでアイコンやアプリ名を自動入力してくれる デメリット Androidでは使えない シミュレータには表示されないので実機での動作確認が必要 Android Androidの場合、iOSと異なり公式のスマートバナーは存在しません。 そのため、サードパーティのライブラリを使用する必要があります。 今回はこちらのライブラリを使用しました。 肝心の使い方なのですが、Safafiのときと同様にmetaタグをheadタグに埋め込むだけです。 <!-- Android SmartBanner --> <meta name="smartbanner:title" content=[アプリ名]> <meta name="smartbanner:author" content=[会社名]> <meta name="smartbanner:price" content=[アプリの値段]> <meta name="smartbanner:price-suffix-google" content=" - Google Play"> <!-- Android用suffix --> <meta name="smartbanner:icon-google" content=[アイコン画像のパス]> <meta name="smartbanner:button" content="開く"> <!-- ボタンの表示名 --> <meta name="smartbanner:button-url-google" content=[Google PlayのアプリURL]> <meta name="smartbanner:enabled-platforms" content="android"> <!-- スマートバナーを利用するOSの指定 --> <meta name="smartbanner:close-label" content="Close"> <link rel="stylesheet" href="css/smartbanner.min.css"> <script src="js/smartbanner.min.js"></script> これでスマートバナーが以下のように表示されます。 サードパーティのスマートバナーのメリットとデメリットは以下になります。 メリット 表示する文をカスタマイズすることができる AndroidとSafariの両方で使える デメリット インストール有無で表示ステータスを変えられない インストール済の場合でもアプリを直接開けない(ストアへの導線だけ) おわりに スマートバナーの導入を検討されている方は、こちらの記事を参考にしていただければ幸いです。
- 投稿日:2021-04-05T22:24:05+09:00
【Swift】RxSwift勉強してみたPart5
はじめに 前回 今回はclosure, delegate, RxSwiftでカウンターアプリを作ってみました。 GitHub CounterRxSwiftにあります。 実装 大切なのは、RxSwiftを使った場合なので、そこの解説だけしておきたいと思います。 まず、こちらを参考にRxSwiftViewController.xibという.xibで初期起動するようにしてください。 そして、以下のようにラベルとボタンをそれぞれ配置します。(stackViewを使うといいと思います) 以下のような手順で作ります。 1.InputとOutputを作る 2.ViewModelを作る 3.ViewControllerを作成 です。 1.InputとOutputを作る CounterViewModelInput import RxSwift import RxCocoa protocol CounterViewModelOutput { var counterText: Driver<String?> { get } } struct CounterViewModelInput { let countUpButton: Observable<Void> let countDownButton: Observable<Void> let countResetButton: Observable<Void> } protocol CounterViewModelType { var outputs: CounterViewModelOutput? { get } func setup(input: CounterViewModelInput) } 2.ViewModelを作る RxCounterViewModel import RxSwift import RxCocoa class RxCounterViewModel: CounterViewModelType { var outputs: CounterViewModelOutput? private let countRelay = BehaviorRelay<Int>(value: 0) private let initialCount = 0 private let disposeBag = DisposeBag() init() { self.outputs = self resetCount() } func setup(input: CounterViewModelInput) { input.countUpButton .subscribe(onNext: { [weak self] in self?.incrementCount() }) .disposed(by: disposeBag) input.countDownButton .subscribe(onNext: { [weak self] in self?.decrementCount() }) .disposed(by: disposeBag) input.countResetButton .subscribe(onNext: { [weak self] in self?.resetCount() }) .disposed(by: disposeBag) } private func incrementCount() { let count = countRelay.value + 1 countRelay.accept(count) } private func decrementCount() { let count = countRelay.value - 1 countRelay.accept(count) } private func resetCount() { countRelay.accept(initialCount) } } extension RxCounterViewModel: CounterViewModelOutput { var counterText: Driver<String?> { return countRelay .map { String($0) } .asDriver(onErrorJustReturn: "") } } 3.ViewControllerを作成 RxSwiftViewController class RxSwiftViewController: UIViewController { @IBOutlet weak var countLabel: UILabel! @IBOutlet weak var countUpButton: UIButton! @IBOutlet weak var countDownButton: UIButton! @IBOutlet weak var countResetButton: UIButton! private let disposeBag = DisposeBag() private var viewModel = RxCounterViewModel() override func viewDidLoad() { super.viewDidLoad() setupViewModel() } private func setupViewModel() { let input = CounterViewModelInput(countUpButton: countUpButton.rx.tap.asObservable(), countDownButton: countDownButton.rx.tap.asObservable(), countResetButton: countResetButton.rx.tap.asObservable()) viewModel.setup(input: input) viewModel.outputs?.counterText .drive(countLabel.rx.text) .disposed(by: disposeBag) } } メリットとデメリット ・メリット ViewController →スッキリした →InputとOutputだけ気にすれば良くなった ViewModel →increment decrement resetがデータの処理に集中できた →ViewControllerのことを意識しなくても良い(delegate(count: count)のようなデータの更新の通知をしなくても良い) →大きなメリットはViewModelがViewControllerのことを知らなくて良いところ(ViewControllerがViewModelの値を監視して変更があったらUIを自動で更新させている) ・デメリット →コード量が多い →書き方に慣れるまで時間がかかる おわりに 少しずつわかってきました!
- 投稿日:2021-04-05T13:48:51+09:00
Swiftで開発を行なっているiOSアプリにUnityを組み込む(Unity as a Library)
TL;DR Swiftで新規開発を行なっているiOSアプリに、Unityを組み込んだ経験をまとめます。 公式のサンプルではObjective-Cでの実装のみである中で、Swiftでの実装事例の1つとなれば幸いです。 また自分の探し方の問題かもしれませんが、Unity as a Library (UaaL)をSwiftのiOSアプリに組み込む英語の資料があまり見つかりませんでした。何かおすすめがありましたら教えていただけると幸いです! ポイント Unityの起動時にローディングの時間がかかるので、アプリを立ち上げた時点でUnityを起動しバックグラウンドでローディング アプリの画面遷移をNavigationControllerで実現したかったため、UnityのViewControllerからViewのみを呼び出す 今回のアプリでは実装のメインがSwiftで、背景としてUnityを使うという方針にしたため、iOSアプリ→Unityの呼び出しのみ実装 参考にした記事 [Swift] Unity as a LibraryをSwiftから呼ぶ 主にこちらの記事を参考にしながら、iOSアプリのViewControllerからUnityのViewControllerのViewを呼び出す実装を進めました。 Unity as a LibraryのサンプルプロジェクトをSwiftで書き直した UaaLのサンプルをSwiftで書き換えてくださっているので、全体像を把握するために大いに活用させていただきました。 (今回、Unityの読み込み方法などがこちらとは異なります) その他の便利な参考はこちら 実装 前提 アプリのメインはSwiftで実装を行い、背景画像としてUnityの3Dモデルを読み込むようなアプリです。 また、メインのSwiftからUnityの関数を呼び出すことも行いました。 iOSアプリ設計パターン入門 開発に当たっては、こちらの書籍を参考にMVPのアーキテクチャパターンを選択しました。 アプリ起動時にUnityを呼び、バックグラウンドでローディング Unity Classの作成 様々な場所から呼び出せるよう、Unityをシングルトンオブジェクトとして実装します Unity.swift import Foundation class Unity: NSObject, UnityFrameworkListener { static let shared = Unity() private let unityFramework: UnityFramework override init() { let bundlePath = Bundle.main.bundlePath let frameworkPath = bundlePath + "/Frameworks/UnityFramework.framework" let bundle = Bundle(path: frameworkPath)! if !bundle.isLoaded { bundle.load() } // It needs disable swiftlint rule due to needs for unwrapping before calling super.init() // swiftlint:disable:next force_cast let frameworkClass = bundle.principalClass as! UnityFramework.Type let framework = frameworkClass.getInstance()! if framework.appController() == nil { var header = _mh_execute_header framework.setExecuteHeader(&header) } unityFramework = framework super.init() } func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) { unityFramework.register(self) unityFramework.setDataBundleId("com.unity3d.framework") unityFramework.runEmbedded(withArgc: CommandLine.argc, argv: CommandLine.unsafeArgv, appLaunchOpts: launchOptions) } var view: UIView { unityFramework.appController()!.rootView! } func sendMessageToUnity(objectName: String, functionName: String, argument: String) { unityFramework.sendMessageToGO(withName: objectName, functionName: functionName, message: argument) } func applicationWillResignActive(_ application: UIApplication) { unityFramework.appController()?.applicationWillResignActive(application) } func applicationDidEnterBackground(_ application: UIApplication) { unityFramework.appController()?.applicationDidEnterBackground(application) } func applicationWillEnterForeground(_ application: UIApplication) { unityFramework.appController()?.applicationWillEnterForeground(application) } func applicationDidBecomeActive(_ application: UIApplication) { unityFramework.appController()?.applicationDidBecomeActive(application) } func applicationWillTerminate(_ application: UIApplication) { unityFramework.appController()?.applicationWillTerminate(application) } } AppDelegateから呼び出す アプリが立ち上がったタイミングでUnityを呼び出します。こうすることで、バックグラウンドでローディングされるため、スプラッシュ画面がユーザーに表示されず、待ち時間も発生しません。 新規のアプリなので、パフォーマンスなどにも今のところは影響ありません。 AppDelegate.swift import Firebase import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application( _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { FirebaseApp.configure() // Unityを呼び出す Unity.shared.application(application, didFinishLaunchingWithOptions: launchOptions) // Unityとは関係のない、sign inの画面 let singInViewController = SignInViewController(nibName: nil, bundle: nil) let navigationController = UINavigationController(rootViewController: singInViewController) let model = SignInModel() let presenter = SignInPresenter(view: singInViewController, model: model) singInViewController.inject(presenter: presenter) window = UIWindow(frame: UIScreen.main.bounds) window?.rootViewController = navigationController window?.makeKeyAndVisible() return true } } UnityのViewControllerからViewのみを呼び出す Integrating Unity into native iOS applications Unity as a Libraryの仕組みとしては、メイン(ホスト)のiOSアプリのUIWIndowとは別に、Unity側でUIWindowを生成しています。そしてホスト側からUnity側のWindowに切り替える際には、showUnityWindowというUnityFrameworkの関数を呼び出す必要があります。(UIApplecation内に、複数のUIWindowが存在していて、最前面のUIWindowがアプリの画面として表示されているということになります。) つまりUnityの画面をiOSアプリ内で表示する公式な方法としては、UnityのUIWindowを最前面に出すということになります。 一方で今回は、アプリの画面遷移をNavigationControllerで実現する必要がありました。 その実現に当たって、以下の2つの方法を検討しました。 a. Unity側のWindowからViewだけを呼び出し、アプリの画面を表示しているViewControllerにaddSubViewする b. Unity側のWindowを後ろに置いたまま、アプリの画面を表示しているViewControllerの背景を透明にすることで、Unity側のWindowを背景にする 結論としては、a.を選択しました。やってみればすぐに分かることですが、b.のやり方だと、NavigatioControllerのアニメーションに対して、背景のUnity側Windowは動きません。つまり、NavigationControllerのアニメーションと連動せず違和感が発生することになるためです。 Unity ClassのViewにアクセスできるようプロパティを実装 先ほどの、AppDelegate.swiftから抜粋。 Unity.swift ... var view: UIView { unityFramework.appController()!.rootView! } ... ホスト側ViewControllerへのaddSubViewと、そのsubViewを背面へ移動 HostViewController.swift import UIKit class HostViewController: UIViewController { // UnityのViewの読み込み private let unityView = Unity.shared.view private var presenter: HostPresenterInput! func inject(presenter: HostPresenterInput) { self.presenter = presenter } init() { super.init(nibName: nil, bundle: nil) // addSubView view.addSubview(unityView) // 追加したsubViewのサイズをViewControllerのViewのサイズに合わせる unityView.frame = view.frame // 追加したsubViewを背面へ(addSubViewは最前面に追加するため、ViewControllerのViewの後ろに設定する必要がある) view.sendSubviewToBack(unityView) } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) navigationController?.isNavigationBarHidden = true } } iOSアプリ→Unityの呼び出し (Calling C# back from native code) iOSアプリからUnityに対して、データの受け渡しや関数の呼び出しを行います。 メインの開発はSwiftを用いたiOSアプリの開発で、Unityは背景として読み込むという方針を取りました。そのため、Unity→iOSのやりとりは発生しません。 今回は、簡易なUnitySendMessageを選択しました。 Building plug-ins for iOS (Calling C# back from native code) 補足 今回Unity→iOSアプリの呼び出しが存在しなかったため、次の手順にあるNativeCallProxy.hのヘッダーファイルは存在しません。 Integrating Unity as a library into standard iOS app (5. Expose NativeCallProxy.h) UnityFrameworkのメソッドを呼び出す 今回MVPのアーキテクチャパターンを選択したため、Modelからの呼び出しになっています。 HostModel.swift import Foundation protocol HostModelInput { func sendData(data: [String?]) } final class HostModel: HostModelInput { func sendData(data: [String?]) { // 引数はStringなので、それに合わせる let dataText = """ {\"data0\": \(data[0]!), \"data1\": \(data[1]!), \"data2\": \(data[2]!), \"data3\": \(data[3]!), \"data4\": \(data[4]!), \"data5\": \(data[5]!)} """ // UnityFrameworkdのメソッドを呼び出す Unity.shared .sendMessageToUnity(objectName: "SampleData", functionName: "SetData", argument: dataText) } } その他の参考 iOSアプリ設計パターン入門 開発に当たっては、こちらの書籍を参考にMVPのアーキテクチャパターンを選択しました。(選択の過程をまとめた記事はこちら→PoCでiOSアーキテクチャにMVPを採用した話) iOSアプリ開発に限らず、設計パターンを学びたい方に損得抜きで強くおすすめです。歴史的経緯も含めた設計パターンの丁寧な解説、さらに各設計パターンのサンプルコードで構成されています。 【Unity】MirrativのEmbedding Unityを更新した話: 実践 Unity as a Library 【Unity】Mirrativのアバターがなんで動いているのか誰にもわからないので説明する 実際にサービスを運用されている事例から、実践的な説明がされています。 2本目はUnity as a Library (UaaL)が実装される前に、iOSアプリにembedを行なった方法を説明されています。 (Unityに詳しい先輩曰く、「黒魔術のようなものなので、今はUaaLを使うべき」とのことでした!) Integrating Unity into native iOS applications 公式のドキュメントで、iOSアプリから呼び出せるメソッドが説明されています。 こぼれ話 Using Unity as a Library in other applications Unity as a Library is intended for specialist users who use native platform technologies such as Java/Android, Objective C/iOS, or Windows Win32/UWP, and want to include Unity-powered features in their games or applications. Unity as a Libraryは、Java/Android、Objective C/iOS、Windows Win32/UWPなどのネイティブプラットフォーム技術を使用し、ゲームやアプリケーションにUnity搭載の機能を搭載したいと考えている専門家ユーザーを対象としています。 公式の紹介ページには、上記の脅し文句があります。iOSアプリ開発自体が初めての自分は最初少しひるみましたが、これ以外に方法がないので意を決して取り組みました。確かにUnityがiOSアプリの中でどのように読み込まれるのか、ライフサイクルにどのように組み込まれるべきなのかなど理解に時間がかかった部分もあります。しかしながら、この記事内で紹介している記事を一通り読んでいただければ、全体像は掴めるのではないかと思います。Unityをネイティブアプリに組み込みたくなった方は、不可能なことではないのでぜひ挑戦してみてください!また、不明点などがあれば一緒に学ばせていただければと思いますので、コメントください。
- 投稿日:2021-04-05T13:00:40+09:00
【Swift】CollectionViewまとめ
はじめに 基本的なCollectionViewの扱い方を練習していく中で学んだことをまとめました。 自分のQiitaの記事も貼っていきます。 GitHub 学んだこと HeaderFooterCollectionView おわりに 随時更新予定
- 投稿日:2021-04-05T12:57:27+09:00
【Swift】CollectionViewでカスタムheaderとfooterを作成する
はじめに 今回はCollectionViewでheaderとfooterを扱う方法を紹介したいと思います。 以下のようなものを作ってみます。 GitHub 以下のHeaderFooterCollectionViewに今回のリポジトリあります! 実装 まず、真ん中の赤い正方形を作ります。 カスタムセルでXIBを作成してコードは以下のようにしてください。 MyCollectionViewCell final class MyCollectionViewCell: UICollectionViewCell { static var toString: String { return String(describing: self) } static let id = MyCollectionViewCell.toString static func nib() -> UINib { return UINib(nibName: MyCollectionViewCell.toString, bundle: nil) } func configure() { backgroundColor = .red } } そして、ViewControllerのviewDidLoad以下のように、UICollectionViewFlowLayoutを利用します。 ViewController let layout = UICollectionViewFlowLayout() layout.scrollDirection = .vertical layout.sectionInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) layout.itemSize = CGSize(width: self.view.frame.size.width / 2.2, height: self.view.frame.size.width / 2.2) collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) collectionView?.register(MyCollectionViewCell.nib(), forCellWithReuseIdentifier: MyCollectionViewCell.id) collectionView?.delegate = self collectionView?.dataSource = self collectionView?.backgroundColor = .white view.addSubview(collectionView!) } collectionViewの縦横のサイズを決めます。 ViewController override func viewDidLayoutSubviews() { super.viewDidLayoutSubviews() collectionView?.frame = self.view.bounds } そして、delegateメソッドを以下のように書きます。 ViewController extension ViewController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { return 4 } func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCell(withReuseIdentifier: MyCollectionViewCell.id, for: indexPath) as! MyCollectionViewCell cell.configure() return cell } } これで真ん中に表示4つの赤い正方形が表示されたと思います。 次はheaderを作成していきます。 headerやfooterは以下のようなUICollectionReusableViewというものを使います。 HeaderCollectionReusableView final class HeaderCollectionReusableView: UICollectionReusableView { static var toString: String { return String(describing: self) } static let id = HeaderCollectionReusableView.toString private let label: UILabel = { let label = UILabel() label.text = "header" label.font = .systemFont(ofSize: 50) label.textAlignment = .center label.textColor = .white return label }() func configure() { backgroundColor = .green addSubview(label) } override func layoutSubviews() { super.layoutSubviews() label.frame = bounds } } そして、ViewControllerないで以下のように書いていきます。 cellを登録 ViewController collectionView?.register(HeaderCollectionReusableView.self, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: HeaderCollectionReusableView.id) UICollectionViewDataSourceメソッド ViewController extension ViewController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: HeaderCollectionReusableView.id, for: indexPath) as! HeaderCollectionReusableView header.configure() return header } } UICollectionViewDelegateFlowLayoutメソッド ViewController extension ViewController: UICollectionViewDelegateFlowLayout { func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize { return CGSize(width: view.frame.size.width, height: 200) } } これで緑色のヘッダーがつかされたと思います。 青色のフッターも同じように作成します。 ただし、UICollectionViewDataSourceのdequeueReusableSupplementaryViewを以下のように変更します。 ViewController extension ViewController: UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView { if kind == UICollectionView.elementKindSectionFooter { let footer = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: FooterCollectionReusableView.id, for: indexPath) as! FooterCollectionReusableView footer.configure() return footer } else { let header = collectionView.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: HeaderCollectionReusableView.id, for: indexPath) as! HeaderCollectionReusableView header.configure() return header } } } 最後に、以下のようにUICollectionViewDelegateのnumberOfSectionsを追加すれば完成です。 ViewController extension ViewController: UICollectionViewDelegate { func numberOfSections(in collectionView: UICollectionView) -> Int { return 5 } } 解説 大切なところだけ解説します。 UICollectionReusableView UICollectionReusableViewでカスタムヘッダーやフッターを作成します。 カスタムヘッダーの手順は以下の通りです。 1.カスタムヘッダーを用意する 2.viewDidLoadでカスタムヘッダーをregisterする 3.collectionView(viewForSupplementaryElementOfKind) 4.collectionView(referenceSizeForHeaderInSection) viewForSupplementaryElementOfKindではヘッダーおよびフッターの設定をします。 referenceSizeForHeaderInSectionでは名前の通り、ヘッダーのサイズを決めます。 おわりに CollectionViewも奥が深いですね、、、