iPadとPlaygroundsで学ぶ:アルバム内の写真を読み出す

以前試した、内蔵カメラでの写真撮影と同じ手順でアルバムから写真を取り出すことができます。

手順

カメラと同様にUIImagePickerControllerを使います。 異なるのはsourceTypeの指定だけです。

  1. UIImagePickerControllerのインスタンスを生成する
  2. 生成したインスタンスのsourceTypeにUIImagePickerController.SourceType. photoLibraryを設定する
  3. プロトコルを登録する
  4. presentで生成したインスタンスへ画面遷移する

撮影結果の受け取りもカメラ使用時と同様にプロトコルで受け取ります。

簡単ですね。

アプリの仕様

ビューにボタンを配置しボタンが押されたときにシステムアルバムアプリを起動します。 画像を選択しキャンセルされなかった場合には画面をアルバムに保存します。 (同じ絵が増えます)

コード

以下コードです。

import UIKit

class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate  {
    var imagePicker: UIImagePickerController?
    var mainView: UIView?
    var button: UIButton?
    override func viewDidLoad() {
        createElements()
        setAnchorConstraint()
    }
    func createElements() {
        self.imagePicker = UIImagePickerController()
        self.mainView = UIView()
        self.button = UIButton()
        guard
            let mainView = self.mainView,
            let button = self.button else {
                return
        }
        button.backgroundColor = UIColor.magenta
        button.addTarget(self, action:#selector(self.buttonEvent(_:)), for: UIButton.Event.touchUpInside)
        mainView.addSubview(button)
        self.view = mainView
    }
    func setAnchorConstraint() {
        guard
            let mainView = self.view,
            let button = self.button else {
                return
        }
        button.translatesAutoresizingMaskIntoConstraints = false
        button.centerXAnchor.constraint(equalTo:mainView.centerXAnchor).isActive = true
        button.bottomAnchor.constraint(equalTo:mainView.bottomAnchor, constant: -100).isActive = true
        button.widthAnchor.constraint(equalToConstant:60).isActive = true
        button.heightAnchor.constraint(equalToConstant:30).isActive = true
    }
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        // 画像データを取り出す
        if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
            // アルバムへ保存
      UIImageWriteToSavedPhotosAlbum(image,self,#selector(self.imageSavingEvent(_:didFinishSavingWithError:contextInfo:)), nil)
        }
        // 遷移元へ戻る
        picker.dismiss(animated:true, completion:nil)
    }
    // カメラを起動するボタンのイベント
    @objc func buttonEvent(_ sender: UIButton) {
        guard let imagePicker = self.imagePicker else {
            return
        }
        // UIImagePickerControllerの設定
        imagePicker.sourceType = UIImagePickerController.SourceType.photoLibrary
        // protocolを設定
        imagePicker.delegate = self
        // 画面遷移
        present(imagePicker, animated:true, completion:nil)
    }
    // 画像をライブラリに保存したときのイベント(エラーの場合も呼ばれる)
    @objc func imageSavingEvent(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
    }
}

import PlaygroundSupport
PlaygroundPage.current.liveView = ViewController()

雑記:iPhoneを紛失した…(戻ってきました!)

昨晩というか今朝、酔っ払ってiPhoneを落としてしまいました。ショック。 最近ストレスが溜まっているのか飲み過ぎてしまいます。

まだ見つかっていませんが、今お困りの方がいるかもしれませんので情報をシェアしたいと思います。

やってはいけないこと

重要です。 iPhoneを探すでサウンドを鳴らすのは自分の家の中などで探す場合だけにしましょう。 なんどか鳴らしたら電源を切られてしまい位置情報が途絶えました...

そりゃあうるさいですものね。

位置情報で探す

iPhoneを探す”が有効の場合にはPCや他のiPhone, iPadなどで落としたiPhoneの場所を探しましょう。 ブラウザを使う場合はこちら。

家の中にあった場合は一件落着。

位置情報が外だった場合や“iPhoneを探す”が無効の場合は次へ。

Appleに位置情報を掴まれたくない!という方はこういうのを使うといいでしょう。なくなる前にですが。

紛失モードを設定する

"iPhoneを探す"内にある"紛失モード"を設定しましょう。

以下はiPadのアプリから実行した場合の画面です。 連絡を受けられる電話番号を入力できます(任意)。 メッセージを入れましょう。 このように表示されます。(サブのiPhoneSEで試しました)

ApplePayの設定を削除する

Appleのアカウント管理画面で落としたiPhoneを選択します。 https://appleid.apple.com/account/manage

ApplePayのすべてを削除を押しましょう。これで使われる危険がなくなります。

シリアル番号は次の遺失物届けに書いておくと良いのでメモっておきましょう。

遺失物届けを書く

最寄りの警察署や交番に行って遺失物届けを出しましょう。

色、ケース、電話番号、キャリア、シリアル番号など特徴の分かるものをなるべくたくさん書きましょう。

あとは待つのみ

早く見つかるといいなぁ届けてくれたかたありがとう!

Metal並列プログラミング:【初期化】の手順

Appleのサイトを確認しながらMetalプログラムの初歩をマスターしたいと思います。 一気に書くと複雑になりそうなので【初期化】と【前処理&実行】に分けます。

座学状態で書いています。 実際に動かしたときに変更が必要であれば随時更新します。

0. MetalKitをimportする

大前提。これなしには始められません。

import MetalKit

1. GPUバイスを取得する

まずは描画や計算に使用するGPUバイスを取得する必要があります。 下記の関数でデフォルトGPUバイスを取得します。

let device = MTLCreateSystemDefaultDevice()

MacOSの場合はGPUバイスが複数搭載されている場合があるため、搭載されているGPUを確認して選択することもできます。 https://developer.apple.com/documentation/metal/choosing_gpus_on_mac

2.コマンドキューを作る

GPUへ何かを要求したいときにはコマンドキューを介して要求を送ります。GPUはキューに溜まっている要求を順々に実行しています。関数を実行して直接結果を受け取るモデルとは少し違いますね。

下記の関数でコマンドキューを作成します。

let commandQueue = device.makeCommandQueue()

3.コマンドバッファを作る

コマンドキュー上に要求を入れるためのバッファを作ります。

let commandBuffer = commandQueue.makeCommandBuffer()

シングルスレッドアプリではコマンドバッファを1つ作ります。 マルチスレッドアプリで並列処理から別々に要求を送りたい場合には必要な数のバッファを作ります。

4.エンコーダーを取得する

コマンドバッファからエンコーダーを取得します。 エンコーダーは複数の種類があり目的に適したものを取得します。

今回使用するエンコーダはMTLComputeCommandEncoderです。

let encoder = commandBuffer.makeComputeCommandEncoder()

5. デフォルトライブラリを取得する

バイスからMTLLibraryを取得します。 MTLLibraryはMetalの並列処理プログラム言語(Metal shading languange)コードを保持してswiftで扱えるよう機能を提供します。

let library = device.makeDefaultLibrary()

初期化はここまで。 基本的にはアプリの起動時に一度だけ初期化します。

作業中に聴きたいクラシック:眠くなっちゃう?バッハ ゴルドベルク変奏曲 BWV 988

作業中にオススメのクラシックをオススメしてみようかな。 ということでクラシックを紹介します。AmazonやAppleMusicのリンクも貼っていきますが今時一曲ずつ買う人なんてあまりいないですよね…

AppleAmazonのストリーミングを聴ける人は聴いてみてくださいね。

J.S.バッハ

みなさまご存知のJ.S.バッハです。 小学校の音楽室でみたことがある人も多いと思います。

実はバッハさんは音楽家の家系でして、バッハだけだとどの人が作曲した曲かわかりません。 今回紹介する曲を作られたJ.S.バッハさんは「チャラリー!鼻から。。。」の曲も作った、バッハ一族でも特に有名な方です。

ゴルドベルク変奏曲 BWV988

ゴルドベルク変奏曲はJ.S.バッハが作曲し、弟子のゴルドベルクが不眠症の貴族に聴かせて眠らせたとか言われている曲です。 鍵盤楽器向けの曲で、現在はピアノによる演奏が多いです。

美しくて温かみのある主題から派生する色々なバリエーションを聴かせてくれます。 華やかになったり憂鬱げになったりと色々な音楽が楽しめます。

不眠症の話とは逆ですが、爽やかな午前中に聴いてみると良いのではないでしょうか。

最後のBWVは後世いJ.S.バッハを研究した人がつけた目録番号です。

ご紹介

柔らかなタッチで爽やかな演奏をご紹介しておきます。

USBハブ:MacBookとiPhone、iPadも繋がる「j5 create JCH346」

先日の記事で少し書きましたがせっかくなので記事にします。

j5 createとは?

台湾のメーカーでUSBハブやMacBookのドックステーションなどを製造販売しています。

概要

いわゆるUSBハブです。MacBookiPhoneを同期しながらMacBookを充電することもできます。

ホストインターフェース

ノートPC給電向けにTypeCケーブル&コネクタが一本出ています。 このコネクタをMacBookに繋いでいます。

ポート構成

上記コネクタの他に、USB3.1 TypeCのポートが2つ(1つはACアダプタ等からの給電専用)、USB3.0 TypeAのポートが3つあります。

TypeAポートはもちろんUSB2.0 / 1.1にも対応しています。

USB3.0の場合は最大5Gbpsの通信か可能です。

サイズ

50.5(W)× 98(H)× 15(D) mm

25cmのケーブルが少し邪魔ではありますが、本体自体は大きくないです。

こんな感じで繋いでます

綺麗な写真ではないですが… 便利ですよ。

iOSアプリ開発:内蔵カメラで写真を撮る【UIImagePickerController編】

実機で動かしてみたくなったのでXCodeを使ってみます。

環境

ProでもAirでもないMacBookです。 持ち運び楽チンのお気に入りマシンです。

X無印です。 最新ではないですが処理能力などに不満はありません。

  • j5 create JCH346(USBハブ)

USBハブです。MacBookとiPhoneXを同期しながら充電することができます。

高めの給電能力が必要な機器向けにTypeCのコネクタが一本出ています。 その他、USB3.1 TypeCのポートが2つ(1つは給電専用)、USB3.0 TypeAのポートが3つあります。

必要十分です。

アプリの仕様

前回Playgroundsで作成した、内蔵カメラを使ったアプリをiPhoneで動かします。

schagerl.hatenablog.com

プロジェクト

XCodeを起動してiOSのSingle View Appを選択して作成します。

ソースコード

ViewController.swiftファイルに前回作成したコードをコピペします。 ただし、以下のPlaygrounds向けコードは除きます。

import PlaygroundSupport
PlaygroundPage.current.liveView = ViewController()

以下コード全体です。

import UIKit

class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate  {
    var imagePicker: UIImagePickerController?
    var mainView: UIView?
    var button: UIButton?
    override func viewDidLoad() {
        createElements()
        setAnchorConstraint()
    }
    func createElements() {
        self.imagePicker = UIImagePickerController()
        self.mainView = UIView()
        self.button = UIButton()
        guard
            let mainView = self.mainView,
            let button = self.button else {
                return
        }
        button.backgroundColor = UIColor.magenta
        button.addTarget(self, action:#selector(self.buttonEvent(_:)), for: UIButton.Event.touchUpInside)
        mainView.addSubview(button)
        self.view = mainView
    }
    func setAnchorConstraint() {
        guard
            let mainView = self.view,
            let button = self.button else {
                return
        }
        button.translatesAutoresizingMaskIntoConstraints = false
        button.centerXAnchor.constraint(equalTo:mainView.centerXAnchor).isActive = true
        button.bottomAnchor.constraint(equalTo:mainView.bottomAnchor, constant: -100).isActive = true
        button.widthAnchor.constraint(equalToConstant:60).isActive = true
        button.heightAnchor.constraint(equalToConstant:30).isActive = true
    }
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        // 撮影結果から画像データを取り出す
        if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
            // アルバムへ保存
            UIImageWriteToSavedPhotosAlbum(image, self, #selector(self.imageSavingEvent(_:didFinishSavingWithError:contextInfo:)), nil)
        }
        // 遷移元へ戻る
        picker.dismiss(animated:true, completion:nil)
    }
    // カメラを起動するボタンのイベント
    @objc func buttonEvent(_ sender: UIButton) {
        guard let imagePicker = self.imagePicker else {
            return
        }
        // UIImagePickerControllerの設定
        imagePicker.sourceType = UIImagePickerController.SourceType.camera
        // protocolを設定
        imagePicker.delegate = self
        // 画面遷移
        present(imagePicker, animated:true, completion:nil)
    }
    // 画像をライブラリに保存したときのイベント(エラーの場合も呼ばれる)
    @objc func imageSavingEvent(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
    }
}

画像保存の部分だけ少し書き換えましたが換えなくても動きます。

ユーザー許可

前回も書きましたが、実機でカメラやフォトライブラリを使用する場合はユーザーに許可を求める必要があります。 (ユーザー許可の設定がない場合にはアプリが落ちます)

設定する場所はXCodeのInfo.plistファイルです。 ファイルにキーを追加してメッセージを追加することでユーザー許可を求めることができます。

今回は以下2つの項目を追加する必要があります。

  • Privacy - Camera Usage Description

カメラ機能許可についてのメッセージです

  • Privacy - Photo Library Additions Usage Description

フォトライブラリへのデータ追加許可についてのメッセージです。

Information Property Listの右クリック(?)メニューのAdd Row で項目を追加し、許可を求める時に表示するメッセージを記入してください。

実行

MacBookiPhoneを接続して上にある再生ボタンを押せば実行できます。

iPhoneのロックを解除しておくとアプリが自動的に起動します。 が、初めて実行した場合は多分起動しません。

iPhone側の設定→一般→プロファイルとデバイス管理→デベロッパAPP に表示されるXCodeで使用しているアカウントを選択し、「"XXXX"を信頼」をタップしてください。

感想

Playgroundsでコーディングに慣れておくと、開発環境周り等に労力が必要でも余裕がありますね。

コーディング&開発環境で引っかかりポイントが一気にくるより断然楽です。

iPadとPlaygroundsで学ぶ:内蔵カメラで写真を撮る【UIImagePickerController編】

内蔵カメラでの写真撮影に挑戦します。

カメラの機能を使う場合、以下の2つの方法があるようです。

  • UIImagePickerControllerでシステムカメラを利用する(お手軽,細かい制御はできない)
  • AVFoundationでカメラの機能を直接使う(色々自分で実装する必要がある,細かい制御ができる)

今回はUIImagePickerControllerを使ってみます。 developer.apple.com

UIImagePickerControllerでカメラを起動

UIImagePickerControllerを使うと以下の手順でカメラを起動することができます。

  1. UIImagePickerControllerのインスタンスを生成する
  2. 生成したインスタンスのsourceTypeにUIImagePickerController.SourceType.cameraを設定する
  3. プロトコルを登録する
  4. presentで生成したインスタンスへ画面遷移する

非常に簡単ですね。3.については次で説明します。

appleのサイトにはsourceTypeとmediaTypeが対応しているものかどうか確認するように書いてありますが今回は割愛します。

撮影結果を受け取る

UIImagePickerControllerの役割は画像データを準備してくれるところまでです。画像データを受け取るためにはプロトコルを使います。

プロトコルは他の言語で言うところのインターフェースクラスみたいなものです。 プロトコルを継承したクラスで必要な関数を実装し、そのクラスのインスタンスをコントローラーに登録することでイベントが発生した時に通知してもらうことが出来ます。

UIImagePickerControllerの通知は次のプロトコルを継承して受け取ります。

// プロトコル
UIImagePickerControllerDelegate

// 画像データが準備できたことを通知してもらう関数
UIImagePickerControllerDelegate. imagePickerController
// キャンセルされたことを通知してもらう関数
UIImagePickerControllerDelegate. imagePickerControllerDidCancel

上記の関数は通知に対して何か処理したいものだけ実装すれば良いです。今回はimagePickerControllerのみ実装します。

画像データはimagePickerController関数の第2引数(info)に格納されて渡されます。 infoは連想配列でいくつかの情報を保持しており、撮影した画像データはキーUIImagePickerController.InfoKey.originalImageを指定して取り出せます。

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
    // 取り出してからUIImageにキャストする
    let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage 
}

またUIImagePickerControllerにプロトコルを登録する場合は画面遷移に関する下記プロトコルも継承する必要があります。こちらについても処理を追加したい関数のみ実装すれば良いです。

UINavigationControllerDelegate

アプリの仕様

ビューにボタンを配置しボタンが押されたときにシステムカメラを起動します。 画像を撮影しキャンセルされなかった場合には画面をアルバムに保存します。

コード

以下コードです。

import UIKit

class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    var imagePicker: UIImagePickerController?
    var mainView: UIView?
    var button: UIButton?
    override func viewDidLoad() {
        createElements()
        setAnchorConstraint()
    }
    func createElements() {
        self.imagePicker = UIImagePickerController()
        self.mainView = UIView()
        self.button = UIButton()
        guard
            let imagePicker = self.imagePicker,
            let mainView = self.mainView,
            let button = self.button else {
            return
        }
        button.backgroundColor = UIColor.magenta
        button.addTarget(self, action:#selector(self.buttonEvent(_:)), for: UIButton.Event.touchUpInside)
        mainView.addSubview(button)
        self.view = mainView
    }
    func setAnchorConstraint() {
        guard
            let mainView = self.view,
            let button = self.button else {
            return
        }
        button.translatesAutoresizingMaskIntoConstraints = false
        button.centerXAnchor.constraint(equalTo:mainView.centerXAnchor).isActive = true
        button.bottomAnchor.constraint(equalTo:mainView.bottomAnchor, constant: -100).isActive = true
        button.widthAnchor.constraint(equalToConstant:60).isActive = true
        button.heightAnchor.constraint(equalToConstant:30).isActive = true
    }
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        // 撮影結果から画像データを取り出す
        if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
            // アルバムへ保存
            UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil)
        }
        // 遷移元へ戻る
        picker.dismiss(animated:true, completion:nil)
    }
    @objc func buttonEvent(_ sender: UIButton) {
        guard let imagePicker = self.imagePicker else {
            return
        }
       // UIImagePickerControllerの設定
        imagePicker.sourceType = UIImagePickerController.SourceType.camera
        // protocolを設定
        imagePicker.delegate = self
        // 画面遷移
        present(imagePicker, animated:true, completion:nil)
    }
}

import PlaygroundSupport
PlaygroundPage.current.liveView = ViewController()

アルバムに保存する関数は実行結果を受け取るイベントを指定できるようですが今回は省略します。

実機で実行する場合

iPad + Playgroundsでは特に問題なく実行できましたが、実機でこのプログラムを動かす場合にはユーザー許可が必要です。 ユーザー許可の設定方法については実機で動かすときにまとめたいと思います。