20200809のSwiftに関する記事は17件です。

【入門】iOS アプリ開発 #1

はじめに

新型コロナウィルス(COVID-19)の影響により、外出自粛が続いている。
どこへも出かけない夏休み、この機会に Mac を購入して iOS のプログラミングを勉強してみようと思う。
iPhone のアプリは Swift という言語で作られるようだ。

アップルは Swift を「モダン、安全、高速、インタラクティブ」を特徴として謳っている。
これだけでは全くイメージつかないが、さぞかし簡単にアプリが開発できるに違いない。

昔のゲームぐらいならば、さくっと作れて、軽快にサクサク動作するのであろう。
さらにタッチパネルやセンサーで操作できると面白そうだ。

プログラミングすることよりも、何を作るか?どんな仕様するか?、企画や上流設計は悩みどころだ。
またキャラクターのデザインやサウンドなどを作るのも骨が折れる。

色々調べてみるとパックマンの仕様書が公開されている。
これをもとに作ればプログラミングに専念できそうだ。

パックマンのゲーム仕様書

下記の人工知能学会誌に仕様書が公開されている。

Img0001.png

一度、何かを作ってしまえばノウハウが溜まり、自分が作りたいものが簡単にできるはずなので、
まずはパックマンを作ってみようと思う。

仕様書をよく読むと、思ったよりも複雑な仕様になっているが、
高級言語 Swift で書けば、1万行もコーディングせずに出来るのではないか。

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

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

シミュレーター

スクリーンショット 2020-07-28 20.52.14.png

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を選択し、EditorEmbedldNavigationControllerを選択してNavigationControllerを設置する。
スクリーンショット 2020-07-28 21.42.32.png

②ViewController.swiftに戻ってoverride func viewDidLoadの中にタイトルを表示させるコードを入力する

navigationItem.title = "果物"

シミュレーター

スクリーンショット 2020-08-09 22.10.02.png

全体のコード

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を表示させてみました!

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

Flutter の iOS 向けビルドでハマったメモ

Flutter の iOS 向けビルド備忘録

とりあえず解決したことを列挙するだけの備忘録

Undefined symbols for architecture arm64

Create Bridging Header で解決した

domain/default pair of ... does not exist

プロジェクトのルートにアセットを置いているとビルドできない模様。

hoge を assets/hoge に移動して。pubspec.yaml の該当箇所も直せばOK。

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

不要になった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"
.
.
.

他にもこんな風に管理しているなどがあれば教えていただけるとありがたいです:bow:

どのような管理方法が理想か

上記のような管理方法を洗い出し、以下の用件が満たせていれば理想だと考えました。

  1. どのキーが使用されなくなったかがわかる状態
  2. 使用されいているキーと不要なキーの管理が分離されている
  3. 既に利用していないキー名を再度利用しようとした場合、ビルド時にエラー検知

新しい管理方法

ドットファイルとスクリプトで管理するパターンを考えてみました。

  • 使用しなくなったキー名を.unused_userdefaults_keyで管理
  • buildPhaseでスクリプトを実行し、使用していないキーを利用する場合エラーを出力

1. ドットファイルを用意

使用しなくなったキー名をまとめた.unused_userdefaults_keyを用意します

is_hoge
is_foo

2. スクリプトファイルを用意

キーをまとめた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
fi

3. Build Phasesに組み込み

unused_userdefaults_key.shをBuildPhasesで実行するようにします
Screen Shot 2020-08-09 at 16.27.46.png

動作GIF

SwiftyUserDefaultsを利用した場合の動作GIFです。
demo.gif

GitHubにもプロジェクトを公開しているので、興味がある人は確認してみてください。
GitHub: https://github.com/funzin/UnusedUserDefaultsKeyExmaple

まとめ

不要となったUserDefaultsキーの管理方法についてまとめてみました。
削除したキーが復活して事故を防ぐには有効だと思いますので、ぜひ参考にしてもらえるとうれしいです。

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

【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?


  1. [The Swift Programming Language - Properties](https://docs.swift.org/swift-book/LanguageGuide/Properties 

  2. 太字による強調は訳者による 

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

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 Ichiro

 returnを使った関数

一方的にただ呼び出す関数も便利ですが、(上記2つのコード??)

関数の中でいろいろな処理をさせて、その「結果」を貰いたいときがあります。
要するに、呼び出しに対する「返事」が欲しいときです。

 with a return value?

swift6_04_01.jpg

 with parameters and a return value?

swift6_04_07.jpg

戻り値を持つ関数
書き方は、こんな感じ。

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を埋め込み。

おしまい。

 参考サイト

[Swift初心者向け] function(メソッド)の使い方

Swiftの関数、引数、戻り値の基本的な書き方と使い方

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

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 Ichiro

 returnを使った関数

一方的にただ呼び出す関数も便利ですが、(上記2つのコード??)

関数の中でいろいろな処理をさせて、その「結果」を貰いたいときがあります。
要するに、呼び出しに対する「返事」が欲しいときです。

 with a return value?

swift6_04_01.jpg

 with parameters and a return value?

swift6_04_07.jpg

戻り値を持つ関数
書き方は、こんな感じ。

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を埋め込み。

おしまい。

 参考サイト

[Swift初心者向け] function(メソッド)の使い方

Swiftの関数、引数、戻り値の基本的な書き方と使い方

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

テクトロジーによる実践的組織構造学

今回の記事では、ソ連の革命家、医師、哲学者、小説作家であったアレクサンダーボグダノフが提唱したテクトロジーと呼ばれる実践的組織構造学について紹介する。
テクトロジーでは組織が安定、成長、破綻する環境、条件について詳細に解説し、安定した組織を生成する手法について解説している。
テクトロジーの概念を拝借し、創造的な組織創造の手法を紹介したいと思う。

テクトロジーにおける組織の定義

テクトロジーでは、組織をオープンクローズ型の成長する有機体システムと定義している。
組織とは以下の要素で構成されている。
ビジョン・・・組織の目指すべき方向性。
経済・・・組織のボディ。巨大であるほど収容できる人の人数が増加する
金融・・・組織を循環する血液。
生産・・・もの、サービスを生産し、組織の経済を巨大化する手段。

これらの有機的な要素が相互作用し、成長することで組織という有機体が構成されていると考える。

テクトロジーにおける成長する有機体システムとは

テクトロジーでは、組織をオープンクローズ型の成長する有機体システムと定義している。
テクトロジーにおける組織の定義を中国の陰陽論によって説明することができる。
陰陽論とは、原初は混沌(カオス)の状態であると考え、この混沌の中から光に満ちた明るい澄んだ気、すなわち陽の気が上昇して天となり、重く濁った暗黒の気、すなわち陰の気が下降して地となった。この二気の働きによって万物の事象を理解し、また将来までも予測しようというのが陰陽思想である。
組織が外部からエネルギーを取り入れ、出力するインプット、アウトプットの運動を陰陽論における陽の気と捉えることができる。
逆に組織内部に沈殿し、成長し、ヒエラルキーを形成する秩序生成を担う運動を陰陽論における陰の気と捉えることができる。

テクトロジーにおける生産の定義

テクトロジーでは、組織における生産活動は以下の3つに分類されている。
人の生産・・・人に教育を施し、組織活動に従事する生産者を作成する
モノ、サービスの生産・・・外部から取得した資材を用いて、モノ、サービスの生産を行う。
アイデア・・モノ、サービスを生成するための知識、アイディアを作成する。
組織における生産活動を高めることで、組織の経済を成長させることができる。

テクトロジーおける組織のフォーム(形態)について

現実の世界で、人が活動を行う場合、必ず外部からの影響、抵抗を受ける。
外部からの影響、抵抗を抑えるために、組織は環境に合わせた最適なフォーム(形態)を取る必要がある。
魚やイルカなど、魚と哺乳類で種族は異なるが、水の抵抗を抑えるために同様の流線形フォルムを取っている。
組織のフォーム(形態)は外部環境によって決定される。
外部環境からの抵抗を最小限にするために、外部との接触の最小化、不要な組織的機能の削除などが求められる。
最適なフォーム(形態)によって、組織は外部からの抵抗を減少させ、健全に成長することができる。

テクトロジーにおける組織が不安定化する条件

テクトロジーにおける組織が不安定化する条件として以下の2点が挙げられる。
・外部からのエネルギー取得の減少・・外部から人、モノ、金の循環が減少することで組織のサイズ、経済を維持することできなくなる。
・ヒエラルキーシステムの固定化・・・ヒエラルキーシステムが巨大化し、組織が硬直化してしまう。
組織不安定化を回避する手法として以下の手段が有効とされている。
生産手段を研究、開発、更新を行い、組織の経済成長のスピードを増加させる。
組織が硬直化の原因になっているヒエラルキーシステムを解体し、適切なサイズに組み替える。

まとめ

アレクサンダーボグダノフテクトロジーに関するアイディアを発表した時期は1920年代である。
独学で組織が破綻する条件、環境を発見し、持続可能な成長のコンセプトを提唱したアレクサンダーボグダノフの先見性は恐るべきものである。
ソ連は軍事、IT、経済においてアメリカと張り合うことができた超大国だった。
ソ連時代に考えられたアイディア、思想などは現代においても見直されるべきものだと思われる。

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

[チュートリアル]アプリに画面遷移を追加する方法

この記事は前回の[チュートリアル]カウントアップアプリを作ってSwiftを触ってみようの続きです。

アプリの完成形はこちら

ソースコードはこちらから

https://github.com/techiro/CountUpAppForBeginners/tree/v1


画面遷移で抑えておきたいポイント

  • 画面遷移先のViewController
  • ViewControllerとプログラムコードとの繋がり
  • Segueという概念

画面遷移先のViewController

画面遷移するために新しいViewControllerを追加
ボタンなどを追加したLibraryからViewControllerを選択してドラッグ&ドロップ

スクリーンショット_2020-08-05_11_20_23.png

スクリーンショット_2020_08_07_23_41.png

画面にViewControllerが追加されたらOK


ViewControllerとプログラムコードとの繋がり

1.画面から新しいViewControllerを選択

スクリーンショット 2020-08-08 0.20.51.png

2.Identity inspectorを選択

新しく追加したViewControllerのCustom Classが空になっている
スクリーンショット_2020_08_07_23_41.png

このままだと画面遷移を行えないので、新しいClassを定義しなければならない。

新しいClassを定義

画面左下の+ボタンをクリック→NewFileをクリック
スクリーンショット 2020-08-08 0.00.12.png

Cocoa Touch ClassNext

スクリーンショット 2020-08-08 0.03.02.png

Class名NextViewControllerに変更

スクリーンショット 2020-08-08 0.04.21.png

Subclass of: UIViewControllerに変更

この項目はClassがどんな用途で使われるかを設定でき、初期段階で必要なプログラムをXcodeが書いてくれます。
スクリーンショット 2020-08-08 0.06.36.png
例えばここでUITableViewControllerを選ぶとテーブルを使うためのコードを自動で入力してくれます。

入力を終えたらNext

最後にファイルの保存場所を聞いてきます。
そのままCreateを押してください

スクリーンショット_2020_08_08_0_17.png


新しいViewControllerに新しいClassを紐づける

StoryBoardに戻って追加したViewControllerを選択
スクリーンショット 2020-08-08 0.20.51.png

スクリーンショット_2020-08-08_0_26_57.png

これで新しいViewControllerに新しいClassを紐づけることができました!


Segueという概念

Segueとは、storyboard上で画面遷移を表す部品です。
Segueは、画面遷移の方法と、どう画面遷移させるかを決めることができます。

詳しく説明しているサイトを紹介

ボタンに対してSegueを作成することもできます。
例えば、「設定ボタンをタップしたら設定画面に遷移する。」を実現したい場合はノンコーディングで画面遷移を行うことができます。

今回使用する方法はViewControllerからSegueを作成してコーディングを行って画面遷移を実装します。


Segueを作成

ViewControllerを選択→⌃(control) + ドラッグ&ドロップ

Manual SegueはPresent Modallyを選択
2020_08_08_00_35.gif

ViewControllerと新しいViewControllerがつながっていればOK
マークが違っていても画面遷移の見え方が変わるだけで、問題ないです。
スクリーンショット_2020_08_08_0_50.png


Segueに名前をつける

Segueを選択→Attribute inspectorIdentifire
→任意の名前をつける(今回はnext)
スクリーンショット_2020-08-08_0_54_04.png

SegueのPresentationをFullScreenに変更(任意)

変更すると、画面全体にViewが表示されます。
スクリーンショット_2020-08-09_14_35_04.png


遷移先のViewControllerに戻るボタンを設置

NextViewControllerに戻るボタンを新しく設置します.

Main.storyboard+ボタンButtonを選択してNextViewControllerドラッグ&ドロップ

それが完了したら、ボタンを選択してTitleをわかりやすい文字に変更してください。
スクリーンショット_2020_08_08_1_14.png

ボタンをNextViewControllerクラスと紐づける

  • 三マークをクリック→Assistantエディタを起動
    StoryBoardとコードを一緒にみることができる
    Xcode_InterfaceBuilderKit_DocumentGearMenu.png

  • 戻るボタンをクリック→ ⌃control +ドラッグ&ドロップ

NextViewControllerクラスにボタンを紐づける

紐付けポイント

  • Connection→Actionになっているか確認 スクリーンショット_2020_08_08_14_04.png

下準備は終了。画面遷移のコードを書いていく


画面遷移するためのコード

画面遷移をさせるためのコードはこれだけ

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に値を反映

スクリーンショット 2020-08-09 14.21.35.png

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.swift
    override 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に興味が湧いた方は参考にしてみてください!

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

[Swift]アプリに画面遷移を追加する方法

この記事は前回の[Swift]カウントアップアプリを作ってSwiftを触ってみようの続きです。

アプリの完成形はこちら

ソースコードはこちらから

https://github.com/techiro/CountUpAppForBeginners/tree/v1


画面遷移で抑えておきたいポイント

  • 画面遷移先のViewController
  • ViewControllerとプログラムコードとの繋がり
  • Segueという概念

画面遷移先のViewController

画面遷移するために新しいViewControllerを追加
ボタンなどを追加したLibraryからViewControllerを選択してドラッグ&ドロップ

スクリーンショット_2020-08-05_11_20_23.png

スクリーンショット_2020_08_07_23_41.png

画面にViewControllerが追加されたらOK


ViewControllerとプログラムコードとの繋がり

1.画面から新しいViewControllerを選択

スクリーンショット 2020-08-08 0.20.51.png

2.Identity inspectorを選択

新しく追加したViewControllerのCustom Classが空になっている
スクリーンショット_2020_08_07_23_41.png

このままだと画面遷移を行えないので、新しいClassを定義しなければならない。

新しいClassを定義

画面左下の+ボタンをクリック→NewFileをクリック
スクリーンショット 2020-08-08 0.00.12.png

Cocoa Touch ClassNext

スクリーンショット 2020-08-08 0.03.02.png

Class名NextViewControllerに変更

スクリーンショット 2020-08-08 0.04.21.png

Subclass of: UIViewControllerに変更

この項目はClassがどんな用途で使われるかを設定でき、初期段階で必要なプログラムをXcodeが書いてくれます。
スクリーンショット 2020-08-08 0.06.36.png
例えばここでUITableViewControllerを選ぶとテーブルを使うためのコードを自動で入力してくれます。

入力を終えたらNext

最後にファイルの保存場所を聞いてきます。
そのままCreateを押してください

スクリーンショット_2020_08_08_0_17.png


新しいViewControllerに新しいClassを紐づける

StoryBoardに戻って追加したViewControllerを選択
スクリーンショット 2020-08-08 0.20.51.png

スクリーンショット_2020-08-08_0_26_57.png

これで新しいViewControllerに新しいClassを紐づけることができました!


Segueという概念

Segueとは、storyboard上で画面遷移を表す部品です。
Segueは、画面遷移の方法と、どう画面遷移させるかを決めることができます。

詳しく説明しているサイトを紹介

ボタンに対してSegueを作成することもできます。
例えば、「設定ボタンをタップしたら設定画面に遷移する。」を実現したい場合はノンコーディングで画面遷移を行うことができます。

今回使用する方法はViewControllerからSegueを作成してコーディングを行って画面遷移を実装します。


Segueを作成

ViewControllerを選択→⌃(control) + ドラッグ&ドロップ

Manual SegueはPresent Modallyを選択
2020_08_08_00_35.gif

ViewControllerと新しいViewControllerがつながっていればOK
マークが違っていても画面遷移の見え方が変わるだけで、問題ないです。
スクリーンショット_2020_08_08_0_50.png


Segueに名前をつける

Segueを選択→Attribute inspectorIdentifire
→任意の名前をつける(今回はnext)
スクリーンショット_2020-08-08_0_54_04.png

SegueのPresentationをFullScreenに変更(任意)

変更すると、画面全体にViewが表示されます。
スクリーンショット_2020-08-09_14_35_04.png


遷移先のViewControllerに戻るボタンを設置

NextViewControllerに戻るボタンを新しく設置します.

Main.storyboard+ボタンButtonを選択してNextViewControllerドラッグ&ドロップ

それが完了したら、ボタンを選択してTitleをわかりやすい文字に変更してください。
スクリーンショット_2020_08_08_1_14.png

ボタンをNextViewControllerクラスと紐づける

  • 三マークをクリック→Assistantエディタを起動
    StoryBoardとコードを一緒にみることができる
    Xcode_InterfaceBuilderKit_DocumentGearMenu.png

  • 戻るボタンをクリック→ ⌃control +ドラッグ&ドロップ

NextViewControllerクラスにボタンを紐づける

紐付けポイント

  • Connection→Actionになっているか確認 スクリーンショット_2020_08_08_14_04.png

下準備は終了。画面遷移のコードを書いていく


画面遷移するためのコード

画面遷移をさせるためのコードはこれだけ

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に値を反映

スクリーンショット 2020-08-09 14.21.35.png

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.swift
    override 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に興味が湧いた方は参考にしてみてください!

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

[Swift]クラスについて

クラスとは

プロパティやメソッドを定義できる型のことをクラスといいます。
もう少し、わかりやすくすると
様々な機能を持たせた設計図のことです
設計図の中に自分の使いたい機能や変数を入れておくことで後から簡単に呼び出して使うことができます

クラスは作った後にインスタンス化を行い使えるようにします
-インスタンス化とは
 作った設計図であるクラスを実体にしてあげることでプログラムの中で使うことができるようになります。
 なので、クラスは作った後にインスタンス化(実体化)させてあげる必要があります。

クラスの使い方

クラスの定義

class クラス名{
クラスに持たせたい機能のコード
}

クラスのインスタンス化

let 定数名 = クラス名()

例:Dogという名前のクラスを作り、nameという変数に「ポチ」を入れたものをクラスに持たせた場合
dogという定数を使いDogクラスをインスタンス化させる
class Dog{
var name = "ポチ"
}

let dog = Dog()

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

RxSwiftのexampleコードを書く vol.2

RxSwiftを理解する vol.2

本記事は、 
@nakagawa1017 さんの RxSwiftのexampleをコードで書く(その2)をRxSwift+Storyboardを使ってコードを書いてみたら、どうなるかという記事となっております。

完成図

入力前 入力後 ボタン押した後
Simulator Screen Shot - iPhone 11 Pro - 2020-08-09 at 11.02.59.png Simulator Screen Shot - iPhone 11 Pro - 2020-08-09 at 11.03.31.png Simulator Screen Shot - iPhone 11 Pro - 2020-08-09 at 11.03.29.png

この完成図を元に作ってみてください!!

StoryboardでUIを作成

スクリーンショット 2020-08-09 10.56.20.png

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)をつける

RxSwiftのexampleをコードで書く(その2)

.bind(to: passwordField.rx.isEnabled).disposed(by: disposeBag)で

true / falseの値を使って、isEnabled(触れる) / isHidden(隠れる)のプロパティを制御している

参考文献

RxSwiftのexampleをコードで書く(その2)

[RxSwift] shareReplayをちゃんと書いてお行儀良くストリームを購読しよう

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

Immutable value "number" was never used;

 たまに見るやつ。

たまに見る警告。そして、自然と消えるやつ。⚠️
いちいち投稿するまでも無いかもだけど、一応まとめておきます。

 Immutable value "number" was never used;

for構文とかで、まだ中身書いてない時に、
一時的に発生する警告。

Immutable value "number" was never used; consider replacing with "_" or removing it
for number in 0...4 { // Immutable value "number" was never used; consider replacing with "_" or removing it
    // まだ中身書いてない。
}

「"number"は使われてないから、"_"に置き換える、もしくは削除することも考え直したらどう?」

と、言われています。

 「 _ 」 とは?

「その値は使用しない」 という意味らしいです。

Swiftでとまどった「_」(アンダースコア)

for number in 0...4 {
    print(number)
}

中身で”number”を使えば、警告は消えます。

おしまい。

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

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 it
for number in 0...4 { // Immutable value "number" was never used; consider replacing with "_" or removing it
    // まだ中身書いてない。
}

「"number"は使われてないから、"_"に置き換える、もしくは削除することも考え直したらどう?」

と、言われています。

 「 _ 」 とは?

「その値は使用しない」 という意味らしいです。

Swiftでとまどった「_」(アンダースコア)

for number in 0...4 {
    print(number)
}

中身で”number”を使えば、警告は消えます。

おしまい。

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

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
    }
}



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

RxSwiftのチュートリアル

RxSwiftのチュートリアル

github https://github.com/Ryosukekamimura/Study_RxSwift-CleanArchitecture/tree/1c87caa16b287ea3a3f4a97d04d8d7efcef27549
URLでコミットしていますので、URLをクリックすることでもページに移動することができます。

レイアウトをstoryboardで構成しております。
Simulator Screen Shot - iPhone 11 Pro - 2020-08-09 at 07.51.52.png

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

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

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()
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む