- 投稿日:2020-08-09T23:23:18+09:00
【入門】iOS アプリ開発 #1
はじめに
新型コロナウィルス(COVID-19)の影響により、外出自粛が続いている。
どこへも出かけない夏休み、この機会に Mac を購入して iOS のプログラミングを勉強してみようと思う。
iPhone のアプリは Swift という言語で作られるようだ。アップルは Swift を「モダン、安全、高速、インタラクティブ」を特徴として謳っている。
これだけでは全くイメージつかないが、さぞかし簡単にアプリが開発できるに違いない。昔のゲームぐらいならば、さくっと作れて、軽快にサクサク動作するのであろう。
さらにタッチパネルやセンサーで操作できると面白そうだ。プログラミングすることよりも、何を作るか?どんな仕様するか?、企画や上流設計は悩みどころだ。
またキャラクターのデザインやサウンドなどを作るのも骨が折れる。色々調べてみるとパックマンの仕様書が公開されている。
これをもとに作ればプログラミングに専念できそうだ。パックマンのゲーム仕様書
下記の人工知能学会誌に仕様書が公開されている。
一度、何かを作ってしまえばノウハウが溜まり、自分が作りたいものが簡単にできるはずなので、
まずはパックマンを作ってみようと思う。仕様書をよく読むと、思ったよりも複雑な仕様になっているが、
高級言語 Swift で書けば、1万行もコーディングせずに出来るのではないか。
- 投稿日:2020-08-09T22:14:53+09:00
10分でTableviewを作ってみた。
やること
Tableviewを作る
セルに表示させるアイテムを定義する
let items = ["リンゴ","バナナ","オレンジ"]Tableviewを作る
①Tableviewを作るコードを記入
let tableView: UITableView = { let tv = UITableView() return tv }()②
override func viewDidLoad
の中にtableviewを表示させるコードを記入view.addSubview(tableView)③tableviewのサイズを指定
tableView.frame.size = view.frame.sizeシミュレーター
tableviewのデリゲートのメソットの設定
①
override func viewDidLoad
の中にデリゲートのメソットを使えるようにするコードを記入tableView.delegate = self tableView.dataSource = selfこれでデリゲートのメソットが使えるようになりました。
②classの一番上のところに
private let cellId = "cellId"
と記入③一番下にデリゲート使うにあたって必ず必要なメソッドを記入
extension ViewController: UITableViewDelegate,UITableViewDataSource{ //セルを何個表示すさせるかのコード func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.items.count } //セルに何を表示させるかのコード func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) cell.textLabel?.text = self.items[indexPath.row] return cell } }④
override func viewDidLoad
の中にTableViewに上記のメソッドを反映させるコードを記入tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellId)タイトルをつける
①Main.storyboardに移動してViewControllerを選択し、
Editor
→Embedld
→NavigationController
を選択してNavigationControllerを設置する。
②ViewController.swiftに戻って
override func viewDidLoad
の中にタイトルを表示させるコードを入力するnavigationItem.title = "果物"シミュレーター
全体のコード
class ViewController: UIViewController { let items = ["リンゴ","バナナ","オレンジ"] private let cellId = "cellId" //tableviewを作っていく let tableView: UITableView = { let tv = UITableView() return tv }() override func viewDidLoad() { super.viewDidLoad() view.addSubview(tableView) tableView.frame.size = view.frame.size tableView.delegate = self tableView.dataSource = self tableView.register(UITableViewCell.self, forCellReuseIdentifier: cellId) navigationItem.title = "果物" } } extension ViewController: UITableViewDelegate,UITableViewDataSource{ func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self.items.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) cell.textLabel?.text = self.items[indexPath.row] return cell } }今回はほとんどコードでtableviewを表示させてみました!
- 投稿日:2020-08-09T19:48:40+09:00
Flutter の iOS 向けビルドでハマったメモ
Flutter の iOS 向けビルド備忘録
とりあえず解決したことを列挙するだけの備忘録
Undefined symbols for architecture arm64
Create Bridging Header
で解決した
- Why linker link static libraries with errors? iOS
- Error when 'flutter run': Undefined symbols for architecture arm64: #41900
- Flutter Pluginの利用でSwiftエラーが出る場合の対処方法
domain/default pair of ... does not exist
プロジェクトのルートにアセットを置いているとビルドできない模様。
hoge を assets/hoge に移動して。pubspec.yaml の該当箇所も直せばOK。
- 投稿日:2020-08-09T17:04:23+09:00
不要になったUserDefaultsキーの管理方法
UserDefaultsについて
「ある画面を表示したかどうか」などの情報をアプリに保存する場合、UserDefaultsを利用する機会が多いと思います。
UserDefaultsを利用して値を保存することに目を向けがちですが、アプリ開発では機能追加だけでなく、利用しなくなった機能を削除することももちろんあります。
その際に機能を削除する際に不要となるUserDefaultsのキーを適切に扱わなければ、同一のキーを利用して意図しない動作につながってしまうケースがありますこの記事では既存の不要となったキーの管理方法についてまとめ、新しい管理方法についてまとめたものとなります。
既存の管理方法
1. キーごと削除する
不要となったキーを削除するパターンです。もし別の機能開発で全く同じキー名が利用されるケースがあった場合、未然に防ぐことができなくなるためあまりおすすめはできません。
2. prefix / suffix
利用していないキー名にprefixやsuffixを加えることで管理するパターンです。
利用されていないキーが明示的にはなりますが、キー名が増えてくると行数が増え見通しが悪くなります。
また、Active Compilation Conditions
を利用して、Release
ビルドでは含めないなどを設定しないとdeprecatedなキーが成果物に含まれてしまいます。static let isHoge = "isHoge" // deprecated keys static let deprecated_isFoo = "isFoo" . . .3. コメント化
使用されなくなったキーをコメントで管理するパターンです。
筆者はこのパターン方法で管理したことはないですが、コメントで管理することで視認性が落ちるのが気になります。static let isHoge = "isHoge" // deprecated keys // static let isFoo = "isFoo" . . .他にもこんな風に管理しているなどがあれば教えていただけるとありがたいです
どのような管理方法が理想か
上記のような管理方法を洗い出し、以下の用件が満たせていれば理想だと考えました。
- どのキーが使用されなくなったかがわかる状態
- 使用されいているキーと不要なキーの管理が分離されている
- 既に利用していないキー名を再度利用しようとした場合、ビルド時にエラー検知
新しい管理方法
ドットファイルとスクリプトで管理するパターンを考えてみました。
- 使用しなくなったキー名を
.unused_userdefaults_key
で管理- buildPhaseでスクリプトを実行し、使用していないキーを利用する場合エラーを出力
1. ドットファイルを用意
使用しなくなったキー名をまとめた
.unused_userdefaults_key
を用意しますis_hoge is_foo2. スクリプトファイルを用意
キーをまとめたswiftファイルを引数として、
.unused_userdefaults_key
と一致するものが含まれていればエラーを出力するスクリプトを用意しますunused_userdefaults_key.sh#!/bin/sh file_path=$1 index_array=`cat -n ${file_path}| nl -nln | grep -f .unused_userdefaults_key | cut -d " " -f1` if (( ${#index_array[@]} )); then for i in ${index_array[@]} do echo "${file_path}:${i}:0: error: It's a key name that's already in use." done exit 1 fi3. Build Phasesに組み込み
unused_userdefaults_key.sh
をBuildPhasesで実行するようにします
動作GIF
SwiftyUserDefaultsを利用した場合の動作GIFです。
GitHubにもプロジェクトを公開しているので、興味がある人は確認してみてください。
GitHub: https://github.com/funzin/UnusedUserDefaultsKeyExmapleまとめ
不要となったUserDefaultsキーの管理方法についてまとめてみました。
削除したキーが復活して事故を防ぐには有効だと思いますので、ぜひ参考にしてもらえるとうれしいです。
- 投稿日:2020-08-09T16:35:05+09:00
【Swift】deinitではwillSet/didSetが呼ばれないというお話
結論
init/deinit内でプロパティに代入を行っても、willSet/didSetは呼ばれないので注意しましょう。
ただし、スーパークラスのプロパティのwillSet/didSetは呼ばれるようです。公式ガイドの注意書き
公式ガイド(The Swift Programming LanguageのProperties)に、以下のような注意書きがあります。
NOTE
The willSet and didSet observers of superclass properties are called when a property is set in a subclass initializer, after the superclass initializer has been called. They are not called while a class is setting its own properties, before the superclass initializer has been called.1注意
サブクラスのイニシャライザ内で、スーパークラスのイニシャライザが呼ばれた後に、プロパティがセットされたとき、スーパークラスのプロパティのwillSetとdidSetのオブザーバーが呼ばれます。スーパークラスのイニシャライザが呼ばれる以前に、自身のプロパティをセットしている間は呼ばれません。2なるほど。スーパークラスのプロパティのdidSet/willSetについては、サブクラス内のイニシャライザでスーパクラスのイニシャライザを呼んだ後であれば、機能するということらしいです。
クラス自身で定義したプロパティのdidSet/willSetについては、イニシャライザで呼ばれないのは当然だろということか、記述が見当たりませんでした。コードで確認
確かにスーパークラスのプロパティのdidSetについては呼ばれました。
class Animal { var age: Int { didSet { print(#function, age, oldValue) } } init(age: Int) { self.age = age // -> 呼ばれない } } class Tiger: Animal { var family: String { didSet { print(#function, family, oldValue) } } init(age: Int, family: String) { self.family = family // -> 呼ばれない super.init(age: age) // -> 呼ばれない self.age += 0 // -> didSetが呼ばれる!! self.family += "" // -> 呼ばれない } deinit { print(#function) age = 0 // -> 呼ばれる family = "" // -> 呼ばれない } }どういうときに注意すべき?
例えば、Timerをdeinitでinvalidateしたいときに、以下のような書き方をすると呼ばれないので注意しましょう。
class ViewController2: UIViewController { var timer: Timer? { willSet { timer?.invalidate() } } deinit { timer = nil // nilを代入してもwillSetは呼ばれない! // -> timer?.invalidate() } ... }参考
Swiftでdeinit時にメンバ変数(property)のdidSetが呼ばれない気がした
Can I use didSet in deinit?
[The Swift Programming Language - Properties](https://docs.swift.org/swift-book/LanguageGuide/Properties ↩
太字による強調は訳者による ↩
- 投稿日:2020-08-09T16:30:58+09:00
Functionに関して、まとめた。【Swift】
Functionの初歩。
func(関数)を学んだので、簡単におさらい。
Functionを何故使うのか。
amazonのような買い物アプリにて、
ユーザーの買い物カゴの合計金額を計算する箇所が5つあるとき、
5箇所で同じコードを書くのは無駄。
コード量が増えて、プログラム自体が複雑で読みにくくなる。
処理が1回しかないものでも、functionには利点あり。
functionにしてマトめる
と、コード量がかなり多いとき読みやすい。function名
を適切につけて、判別しやすい。パラメータと引数の違い
意思疎通にはそれほど困らないけど・・・
「引数 == パラメータ」ではない
- パラメータ (仮引数)は、関数に受け渡されるものの宣言
- 引数は、関数に渡した実際の値
// Funtion with parameters func declare(name: String) { print(name) } declare(name: "Shimura") // 呼び出し declare(name: "Ken") // 呼び出し「志村」と「けん」が引数で、nameがパラメータ。
パラメータと引数の違い因みに、「ひきすう」と読みます。
関数3パターン、おさらい。
with no parameters?
func declareName() { print("MyName") } declareName() // 呼び出しwith parameters?
func declare(name: String) { print(name) } declare(name: "Shimura") // 呼び出し declare(name: "Ken") // 呼び出しwith a return value?
func 一日の秒数() -> Int { return 24 * 60 * 60 } let 秒数 = 一日の秒数() // 呼び出し & 代入 (=インスタンス化) print("一日は\(秒数)秒!") // 一日は86400秒!with parameters and a return value?
func createFullName(firstName: String, lastName: String) -> String { return firstName + " " + lastName } //let fullName = createFullName(firstName: String, lastName: String) let fullName = createFullName(firstName: "Suzuki", lastName: "Ichiro") // 呼び出し & 代入 (=インスタンス化) print(fullName) // Suzuki Ichiroreturnを使った関数
一方的にただ呼び出す関数も便利ですが、(上記2つのコード??)
関数の中でいろいろな処理をさせて、その「結果」を貰いたいときがあります。
要するに、呼び出しに対する「返事」が欲しいときです。with a return value?
with parameters and a return value?
戻り値を持つ関数。
書き方は、こんな感じ。func 関数の名前() -> 戻り値の型 { // 実行する処理 return 戻り値 }具体例。
func 一日の秒数() -> Int { return 24 * 60 * 60 }でも、上記コードだけでは実行されない。
「関数の呼び出し」
を、変数or定数に代入
。(=インスタンス化)let seconds = 一日の秒数() // インスタンス化 print("一日は\(seconds)秒!") // 一日は86400秒!
「
\()
」の中に変数を入れると、その内容が埋め込まれます。Swiftでは『戻り値』のデータ型を指定する 必要があります。
『->
データ型』で指定。これがないとエラー。{}内でreturnが実行されると 関数内の処理は終了なので、
関数{}の中の、一番最後に書く。func 一日の秒数() -> Int { // 今回はInt型。(Integer: 整数) return 24 * 60 * 60 print("Hello World") // エラー。 Code after 'return' will never be executed } let seconds= 一日の秒数() print("一日は\(seconds)秒!") // 『\()』を使って、変数secondsを埋め込み。おしまい。
参考サイト
- 投稿日:2020-08-09T16:26:33+09:00
Functionに関して、まとめた。【Swift】
Functionの初歩。
func(関数)を学んだので、簡単におさらい。
Functionを何故使うのか。
amazonのような買い物アプリにて、
ユーザーの買い物カゴの合計金額を計算する箇所が5つあるとき、
5箇所で同じコードを書くのは無駄。
コード量が増えて、プログラム自体が複雑で読みにくくなる。
処理が1回しかないものでも、functionには利点あり。
functionにしてマトめる
と、コード量がかなり多いとき読みやすい。function名
を適切につけて、判別しやすい。パラメータと引数の違い
意思疎通にはそれほど困らないけど・・・
「引数 == パラメータ」ではない
- パラメータ (仮引数)は、関数に受け渡されるものの宣言
- 引数は、関数に渡した実際の値
// Funtion with parameters func declare(name: String) { print(name) } declare(name: "Shimura") // 呼び出し declare(name: "Ken") // 呼び出し「志村」と「けん」が引数で、nameがパラメータ。
パラメータと引数の違い因みに、「ひきすう」と読みます。
関数3パターン、おさらい。
with no parameters?
func declareName() { print("MyName") } declareName() // 呼び出しwith parameters?
func declare(name: String) { print(name) } declare(name: "Shimura") // 呼び出し declare(name: "Ken") // 呼び出しwith a return value?
func 一日の秒数() -> Int { return 24 * 60 * 60 } let 秒数 = 一日の秒数() // 呼び出し & 代入 print("一日は\(秒数)秒!") // 一日は86400秒!with parameters and a return value?
func createFullName(firstName: String, lastName: String) -> String { return firstName + " " + lastName } //let fullName = createFullName(firstName: String, lastName: String) let fullName = createFullName(firstName: "Suzuki", lastName: "Ichiro") // 呼び出し & 代入 print(fullName) // Suzuki Ichiroreturnを使った関数
一方的にただ呼び出す関数も便利ですが、(上記2つのコード??)
関数の中でいろいろな処理をさせて、その「結果」を貰いたいときがあります。
要するに、呼び出しに対する「返事」が欲しいときです。with a return value?
with parameters and a return value?
戻り値を持つ関数。
書き方は、こんな感じ。func 関数の名前() -> 戻り値の型 { // 実行する処理 return 戻り値 }具体例。
func 一日の秒数() -> Int { return 24 * 60 * 60 }でも、上記コードだけでは実行されない。
「関数の呼び出し」
を、変数or定数に代入
。let seconds = 一日の秒数() // 呼び出し & 代入 print("一日は\(seconds)秒!") // 一日は86400秒!
「
\()
」の中に変数を入れると、その内容が埋め込まれます。Swiftでは『戻り値』のデータ型を指定する 必要があります。
『->
データ型』で指定。これがないとエラー。{}内でreturnが実行されると 関数内の処理は終了なので、
関数{}の中の、一番最後に書く。func 一日の秒数() -> Int { // 今回はInt型。(Integer: 整数) return 24 * 60 * 60 print("Hello") // エラー。 Code after 'return' will never be executed } let seconds= 一日の秒数() print("一日は\(seconds)秒!") // 『\()』を使って、変数secondsを埋め込み。おしまい。
参考サイト
- 投稿日:2020-08-09T15:29:47+09:00
テクトロジーによる実践的組織構造学
今回の記事では、ソ連の革命家、医師、哲学者、小説作家であったアレクサンダーボグダノフが提唱したテクトロジーと呼ばれる実践的組織構造学について紹介する。
テクトロジーでは組織が安定、成長、破綻する環境、条件について詳細に解説し、安定した組織を生成する手法について解説している。
テクトロジーの概念を拝借し、創造的な組織創造の手法を紹介したいと思う。テクトロジーにおける組織の定義
テクトロジーでは、組織をオープンクローズ型の成長する有機体システムと定義している。
組織とは以下の要素で構成されている。
ビジョン・・・組織の目指すべき方向性。
経済・・・組織のボディ。巨大であるほど収容できる人の人数が増加する
金融・・・組織を循環する血液。
生産・・・もの、サービスを生産し、組織の経済を巨大化する手段。これらの有機的な要素が相互作用し、成長することで組織という有機体が構成されていると考える。
テクトロジーにおける成長する有機体システムとは
テクトロジーでは、組織をオープンクローズ型の成長する有機体システムと定義している。
テクトロジーにおける組織の定義を中国の陰陽論によって説明することができる。
陰陽論とは、原初は混沌(カオス)の状態であると考え、この混沌の中から光に満ちた明るい澄んだ気、すなわち陽の気が上昇して天となり、重く濁った暗黒の気、すなわち陰の気が下降して地となった。この二気の働きによって万物の事象を理解し、また将来までも予測しようというのが陰陽思想である。
組織が外部からエネルギーを取り入れ、出力するインプット、アウトプットの運動を陰陽論における陽の気と捉えることができる。
逆に組織内部に沈殿し、成長し、ヒエラルキーを形成する秩序生成を担う運動を陰陽論における陰の気と捉えることができる。テクトロジーにおける生産の定義
テクトロジーでは、組織における生産活動は以下の3つに分類されている。
人の生産・・・人に教育を施し、組織活動に従事する生産者を作成する
モノ、サービスの生産・・・外部から取得した資材を用いて、モノ、サービスの生産を行う。
アイデア・・モノ、サービスを生成するための知識、アイディアを作成する。
組織における生産活動を高めることで、組織の経済を成長させることができる。テクトロジーおける組織のフォーム(形態)について
現実の世界で、人が活動を行う場合、必ず外部からの影響、抵抗を受ける。
外部からの影響、抵抗を抑えるために、組織は環境に合わせた最適なフォーム(形態)を取る必要がある。
魚やイルカなど、魚と哺乳類で種族は異なるが、水の抵抗を抑えるために同様の流線形フォルムを取っている。
組織のフォーム(形態)は外部環境によって決定される。
外部環境からの抵抗を最小限にするために、外部との接触の最小化、不要な組織的機能の削除などが求められる。
最適なフォーム(形態)によって、組織は外部からの抵抗を減少させ、健全に成長することができる。テクトロジーにおける組織が不安定化する条件
テクトロジーにおける組織が不安定化する条件として以下の2点が挙げられる。
・外部からのエネルギー取得の減少・・外部から人、モノ、金の循環が減少することで組織のサイズ、経済を維持することできなくなる。
・ヒエラルキーシステムの固定化・・・ヒエラルキーシステムが巨大化し、組織が硬直化してしまう。
組織不安定化を回避する手法として以下の手段が有効とされている。
生産手段を研究、開発、更新を行い、組織の経済成長のスピードを増加させる。
組織が硬直化の原因になっているヒエラルキーシステムを解体し、適切なサイズに組み替える。まとめ
アレクサンダーボグダノフがテクトロジーに関するアイディアを発表した時期は1920年代である。
独学で組織が破綻する条件、環境を発見し、持続可能な成長のコンセプトを提唱したアレクサンダーボグダノフの先見性は恐るべきものである。
ソ連は軍事、IT、経済においてアメリカと張り合うことができた超大国だった。
ソ連時代に考えられたアイディア、思想などは現代においても見直されるべきものだと思われる。
- 投稿日:2020-08-09T14:56:42+09:00
[チュートリアル]アプリに画面遷移を追加する方法
この記事は前回の[チュートリアル]カウントアップアプリを作ってSwiftを触ってみようの続きです。
アプリの完成形はこちら
ソースコードはこちらから
https://github.com/techiro/CountUpAppForBeginners/tree/v1
画面遷移で抑えておきたいポイント
- 画面遷移先のViewController
- ViewControllerとプログラムコードとの繋がり
- Segueという概念
画面遷移先のViewController
画面遷移するために新しいViewControllerを追加
ボタンなどを追加したLibraryからViewController
を選択してドラッグ&ドロップ画面にViewControllerが追加されたらOK
ViewControllerとプログラムコードとの繋がり
1.画面から新しいViewControllerを選択
2.Identity inspectorを選択
新しく追加したViewControllerのCustom Classが空になっている
このままだと画面遷移を行えないので、新しいClassを定義しなければならない。
新しいClassを定義
Cocoa Touch Class
→Next
Class名
NextViewController
に変更Subclass of: UIViewControllerに変更
この項目はClassがどんな用途で使われるかを設定でき、初期段階で必要なプログラムをXcodeが書いてくれます。
例えばここでUITableViewController
を選ぶとテーブルを使うためのコードを自動で入力してくれます。入力を終えたら
Next
最後にファイルの保存場所を聞いてきます。
そのままCreate
を押してください
新しいViewControllerに新しいClassを紐づける
StoryBoardに戻って追加したViewControllerを選択
これで新しいViewControllerに新しいClassを紐づけることができました!
Segueという概念
Segueとは、storyboard上で画面遷移を表す部品です。
Segueは、画面遷移の方法と、どう画面遷移させるかを決めることができます。詳しく説明しているサイトを紹介
ボタンに対してSegueを作成することもできます。
例えば、「設定ボタンをタップしたら設定画面に遷移する。」を実現したい場合はノンコーディングで画面遷移を行うことができます。今回使用する方法はViewControllerからSegueを作成してコーディングを行って画面遷移を実装します。
Segueを作成
ViewControllerを選択→
⌃(control)
+ドラッグ&ドロップ
Manual Segueは
Present Modally
を選択
ViewControllerと新しいViewControllerがつながっていればOK
マークが違っていても画面遷移の見え方が変わるだけで、問題ないです。
Segueに名前をつける
Segueを選択→
Attribute inspector
→Identifire
→任意の名前をつける(今回はnext
)
SegueのPresentationをFullScreenに変更(任意)
遷移先のViewControllerに戻るボタンを設置
NextViewController
に戻るボタンを新しく設置します.
Main.storyboard
→+ボタン
→Button
を選択してNextViewController
ドラッグ&ドロップそれが完了したら、ボタンを選択してTitleをわかりやすい文字に変更してください。
ボタンを
NextViewController
クラスと紐づける
NextViewController
クラスにボタンを紐づける紐付けポイント
下準備は終了。画面遷移のコードを書いていく
画面遷移するためのコード
画面遷移をさせるためのコードはこれだけ
performSegue(withIdentifier: "next", sender: nil)これを画面遷移させたい場所に書くだけで画面遷移します。
試しにやってみる
前回の記事[初心者向け]カウントアップアプリを作ってSwiftを触ってみようで
countUpButton
を作成したので、
count
が2になったら画面遷移するようにコードを書いていきます。ViewController.swift内@IBAction func countUpButton(_ sender: Any) { //+ボタンを押すとラベルの文字をカウントアップ count = count + 1 //画面遷移 if count == 2 { performSegue(withIdentifier: "next", sender: nil) count = 0 } countLabel.text = String(count) //カウントにあわせて文字の色を変更 changeColor() }⌘ + Rで実行
画面遷移できました?
画面遷移から戻る方法
これもたった1行で完了します。
dismiss(animated: true, completion: nil)これを画面遷移させたい場所に書くだけで画面遷移します。
試しにやってみる
NextViewController.swift内@IBAction func backButton(_ sender: Any) { dismiss(animated: true, completion: nil) }⌘ + Rで実行
画面遷移から戻ることができました?
画面遷移を行いながら値を渡す方法
画面遷移が無事終わったら、画面遷移を行う際に値も一緒に渡してみましょう!
今回は一般的に使われている方法を紹介します。NextViewController.swiftに値を渡すための準備
- 画面に値を表示するためのLabelを設置(+ボタンからUIパーツLabelを選択→
NextViewController.swift
と紐づける。)- ViewControllerから値をもらうための変数
passdata
を定義- label.text = String(passdata)でLabelに値を反映
NextViewController.swift@IBOutlet weak var label: UILabel! var passdata = 0 override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. label.text = String(passdata) }
値を渡すためのメソッドを定義
prepare(for segue: UIStoryboardSegue, sender: Any?)
メソッドを定義ViewController.swiftoverride func prepare(for segue: UIStoryboardSegue, sender: Any?) { //引数segueの中に画面遷移に関する情報が含まれている。 if segue.identifier == "next" { //NextViewControllerに値を渡す処理を書く。 } }
performSegue(withIdentifier: "ID", sender: nil)
で画面遷移を行うと自動的に、
prepare(for segue: UIStoryboardSegue, sender: Any?)
メソッドが呼ばれます。これはSegueが実行されようとしていることをView Controllerに通知するメソッドです。
このメソッドを使用して、新しいViewControllerに値を渡すことができます。
注意点
このメソッドはSegueが実行されようとしていることを通知するので、複数のSegueから呼び出されます。
したがって、if segue.identifier == "next"{・・・}
のようにnext
のSegueからの情報かどうかを判定してから値を渡す処理を書く必要があります.
メソッド内に処理を書く
ViewController.swift@IBAction func countUpButton(_ sender: Any) { //中略 //画面遷移 performSegue(withIdentifier: "next", sender: nil) } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "next" { let nextVC = segue.destination as! NextViewController nextVC.passdata = count }⌘ + R で実行
performSegue(withIdentifier: "next", sender: nil)
を実行した時のcount
の値を渡すことができました!
今回やったこと
- 新しいViewControllerを作成して画面遷移を実装
- ViewControllerとプログラムコードとの繋がり
- Segueという概念の理解
以上でカウントアップアプリのチュートリアルは終わりです。
お疲れ様でした!次はこのアプリに動きをつける画面遷移を実装していきます。
Twitterで主にSwiftについてのツイートをしているのでのぞいてみてください!
今回はSwiftの言語についてあまり詳しく説明しませんでしたが、
ここでもっと詳しい記事を書いているのでSwiftに興味が湧いた方は参考にしてみてください!
- 投稿日:2020-08-09T14:56:42+09:00
[Swift]アプリに画面遷移を追加する方法
この記事は前回の[Swift]カウントアップアプリを作ってSwiftを触ってみようの続きです。
アプリの完成形はこちら
ソースコードはこちらから
https://github.com/techiro/CountUpAppForBeginners/tree/v1
画面遷移で抑えておきたいポイント
- 画面遷移先のViewController
- ViewControllerとプログラムコードとの繋がり
- Segueという概念
画面遷移先のViewController
画面遷移するために新しいViewControllerを追加
ボタンなどを追加したLibraryからViewController
を選択してドラッグ&ドロップ画面にViewControllerが追加されたらOK
ViewControllerとプログラムコードとの繋がり
1.画面から新しいViewControllerを選択
2.Identity inspectorを選択
新しく追加したViewControllerのCustom Classが空になっている
このままだと画面遷移を行えないので、新しいClassを定義しなければならない。
新しいClassを定義
Cocoa Touch Class
→Next
Class名
NextViewController
に変更Subclass of: UIViewControllerに変更
この項目はClassがどんな用途で使われるかを設定でき、初期段階で必要なプログラムをXcodeが書いてくれます。
例えばここでUITableViewController
を選ぶとテーブルを使うためのコードを自動で入力してくれます。入力を終えたら
Next
最後にファイルの保存場所を聞いてきます。
そのままCreate
を押してください
新しいViewControllerに新しいClassを紐づける
StoryBoardに戻って追加したViewControllerを選択
これで新しいViewControllerに新しいClassを紐づけることができました!
Segueという概念
Segueとは、storyboard上で画面遷移を表す部品です。
Segueは、画面遷移の方法と、どう画面遷移させるかを決めることができます。詳しく説明しているサイトを紹介
ボタンに対してSegueを作成することもできます。
例えば、「設定ボタンをタップしたら設定画面に遷移する。」を実現したい場合はノンコーディングで画面遷移を行うことができます。今回使用する方法はViewControllerからSegueを作成してコーディングを行って画面遷移を実装します。
Segueを作成
ViewControllerを選択→
⌃(control)
+ドラッグ&ドロップ
Manual Segueは
Present Modally
を選択
ViewControllerと新しいViewControllerがつながっていればOK
マークが違っていても画面遷移の見え方が変わるだけで、問題ないです。
Segueに名前をつける
Segueを選択→
Attribute inspector
→Identifire
→任意の名前をつける(今回はnext
)
SegueのPresentationをFullScreenに変更(任意)
遷移先のViewControllerに戻るボタンを設置
NextViewController
に戻るボタンを新しく設置します.
Main.storyboard
→+ボタン
→Button
を選択してNextViewController
ドラッグ&ドロップそれが完了したら、ボタンを選択してTitleをわかりやすい文字に変更してください。
ボタンを
NextViewController
クラスと紐づける
NextViewController
クラスにボタンを紐づける紐付けポイント
下準備は終了。画面遷移のコードを書いていく
画面遷移するためのコード
画面遷移をさせるためのコードはこれだけ
performSegue(withIdentifier: "next", sender: nil)これを画面遷移させたい場所に書くだけで画面遷移します。
試しにやってみる
前回の記事[初心者向け]カウントアップアプリを作ってSwiftを触ってみようで
countUpButton
を作成したので、
count
が2になったら画面遷移するようにコードを書いていきます。ViewController.swift内@IBAction func countUpButton(_ sender: Any) { //+ボタンを押すとラベルの文字をカウントアップ count = count + 1 //画面遷移 if count == 2 { performSegue(withIdentifier: "next", sender: nil) count = 0 } countLabel.text = String(count) //カウントにあわせて文字の色を変更 changeColor() }⌘ + Rで実行
画面遷移できました?
画面遷移から戻る方法
これもたった1行で完了します。
dismiss(animated: true, completion: nil)これを画面遷移させたい場所に書くだけで画面遷移します。
試しにやってみる
NextViewController.swift内@IBAction func backButton(_ sender: Any) { dismiss(animated: true, completion: nil) }⌘ + Rで実行
画面遷移から戻ることができました?
画面遷移を行いながら値を渡す方法
画面遷移が無事終わったら、画面遷移を行う際に値も一緒に渡してみましょう!
今回は一般的に使われている方法を紹介します。NextViewController.swiftに値を渡すための準備
- 画面に値を表示するためのLabelを設置(+ボタンからUIパーツLabelを選択→
NextViewController.swift
と紐づける。)- ViewControllerから値をもらうための変数
passdata
を定義- label.text = String(passdata)でLabelに値を反映
NextViewController.swift@IBOutlet weak var label: UILabel! var passdata = 0 override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. label.text = String(passdata) }
値を渡すためのメソッドを定義
prepare(for segue: UIStoryboardSegue, sender: Any?)
メソッドを定義ViewController.swiftoverride func prepare(for segue: UIStoryboardSegue, sender: Any?) { //引数segueの中に画面遷移に関する情報が含まれている。 if segue.identifier == "next" { //NextViewControllerに値を渡す処理を書く。 } }
performSegue(withIdentifier: "ID", sender: nil)
で画面遷移を行うと自動的に、
prepare(for segue: UIStoryboardSegue, sender: Any?)
メソッドが呼ばれます。これはSegueが実行されようとしていることをView Controllerに通知するメソッドです。
このメソッドを使用して、新しいViewControllerに値を渡すことができます。
注意点
このメソッドはSegueが実行されようとしていることを通知するので、複数のSegueから呼び出されます。
したがって、if segue.identifier == "next"{・・・}
のようにnext
のSegueからの情報かどうかを判定してから値を渡す処理を書く必要があります.
メソッド内に処理を書く
ViewController.swift@IBAction func countUpButton(_ sender: Any) { //中略 //画面遷移 performSegue(withIdentifier: "next", sender: nil) } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "next" { let nextVC = segue.destination as! NextViewController nextVC.passdata = count }⌘ + R で実行
performSegue(withIdentifier: "next", sender: nil)
を実行した時のcount
の値を渡すことができました!
今回やったこと
- 新しいViewControllerを作成して画面遷移を実装
- ViewControllerとプログラムコードとの繋がり
- Segueという概念の理解
以上でカウントアップアプリのチュートリアルは終わりです。
お疲れ様でした!もっと深掘りしたい方
この続きとして、
カウントアップアプリを改造して,通知をn秒後に通知を出してみる (n>0)
の記事も参考にしてみてください!最後に
最後までご覧いただきありがとうございます。
Twitterで主にSwiftについてのツイートをしているのでのぞいてみてください!今回はSwiftの言語についてあまり詳しく説明しませんでしたが、
ここでもっと詳しい記事を書いているのでSwiftに興味が湧いた方は参考にしてみてください!
- 投稿日:2020-08-09T13:14:26+09:00
[Swift]クラスについて
クラスとは
プロパティやメソッドを定義できる型のことをクラスといいます。
もう少し、わかりやすくすると
様々な機能を持たせた設計図のことです
設計図の中に自分の使いたい機能や変数を入れておくことで後から簡単に呼び出して使うことができますクラスは作った後にインスタンス化を行い使えるようにします
-インスタンス化とは
作った設計図であるクラスを実体にしてあげることでプログラムの中で使うことができるようになります。
なので、クラスは作った後にインスタンス化(実体化)させてあげる必要があります。クラスの使い方
クラスの定義
class クラス名{
クラスに持たせたい機能のコード
}クラスのインスタンス化
let 定数名 = クラス名()
例:Dogという名前のクラスを作り、nameという変数に「ポチ」を入れたものをクラスに持たせた場合
dogという定数を使いDogクラスをインスタンス化させる
class Dog{
var name = "ポチ"
}
let dog = Dog()
- 投稿日:2020-08-09T11:15:04+09:00
RxSwiftのexampleコードを書く vol.2
RxSwiftを理解する vol.2
本記事は、
@nakagawa1017 さんの RxSwiftのexampleをコードで書く(その2)をRxSwift+Storyboardを使ってコードを書いてみたら、どうなるかという記事となっております。完成図
入力前 入力後 ボタン押した後 この完成図を元に作ってみてください!!
StoryboardでUIを作成
ViewControllerのコードは以下です。
ViewController.swift// // ViewController.swift // Study_RxSwift // // Created by 神村亮佑 on 2020/08/08. // Copyright © 2020 神村亮佑. All rights reserved. // import UIKit import RxSwift import RxCocoa fileprivate let minimalUsernameLength = 5 fileprivate let minimalPasswordLength = 5 class ViewController: UIViewController { @IBOutlet weak var usernameLabel: UILabel! @IBOutlet weak var usernameTextField: UITextField! @IBOutlet weak var usernameErrorLabel: UILabel! @IBOutlet weak var passwordLabel: UILabel! @IBOutlet weak var passwordTextField: UITextField! @IBOutlet weak var passwordErrorLabel: UILabel! @IBOutlet weak var addButton: UIButton! @IBAction func addButtonAction(_ sender: Any) { let alert = UIAlertController(title: "RxSwift", message: "RxSwift is fun", preferredStyle: UIAlertController.Style.alert) alert.addAction(UIAlertAction(title: "OK", style: UIAlertAction.Style.default, handler: nil)) self.present(alert, animated: true, completion: nil) } let disposeBag = DisposeBag() override func viewDidLoad() { super.viewDidLoad() usernameErrorLabel.text = "ユーザーネームは、\(minimalUsernameLength)以上です。" passwordErrorLabel.text = "パスワードは、\(minimalPasswordLength)以上です。" let userNameValid = usernameTextField.rx.text.orEmpty .map{ $0.count >= minimalUsernameLength } .share(replay: 1) let passWordValid = passwordTextField.rx.text.orEmpty .map{ $0.count >= minimalPasswordLength } .share(replay: 1) let everythingValid = Observable.combineLatest(userNameValid, passWordValid){ $0 && $1 } .share(replay: 1) userNameValid.bind(to: passwordTextField.rx.isEnabled).disposed(by: disposeBag) userNameValid.bind(to: usernameErrorLabel.rx.isHidden).disposed(by: disposeBag) passWordValid.bind(to: passwordErrorLabel.rx.isHidden).disposed(by: disposeBag) everythingValid.bind(to: addButton.rx.isEnabled).disposed(by: disposeBag) everythingValid.bind(to: addButton.rx.isEnabled).disposed(by: disposeBag) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } }以下は、コードについての説明
userNameValid について
userNameField.rx.textでString?型を取り出す
さらに、.orEmptyでString型を取り出す.map
.map{ $0.count >= minimalUsernameLength }
で.countで流れてきたデータ(String型)に対してIntで返している.share(replay: 1)
一本のストリームから同じ値を購読したい、という場合には不必要な処理や意図しない処理が走らないようにshare(replay: 1)をつける
.bind(to: passwordField.rx.isEnabled).disposed(by: disposeBag)で
true / falseの値を使って、isEnabled(触れる) / isHidden(隠れる)のプロパティを制御している
参考文献
- 投稿日:2020-08-09T09:37:21+09:00
Immutable value "number" was never used;
たまに見るやつ。
たまに見る警告。そして、自然と消えるやつ。⚠️
いちいち投稿するまでも無いかもだけど、一応まとめておきます。Immutable value "number" was never used;
for
構文とかで、まだ中身書いてない時に、
一時的に発生する警告。Immutable value "number" was never used; consider replacing with "_" or removing itfor number in 0...4 { // Immutable value "number" was never used; consider replacing with "_" or removing it // まだ中身書いてない。 }「"number"は使われてないから、"_"に置き換える、もしくは削除することも考え直したらどう?」
と、言われています。
「 _ 」 とは?
「その値は使用しない」
という意味らしいです。for number in 0...4 { print(number) }中身で”number”を使えば、警告は消えます。
おしまい。
- 投稿日:2020-08-09T09:37:21+09:00
Immutable value "number" was never used; consider replacing with "_" or removing it
たまに見るやつ。
たまに見る警告。そして、自然と消えるやつ。⚠️
いちいち投稿するまでも無いかもだけど、一応まとめておきます。Immutable value "number" was never used;
for
構文とかで、まだ中身書いてない時に、
一時的に発生する警告。Immutable value "number" was never used; consider replacing with "_" or removing itfor number in 0...4 { // Immutable value "number" was never used; consider replacing with "_" or removing it // まだ中身書いてない。 }「"number"は使われてないから、"_"に置き換える、もしくは削除することも考え直したらどう?」
と、言われています。
「 _ 」 とは?
「その値は使用しない」
という意味らしいです。for number in 0...4 { print(number) }中身で”number”を使えば、警告は消えます。
おしまい。
- 投稿日:2020-08-09T09:12:10+09:00
Swift imagePickerControllerの引数について
2020/08/09
UIImagePickerControllerのimagePickerController関数の引数についてメモします。
動画をカメラロールから読み込む際、「選択」を押した後に呼ばれるのがこの関数のようです。
動画のURLの取得が諸々のサイトをコピペした通りに描いてもうまくいかなかったので、うまく行った結果をここに掲載しておきます。func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { videoURL = info[UIImagePickerController.InfoKey.mediaURL] as? URL imagePickerController.dismiss(animated: true, completion: nil) }これでこの関数をもつクラスの予め定義しておいたメンバーvideoURLにURLが渡される訳です。
infoの型とinfoからURLを取り出す際に困った人は試してみてください。以下のコードが説明しないけど開発中に作ったクラス。カメラロールから動画をとってきて、Assetsから写真を選んで動画に合成している。う○こみたいなコードです。Assetsには予め自分で画像を入れておいた前提です。人のコードをチグハグにつなぎ合わせて、少しづついじっています。僕と同じくらいSwift初心者のためにいいますが、このクラスをインスタンス化してpresentで画面遷移して画像をAssetsに用意しておけば使えるはずです。
import UIKit import AVKit import AVFoundation import Photos class Sample: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate { let imagePickerController = UIImagePickerController() var videoURL: URL? var carouselView: CarouselView! //以下編集のために追加したやつ var _assetExport: AVAssetExportSession! @IBOutlet weak var imageView: UIImageView! override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. let width = self.view.frame.width let height = self.view.frame.height carouselView = CarouselView(frame: CGRect(x: 0, y: 0, width: width, height: height)) carouselView.center = CGPoint(x: width/2, y: height/2) self.view.addSubview(carouselView) addEditButton() saisei() addHaikeiSashikaeButton() } @IBAction func selectImage(_ sender: Any) { print("UIBarButtonItem。カメラロールから動画を選択") imagePickerController.sourceType = .photoLibrary imagePickerController.delegate = self //imagePickerController.mediaTypes = ["public.image", "public.movie"] //動画だけ imagePickerController.mediaTypes = ["public.movie"] //画像だけ //imagePickerController.mediaTypes = ["public.image"] present(imagePickerController, animated: true, completion: nil) } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { print("imagePickerControllerが呼ばれたよ") // for key in info.keys { // print(key) // } // print("keyの表示終了") // UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerMediaURL) // UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerMediaType) // UIImagePickerControllerInfoKey(_rawValue: UIImagePickerControllerReferenceURL) // videoURL = info[UIImagePickerController.InfoKey(rawValue: "UIImagePickerControllerReferenceURL")] as? URL //これでうまく行ったー!!!!!なんで.mediaURLなんだろう=>構造体だから videoURL = info[UIImagePickerController.InfoKey.mediaURL] as? URL print(videoURL!) print(UIImagePickerController.InfoKey.mediaURL) print("infoのキーを表示") //確かにinfo.keysの値の一つとなっている // print(previewImageFromVideo(videoURL!)!) print("プレビュー") print(previewImageFromVideo(videoURL!)!) //こいつはうまく行った //まあimageViewは今はいいや imageViewはOutLet接続してないからかnilになる // print(imageView) // imageView.image = previewImageFromVideo(videoURL!)! // imageView.contentMode = .scaleAspectFit imagePickerController.dismiss(animated: true, completion: nil) } func previewImageFromVideo(_ url:URL) -> UIImage? { print("動画からサムネイルを生成する") let asset = AVAsset(url:url) print(asset) print("asset表示") let imageGenerator = AVAssetImageGenerator(asset:asset) print(imageGenerator) print("imageGeneratorの表示") imageGenerator.appliesPreferredTrackTransform = true var time = asset.duration print(time) print("時間表示") time.value = min(time.value,2) print(time.value) print("time.valueを表示してみた") do { let imageRef = try imageGenerator.copyCGImage(at: time, actualTime: nil) return UIImage(cgImage: imageRef) } catch { print("死亡") return nil } } @IBAction func playMovie(_ sender: Any) { if let videoURL = videoURL{ let player = AVPlayer(url: videoURL) let playerViewController = AVPlayerViewController() playerViewController.player = player present(playerViewController, animated: true){ print("動画再生") playerViewController.player!.play() } } } func imagePickerControllerDidCancel(_ picker: UIImagePickerController) { print("キャンセル") // モーダルビューを閉じる self.dismiss(animated: true, completion: nil) } func addEditButton() { let btn = UIButton(frame: CGRect(x: 200, y: 200, width: 200, height: 50)) btn.setTitle("動画を選択", for: .normal) btn.backgroundColor = .red btn.addTarget(self, action: #selector(selectImage), for: .touchUpInside) view.addSubview(btn) } func saisei() { let btn = UIButton(frame: CGRect(x: 200, y: 300, width: 200, height: 50)) btn.setTitle("再生", for: .normal) btn.backgroundColor = .blue btn.addTarget(self, action: #selector(playMovie), for: .touchUpInside) view.addSubview(btn) } //こっからが本題 func addHaikeiSashikaeButton() { let btn = UIButton(frame: CGRect(x: 200, y: 400, width: 200, height: 50)) btn.setTitle("動画の背景を差し替え", for: .normal) btn.backgroundColor = .blue btn.addTarget(self, action: #selector(mergeMovie), for: .touchUpInside) view.addSubview(btn) } @objc func haikeiSashikae() { } @objc func mergeMovie() { //元の動画のURLを取得 let baseMovieURL = self.getBaseMovieURL() print(baseMovieURL) print("URLは取得できてる?") //アセットの作成 //動画のアセットとトラックを作成 var videoAsset: AVURLAsset var videoTrack: AVAssetTrack var audioTrack: AVAssetTrack videoAsset = AVURLAsset(url: baseMovieURL, options:nil) print(videoAsset) print("videoAssetを取得できている?") let videoTracks = videoAsset.tracks(withMediaType: AVMediaType.video) videoTrack = videoTracks[0] //トラックの取得 let audioTracks = videoAsset.tracks(withMediaType: AVMediaType.audio) audioTrack = audioTracks[0] //トラックの取得 //コンポジション作成 let mixComposition : AVMutableComposition = AVMutableComposition() // ベースとなる動画のコンポジション作成 let compositionVideoTrack: AVMutableCompositionTrack! = mixComposition.addMutableTrack(withMediaType: AVMediaType.video, preferredTrackID: kCMPersistentTrackID_Invalid) // ベースとなる音声のコンポジション作成 let compositionAudioTrack: AVMutableCompositionTrack! = mixComposition.addMutableTrack(withMediaType: AVMediaType.audio, preferredTrackID: kCMPersistentTrackID_Invalid) // コンポジションの設定 // 動画の長さ設定 try! compositionVideoTrack.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: videoAsset.duration), of: videoTrack, at: CMTime.zero) // 音声の長さ設定 try! compositionAudioTrack.insertTimeRange(CMTimeRangeMake(start: CMTime.zero, duration: videoAsset.duration), of: audioTrack, at: CMTime.zero) // 回転方向の設定 compositionVideoTrack.preferredTransform = videoAsset.tracks(withMediaType: AVMediaType.video)[0].preferredTransform // 動画のサイズを取得 let videoSize: CGSize = videoTrack.naturalSize // キャプチャ画像レイヤの作成 let movieLayer = self.makeMovieLayer(videoSize) // 親レイヤーを作成 let parentLayer: CALayer = CALayer() let videoLayer: CALayer = CALayer() parentLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height) videoLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height) parentLayer.addSublayer(videoLayer) parentLayer.addSublayer(movieLayer) //キャプチャ画像レイヤを追加 // 合成用コンポジション作成 let videoComp: AVMutableVideoComposition = AVMutableVideoComposition() videoComp.renderSize = videoSize videoComp.frameDuration = CMTimeMake(value: 1, timescale: 30) videoComp.animationTool = AVVideoCompositionCoreAnimationTool.init(postProcessingAsVideoLayer: videoLayer, in: parentLayer) // 最後にanimationToolに設定 // 合成用コンポジション作成 // let videoComp: AVMutableVideoComposition = AVMutableVideoComposition() // videoComp.renderSize = videoSize // videoComp.frameDuration = CMTimeMake(value: 1, timescale: 30) // インストラクションを合成用コンポジションに設定 let instruction: AVMutableVideoCompositionInstruction = AVMutableVideoCompositionInstruction() instruction.timeRange = CMTimeRangeMake(start: CMTime.zero, duration: videoAsset.duration) let layerInstruction: AVMutableVideoCompositionLayerInstruction = AVMutableVideoCompositionLayerInstruction.init(assetTrack: compositionVideoTrack) instruction.layerInstructions = [layerInstruction] videoComp.instructions = [instruction] // 動画のコンポジションをベースにAVAssetExportを生成 _assetExport = AVAssetExportSession.init(asset: mixComposition, presetName: AVAssetExportPresetHighestQuality) // 合成用コンポジションを設定 _assetExport?.videoComposition = videoComp // エクスポートファイルの設定 let exportPath: String = NSHomeDirectory() + "/tmp/createdMovie.mov" let exportUrl: URL = URL(fileURLWithPath: exportPath) _assetExport?.outputFileType = AVFileType.mov _assetExport?.outputURL = exportUrl _assetExport?.shouldOptimizeForNetworkUse = true // ファイルが存在している場合は削除 if FileManager.default.fileExists(atPath: exportPath) { try! FileManager.default.removeItem(atPath: exportPath) } // エクスポート実行 _assetExport?.exportAsynchronously(completionHandler: {() -> Void in if self._assetExport?.status == AVAssetExportSession.Status.failed { // 失敗した場合 print("failed:", self._assetExport?.error) } if self._assetExport?.status == AVAssetExportSession.Status.completed { // 成功した場合 print("completed") // カメラロールに保存 PHPhotoLibrary.shared().performChanges({ PHAssetChangeRequest.creationRequestForAssetFromVideo(atFileURL: exportUrl) }) } }) } //元の動画の取得 func getBaseMovieURL() -> URL { // プロジェクト入れたファイルはこれで取得可能 // let baseMovieURL:URL = Bundle.main.bundleURL.appendingPathComponent("basemovie.mov") // return baseMovieURL return videoURL! } func makeMovieLayer(_ videoSize: CGSize) -> CALayer { //親レイヤの作成 let movieLayer: CALayer = CALayer() // movieLayer.frame = CGRect(x: 0, y: 0, width: 607, height: 1080) //x: 100->0 movieLayer.frame = CGRect(x: 0, y: 0, width: videoSize.width, height: videoSize.height) movieLayer.opacity = 1.0 movieLayer.masksToBounds = true //静止画のレイヤー作成 元々親レイヤに大きさを揃えている let imageLayer: CALayer = CALayer() imageLayer.frame = CGRect(x: 0, y: 0, width: movieLayer.frame.width, height: movieLayer.frame.height) imageLayer.contentsGravity = CALayerContentsGravity.resizeAspectFill // 元の動画を取得 //マージする元動画のURLを取得 let orgVideoURL = self.getBaseMovieURL() //動画のアセットを作成 var videoAsset: AVURLAsset videoAsset = AVURLAsset(url: orgVideoURL, options:nil) //元動画から静止画を抜き出す var gene : AVAssetImageGenerator gene = AVAssetImageGenerator(asset: videoAsset) gene.requestedTimeToleranceAfter = CMTimeMake(value: 1,timescale: 30) gene.requestedTimeToleranceBefore = CMTimeMake(value: 1,timescale: 30) gene.maximumSize = videoSize //オリジナルサイズ //静止画アニメーション let animImg:CAKeyframeAnimation = CAKeyframeAnimation(keyPath: "contents") animImg.beginTime = -1.0 //0.0だとうまくいかなかった animImg.duration = 3.0 animImg.repeatCount = 1 animImg.autoreverses = false animImg.isRemovedOnCompletion = false animImg.fillMode = CAMediaTimingFillMode.forwards animImg.calculationMode = CAAnimationCalculationMode.discrete var imgKeyTimes:Array<NSNumber> = [] let frameCount = 90 // (3.0秒 x 30フレーム) for i in 0 ... frameCount { imgKeyTimes.append((Double(i)/Double(frameCount)) as NSNumber) } animImg.keyTimes = imgKeyTimes // 始めの3秒を90フレームに分割して静止画を取得=>一つの画像に変更 var imgValue:Array<CGImage> = [] for i in 0 ... frameCount { // imgValue.append(try! gene.copyCGImage(at: CMTimeMultiplyByFloat64(CMTimeMake(value: 3 ,timescale: 1), multiplier: Double(i)/Double(frameCount)), actualTime: nil)) //画像をAssetsから取り出してCGImageに変換したい // let image: UIImage! = self.getImageToDocumentDirectory(fileName: "あなたがつけたファイル名.png") let image: UIImage! = UIImage(named: "あなたがつけたファイル名.png") let cgImage: CGImage! = image?.cgImage //挿入する画像を全て静止画にした // imgValue.append(try! gene.copyCGImage(at: CMTimeMultiplyByFloat64(CMTimeMake(value: 3 ,timescale: 1), multiplier: Double(0)/Double(frameCount)), actualTime: nil)) //読み込んで作ったCGImageを利用 imgValue.append(try! cgImage!) } animImg.values = imgValue imageLayer.add(animImg, forKey: nil) //サブレイヤに追加 movieLayer.addSublayer(imageLayer) return movieLayer } //画像をAssetsから取り出す func getImageToDocumentDirectory(fileName: String) -> UIImage? { if let documentURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first { let fileURL = documentURL.appendingPathComponent(fileName) if let imageData = try? Data(contentsOf: fileURL), let image = UIImage(data: imageData) { return image } } return nil } }
- 投稿日:2020-08-09T07:55:52+09:00
RxSwiftのチュートリアル
RxSwiftのチュートリアル
github https://github.com/Ryosukekamimura/Study_RxSwift-CleanArchitecture/tree/1c87caa16b287ea3a3f4a97d04d8d7efcef27549
URLでコミットしていますので、URLをクリックすることでもページに移動することができます。storyboardで、
TextField3つとlabelを用意してください。あとは、以下のコードをViewControllerに張ってけるだけです
ViewController.swift// // ViewController.swift // Study_RxSwift // // Created by 神村亮佑 on 2020/08/08. // Copyright © 2020 神村亮佑. All rights reserved. // import UIKit import RxSwift import RxCocoa class ViewController: UIViewController { @IBOutlet weak var textField1: UITextField! @IBOutlet weak var textField2: UITextField! @IBOutlet weak var textFiled3: UITextField! @IBOutlet weak var displayNum: UILabel! let disposeBag = DisposeBag() func numCheck(str: String) -> Int { return (Int(str) ?? 0) } override func viewDidLoad() { super.viewDidLoad() Observable.combineLatest(textField1.rx.text.orEmpty, textField2.rx.text.orEmpty, textFiled3.rx.text.orEmpty) { textValue1, textValue2, textValue3 -> Int in return self.numCheck(str: textValue1) + self.numCheck(str: textValue2) + self.numCheck(str: textValue3) } .map{ $0.description } .bind(to: displayNum.rx.text) .disposed(by: disposeBag) } }コード内の説明
CombineLatestという関数
この関数は、観測する値(UITextFieldのtextの値)を複数入れて一つのデータの流れにする関数
そしてクロージャーを返すクロージャとは
クロージャとは、(Closure: 関数閉包)関数のスコープにある変数を自分が定義された環境に閉じ込めるためのデータ構造である。
例えば、Swiftとは、funcで定義した関数はクロージャである。
orEmpty
orEmptyとはString? 型(String or nil)のコントロールプロパティをString型のコントロールプロパティに変換します。
UITextFieldに入っているのか入っていないのかわからない値であるためString? というのが前提条件だと思うのだが、このデータの流れに入るさいはString型に変換してくれる便利な関数。
$0.description
直前のクロージャの処理を抜けると、Int型のデータ型が流れてくる。
map関数は、流れたデータそれぞれに処理を行う関数である。$0.descriptionをつなげると入ってきたInt型をString型に変換してくれる。
.bind関数
bind関数とは、UIに変更点を入れてくれる関数である。
今回の場合、Stringに変換した値は、UILabelクラスのインスタンスであるlabelのtextに入れるということらしい.disposed
これは、処理が終わった後は観測を止める必要があるとのことでおまじないのように付けている感覚。
[参照URL]
「RxSwiftのexmapleコードで書く」
https://qiita.com/nakagawa1017/items/f894b1ac051fab08b95d「Swiftでクロージャを理解する」
https://thinkit.co.jp/article/15629
- 投稿日:2020-08-09T01:21:16+09:00
Swift:ただ音を鳴らすだけ
複雑なことはしたくないけど、とにかく単音を鳴らしたいという場合に有効。
ただし、音の再生開始時と停止時に微妙にノイズが入る。import AVFoundation class Sampler { let engine: AVAudioEngine let unitSampler: AVAudioUnitSampler init() { engine = AVAudioEngine() unitSampler = AVAudioUnitSampler() engine.attach(unitSampler) engine.connect(unitSampler, to: engine.mainMixerNode, format: nil) try? engine.start() } deinit { if engine.isRunning { engine.disconnectNodeOutput(unitSampler) engine.detach(unitSampler) engine.stop() } } func play() { // たぶん ソ♪ unitSampler.startNote(67, withVelocity: 80, onChannel: 0) } func stop() { unitSampler.stopNote(67, onChannel: 0) } }let sampler = Sampler() // 音を再生 sampler.play() // 音を停止 sampler.stop()