20200721のSwiftに関する記事は12件です。

サーバサイド Swift フレームワーク Vapor の開発環境セットアップ方法

はじめに

私は普段iOSフロントをSwiftで書いているため、XcodeやSwift等の環境がある程度ありましたので、
Xcodeの導入部分等は省略しております。

環境

  • macOS Catalina 10.15.5
  • Xcode 11.5
  • Swift 5.2.4

Vapor インストール

なにはともあれVaporを導入
homebrewを導入をお願いします。

brew tap vapor/tap
brew install vapor/tap/vapor

Vapor プロジェクトを作成

私の場合はAPI開発がしたかったのでテンプレートオプションでAPIモードとしてプロジェクト作成しました。

vapor new Hello --template=api

いくつかイニシャライズ時の質問がありますので設定してきます。

Would you like to use Fluent?

Fluent とはモデルの作成、削除、読み取り、更新やクエリの発行などを行ってくれるDB周りの便利パッケージのようです。

y を入力して回答していきます。

Which database would you like to use?

どのDB使うか聞かれるので 1 の Postgres を選択します。(2020/07/21 現在 MySQL、MONGO DB 等はbeta版のようでした)

作成されたプロジェクトのディレクトリに移動します。

vapor new Hello

Xcodeプロジェクトを作成します

vapor xcode

上記でXcodeが自動的に立ち上がるのでSwiftPMのインストールが終わりましたら、
ShemeMy Mac に設定して(おそらく自動的に設定されている) Cmd + R or ▶︎ボタン で実行します。
http://localhost:8080 にアクセスすると下記が表示されると思います。

スクリーンショット 2020-07-21 22.43.34.png

以上で初期の開発環境セットアップ完了です。

参考

https://docs.vapor.codes/2.0/getting-started/install-on-macos/
https://docs.vapor.codes/3.0/getting-started/hello-world/

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

UIViewControllerでpresentすると循環参照する?!

結論

UIViewControllerでpresentすると循環参照するんですね。
windowのrootViewControllerを差し替える時とかに注意が必要ですね。

サンプル

playgroundにて下記のコードで検証

import Foundation
import UIKit
import PlaygroundSupport

class VC: UIViewController {

    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)

        print("init")
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        print("init2")
    }

    deinit {
        print("deinitialized")
    }

}

var p: VC? = VC()

PlaygroundPage.current.liveView = p

var p2: VC? = VC()

p?.present(p2!, animated: true, completion: nil)

_ = p?.presentedViewController

p = nil

_ = p2
_ = p2?.presentingViewController
init
init
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Swift歴半年、初めてのアプリ公開までの道のり

はじめに

タイトルの通りです。少し大袈裟に書いたのは、自分にとってアプリを公開するということは、実務でバリバリSwift書いてる人や一部の秀才たちにのみ許された聖域のように見えていて、なかなか踏み出せなかったからです。(つまりただイモってただけ)
でも、やってみたら案外あっさり承認されたのでもっと早くやってればよかったとちょっと後悔しています。
なので、同じように悩んでいる人がいたら、とりあえずさっさとApple Developer Accountにお金払ってアプリを公開しましょう!って背中を押したいのでこの記事を書いてます。

公開したアプリ 【復習サイクル】

インストールはこちら
GitHubはこちら

スクリーンショット 2020-07-21 17.38.49.png

機能的にはかなり薄っぺらいアプリです...

流れ

流れとは言ってもただ参考にしたリンクの羅列になりそう。せめて体験談を踏まえながら書いていきます。

  1. まずはこちらの1〜4を参考に下準備。ここでは特に何も困りませんでした。丁寧でとてもわかりやすい記事です、ありがとうございます。

  2. 字を読んで理解するのが苦手なので、まずはこちらのyoutubeを見て流れを掴む。英語ですが、とてもわかりやすくしゃぺってくれてます。

  3. そしてApp Store Connect→マイAppにて、実際に必要事項入力していきます。基本的に全ての項目を入力していく感じです。僕が一番困ったのが電話番号入力。こちらを参考にして解決。"+81"というのが抜けていてずっとエラーが消えずめっちゃイライラしてました笑 それからスクリーンショットにも苦労しました。サイズが明確に決められていて、該当のスクショを選択→ツール→サイズを調整、鍵マークを外し指定のピクセルで入力します。
     スクリーンショット 2020-07-21 17.43.12.png
    初めてこれをやったら、元の画像がめちゃくちゃ拡大されてびっくりしますが、それで問題ありません。ここで無駄に試行錯誤してました...

  4. 個人情報の取り扱いについてはこちらを使うと自動で取り扱いに関するリンクを作ることができます。すごい。これついて、何かトラブル等あった際の責任は一切取れませんので、ご自身でしっかり判断されてください。

  5. アプリのアイコンはFigmaでそれっぽいの作って、それをスクショして、それをこちらのサイトに投げれば自動で全部やってくれます!

  6. アプリの審査してくれる方々はおそらく外国人なので、審査用の機能メモ欄は英語で書きました。Thank you in advanceと最後につけてちょっといい人を演じてみたりもしました笑

最後に

自分は24時間くらいしてokのメールをいただきました!これからどんどん公開していくぞ!そして早く転職活動終わらせたい...

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

iOSアプリ開発で参考にしたiOSエンジニア YouTuber

はじめに

本格的にiOSアプリを開発してから約2年が経ちましたが、始めた当初から独学しています。というのもインターネット上に情報が溢れているのでネイティブアプリの構成やデバッグ方法、作りたい機能は大体検索すれば出てきます。

参考にしたサービス:
・YouTube
・StackOverflow
・Medium
・Qiita
・Teratail
・Udemy

始めた当初は動画で見れるYouTubeは特に参考になりましたので、iOSエンジニアYouTuber(英語)を紹介します。

iOSエンジニアYouTuber

・ Let's build that app(私のメンター)
https://www.youtube.com/channel/UCuP2vJ6kRutQBfRmdcI92mA
アジア系アメリカ人のブライアン。この人がいなかったらiOSアプリ開発出来ていなかったと思います。
iOSプログラミングの基本、応用、デザインはほぼから彼から学びました。
このチャンネルは『このアプリを作ろう』的なシリーズ系をたくさん載せています。例えば、『Firebaseを使ってチャットアプリを作ってみよう』、『YouTubeを作ってみよう』、『Twitterを作ってみよう』などなど。
そのほかにもアルゴリズム系の動画を載せています。

・Code with Chris
https://www.youtube.com/user/CodeWithChris
ビギナー用の動画がたくさんあります。
1年前に公開された『How to make an app for Beginners』は243万回も再生されています。
ほかにもサードパーティーライブラリ(SDWEBIMAGEやSVProgressHUD)の使い方も解説しています。

・Sean Allen
https://www.youtube.com/channel/UCbTw29mcP12YlTt1EpUaVJw
基本的なSwift(ViewControllerの解説、ClassやStructの使い方、if文やswitch文の使い方、enumの使い方)の構造などを説明しています。

・ArchetApp
https://www.youtube.com/user/Archetapp
サードパーティーサービス(Firebase, Stripe, Realm)を使ったプロジェクト作成動画を載せています。

最後に

YouTubeは無料で見れる上、内容の濃いコンテンツが多いのでおすすめです。
技術的な面を解説している日本人iOSエンジニアYouTuberの方が見つからなかったため、外国人YouTuberを参考にしています。

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

CreateMLでイケメン分類器をつくる。かんたんモバイル・ディープラーニング。

ディープラーニングをかんたんにできるCreateML

IMG_0574.jpeg
数学などの専門知識は不要です。
AppleのツールCreateMLで、あなたも簡単にディープラーニングのモデルを学習させることができます。
そして、そのモデルはiOSアプリでつかうことができます。

データをあつめましょう

トレーニング・データ

イケメンの画像100枚と、イケメンではない画像100枚をあつめました。
そして、それらをフォルダわけしました。
スクリーンショット 2020-07-21 5.18.06.png
イケメンフォルダ
スクリーンショット 2020-07-21 5.19.46.png
イケメンじゃないフォルダ
スクリーンショット 2020-07-21 5.18.41.png

テスティング・データ

トレーニング・データと同じフォルダ構成で、テスティング・データのフォルダもつくります。
イケメン、イケメンじゃない、10枚ずつです。
トレーニング・データに含まれない画像を用意する必要があります。
分類の子フォルダ名(この場合、「ikemen」と「ikemennot」)はトレーニング・データと同じであることが要求されます。
スクリーンショット 2020-07-21 5.37.33.png

CreateMLを起動

XcodeをControllキーを押しながらクリック、そしてOpenDeveloperTools、CreateMLを選択。
スクリーンショット 2020-07-21 5.27.36.png
CreateMLのメニューからImage Classification(画像分類)を選びます。
スクリーンショット 2020-07-21 5.30.47.png
それから、好きなプロジェクト名を入力します。

CreatMLで画像分類モデルを学習させる

Training DataとTesting Dataの+ボタンをクリックして、用意したフォルダを指定します。
スクリーンショット 2020-07-21 5.34.57.png

それから、▶️Trainボタンを押すと学習が開始されます。
スクリーンショット 2020-07-21 6.10.15.png

あっという間に終わります。
スクリーンショット 2020-07-21 5.45.15.png

テスティング・データでテストした結果を表示してくれます。
スクリーンショット 2020-07-21 5.46.45.png
イケメンのPrecision89%、Recall80%。
モデルがイケメンと予測した画像の89%が正解。
イケメン画像の80%がイケメンと予測されました。

Previewを選択してオリジナル画像をドロップすると、その画像でテストできます。
スクリーンショット 2020-07-21 5.58.43.png
99%の確信でイケメンではない、と予測されました。

モデルをiOSモバイルアプリでつかう

mlmodelをゲット

Outputタブを選択すると、学習したモデルが表示されます。
これはCoreMLモデル形式です。
Getボタンを押して、モデルを保存します。
スクリーンショット 2020-07-21 6.01.45.png
保存されたモデルを、Xcodeプロジェクトにドラッグ&ドロップします。
スクリーンショット 2020-07-21 6.07.15.png

Visionフレームワークでモデルをつかう

Visionフレームワークでmlmodelを手軽に使えます。
VNCoreMLRequestで、CoreMLモデルの推論リクエストをつくります。

ViewController.swift
func setupVision() -> NSError? {
        // Setup Vision parts
        let error: NSError! = nil

        guard let modelURL = Bundle.main.url(forResource: "ikemenclasifier 1", withExtension: "mlmodelc") else {
            return NSError(domain: "VisionObjectRecognitionViewController", code: -1, userInfo: [NSLocalizedDescriptionKey: "Model file is missing"])
        }
        do {
            let visionModel = try VNCoreMLModel(for: MLModel(contentsOf: modelURL))
            let objectRecognition = VNCoreMLRequest(model: visionModel, completionHandler: { (request, error) in
                DispatchQueue.main.async(execute: {
                    // perform all the UI updates on the main queue
                    if let results = request.results {
                        self.mlCompletion(results)
                    }
                })
            })
            self.mlRequest = [objectRecognition]
        } catch let error as NSError {
            print("Model loading went wrong: \(error)")
        }
               return error
    }

VNImageRequestHandlerを使って、用意した画像でCoreMLリクエストを実行します。

ViewController.swift
let imageRequestHandler = VNImageRequestHandler(ciImage: image, orientation: exifOrientation, options: [:])
            do {
                try imageRequestHandler.perform(self.mlRequest)
            } catch {
                print(error)
            }

リクエストは結果をVNClassificationObservationとして返します。
identifierは結果の分類名です。それは、データのフォルダ名と同じになります。この場合、「ikemen」か「ikemennot」です。
confidenceはパーセンテージの確信度です。

ViewController.swift
    func mlCompletion(_ results: [Any])  {
        guard let observation = results.first as? VNClassificationObservation else {
            print("its not ml observation")
            return
        }
        print(observation.identifier,observation.confidence)
        if observation.identifier == "ikemen" {
            resultLabel.text = "イケメン\n\(floor(observation.confidence * 100))"
            ikemenCount += 1
        } else {
            resultLabel.text = "イケメンではない\n\(floor(observation.confidence * 100))"
        }
    }

IMG_0574.jpeg

実際のアプリはこちら

GitHubにアプリ置いときます。
https://github.com/john-rocky/HandsomeClassifier

ぼくのTwitterをフォローしてください。お願いします。
https://twitter.com/JackdeS11

お仕事のご依頼をこのメールにお願いします。
rockyshikoku@gmail.com

あと、Looks Good For Me(わるくないね)、押してください。
        ここです ↓
チャオ?!

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

YUV(YCbCr)のCVPixelBufferをBGRAに高速に変換する

ARKitのcapturedImageをmediapipeで利用する時など、CVPixelBufferのpixelFormatを変換する必要があることがあります。

以前の記事ではvImageを利用しましたが、vImageはCPUでの計算に最適化されており映像処理などには向かないものでした。
https://qiita.com/noppefoxwolf/items/b12d56e052664a21d8b6

そこで、GPUを利用してYCbCrをBGRAに変換するライブラリを作りました。Xcode12のSwiftPMで簡単に導入することができます。
https://github.com/noppefoxwolf/BlueDress

使い方

簡単に使うことができます。

import BlueDress

let converter = YCbCrImageBufferConverter()
let bgraBuffer = try converter.convertToBGRA(imageBuffer: imageBuffer)

変換の流れ

実際は変換ではなく、新規に新しいBGRAのPixelBufferを作ってそこにYCbCrを焼いています。

YCbCrのPixelBufferは、内部に2枚の画像を持っておりそれぞれ

  • Y: 輝度
  • CbCr 青と赤の色差

をサイズ違いでもっています。
YCbCrは以下の計算式で変換することが出来るので、Metalシェーダを使ってこの計算を行うというわけです。

R = 1.164(Y-16)                 + 1.596(Cr-128)
G = 1.164(Y-16) - 0.391(Cb-128) - 0.813(Cr-128)
B = 1.164(Y-16) + 2.018(Cb-128)

実装で詰まりがちなところ

各PlaneのPixelFormat

YCbCrから2枚のPlaneを取り出す際に、最初はそれぞれをBGRAとして取り出していたのですがこれは間違いでそれぞれ

  • Y: r8Unorm
  • CbCr: rg8Unorm

で取り出す必要があります。
よく考えれば当然なのですが、ARKitのMetalテンプレートを読んで気がつきました。

CVPixelBufferとMTLTextureの関係

MTLTextureはあくまでCVPixelBufferの参照を持つ概念なので、MTLTextureに対して行った描画処理は特に取り出す必要はなく、MTLTextureのソースとして渡したCVPixelBufferに適用されます。

MTLTextureが-6660エラーで作れない

最終描画先の空のMTLTextureを作りたくて、そのソースとなる空のCVPixelBufferを自前で作っていたのですがどうもエラーでMTLTextureが作れませんでした。
MTLTextureを作る場合は、CVPixelBufferを生成する時にkCVPixelBufferMetalCompatibilityKey:trueを与えるのを忘れないようにしましょう。

Bundle.moduleが生成されない

これはXcode12が正式リリースされたら追記します。

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

自分でレイアウトを組んだ時の注意

UIをコードで組んだ時に

translatesAutoresizingMaskIntoConstraints = false

を入れてあげないと、自分が組んだUIレイアウトが優先されない。

自分の場合

レイアウトを組む際は、関数を定義してあげて使ってます。

func anchor(top: NSLayoutYAxisAnchor? = nil,
                left: NSLayoutXAxisAnchor? = nil,
                bottom: NSLayoutYAxisAnchor? = nil,
                right: NSLayoutXAxisAnchor? = nil,
                paddingTop: CGFloat = 0,
                paddingLeft: CGFloat = 0,
                paddingBottom: CGFloat = 0,
                paddingRight: CGFloat = 0,
                width: CGFloat? = nil,
                height: CGFloat? = nil) {
        translatesAutoresizingMaskIntoConstraints = false

こんな風に、書いてます。

これでanchor()を呼び出して使ってます。

使用例

let titleLabel : UILabel = {
    let label = UILabel()
    label.text = "hoge"
    label.font = UIFont.boldSystemFont(ofSize: 14)
    return label
}()

override func viewDidLoad() {
    super.viewDidLoad()
    view.addSubview(titleLabel)
  titleLabel.anchor(top: view.safeAreaLayoutGuide.topAnchor,
            right: view.safeAreaLayoutGuide.rightAnchor,
            paddingTop: 50, paddingRight: 147, width: 100, height: 56)
  titleLabel.layer.cornerRadius = 56/2
}

みたいな感じで書いてあげればおそらくかけるのかなと思います。

image.png

こんな感じにできます。

レイアウトのコードは

https://github.com/tiking76/chatapp/blob/master/chatApp/Utiles/Extentions.swift

に置いてあります。

もっと詳しく知りたいよ!!ってひと

↓のスライドをみてね!

LTで登壇した時のスライド

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

Swiftで自分でレイアウトを組んだ時の注意

UIをコードで組んだ時に

translatesAutoresizingMaskIntoConstraints = false

を入れてあげないと、自分が組んだUIレイアウトが優先されない。

自分の場合

レイアウトを組む際は、関数を定義してあげて使ってます。

func anchor(top: NSLayoutYAxisAnchor? = nil,
                left: NSLayoutXAxisAnchor? = nil,
                bottom: NSLayoutYAxisAnchor? = nil,
                right: NSLayoutXAxisAnchor? = nil,
                paddingTop: CGFloat = 0,
                paddingLeft: CGFloat = 0,
                paddingBottom: CGFloat = 0,
                paddingRight: CGFloat = 0,
                width: CGFloat? = nil,
                height: CGFloat? = nil) {
        translatesAutoresizingMaskIntoConstraints = false

こんな風に、書いてます。

これでanchor()を呼び出して使ってます。

使用例

let titleLabel : UILabel = {
    let label = UILabel()
    label.text = "hoge"
    label.font = UIFont.boldSystemFont(ofSize: 14)
    return label
}()

override func viewDidLoad() {
    super.viewDidLoad()
    view.addSubview(titleLabel)
  titleLabel.anchor(top: view.safeAreaLayoutGuide.topAnchor,
            right: view.safeAreaLayoutGuide.rightAnchor,
            paddingTop: 50, paddingRight: 147, width: 100, height: 56)
  titleLabel.layer.cornerRadius = 56/2
}

みたいな感じで書いてあげればおそらくかけるのかなと思います。

image.png

こんな感じにできます。

レイアウトのコードは

https://github.com/tiking76/chatapp/blob/master/chatApp/Utiles/Extentions.swift

に置いてあります。

もっと詳しく知りたいよ!!ってひと

↓のスライドをみてね!

LTで登壇した時のスライド

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

UIKitのレイアウトをコードで組んだ時の備忘録

UIをコードで組んだ時に

translatesAutoresizingMaskIntoConstraints = false

を入れてあげないと、自分が組んだUIレイアウトが優先されない。

自分の場合

レイアウトを組む際は、関数を定義してあげて使ってます。

func anchor(top: NSLayoutYAxisAnchor? = nil,
                left: NSLayoutXAxisAnchor? = nil,
                bottom: NSLayoutYAxisAnchor? = nil,
                right: NSLayoutXAxisAnchor? = nil,
                paddingTop: CGFloat = 0,
                paddingLeft: CGFloat = 0,
                paddingBottom: CGFloat = 0,
                paddingRight: CGFloat = 0,
                width: CGFloat? = nil,
                height: CGFloat? = nil) {
        translatesAutoresizingMaskIntoConstraints = false

こんな風に、書いてます。

これでanchor()を呼び出して使ってます。

使用例

let titleLabel : UILabel = {
    let label = UILabel()
    label.text = "hoge"
    label.font = UIFont.boldSystemFont(ofSize: 14)
    return label
}()

override func viewDidLoad() {
    super.viewDidLoad()
    view.addSubview(titleLabel)
  titleLabel.anchor(top: view.safeAreaLayoutGuide.topAnchor,
            right: view.safeAreaLayoutGuide.rightAnchor,
            paddingTop: 50, paddingRight: 147, width: 100, height: 56)
  titleLabel.layer.cornerRadius = 56/2
}

みたいな感じで書いてあげればおそらくかけるのかなと思います。

image.png

こんな感じにできます。

レイアウトのコードは

https://github.com/tiking76/chatapp/blob/master/chatApp/Utiles/Extentions.swift

に置いてあります。

もっと詳しく知りたいよ!!ってひと

↓のスライドをみてね!

LTで登壇した時のスライド

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

Udemyのswiftコース(英語のやつ)でiOSアプリ開発を学ぶ その2

勉強するコースはこれです:point_down:
https://www.udemy.com/course/ios-13-app-development-bootcamp/

進捗

セクション2まで終了(全部で36セクション)
Xcodeの使い方など開発環境についてのお話がメインだった

最初の感想

<Udemyのコースについて>
- 本当に手取り足取り教えてくれるので、知識ゼロからでも学べそうなレベル
- 英語のレベルは、アメリカの小学校高学年~中学校くらいのレベルな気がする(小難しい単語は出ない)
- 先生(Dr. Angela Yu)の英語は、少しイギリス英語アクセントっぽいが非常に聞き取りやすい(アジア人にとって分かりやすい英語だと思う)
- やや説明するスピードが速いが、英語字幕があるので大丈夫そう
- 先生の英語はとても勉強になるし、ちゃんとした授業形式になっているので学びにつながる
- これやっとけばセブ島留学とか行く必要ないんじゃないかとすら思う。。

<iOS開発について>
- レイヤーになっているところとかPhotoshopっぽい
- やはりwindowsとは違うので、操作には慣れが必要になる
- windowsよりも開発環境が全体的にデザインがポップな印象、ちょっとテンション上がる

今後の予定

特に問題ないので、この調子で進めていく。

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

ARKitでバーチャル背景をつくる

例の機能をつくる

argif.gif
バーチャル背景は、ZoomやTikTokで見かける機能です。
それは、人物の背景を画像や動画に変えます。

ARKitを使うことで、かんたんにバーチャル背景をつくることができます。
(ARSCNViewを使います。)

ViewController.swift
@IBOutlet var sceneView: ARSCNView!
var virtualBackgroundNode = SCNNode()

人物だけを手前に残す(People Occlution)

背景をバーチャルにするために、人物だけを手前にのこす必要があります。
ARKitのPeople Occlutionという機能を使うことで、人物をARコンテンツの手前に表示できます。
これを設定しないと、ARコンテンツが人物をオーバーラップしてしまいます。

Occlutionなし
スクリーンショット 2020-07-21 0.16.29.png

Occlutionあり
スクリーンショット 2020-07-21 0.17.24.png

ARSessionにPeople Occlutionを設定します。

ViewController.swift
 let config = ARFaceTrackingConfiguration()                    
      if ARFaceTrackingConfiguration.supportsFrameSemantics(.personSegmentation) {
            config.frameSemantics.insert(.personSegmentation)
      } else {
            presentAlert(NSLocalizedString("この端末/OSではピープルオクルージョンを利用できません", comment: ""))
      }
 sceneView.session.run(config, options: [])

バーチャル背景を置く(SCNNode)

バーチャル背景をつくる

板マテリアルでSCNNodeをつくり、その表面に画像や動画をコンテンツとして貼ります。

貼り付けるコンテンツはSpriteKitのSKSceneとしてつくります。
動画を貼るためにこうしました、と思う。

画像の場合

ViewController.swift
//画像の縦横比が歪まないように調整しています
let width = uiImage.size.width
let height = uiImage.size.height
let mediaAspectRatio = Double(width / height)
let cgImage = uiImage.cgImage
let newUiImage = UIImage(cgImage: cgImage!, scale: 1.0, orientation: .up)

let skScene = SKScene(size: CGSize(width: 1000  * mediaAspectRatio, height: 1000))
let texture = SKTexture(image:newUiImage)
let skNode = SKSpriteNode(texture:texture)
skNode.position = CGPoint(x: skScene.size.width / 2.0, y: skScene.size.height / 2.0)
skNode.size = skScene.size
skNode.yScale = -1.0
skScene.addChild(skNode)

動画の場合

ViewController.swift
let avPlayer = AVPlayer(url: videoUrl)
//画像の縦横比が歪まないように調整しています
var mediaAspectRatio: Double!
guard let track = AVURLAsset(url: url).tracks(withMediaType: AVMediaType.video).first else { return (nil,nil) }
let size = track.naturalSize.applying(track.preferredTransform)
let resolution = (CGSize(width: abs(size.width), height:abs(size.height)),track.preferredTransform)
let width = resolution.0?.width
let height = resolution.0?.height
mediaAspectRatio = Double(width! / height! )
avPlayer.actionAtItemEnd = AVPlayer.ActionAtItemEnd.none;
NotificationCenter.default.addObserver(self,
                                       selector: #selector(ViewController.didPlayToEnd),
                                       name: NSNotification.Name("AVPlayerItemDidPlayToEndTimeNotification"),
                                       object: avPlayer.currentItem)
let skScene = SKScene(size: CGSize(width: 1000 * mediaAspectRatio, height: 1000))
if resolution.1?.b != 0{
    skScene.size = CGSize(width: 1000, height: 1000 )
    skScene.zRotation = 1.5708
} else if resolution.1?.a != 1.0 {
    skScene.zRotation = 1.5708 * 2
}

let skNode = SKVideoNode(avPlayer: avPlayer)
skNode.position = CGPoint(x: skScene.size.width / 2.0, y: skScene.size.height / 2.0)
skNode.size = skScene.size
skNode.yScale = -1.0
if resolution.1?.b != 0{
     skNode.zRotation = 1.5708
} else if resolution.1?.a != 1.0 {
     skNode.zRotation = 1.5708 * 2
}
skNode.play()
skScene.addChild(skNode)

ARKit(ARSCNView)で扱えるSCNNodeにコンテンツを貼り付けます。

ViewController.swift
virtualBackgroundNode.geometry = SCNPlane(width: size, height: size)
let material = SCNMaterial()
material.diffuse.contents = skScene
virtualBackgroundNode.geometry?.materials = [material]
virtualBackgroundNode.scale = SCNVector3(1.7  * mediaAspectRatio, 1.7, 1)
sceneView.scene.rootNode.addChildNode(node)

バーチャル背景をカメラの前に置く

ViewController.swift
let cameraPosition = sceneView.pointOfView?.scale
let position = SCNVector3(cameraPosition!.x, cameraPosition!.y, cameraPosition!.z - 10)
virtualBackgroundNode.position = position

バーチャル背景の位置を固定する

このままだと、端末カメラを動かすと、バーチャル背景がその置かれた「場所」にステイしてしまいます。
スクリーンショット 2020-07-21 0.24.39.png

バーチャル背景がカメラを追いかけて、ずっとその前に居続けるようにさせます。
スクリーンショット 2020-07-21 0.29.53.png

ARSCNViewのrendererメソッドでバーチャル背景SCNNodeにカメラ前のポジションを伝え続けます。

ViewController.swift
var frontOfCamera = SCNVector3()
func renderer(_ renderer: SCNSceneRenderer, updateAtTime time: TimeInterval) {
     let position = SCNVector3(x: 0, y: 0, z: -10.0) // ノードの位置は、左右:0m 上下:0m 奥に50cm
        if let camera = sceneView.pointOfView {
            virtualBackgroundNode.position = camera.convertPosition(position, to: nil)
            virtualBackgroundNode.eulerAngles = camera.eulerAngles
        }
    }

バーチャル背景の大きさを変えられるようにする

このままだと、背景のサイズが画面のフレームにあっているかわかりません。
なので、ユーザーがピンチ・ジェスチュアで背景の大きさをちょうど良いサイズに変えられるようにします。

ViewController.swift
var lastGestureScale:Float = 1

@objc func scenePinchGesture(_ recognizer: UIPinchGestureRecognizer) {
     switch recognizer.state {
     case .began:
         let logation = recognizer.location(in: sceneView)
         let hitResults = sceneView.hitTest(logation, options: [SCNHitTestOption.ignoreHiddenNodes:true])
         if hitResults.count > 0 {
             guard let node = hitResults.first?.node else {return}
             materialNode = node
         }
         lastGestureScale = 1
     case .changed:
         let newGestureScale: Float = Float(recognizer.scale)

         let diff = newGestureScale - lastGestureScale

         let currentScale = virtualBackgroundNode.scale

         virtualBackgroundNode.scale = SCNVector3Make(
             currentScale.x * (1 + diff),
             currentScale.y * (1 + diff),
             currentScale.z * (1 + diff)
         )
         lastGestureScale = newGestureScale
     default :break
     }
}
ViewController.swift
let pinch = UIPinchGestureRecognizer(
            target: self,
            action: #selector(type(of: self).scenePinchGesture(_:))
        )

pinch.delegate = self
sceneView.addGestureRecognizer(pinch)

ピンチ・ジェスチュアでSCNNodeを操作する方法は、天才プログラマーK-Boyさんのコードをお借りしました。ありがとうございます。

実際のアプリはこちら

ARCamera.
AppStore:https://apps.apple.com/us/app/id1488699740

GitHub:https://github.com/john-rocky/ARCamera

ぼくのTwitterをフォローしてください。お願いします。
https://twitter.com/JackdeS11

お仕事のご依頼をこのメールにお願いします。
rockyshikoku@gmail.com

あと、Looks Good For Me(わるくないね)、押してください。
        ここです ↓
チャオ?!

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

viewWithTagで簡単に対象オブジェクトを呼び出して処理できる。

Swift初心者です。
こういうケースを想定してみてください

  • ボタン(UIButton)がmain.storyboardにボタンA〜ボタンHまで8個配置されている
  • ボタンAを押されたらボタンC,E,Gに対する動作を指定する
  • ボタンBを押されたらボタンD,F,Hに対する動作を指定する

Swift初心者の私はこう書きました。
どうも、UIButton型の配列を作るのがうまくできなかったので、まずはボタンC〜Hを操作できるよう、ViewController.swift上で変数を宣言しました。

@IBOutlet weak var btnC: UIButton!
@IBOutlet weak var btnD: UIButton!
@IBOutlet weak var btnE: UIButton!
@IBOutlet weak var btnF: UIButton!
@IBOutlet weak var btnG: UIButton!
@IBOutlet weak var btnH: UIButton!

そのうえで、

@IBAction func play(sender :UIButton){ 
  switch sender.tag {
    case 1:
     btnC.imageView?.startAnimating()
     btnE.imageView?.startAnimating()
     btnG.imageView?.startAnimating()
    default:
     btnD.imageView?.startAnimating()
     btnF.imageView?.startAnimating()
     btnH.imageView?.startAnimating()

といったコードを書いていきました・・・

もっといい方法があるんだろうな、と思いながら書いていましたが、もちろんありました。
viewクラスの中にあるviewWithTagというメソッド。

let button = self.view.viewWithTag(tag) as! UIButton

main.storyboard上に、Viewというエリアがあり、ここにタグを入れられることは知ってました。
スクリーンショット 2020-07-20 23.42.30.png

このタグの番号を持つボタンを取得してくれるというメソッドが、viewWithTagメソッド。
これを使えば、掲題の課題は下記のようにして記述ができる

    @IBAction func play(sender :UIButton){
        play(tag: sender.tag)
    }

    @IBAction func playThreeOdd(sender: UIButton) {
        play(tag: 1)
        play(tag: 3)
        play(tag: 5)
    }

    @IBAction func playThreeEven(sender: UIButton) {
        play(tag: 2)
        play(tag: 4)
        play(tag: 6)
    }

としてあげれば、あとはplayメソッドの引数をtag番号にして、viewWithTagメソッドでそのボタンを取得し、操作を書いてあげるだけ。

    func play(tag: Int){
        let button = self.view.viewWithTag(tag) as! UIButton
        button.imageView?.startAnimating()
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む