20200402のiOSに関する記事は6件です。

AVFoundationでカメラモジュールを作る

概要

iOSのAVFoundationを用いて汎用カメラモジュールを作ります。
Vision.frameworkと組み合わせて画像認識したり,リアルタイムビデオエフェクトアプリを作る際にも利用できるように,汎用のモジュールとして作りましょう。いろいろと使いまわせて便利です。

まずはシングルトンのカメラコントローラクラスを作り,そこにAVCaptureSessionのインスタンスを持たせます。これがカメラのセッションを管理するクラスですね。
このAVCaptureSessionに,インプットとアフトプットを接続し,さらにカメラプレビューとなるAVCaptureVideoPreviewLayerを接続すれば準備完了。
AVCaptureSessionをrunしてやればカメラが起動し,AVCaptureVideoPreviewLayerにカメラの映像が表示されます。

CameraControllerの実装

汎用カメラオブジェクトとして,私はいつもCameraControllerというクラスを作っています。

CameraController.swift
class CameraController: NSObject, AVCaptureVideoDataOutputSampleBufferDelegate, AVCapturePhotoCaptureDelegate {

// MARK: - lifecycle
    static let shared = CameraController()
    private override init() {    

    }
}

Swiftでのシングルトンの実装方法もいくつかあるようですが,シンプルに。このクラスはSampleBufferDelegateとPhotoCaptureDelegateを採用していますが,必要な機能によっては他にも採用するプロトコルはあり得ます。

また,カメラの起動に必要なものは,プロパティとして宣言しておきます。

CameraController.swift
 // MARK: - properties
    let captureSession:AVCaptureSession = AVCaptureSession()
    let videoOutput:AVCaptureVideoDataOutput = AVCaptureVideoDataOutput()
    let photoOutput:AVCapturePhotoOutput = AVCapturePhotoOutput()
    let previewLayer = AVCaptureVideoPreviewLayer()
    var preview:UIView!

最後のpreviewだけ,宣言時にはnilです。
このクラス,例えば他のviewControllerなどから簡単に呼べるように,カメラをスタートするメソッドとストップするメソッドを持たせます。カメラ起動中の映像を表示するために,スタートメソッドにはプレビューを表示するUIViewをパラメータに持たせます。スタートメソッドで受け取ったviewを,プロパティのpreviewにセットするわけですね。

次に,カメラをスタートするメソッド,ストップするメソッドを追加します。スタートメソッド内でcapture sessionやinput, outputを設定します。

CameraController.swift
func startSession(preview:UIView){

    //カメラプレビュー
    self.preview = preview
    self.previewLayer.setSessionWithNoConnection(self.captureSession)
    self.previewLayer.frame = preview.bounds
    self.previewLayer.videoGravity = .resizeAspectFill
    preview.layer.addSublayer(self.previewLayer)

    self.captureSession.beginConfiguration()

    // input
    guard let videoDevice = AVCaptureDevice.default(for: .video) else { return }

    do {
        let deviceInput = try AVCaptureDeviceInput(device: videoDevice)

            if self.captureSession.canAddInput(deviceInput){
                self.captureSession.addInput(deviceInput)
            }
            else{
                print("input error")
            }
        }
        catch {

        }

    // output
    if self.captureSession.canAddOutput(self.photoOutput){        
        self.captureSession.addOutput(self.photoOutput)
        self.photoOutput.isHighResolutionCaptureEnabled = true
        self.photoOutput.isLivePhotoCaptureEnabled = self.photoOutput.isLivePhotoCaptureSupported
    }
    else{
        print("photo output error!")
    }

    if self.captureSession.canAddOutput(self.videoOutput){
        self.videoOutput.setSampleBufferDelegate(self, queue: self.sampleBufferQueue)
        self.captureSession.addOutput(self.videoOutput)
    }
    else{
        print("video output error!")
    }

    self.captureSession.commitConfiguration()
    self.captureSession.startRunning()
}

本来はマルチスレッドを活用して,UIに関わる部分以外はバックグラウンドのqueueで行いましょう。エラーハンドリングも適切に。このコードでは割愛してます。

続いてカメラをストップするメソッドを書きたいところですが,長くなるのでこれも割愛。基本的には,sessionをstopしてinputとoutputを外しておけばOKです。

最後に,必要なデリゲートプロトコルを実装。ここでは使用頻度が高いと思われる2つだけ書いておきます。メソッドの中身は,必要に応じて頑張って書きましょう。

CameraController.swift
// MARK: - capture photo delegate
// 写真撮影後に呼ばれる
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?){

}

// MARK: - sample buffer delegate
// 1フレームごとに呼ばれる
func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {

}

これがCameraControllerの実装は終わり(実際にはもっと書かなきゃいけませんよ!)。viewControllerからカメラを起動する場合には,例えば以下のようにします。

ViewController.swift
override func viewDidAppear(_ animated:Bool){

    super.viewDidAppear(animated)

    //ここ
    CameraController.shared.startSession(self.view)
}

以上,汎用カメラクラスの基本設計でした。
カメラなど,特定のデバイスを扱うコードはできる限りシングルトンの専用クラスに書いておいた方が良いと思います。しばしばViewControllerにすべて書いた実装を見ますが,それはあまり良くないです。理由は以下。

・デバイスは1つしかないので,カメラを制御するコードが複数のインスタンスから同時にコールされないようにするため
・コードの再利用が容易(←重要)
・専用のオブジェクトにまとめておけば,どのviewControllerからでも呼びやすい

というところです。

上記コードですが,くれぐれも,これだけではまだ十分ではないのでいろいろと頑張りましょう。改善提案,間違いの指摘は歓迎です。

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

Unity+ARFoundationでiOS用ARサンプルを動かす

Unity+ARFoundationでiOS用ARサンプルを動かす

1. 開発環境

  • MacBook Pro (macOS Catalina 10.15.4)
    • Unity 2019.3.7f1
    • XCode 11.4
  • iPad Pro 11 inch 第2世代 Wi-Fi+Cellular LiDARも搭載!

2. ARFoundationの導入

arfoundation-samples (GitHub)よりクローンを取得します。
ここで紹介する内容は、Update to ARFoundation 4.0.0-preview.1以降あたりのコミット分のクローンとしました。
image.png
これをMacのローカルドライブに展開します。ここでは、~/Documents/Unity/arfoundation-samples-masterに展開しました。

3. Unity Project を開きビルド

上で展開した ~/Documents/Unity/arfoundation-samples-masterを Unityで開きます。
image.png

AR関係パッケージの確認

まず念の為、ARFoundationで必要なパッケージが導入されているか確認します。[Window]-[Package Manager]を開きます。
赤色で囲んだ、AR関係のpreview.1 - 4.0.0があればOKです。
image.png

Bundle Indentifierを変更

[Edit]-[Project Settings...]でプロジェクト設定を開き、その中のPlayerタブを選択します。
Bundle Identifierがそのままでは、このあとのXCodeでのビルドが通らないので、ユニークな識別コードに変更します。
image.png

ビルド

[File]-[Build Settings...]Build Settingsウインドウを開きます。
Scenes In Buildでビルド対象のシーンを1つ選択します。ここではまずScenes/SimpleAR/SimpleARを選択します。
Platformには、もちろんiOSを選択します。
image.png
Buildボタンを押し、XCode用のプロジェクトを書き出すフォルダを指定します。ここでは、このプロジェクトのフォルダの下に iOSという名前のフォルダを作成して指定します。

4. XCodeでビルドとiPadへ転送

上で保存したプロジェクトフォルダiOSの下にARFoundationというフォルダがあり、その中のXCodeプロジェクトファイルUnity-iPhone.xcodeprojをXCodeで開きます。
そして、iPadをMacBookに接続しておきます。
Signingで次の設定を行います。
- Automatically manage signingをチェック
- Team には、自分のApple Developper Accountを指定する
デプロイ先のiPadを右上のリストで選んで再生ボタンを押すと、iPadにプログラムが転送されます。
image.png

5. iPadでの実行結果

iPadにプログラムが転送されると、しばらくして "Made with Unity"のロゴが表示されてプログラムが起動します。
机と壁にiPadを向けると、なにやら平面を認識した結果がAR表示されました。画面上をタップすると、キューブも表示されました。
image.png

6. 今後やってみたいこと

Unityのサンプルプロジェクトには、今回試した"SimpleAR"というシーン以外にもたくさんのサンプルシーンがあります。それらも順次試したいのですが、たくさんあるシーンを「UnityのBuild Settingsで選んではビルド→XCodeでもビルド→iPadへ転送」と繰り返さなければならないので面倒です。シーン選択用のメニューをUnity上で作ってやろうかと思います。

とりあえず3次元情報を取得して認識していそうなサンプルプログラムは動きました。あとはやっぱり、生の3次元点群を取得して、iPadを完全な3Dスキャナとしていろいろ活用したいところです。特にこの新型iPad Pro には LiDARが搭載されているのですから!ソースをこれから詳しく追っていきます。

参考文献

大変参考になりました。ありがとうございます。
- UnityでiOS向けのARアプリを開発する方法 ( AR Foundation + ARKit-XR-Plugin 導入編 )

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

貴方に向いた端末は

じゃあまず使用方法を教えてくれるかな

使用方法 Windows MacOSX(MacBookシリーズ) iPhone Android(スマホ) Android(タブレット) iPad ChromeBook      備考    
ブラウジング 広い画面でUIが使いやすいとブラウジングしやすい。性能とかより機動性が大事
動画編集 ×~◎ ×~△ ×~〇 △~◎ ×~△ 性能が低かったり画面が小さいと厳しい。
電子書籍 ×~◎ ChromeBookを買うときはAndroidアプリ対応機種を買うこと。
絵を描く ×~△ △~◎ 〇~◎ ×~△ 動画編集ほどではないが性能が必要
ゲームをする △~〇 ×~△ △~◎ △~◎ 〇~◎ × もしあなたがゲームを楽しみたいのなら機動性や可用性が優れたハイエンドタブレットorハイエンドスマホのどちらかが必要。Windowsはエロゲは揃いが良いらしいがそれ以外(特にゲーム性の良いゲーム)はタブレットやスマホと比べると劣る。ChromeBookはゲームが苦手。
Office × × 間違いなく一番この用途ではChromeBookが使いやすい。
コスパ × × Google系がオープンソースであるためかコスパが良い
電話 × × × これは携帯電話から派生したスマホが良い
初期ソフトウェア × WindowsはAndroidより要らない初期アプリが多すぎる。無駄にお金がかけられていると思うとイライラする。
セキュリティー × 〇(野良アプリ(GooglePlayStore以外から入手したアプリ)を使用したときは×) 〇(野良アプリ(GooglePlayStore以外から入手したアプリ)を使用したときは×) 〇~◎ Windowsはアプリストアが貧弱すぎるため基本的にインターネット上からのダウンロードとなる
プログラミング × × × × ChromeBookはプログラミングでも案外使いやすい
各種3DCGソフトウェア(Blender等) △~◎ 〇~◎ × × × △~〇 × Blender等を触りたければChromeBook以外のPCを買いましょう

こちらの記事に質問させてもらいました。

【スマホVSパソコン】ハイスペックスマホにお金をかける時代は完全に終わった!
 https://www.digigaze.com/pc-vs-phone

人形百合姫(私):
2020年3月23日 21:21
これも用途による気がします。
ゲームをするならスマホ、動画編集等をするならPC、効率よくメモを取りたい、Office系ソフトを使いたいならChromeBook(Google製の手軽なノートPC)にそれぞれ金をかけるべきだと思う。ChromeBookならコーディング(MarkDown,HTML,CSS,PHP,JavaScript,C#等)ができるし、Google Playがあると、電子書籍を読めたりするし、ハイエンドでも安いので、PC代わりにもなり、大体の作業はこちらで代用可能です(ゲームと動画編集、3DCGソフトは無理諦めましょう)。

返信
上田 龍上田 龍 より:
2020年3月31日 23:54
確かにその通りだと思います。
コメントの人形百合姫様はパソコンやスマホの知識が豊富であるようですが、当記事は最新iphoneなどを購入して機能のうちの10%も使いこなせていない人が多いという現状を知った時に執筆した記事となっています。
使いこなせない機能盛りだくさんな15万円のスマホをショップで契約させられるのであれば
スマホ5万円
パソコン10万円
という選択肢をとったほうは有利に働くことが多いと感じます。
あくまでマクロ的な視点での意見ですので、ミクロ視点だと人形百合姫様の意見が必要な方が多いのも事実です(‘◇’)ゞ
コメントありがとうございました!

ゲームをする場合ハイエンドスマホまたはゲーミングスマホ(ゲームに特化した高性能アンドロイドスマホ)もしくはハイエンドタブレットPC(できればiPad pro)があれば快適。

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

[WKwebview]Monaca+Iosでデバッグビルドしたアプリがスプラッシュ後、画面が真っ白になる

MonacaでWkwebviewを利用してデバッグビルドしたアプリをiphoneで実行したときに画面が真っ白になってしまう

上記の現象に遭遇したので、そのときの解決策を記載します。

前提条件
・AndroidやcloudIDEでは動作している
・iphoneで動作しない
・WKWebViewを利用している

最初に結論から言うと、HTMLを取得する際のXHRでエラーとなっていました。
iphoneのログを見ても特にエラーは出てませんでした。

3/31に公開されたプラグイン、"Custom Scheme"を追加すると起動するようになりました。

追加方法
CloudIDEの[設定]→[cordovaプラグインの管理]を開き
検索窓に[Custom Scheme]と入れて有効にします。

これだけで起動するようになりました。

"Custom Scheme"プラグインが公開されるまでは
"cordova-plugin-wkwebview-file-xhr"プラグインを入れていたのですが、私の場合、初回起動はしたものの、2回目以降の起動で画面が真っ白になる現象が再発してしまっていたので、"Custom Scheme"を使っています。

初心者なので、何か間違っていること等あればご指摘ください<(_ _)>

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

CAMetalLayerを実機でもシミュレータでも良い感じにビルドする

時々、とても奇妙なクラスに出会うことがある。
CAMetalLayerはまさにそれで、実機ではiOS8+、シミュレータではiOS13+が必要になる。
これはシミュレータでMetalが使えるようになったのがiOS13~だったからという歴史的経緯によるものだ。
ちなみに内部的には実機とシミュレータでSDKが別れているため、それぞれのSDKごとにavailableが定義されて実現している。

実機SDK
API_AVAILABLE(macos(10.11), ios(8.0), watchos(2.0), tvos(9.0))
@interface CAMetalLayer : CALayer
シミュレータSDK
@available(iOS 13.0, *)
open class CAMetalLayer : CALayer {

さて、問題はアプリでCAMetalLayerを利用するときで、iOS12未満もサポートしているアプリだと次のような挙動になる。

実機 シミュレータ
iOS12 コンパイルエラー
iOS13

この挙動をどう見るか難しいところだが、シミュレータ相手であればさっとコンパイルだけ通したいこともあると思う。
そんなときは

public protocol CAMetalLayerInterface: class {
  var pixelFormat: MTLPixelFormat { get set }
  var framebufferOnly: Bool { get set }
  var presentsWithTransaction: Bool { get set }
  func nextDrawable() -> CAMetalDrawable?
}
#if targetEnvironment(simulator)
@available(iOS 13, *)
extension CAMetalLayer: CAMetalLayerInterface {}
#else
@available(iOS 12, *)
extension CAMetalLayer: CAMetalLayerInterface {}
#endif
open class AnimationView: UIView {
  typealias LayerClass = CAMetalLayerInterface & CALayer
  override open class var layerClass: Swift.AnyClass {
    #if targetEnvironment(simulator)
    if #available(iOS 13, *) {
      return CAMetalLayer.self
    } else {
      preconditionFailure("Not support simurator older than iOS12.")
    }
    #else
    return CAMetalLayer.self
    #endif
  }
  private var gpuLayer: LayerClass { self.layer as! LayerClass }
}

こんな感じで型を消しつつ、iOS12 & Simulator以外のときに型を入れてあげるのが良い気がする。

#if canImport(QuartzCore.CAMetalLayer)

とか

#if targetEnvironment(simulator) && available(iOS 13, *)

とか出来ればいいのになーって思ってしまった。レアケースだけど…

https://developer.apple.com/documentation/quartzcore/cametallayer

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

SwiftUIでiPhoneのバッテリー残量を表示してみる

はじめに

たまたまTwitterで#swiftuiを検索していたら見つけたので、ちょっと作ってみました。

バッテリー残量を取るには

バッテリー残量を取るには、下記のコードで取得できます。

BatteryViewModel.swift
        //バッテリー監視を開始する
        UIDevice.current.isBatteryMonitoringEnabled = true
        //変化通知登録しただけなので初回は更新する必要がある
        remain = String(format: "%0.1f", UIDevice.current.batteryLevel * 100)
        status = UIDevice.current.batteryState

その後の変化通知をNotificationCenterで受け取れます。

BatteryViewModel.swift
        //バッテリーレベル変化通知を受け取れるようにする
        NotificationCenter.default.addObserver(self, selector: #selector(batteryLevelChanged(notification:)), name: UIDevice.batteryLevelDidChangeNotification, object: nil)
        //バッテリー状態編か通知を受け取れるようにする
        NotificationCenter.default.addObserver(self, selector: #selector(batteryStateChanged(notification:)), name: UIDevice.batteryStateDidChangeNotification, object: nil)

バッテリー残量をViewに反映する

ObservableObjectで反映します。

BatteryViewModel.swift
class BatteryViewModel: ObservableObject {
    ///バッテリー状態
    @Published var status: UIDevice.BatteryState = .unknown
    ///バッテリー残量
    @Published var remain = ""

終わりに

サンプルコードは、githubにアップしています。
参考にどうぞ。

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