20210405のSwiftに関する記事は15件です。

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 課題管理- 決断メーカー
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

複数の 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 内に書くことで対処しました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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)が表示されるはずです。 おわりに おわりです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Firestoreを使用したデータの追加、読み取り

Firebaseのドキュメントにも書いてある通りだが、ViewControllerにFirebaseをimportしない方法で記述する。 FirestoreContoroller.swift import UIKit class FirestoreViewController: UIViewController { override func viewDidLoad() { super.viewDidLoad() } @IBAction func addAction(_ sender: Any) { Database.addTest() } @IBAction func readAction(_ sender: Any) { Database.readTest() { users in for user in users { print("\(user.first), \(user.last), \(user.born)") } } } } UserModel.swift import Foundation import FirebaseAuth struct UserModel: Codable { private var _first: String? var first: String { _first ?? "" } private var _last: String? var last: String { _last ?? "" } private var _born: Int? var born: Int { _born ?? 0 } private var _weight: Double? var weight: Double { _weight ?? 0 } private var _height: String? var height: String { _height ?? "" } private let auth = Auth.auth() var isEmailValidation: Bool { auth.currentUser?.isEmailVerified ?? false } private enum CodingKeys: String, CodingKey { case _first = "first" case _last = "last" case _born = "born" } } Database.swift import Foundation import Firebase import FirebaseFirestore import FirebaseFirestoreSwift class Database { private static let db = Firestore.firestore() static func addTest() { var ref: DocumentReference? = nil ref = db.collection("users").addDocument(data: [ "first": "Ada", "last": "Lovelace", "born": 1815 ]) { err in if let err = err { print("Error adding document: \(err)") } else { print("Document added with ID: \(ref!.documentID)") } } } static func readTest(completion: (([UserModel]) -> Void)? = nil) { let _completion: ((QuerySnapshot?, Error?) -> Void) = { (querySnapshot, err) in var users: [UserModel] = [] if let err = err { print("Error getting documents: \(err)") completion?(users) return } for document in querySnapshot!.documents { if let _user = try? Firestore.Decoder().decode(UserModel.self, from: document.data()) { users.append(_user) } } completion?(users) } db.collection("users").getDocuments(completion: _completion) //全部終わるまで待つ } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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を自動で更新させている) ・デメリット →コード量が多い →書き方に慣れるまで時間がかかる おわりに 少しずつわかってきました!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swiftオブジェクトとvoid*の連携

Cライブラリではしばしば見られる「void*でコンテキスト情報を渡し、後のコールバックではその値が引数として回ってくる」タイプのインターフェースを Swift で使う場合のメモ。平たく言うと Swift オブジェクトの参照をどうやって void* にキャストするか。 Cインターフェース さしあたりCインターフェースは次のようになっているとします。 c_call はコンテキスト情報とコールバックを受け取り、コールバックに info を渡す単純なトランポリンです。 c_call.h typedef void (callback_t)(void* _Nullable info); void c_call(void* _Nullable info, callback_t cb); c_call.c void c_call(void* _Nullable info, callback_t cb) { cb(info); } Swift インターフェース 今回は Hello を表示する Greeting クラスを作り、それを info に渡して C 側から呼ぶことを考えます。 Greeting.swift class Greeting { // これを c_call 経由で呼び出したい func hello() { print("Hello") } } Swift コールバックの定義 Swift では void* に UnsafeMutableRawPointer (const なら UnsafeRawPointer) が対応します。通常この手の引数は nullable だと思うので UnsafeMutableRawPointer? としています。 Callback.swift func swift_cb(info: UnsafeMutableRawPointer?) { guard let greeting = info?.bindMemory(to: Greeting.self, capacity: 1).pointee else { return } greeting.hello() } Swift 関数はそのまま C関数として渡せるので特に細かいことは考えなくて大丈夫です。UnsafeRawPointer を特定の型に変換するには bindMemory を使います。 オブジェクトを取り出すことができれば、あとは普通に hello() を呼び出すだけです。 UnsafeRawPonter への変換 逆にオブジェクトの参照を UnsafeRawPointer へと変換するには withUnsafePointer/withUnsafeMutablePointer を使います。 Callback.swift func doGreeting() { var greeting = Greeting() withUnsafeMutablePointer(to: &greeting) { info in c_call(info, swift_cb) } } この関数は変換したいオブジェクトとクロージャを取り、クロージャは UnsafeRawPointer にキャストされた info を受け取ります。これで Swift オブジェクトを void* として C に渡してなにかさせるという事ができるようになりました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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をネイティブアプリに組み込みたくなった方は、不可能なことではないのでぜひ挑戦してみてください!また、不明点などがあれば一緒に学ばせていただければと思いますので、コメントください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】CollectionViewまとめ

はじめに 基本的なCollectionViewの扱い方を練習していく中で学んだことをまとめました。 自分のQiitaの記事も貼っていきます。 GitHub 学んだこと HeaderFooterCollectionView おわりに 随時更新予定
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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も奥が深いですね、、、
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

画像を読み込み、UIImageViewに表示

iPhoneから画像を読み込み、写真を選択→UIImageViewに表示する。 最終的にはCellに情報を渡して表示したい。 AddCellViewController import UIKit final class AddCellViewController: UIViewController { private var list = AddModel.init() @IBOutlet private weak var imageView: UIImageView! { didSet { // デフォルトの画像を表示する imageView.image = UIImage(named: "no_image.png") } } @IBOutlet private weak var pickerView: UIPickerView! override func viewDidLoad() { super.viewDidLoad() pickerView.delegate = self pickerView.dataSource = self } @IBAction private func selectPicture(_ sender: UIButton) { // カメラロールが利用可能か? if UIImagePickerController.isSourceTypeAvailable(.photoLibrary) { // 写真を選ぶビュー let picturepickerView = UIImagePickerController() // 写真の選択元をカメラロールにする // 「.camera」にすればカメラを起動できる picturepickerView.sourceType = .photoLibrary // デリゲート picturepickerView.delegate = self // ビューに表示 self.present(picturepickerView, animated: true) } } @IBAction private func deletePicture(_ sender: UIButton) { // アラート表示 showAlert() } /// アラート表示 private func showAlert() { let alert = UIAlertController(title: "確認", message: "画像を削除してもいいですか?", preferredStyle: .alert) let okButton = UIAlertAction(title: "OK", style: .default, handler:{(action: UIAlertAction) -> Void in // デフォルトの画像を表示する self.imageView.image = UIImage(named: "no_image.png") }) let cancelButton = UIAlertAction(title: "キャンセル", style: .cancel, handler: nil) // アラートにボタン追加 alert.addAction(okButton) alert.addAction(cancelButton) // アラート表示 present(alert, animated: true, completion: nil) } } extension AddCellViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate { // 写真を選んだ後に呼ばれる処理 func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { // 選択した写真を取得する let image = info[.originalImage] as! UIImage // ビューに表示する imageView.image = image // 写真を選ぶビューを引っ込める self.dismiss(animated: true) } } extension AddCellViewController: UIPickerViewDelegate { func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { return list.List.count } func numberOfComponents(in pickerView: UIPickerView) -> Int { return 1 } // UIPickerViewの最初の表示 func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? { return list.List[row] } } extension AddCellViewController : UIPickerViewDataSource { }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

リワード インタースティシャルの実装方法。

admobをいじってたらリワード インタースティシャル(ベータ) と書いてるのを発見し、調べてみたところ、実装方法がほとんど載っていなかったので記事にします。

一応、ここにサンプルがあるものの、Obj-Cなのとなんか動かなかった。

リワードインタースティシャル #とは

リワード インタースティシャルは、アプリの画面が変わる自然なタイミングで自動的に表示される広告に対して報酬(例: コイン、追加ライフ)を提供できる、新しいタイプのインセンティブ広告フォーマットです。
リワード広告とは異なり、ユーザーはリワード インタースティシャルを表示するためにオプトインする必要はありません。
リワード広告のオプトイン プロンプトの代わりに、リワード インタースティシャルでは、報酬について通知し、ユーザーが希望する場合にはオプトアウトできる選択肢を提示する導入画面が必要です。

なるほど。

前提条件

  • Google Mobile Ads SDK 7.60.0 以降
  • スタートガイドの手順に沿って、Google Mobile Ads SDK をインポートしていること

実装

インタースティシャル広告を組み込む主な手順は、以下のとおりです。

  • 広告を読み込む
  • コールバックを登録する
  • 広告を表示してリワード イベントを処理する
ViewController.swift
var rewardedInterstitialAd: GADRewardedInterstitialAd!

override func viewDidload() {
    super.viewDidLoad()

    loadRewordAds()
}


func loadRewordAds() {
    let request: GADRequest = GADRequest()
    let adsView = GADRewardedInterstitialAd.load(
        withAdUnitID: "ca-app-pub-XXXXXXXXX/xxxxx",
        request: request,
        completionHandler: { [self] ad, error in
            if let error = error {
              print("error: \(error.localizedDescription)")
              return
            }else {
                rewardedInterstitialAd = ad
            }
         }
    )
}

func callRewordAds() {
    rewardedInterstitialAd?.present(fromRootViewController: self, userDidEarnRewardHandler: {
        // ユーザー報酬の処理
        print(rewardedInterstitialAd.adReward.amount) // 報酬額(admobで設定したやつ)
    })

}

あとは好きなタイミングでcallRewordAdsを呼ぶだけです。

ただ2回目以降は呼び出されませんでした。多分GADRewardedInterstitialAdは使い捨てオブジェクトなんでしょう。

fullScreenContentDelegateを使って終わったら再度読み込むようにしてみます。

func loadRewordAds() {
    let request: GADRequest = GADRequest()
    let adsView = GADRewardedInterstitialAd.load(
        withAdUnitID: "ca-app-pub-XXXXXXXXX/xxxxx",
        request: request,
        completionHandler: { [self] ad, error in
            if let error = error {
              print("error: \(error.localizedDescription)")
              return
            }else {
                rewardedInterstitialAd = ad
                rewardedInterstitialAd.fullScreenContentDelegate = self // Add
            }
         }
    )
}
extension ViewController: GADFullScreenContentDelegate {
    /// Tells the delegate that the ad failed to present full screen content.
    func ad(_ ad: GADFullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) {
      print("Ad did fail to present full screen content.")
    }

    /// Tells the delegate that the ad presented full screen content.
    func adDidPresentFullScreenContent(_ ad: GADFullScreenPresentingAd) {
      print("Ad did present full screen content.")
    }

    /// Tells the delegate that the ad dismissed full screen content.
    func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) {
      print("Ad did dismiss full screen content.")
      // ここで再読み込み
        loadRewordAds()
    }
}

さいごに

実装方法が全く乗ってなかったのですが最近でたやつなのかな?

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

リワード インタースティシャル広告の実装方法。

admobをいじってたらリワード インタースティシャル(ベータ) と書いてるのを発見し、調べてみたところ、実装方法がほとんど載っていなかったので記事にします。 一応、ここにサンプルがあるものの、Obj-Cなのとなんか動かなかった。 リワードインタースティシャル #とは リワード インタースティシャルは、アプリの画面が変わる自然なタイミングで自動的に表示される広告に対して報酬(例: コイン、追加ライフ)を提供できる、新しいタイプのインセンティブ広告フォーマットです。 リワード広告とは異なり、ユーザーはリワード インタースティシャルを表示するためにオプトインする必要はありません。 リワード広告のオプトイン プロンプトの代わりに、リワード インタースティシャルでは、報酬について通知し、ユーザーが希望する場合にはオプトアウトできる選択肢を提示する導入画面が必要です。 なるほど。 前提条件 Google Mobile Ads SDK 7.60.0 以降 スタートガイドの手順に沿って、Google Mobile Ads SDK をインポートしていること 実装 インタースティシャル広告を組み込む主な手順は、以下のとおりです。 広告を読み込む コールバックを登録する 広告を表示してリワード イベントを処理する ViewController.swift var rewardedInterstitialAd: GADRewardedInterstitialAd! override func viewDidload() { super.viewDidLoad() loadRewordAds() } func loadRewordAds() { let request: GADRequest = GADRequest() let adsView = GADRewardedInterstitialAd.load( withAdUnitID: "ca-app-pub-XXXXXXXXX/xxxxx", request: request, completionHandler: { [self] ad, error in if let error = error { print("error: \(error.localizedDescription)") return }else { rewardedInterstitialAd = ad } } ) } func callRewordAds() { rewardedInterstitialAd?.present(fromRootViewController: self, userDidEarnRewardHandler: { // ユーザー報酬の処理 print(rewardedInterstitialAd.adReward.amount) // 報酬額(admobで設定したやつ) }) } あとは好きなタイミングでcallRewordAdsを呼ぶだけです。 ただ2回目以降は呼び出されませんでした。多分GADRewardedInterstitialAdは使い捨てオブジェクトなんでしょう。 fullScreenContentDelegateを使って終わったら再度読み込むようにしてみます。 func loadRewordAds() { let request: GADRequest = GADRequest() let adsView = GADRewardedInterstitialAd.load( withAdUnitID: "ca-app-pub-XXXXXXXXX/xxxxx", request: request, completionHandler: { [self] ad, error in if let error = error { print("error: \(error.localizedDescription)") return }else { rewardedInterstitialAd = ad rewardedInterstitialAd.fullScreenContentDelegate = self // Add } } ) } extension ViewController: GADFullScreenContentDelegate { /// Tells the delegate that the ad failed to present full screen content. func ad(_ ad: GADFullScreenPresentingAd, didFailToPresentFullScreenContentWithError error: Error) { print("Ad did fail to present full screen content.") } /// Tells the delegate that the ad presented full screen content. func adDidPresentFullScreenContent(_ ad: GADFullScreenPresentingAd) { print("Ad did present full screen content.") } /// Tells the delegate that the ad dismissed full screen content. func adDidDismissFullScreenContent(_ ad: GADFullScreenPresentingAd) { print("Ad did dismiss full screen content.") // ここで再読み込み loadRewordAds() } } さいごに 実装方法が全く乗ってなかったのですが最近でたやつなのかな?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Result型のアウトプット!

はじめに Result型についてのアウトプットです! Result型とは Result型とはSwift5から登場した機能で非同期でクロージャに値を渡す時やエラーハンドリングにめちゃくちゃ使えます! 中身は以下のようになっています。 // Result型 public enum Result<Success, Failure: Error> { case success(Success) case failure(Failure) } Resultがsuccessの場合は付属値にSucess型の値が入り、failureの場合は付属値にFailure型の値が入るんですね〜 肝は、SuccessとFailureにそれぞれ任意の型を指定するところです。 (わからない方は「Swift ジェネリクス」でググるか、Swift実践入門を読むなりしてみてくださいね。5分もあれば理解できます!) Result型の使い方は、見た方が早いので早速いきましょう。 Result型を使うとき クロージャに値を渡す時の例でいきましょう。 ポケモンを例に見ていきます! 以下、Pokemon,PokeErrorを使うので一応載せておきます。 // Pokemonの定義 struct Pokemon { var name: String? var type: String? var technique: String? } // pokeErrorの定義(Errorプロトコルに準拠しないとResult型のFailureに指定できません) struct PokeError: Error {} 以下はクロージャを引数にとる関数と、受け取る側を実装した部分になります。 // completionに渡す値の型をPokemon型、PokeError型に指定 func getPokemon(completion: ((Result<Pokemon, PokeError>) -> Void)? = nil) { // インスタンス化 let picatyuuu = Pokemon(name: "ピカチュウ", type: "でんき", technique: "十万ボルト") let error = PokeError() // ポケモンゲット if isGot { // Pokemon型の値を渡す completion?(.success(picatyuuu)) } else { // ポケモンゲットならず // PokeError型の値を渡す completion?(.failure(error)) } } // completionの結果を受け取る側 getPokemon { result in switch result { case .success(let picatyuuu): // picatyuuuが使える print("\(String(describing: picatyuuu.name))ゲットだぜ!") case .failure(let error): // errorが使える print("\(error)だぜ!") } } ポイントで解説します。 completionに渡す値の型はResult型で、Pokemon型またはPokeError型をcompletionに渡します。 この場合はピカチュウを捕まえたので、ピカチュウを付属値とするsuccessを渡してcompletionに渡して、呼び出し先でピカチュウを使って何かしら処理をしたいわけですね。 捕まえられなかった場合はerrorを付属値とするfailureを渡してあげます。 呼び出す側は受け取ったsuccess(hoge)またはfailure(fuga)の付属値hoge,fugaを使って処理したいわけです。(単にエラーかどうか知りたい場合は除く) パターンマッチという技があるのでそいつを使って付属値が取り出せます。 switch result { case .success(let picatyuuu): // picatyuuuが使える print("\(String(describing: picatyuuu.name))ゲットだぜ!") case .failure(let error): // errorが使える print("\(error)だぜ!") } この部分がパターンマッチを使ってるところです。 .success(let picatyuuu)でpicatyuuuが使えます。errorも然り。 ジェネリクスやenumについては解説していませんがそれらの知識があれば簡単に使えるので是非! 非同期処理に渡す値の型ををcompletion?(Pokemon?, PokeError?)みたいにしてる人はResult型に変更した方がいいかもですね! 最後に 相変わらず文章下手やな、、 最後まで読んでいただきありがとうございました! ご指摘あればお願いします〜
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Xcode】TabBarとNavigationBarを配置しているviewControllerでスクロールを可能にする方法

シンプルな(TabBarなどが配置されていない)ViewControllerでのスクロールに関してはわかりやすい記事がいくつもありますが、TabBarやNavigationBarを配置している場合は少し異なるようです。 ざっと、以下のような流れになります。 1. NavigationBarとTabBarの配置 2. Scroll Viewの配置 3. Viewの配置 4. Constraintsの追加 0. プロジェクトの作成 プロジェクトを作成した、この状態から初めていきます。 1. NavigationBarとTabBarの配置 Main.storyboardの右下にあるEmbed In View Controllerを押して、その中のNavigationControllerとTab Bar Controllerを選択してください。 すると、このような画面になっていると思います。 1番右のViewControllerをスクロールできるようにしていきます。 2. Scroll Viewの配置 右上にあるObjectライブラリ(+ボタン)を開き、Scroll Viewを選択して、ViewCotrollerの画面いっぱいに配置します。 3. Viewの配置 次に、ObjectライブラリからViewを選択して、ViewControllerの画面に配置します。 ここで縦方向のスクロールのみの場合は、下図のように横幅はViewControllerに合わせて、縦幅が下にはみ出るようにします。(大きさはだいたいで) また、NavigationBarにScrollViewが被らないようにしてください。(スクロールがうまくいきません) 4. Constraintsの追加 ここまでできたらあと少しです。 ViewControllerを選択した状態で、Main.storyboardの右下にあるResolve Auto Layout Issuesを押して、All Views in View ControllerのAdd Missing Constraintsを選択すると、図のようにConstraintsが作られます。 最後に、スクロールがわかりやすいように2つのlabelを配置しておきましょう。 5. 完成! ビルドしてみましょう。 ちゃんとスクロールできるようになりました! 参考にしたサイト
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftUIチュートリアルで Multiple commands produce というエラーが出た時の対処法

SwiftUIのチュートリアルに取り組んでいたところ、エラーが出てきました。とりあえず解決したので対処法をまとめておきます。 初心者ですので、間違っていたらご教授ください?‍♂️ 状況 SwiftUIのチュートリアルをやっていたところMultiple commands produceという文言で始まるエラーに遭遇した。 場所:Building Lists and Navigation Section 2 Step 4 原因 landmarkData.json が2箇所にある。 Section1 Step 11 でフォルダ階層を整える操作がある。 この時にResourcesを移動させずに新しく作成してしまうと、landmarkData.jsonがLandmarks直下のResourcesフォルダとLandmarks/Landmarks/Resourcesの2箇所にlandmarkData.jsonが配置されてしまう。 この後に、Section2の操作を続けていくとプレビュー表示の際にエラーとなる。 対処 Landmarks直下のResourcesフォルダの中身をすべてLandmarks/Landmarks/Resourcesにうつし、Landmarks直下のResourcesフォルダを削除する。(チュートリアルではそもそもこういう意味の指示だったようだが、間違えて読んでしまった。) LandmarkRow.swiftに戻って、Previewでtry againを押すと問題なくPreviewが表示できるようになる。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む