20210610のSwiftに関する記事は8件です。

【Swift】MoyaでエンドポイントのURLを取得する

どういうことか 例えばAPIを叩いてみたはいいけど 404 が返ってくるとかで検証したい。 叩く場所は任意。 provider.request(.articles) { (result) in switch result { case .success(let response): print("コレ", response.request?.url) // 以下略 おわり(´・ω・`)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Swift】AVFoundationを使用して動画撮影機能を実装

AVFoundationとは AVFoundationとは、静止画・音声・動画などのメディアの作成・再生・編集を行うことの出来るフレームワークです。 AVFoundationは、大まかに下記の3つの構成になっています。 ・デバイス(AVCaptureDevice) ・セッション(AVCaptureSession) ・出力(AVCaptureOutput) AVCaptureDeviceクラスで取得した入力テータを、AVCaptureDeviceInputクラスを用いてAVCaptureSessionに渡します。AVCaptureSessionクラスから出力されるデータは、AVCaptureOutputクラスによって、静止画、音声ファイル、動画ファイル、動画フレームデータ、メタデータなど様々な形式で出力されます。 サンプルの挙動 ボタンタッチで録画開始→もう一度ボタンタッチで録画終了→シェア コード import UIKit import AVFoundation final class ViewController: UIViewController { let captureSession = AVCaptureSession() let fileOutput = AVCaptureMovieFileOutput() var recordButton: UIButton! override func viewDidLoad() { super.viewDidLoad() self.view.backgroundColor = .black self.setUpCamera() } // デバイスの設定 private func setUpCamera() { // デバイスの初期化 let videoDevice: AVCaptureDevice? = AVCaptureDevice.default(for: AVMediaType.video) let audioDevice: AVCaptureDevice? = AVCaptureDevice.default(for: AVMediaType.audio) //ビデオの画質 captureSession.sessionPreset = AVCaptureSession.Preset.high // ビデオのインプット設定 let videoInput: AVCaptureDeviceInput = try! AVCaptureDeviceInput(device: videoDevice!) captureSession.addInput(videoInput) // 音声のインプット設定 let audioInput = try! AVCaptureDeviceInput(device: audioDevice!) captureSession.addInput(audioInput) captureSession.addOutput(fileOutput) captureSession.startRunning() // ビデオ表示 let videoLayer : AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer(session: captureSession) videoLayer.frame = self.view.bounds videoLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill self.view.layer.addSublayer(videoLayer) // 録画ボタン self.recordButton = UIButton(frame: CGRect(x: 0, y: 0, width: 80, height: 80)) self.recordButton.backgroundColor = .white self.recordButton.layer.masksToBounds = true self.recordButton.layer.cornerRadius = 80 / 2 self.recordButton.layer.position = CGPoint(x: self.view.bounds.width / 2, y:self.view.bounds.height - 120) self.recordButton.addTarget(self, action: #selector(self.tappedRecordButton(sender:)), for: .touchUpInside) self.view.addSubview(recordButton) } @objc func tappedRecordButton(sender: UIButton) { if self.fileOutput.isRecording { // 録画終了 fileOutput.stopRecording() self.recordButton.backgroundColor = .white } else { // 録画開始 let tempDirectory: URL = URL(fileURLWithPath: NSTemporaryDirectory()) let fileURL: URL = tempDirectory.appendingPathComponent("sampletemp") fileOutput.startRecording(to: fileURL, recordingDelegate: self) self.recordButton.backgroundColor = .red } } } extension ViewController: AVCaptureFileOutputRecordingDelegate { func fileOutput(_ output: AVCaptureFileOutput, didFinishRecordingTo outputFileURL: URL, from connections: [AVCaptureConnection], error: Error?) { //動画をシェア let activityItems = [outputFileURL as Any, "#SampleVideo"] as [Any] let activityController = UIActivityViewController(activityItems: activityItems, applicationActivities: nil) activityController.popoverPresentationController?.sourceView = self.view activityController.popoverPresentationController?.sourceRect = self.view.frame self.present(activityController, animated: true, completion: nil) } } 参考記事 https://qiita.com/t_okkan/items/f2ba9b7009b49fc2e30a https://dev.classmethod.jp/articles/ios-avfoundation/ https://superhahnah.com/swift-avcapture-settings/
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

RxSwiftのCold・Hotについて理解を深める

はじめに 備忘録としてQiitaに残します。 Cold・Hotを理解する前に ColdもHotもObservableの性質のことを指します。 ・subjectはHotである subjectは全てHotなObservableに該当します。 ・OperatorはColdである 各種Operatorは内部でObservableを返しますがColdなObservableを返します。 ※もちろん、例外もあります。 ColdなObservableの性質 ①subscribeされるまで動作しない ColdなObservableはsubscribeしないと動作しない性質を持っています。 // sequence(Cold) → map(Cold) Observable.of("event") .map { text -> String in print(text) return text } // 何も出力されない subscribeすると出力される。 Observable.of("event") .map { text -> String in print(text) return text } .subscribe() .disposed(by: disposeBag) // event ②分岐ができない これは説明するより、以下のコードで確認した方が分かりやすいです。 // textField(Hot) -> map(Cold) let observable = textField.rx.text.filterNil().map { text -> String in print("\(text.count)") return String(describing: text.count) } observable.bind(to: label1.rx.text).disposed(by: disposeBag) observable.bind(to: label2.rx.text).disposed(by: disposeBag) 上記のコードではfilterNil( )でnilを除外し、TextFieldに入力された値をmapで加工して2つのLabelに反映しています。 出力結果はどうなるでしょうか? 0 0 1 1 2 2 文字を入力する度に、map内の処理が2回呼ばれました。 ColdなObservableでは、2つのオブジェクトに対してsubscribeすると 別々の2本のストリームが流れます。 1.Hot -> Cold -> Observer // 1つしかObserverをsuscribe出来ないので余分なストリームが流れてしまう 2.Hot -> Cold -> Observer つまり、ColdなObservableでは1つのObserverしかsubscribeできないという事になります。 HotなObservableの性質 ①前のColdを稼働させる 例えば以下を実行したとします。 var behaviorSubject = BehaviorSubject<String>(value: "value") // subject(Hot) -> map(Cold) behaviorSubject.map { text -> String in print(text) return text } // 何も出力されない ColdなObservableの性質で述べたように何も起きません。 subscribeされるまで動作しないからですね。 ではHotなObservableに変換してみましょう。 冒頭でも述べたように各種OperatorはColdなObservableを返します。 ですがHot変換オペレータと呼ばれるOperatorを使用すればHotなObservableに変換することが出来ます。 今回は、その中の1つであるPublish()を使ってみたいと思います。 var behaviorSubject = BehaviorSubject<String>(value: "value") // subject(Hot) -> map(Cold) -> publish(Hot) behaviorSubject.map { text -> String in print(text) return text } .publish() // Hot変換する .connect() .disposed(by: disposeBag) // value valueと表示されました。 subscribeしていないのに出力されましたね。 これは何故かというと、HotなObservableでは内部で 前のColdなObservableをsubscribeしているからなんです。 ②分岐できる HotObservableは分岐が出来ます。 つまり、Observerを複数subscribeする事ができるという意味です。 では、先ほどのコードをHot変換して分岐してみましょう。 // textField(Hot) -> map(Cold) -> publish(Hot) let observable = textField.rx.text.filterNil().map { text -> String in print("\(text.count)") return String(describing: text.count) } .publish() observable.bind(to: label1.rx.text).disposed(by: disposeBag) observable.bind(to: label2.rx.text).disposed(by: disposeBag) observable.connect().disposed(by: disposeBag) 結果として以下のようになりました。 0 1 2 文字を入力する度に、map内の処理が1回しか呼ばれていない事がわかりますね。 ColdなObservableではObserverを1つしかsubscribe出来ないのに対し、HotObservableでは複数のObserverをsubscribe出来るので余分にストリームが流れることはありません。 1. Hot -> Cold -> Hot -> Observer | // 分岐 -> Observer おわりに RxSwiftは難しいということが理解できました。 正直合っているか分からないので間違っている部分や疑問に思うところがあれば コメントして下さると有り難いです。 次回はもっとHotなObservableについて理解を深めたいと思います。 参考資料 今日こそ理解するHot / Cold RxSwift + RxCocoaに関する基礎知識 RxSwiftについてようやく理解できてきたのでまとめることにした(4)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

メソッドの命名について考えてみた(Swift)

はじめに Twitterで気になったツイートを引用RTしたら、リプでいろいろ意見を頂きました。 非常に興味深かったので、まとめてみることにします。 updateAge(_ age: Int) vs update(age: Int) updateAge(_ age: Int) と update(age: Int) メソッド、どちらの名前のほうがいいと思いますか? 少しだけ考えてみてください。 正解は… . . . . . . . . . . メソッドの処理によって異なる だと思います。 updateAge(_ age: Int) updateAge(_ age: Int) というメソッドを見て、どのような処理を想像するでしょうか? 私は「 年齢のプロパティを更新する 」ことを想像しました。 コードに落とし込むとこんな感じです。 FooCollectionViewCell.swift final class FooCollectionViewCell: UICollectionViewCell { private var age: Int = 0 func updateAge(_ age: Int) { self.age = age } } 呼び出しはこのようになります。 cell.updateAge(31) // `cell.age` が `31` に更新される 同じ処理を想像した人が多いと思っています。 余談ですがSwiftは引数にラベルを付けることができるので、 updateAge(_ age: Int) より updateAge(to age: Int) のほうが英語で意味が読み下せて、より処理を想像しやすくなります。 cell.updateAge(to: 31) // 「update age to 31」で意味が通る ただ今回のようにプロパティにセットするだけなら setAge(_ age: Int) が最も処理を想像しやすいと思います。 cell.setAge(31) // ほとんどの人が `age` というプロパティに値をセットするとわかると思う update(age: Int) 続いて update(age: Int) です。 updateAge(to age: Int) よりも短く、意味も同じように捉えられそうですが、どうでしょうか。 実はよく考えると意味が異なることに気づきます。 先に呼び出し側のコードから見てみましょう。 cell.update(age: 31) age はセルを更新するために使う要素のひとつに過ぎず「 年齢を使ってセルを更新する 」ことが想像できると思います。 コードに落とし込むとこんな感じです。 FooCollectionViewCell.swift final class FooCollectionViewCell: UICollectionViewCell { @IBOutlet private weak var birthYearLabel: UILabel! func update(age: Int) { birthYearLabel.text = convertAgetoBirthYearString(31) } private func convertAgetoBirthYearString(_ age: Int) -> String { // ... } } あくまで例ですが、 age というプロパティを持たず、年齢から生まれた年を算出してラベルに表示する、というような処理が想像できます。 余談ですが、私はセルを初期化するメソッドは setup() という名前にしています。 cell.setup(age: 31) 参考: https://github.com/uhooi/UhooiPicBook/blob/v1.4.0/UhooiPicBook/Modules/MonsterList/Views/MonsterCollectionViewCell.swift#L38 どこかで見たのをマネしたと思うのですが、どこで見たかは覚えていません。 おわりに 他にも「 updateAge(_ age: Int) はObjective-C感がある」「API Design Guidelinesに従っていればこだわらない」などの意見がありました。 他にも意見があればコメントなどで教えていただけると嬉しいです 参考リンク https://twitter.com/___freddi___/status/1402784455899578372?s=20 https://twitter.com/terrypy6/status/1402834977771855872?s=20 https://twitter.com/macneko_ayu/status/1402800940084711430?s=20https://twitter.com/_take_hito_/status/1402810071835238402?s=20 Swift.org - API Design Guidelines
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftUI: 簡単にフォント名を指定する方法!

SwiftUIのフォント SwiftUIにおいて文字のフォントを指定したいときは、以下のようにフォント名を文字列として入力します。 SampleText.swift import SwiftUI struct ContentView: View { var body: some View { Text("Hello,World!") .font(.custom("AcademyEngravedLetPlain", size: 100)) } } ただ、毎回コピペしたり正しい名前になっているか確認するのは面倒ですし、名前を間違えてしまう可能性もあります。 また、デザインに合うフォントを色々試してみたいときにも切り替えが早い方が効率的です。 改善策 以下のようなextensionを用いると比較的ラクにフォント名の指定ができるようになります。例のように「タイトル用」などとコメントを残しておくと後で使うときに迷わずに済みます。 FontExtension.swift extension Font { enum Keys: String { case AcademyEngravedLetPlain case AlNile ///タイトル用 case Damascus } static func namedFont(_ name: Keys, size: CGFloat) -> Font { return Font.custom(name.rawValue, size: size) } } 使用例 SampleText.swift import SwiftUI struct ContentView: View { var body: some View { Text("Hello,World!") .font(.namedFont(.AcademyEngravedLetPlain, size: 100)) } } コードの見た目や長さはほとんど同じですが、Xcodeならコード補完で正確にフォント名を指定できるようになります。 余談 同様の意図で実装の仕方を調べるとカスタムModifierを用いたものが紹介されていることが多いです。 そちらでも問題はないのですが、今回紹介したextensionを用いる方法だとフォントに対して変更を加えたことが明示的になるので、多人数開発のときには少しだけ便利かな...と思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Unity]TextureをSwiftのNativePluginから送る

Unity に Texture を Swift の Native Plugin から送る UnityのTextureのポインタをSwiftだけで作ったNative Pluginに渡して処理するの逆の話です ネイティブで作成した Texture を Unity へ送って表示させたい場合があります 例えば、macOS のスクリーンキャプチャの映像を Unity で表示させる事ができます macOS や iOS では、MTLTexture から Texture2D.CreateExternalTexture を使って Texture2D を取得可能です 今回のサンプル リポジトリはこちらです Swift だけで、Unity の iOS の Native Plugin を作る の内容をベースにしています macOS で、スクリーンキャプチャした結果を Cube のテクスチャに貼っています unityでmacのデスクトップキャプチャできた! pic.twitter.com/x2AkZ6z6zF— ふじき (@fzkqi) June 10, 2021 Texture2D.CreateExternalTexture Texture2D.CreateExternalTexture を使うことで、ネイティブのテクスチャオブジェクトから Unity テクスチャを作成できます iOS や macOS の場合は、ネイティブのテクスチャオブジェクトは MTLTexture です そこで、MTLTexture のポインタ、テクスチャの高さと幅を用意します Unityの実装 Native Plugin の定義 width と height のポインタを渡して、ネイティブに代入させます 戻り値として、MTLTexture のポインタを IntPtr として受け取ります [DllImport("mcDesktopCapture")] private static extern IntPtr mcDesktopCapture_getCurrentFrame2(ref int width, ref int height); Texture を取得する 取得したポインタから、Texture2D.CreateExternalTexture を使って Texture を得て設定しています int w = -1; int h = -1; IntPtr texturePtr = mcDesktopCapture_getCurrentFrame2(ref w, ref h); Texture2D texture = Texture2D.CreateExternalTexture(w, h, TextureFormat.ARGB32, false, false, texturePtr); Renderer m_Renderer = GetComponent<Renderer>(); m_Renderer.material.SetTexture("_MainTex", texture); Swift の実装 @_cdecl を付けて、C# で作成した関数を実装します ref を UnsafeMutablePointer として、IntPtr を UnsafeMutableRawPointer として読み替えます width と height にテクスチャの大きさを設定します retain してテクスチャのポインタを返します @_cdecl("mcDesktopCapture_getCurrentFrame2") public func mcDesktopCapture_getCurrentFrame2(_ width: UnsafeMutablePointer<Int64>, _ height: UnsafeMutablePointer<Int64>) -> UnsafeMutableRawPointer { let texture: MTLTexture = getTexture() width.initialize(to: Int64(texture.width)) height.initialize(to: Int64(texture.height)) return Unmanaged.passRetained(texture).toOpaque() } 破棄が必要な場合は、takeRetainedValue を使います @_cdecl("mcDesktopCapture_clearFrame") public func mcDesktopCapture_clearFrame(_ texturePtr: UnsafeMutableRawPointer) { _ = Unmanaged<MTLTexture>.fromOpaque(texturePtr).takeRetainedValue() } ライブラリの出力と設定 こちらの記事の Framework でビルドする の章以降と同じ手順で実行ビルド可能です 作成した ライブラリ を Unity のプロジェクトにコピーして、Texture を取得できていることを確認します おわりに Texture2D.CreateExternalTexture を使うことで、Swift だけで作ったライブラリから、Unity へ Texture を送る仕組みを実装できました CreateExternalTexture を関連する関数に、UpdateExternalTexture があり、Texture の作成だけでなく、更新も可能です Swift から Unity に Texture を送る仕組みは、応用の幅が広いので、活用して行きたいです
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

クロージャ (Closure) is 何

なぜこの記事を書こうと思ったか Swiftの勉強をしているときに「クロージャ」という単語が出てきてググっていたのですが,理解するまでに結構時間がかかってしまいました.クロージャを理解しにくい要因は,言葉の定義と具体的な実装の間をつなぐ説明が足りていないところにあると思ったので,この記事ではその間を埋めることを試みたいと思います. 具体例 まず一番わかりやすかったクロージャの例を出します. example.swift // func 関数名(外部引数名 引数: 型) -> 返り値の型 { // 処理内容 // } func makeIncrementer(amount: Int) -> () -> Int { var runningTotal = 0 func incrementer() -> Int { runningTotal += amount return runningTotal } return incrementer } let incrementByOne = makeIncrementer(amount: 1) incrementByOne() // 1 incrementByOne() // 2 makeIncrementer() は「Int型を返す関数を返す関数」です.そして,その「Int型を返す関数」は incrementer() で makeIncrementer() の内部で定義されています.incrementer() は自身が定義されているスコープにある変数 runningTotal を参照してそれに amount を加えるという処理を行います. 最後に,実際にmakeIncrementer()を引数 amount を1にして呼び出して,帰ってきた関数を定数 incrementByOne に代入します.incrementByOne を2度呼び出してみます.すると一度目の実行結果は1で二度目の実行結果は2が返ってきます. なぜインクリメントできるのか どうして一度目と二度目の実行結果が両方とも 1 にはならないのでしょうか.言い換えると,二回目に呼ばれたときに前回の値がどうして保存されているのでしょう.これは,そもそもmakeIncrementerの内部にあるincrementerがクロージャとして定義されるからです. クロージャとはなにか クロージャとは関数が自身の定義されているコンテクストに含まれる変数や定数への参照を取り込んで保持しておく機能です.先程の例では,incrementer()の定義に含まれているrunningTotalへの参照が保持されています. クロージャはwikipediaでは次のように説明されています クロージャ(クロージャー、英語: closure)、関数閉包はプログラミング言語における関数オブジェクトの一種。いくつかの言語ではラムダ式や無名関数にて利用可能な機能・概念である。引数以外の変数を実行時の環境ではなく、自身が定義された環境(静的スコープ)において解決することを特徴とする。関数とそれを評価する環境のペアであるともいえる この値を保持しておくこと自体は「キャプチャ(capture)」するといったりします. なぜクロージャが理解しにくいか 以上の説明でクロージャの概念は大体掴めたかと思います.ここからはクロージャを理解しにくくなっている原因について,僕の意見を書きたいと思います. クロージャ式だけがクロージャではない Swiftにはクロージャ式(Closure Expression)というものがありクロージャを簡潔に記述できます.他言語でもラムダ式や無名関数を使ったことがあればその便利さがわかるかと思います.Swiftのクロージャ式では関数名を省略できたり,returnを省略したりできます. example_of_closure_expression.swift // {(引数名1: 型, 引数名2: 型...) -> 返り値の型 in // 処理内容 // } let incrementByOne = { (x: Int) -> Int in x + 1 } incrementByOne(2) // 3 上のようなクロージャ式のことを指して,「これがクロージャです」と説明されていることが稀にあります.下で説明するようにswiftで関数を書いた場合はクロージャの要件を満たしていますから,その説明は間違っていません.間違ってはいませんがミスリードをしていると思います.クロージャとクロージャ式は別概念です.関数で書いてもクロージャ式で書いてもクロージャはクロージャです. キャプチャする値がなくてもクロージャ クロージャは言い換えると (1) 関数と (2) 関数の定義に必要だった変数や定数(への参照)の2つの組み合わせです.しかしながら,ややこしいことに,(2) がない場合もクロージャの特別な場合として説明されます.例えば以下のようなものがクロージャの例としてswiftの公式ページで説明されています グローバル関数は、名前を持ち、値をキャプチャしないクロージャです。 ネストされた関数は、名前を持ち、包含する関数から値をキャプチャできるクロージャです。 クロージャ式は、周囲のコンテキストから値をキャプチャできる軽量な構文で記述された名前なしクロージャです。 2つめのネストされた関数は最初に出した例です.値をキャプチャできると言っているだけで,値をキャプチャしていなくてもクロージャの一種であることに注意してください. まとめ 「クロージャ」とは関数が自身の定義されているコンテクストに含まれる変数や定数をキャプチャする機能です.しかしクロージャ式で書かれた値をキャプチャしていない関数を例として挙げて説明されることがあります.この定義と具体例の乖離がすべての混乱の原因になっていると思います. 僕も100%理解したわけではないですが,ググって得た情報を組み合わせて,わかったことを書き起こして置きました.なにか不明な点があればコメント等でご指摘いただけると助かります. 参考 What is a 'Closure'? - stackoverflow https://docs.swift.org/swift-book/LanguageGuide/Closures.html https://docs.swift.org/swift-book/LanguageGuide/Closures.html#ID103 なぜクロージャ(Closure)と言うのか? Swiftでクロージャを理解する クロージャまとめ(Swift )
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

UITextField に入力がある時だけボタンを押せるようにする

はじめに UITextField と UIButton を連動させて、テキストが入力されている時だけボタンを押せるようにする方法です。 ソースコード ソースコードを見るのが早いと思うので、ソースコードを抜粋 ボタンとテキスト @IBOutlet weak var someText: UITextView! @IBOutlet weak var execButton: UIButton! Delegateの指定 override func viewDidLoad() { super.viewDidLoad() someText.delegate = self } EditingChangedイベント @IBAction func someTextEditingChanged(_ sender: Any) { if someText.text?.count == 0 { execButton.isEnabled = false } else { execButton.isEnabled = true } } Editing Changed イベントでテキストの文字数を判定しボタンの Enable を設定する。 参考 UITextField関連で役立ちそうなリンクです。 【バリデーション 】TextFieldが未入力の場合はボタンを押させない [初心者向け]UITextFieldDelegate UITextFieldのイベント種類 UITextFieldを使用する時に必要なあれこれ
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む