- 投稿日:2020-11-28T21:43:13+09:00
【swift】google admobを実装した際に「[UIView setAdUnitID:]: unrecognized selector sent to instance」が出力した場合の対応
swiftでgoogle admobを実装している際に下記エラーが出力した。
エラー内容
Thread 1: "-[UIView setAdUnitID:]: unrecognized selector sent to instance 0x7fce3a70abc0"対応方法
広告の枠としたいUIViewと「@IBOutlet weak var bannerView: GADBannerView!」をドラッグアンドドロップなどで紐付けるのだが、その際UIViewのCustom ClassのClass名に「GADBannerView」が記載されておらず実行時エラーとなっていた。
UIViewのCustom ClassのClass名に「GADBannerView」を記載することで、無事エラー回避できた。
修正前
修正後
最後に
コードを先に書いたあとに、UIViewにドラッグアンドドロップで紐付けると、UIViewのCustomClassは更新されないんですね。。。
ともあれ解消したのでよかったです。
- 投稿日:2020-11-28T21:19:00+09:00
【swift5】APIを叩くときは「HTTP」を「HTTPS」に直しましょう
※本記事が筆者の初投稿です! 優しい目で参考程度にご活用ください!
はじめに
なーんにも考えずに、AlamoFireでAPIを叩いたりなどでHTTP通信をすると、
「エラーが出る」とか「想定より少ないデータしか取れない」
って問題が発生したりします。HTTP通信は暗号化されていない安全性の低い通信のため、
appleがデフォルトでブロックさせています。この状態でrequestを出しても、セキュリティでブロックされてる状態なので、
responseは返ってきません。
キャッチボールしようとしてるのに、二人の間に壁があるようなものです。
これでは投げても相手に届かないので投げ返してくれませんね。壁に邪魔されない形式の通信に変更しましょう。
解決策
例えばAPIを使おうとして、サイトに載ってるサンプルクエリをコピペしてみます。
そうするとありがちなのが以下みたいなURL。
「http://webservice.recruit.co.jp/hotpepper/gourmet/v1/?key=[APIキー]&large_area=Z011」このデフォルトで文頭が「http://」になってるのが引っかかりポイント。
単純に「http://」を「https://」に直してあげましょう。これで壁に邪魔されない、安全性の高い通信形式に変更できました。
それでもダメな場合は
API側が、http通信しか対応していない場合があります。
「httpsに直してもダメだ〜」って時は、そもそもの壁を取っ払ってやりましょう。①ATSを無効化する (簡易版)
下記URLの「info.plistの編集」を参照してください。
【swift】XcodeでiOSアプリのhttp通信を許可する方法
Info.plist
に飛んで、ATSを無効化しましょう。
ただ、上記の方法は全てのHTTP通信を許可しているため、安全性に欠けます。
審査に提出するとなると、apple様はこの方法では通してくれません。特定のボール(ドメイン)だけ、壁(ATS)を通り抜けるようにしましょう。
②特定のドメインのみ許可する (発展版)
下記URLを参照して、特定のドメインだけ許可してあげましょう。
お菓子検索アプリ:iOS10でのATS設定についてこれで指定したボール(ドメイン)だけは通り抜けられるようになりました。
ただATSという壁を設けている時点で、そもそもHTTP通信をappleは推奨していないことには留意してください。
2020年11月現在でも審査に通るかは未検証です。終わりに
何がハマったって、これやらなくても「少量ならレスポンスが返ってきた時があった」のが、
1番のハマり&誤認ポイントだった。「無制限にデータ受け取りたいのに、取得データが少ない」とか、
「10件くらいの少量なら大丈夫だけど、100件とか多量のデータを受け取ろうとするとエラー吐く」
とかいう方は「http通信になっていないか」、もしくは「ATSはどうなっているか」を確認してみてください。自分と同じくらいの初学者さんの助けになれれば。
参考文献
- 投稿日:2020-11-28T19:30:25+09:00
varとletの違い
Swiftにおける var と let の違い。
var と let の違いについてご存知でしょうか?
プログラミングを始めたばかりの時に違いがよくわからなかったのでメモとして共有します。まず始めに、var は変数、let は定数と呼ばれているので覚えてください。
変数はその名の通り、変化する数です。
定数もその名の通り、定まっている数です。これを頭に入れておけばもう var と let のどちらを使えばいいの?と悩むことはありません。
変数(var) と 定数(let)
変数と定数は、値の一時保存に使います。
ある処理の結果を変数や定数に保存すると、後続の処理でその結果を再利用できます。一時保存ということは、
アプリをタスクキルすると変数や定数の中身の情報が失われるということです。もちろん永久保存する方法もありますが、
それに関しては発信できるほどの知識がついたら共有します。では、一時保存で役に立つのかというと、
簡単なコードですが下記のような使用方法などがございます。var a: Int a = 1 + 1 // a の中には 1 + 1 の結果が"代入"されている var b: Int b = a + 10 // b の中には a + 10 の結果が"代入"されている一時保存することにより、別のところで変数を使用することができるようになります。
また、上記のコードで強調したのですが、変数や定数に値を入れることを代入と言います。
変数と定数の違いは代入できる回数にあり、
変数には何度も代入できますが、定数には一度しか代入することができません。定数に二度目の代入を行うとコンパイルエラーが発生します。
var a: Int //変数なので何度も代入できる a = 1 a = 2 a = 100 let b: Int //定数なので一度しか代入できない b = 10 //1回目 b = 100 //2回目 コンパイルエラー発生!名前やメールアドレスなどは、
不用意な変更はされたくないので定数に入れる方がいいかもしれませんね!この様に、変数と定数を使い分けることが
安全で可読性の高いプログラムの実現に繋がります。変数や定数に対する代入
変数や定数に代入可能な値は、変数や定数の型と一致しているもののみになります。
Int型の変数に対してはInt型、つまり整数しか代入することはできませんし、
String型の変数に対してはString型、つまり文字列しか代入することができません。型以外の値を代入しようとするとコンパイルエラーになります。
var a: Int //Int型 a = 100 //エラーなし var b: Int //Int型 b = "Hello" //Int型にString型を代入しようとしているのでコンパイルエラーが発生このように、変数や定数に対する代入は値の型に注意してください。
また、変数 a を宣言し a に100を代入しましたが、これを一度に済ませることもできます。
さらには、前回の記事に記載した、型推論による簡潔な記述も可能です。var a: Int = 100 let b: String = "Hello" var c = 10 //変数cの値が10なのでInt型だと推論してくれる let d = "World" //変数dの値がWorldなのでString型だと推論してくれる変数・定数の型の確認方法
変数や定数に代入する際のルールや型推論については理解できましたが、
肝心の型がわからなくなった場合にどうすればいいのだろうかと当時は疑問に思ったので、
それに関しても記載しておきます。まず、前提として、
型が分かりにくくなるような変数名はつけないようにする。というのが大事かと思います。例:name
-> name は名前とかだからString型かな? となんとなく分かる
例:book
-> book は本だからString型? でも本のページ数とか、本の重さもある・・・。というふうになるので、
bookName(本の名前)、bookWeight(本の重さ)など分かる変数名にする方がいい。では、他人が書いたコードでよくわからない場合はどうすればいいか
方法としては、
1. エラーコードを読んで判断する
2. type(of:)関数を利用する
のどちらかだと思います。エラーコードは、Google翻訳などで読み解いてください・・・。
type(of:)関数は下記のように使用します
let a = 100 type(of: a) // Int print(type(of: a)) //ログに出力したい場合型が分かってしまえば、こちらのものですね!
スラスラとコードを書いていきましょう。ですが、何よりも大事なのは、
誰が見ても分かるようなコードを書くようにすることだなと、この記事を書いていて思いました。以上、ありがとうございました。
- 投稿日:2020-11-28T17:52:05+09:00
Guideline 4.2 - Design - Minimum Functionality からの脱出
自己紹介
これまで MS/C#系の世界でしたが、2020年より mac/iPhone/Swift をはじめました。何もかもわからないことだらけでとまどうばかりです。Qiita の投稿も初めてですが、よろしくお願いします。
Guideline 4.2 - Design - Minimum Functionality とは
今回、3つ目となる iPhone アプリを申請したところ、Guideline 4.2 - Design - Minimum Functionalityでリジェクトされてしまいました。
メッセージは、次の通りです。We found that the usefulness of your app is limited by the minimal amount of content or features it includes.
つまり、Guideline 4.2 は、Qiita: どこまでショボいアプリがAppleの審査に通るのか試してみたにあるように、いわゆる最低限の機能が無く便利さが限定的であることが原因で、一度リジェクトをくらうとなかなか抜け出せないため、恐れられているという記事をいくつか見かけました。もうこの時点で、半分心が折れかかっていましたが、幸いなことにアプリを修正することなく一発で抜け出すことができました。今回 Guideline 4.2 をどのように脱出できたのか説明します。多少なりともご参考になればと思います。
アプリ概要と開発のきっかけ
秩父雲海検出 アプリは、機械学習により により、秩父の雲海の発生を検出する無償のアプリです。秩父雲海ライブカメラの画像を定期的に機械認識し、雲海が発生しているかどうか検出を行います。必要に応じて、雲海が発生した場合に、通知を行うことができます。また、検出間隔の変更を行うことができます。
このアプリの開発のきっかけですが、私は神奈川県相模原市在住ですが、秩父の雲海がとても綺麗だという噂を聞き、一度見に行きたいものだと思っていました。しかし、雲海は前日が雨で、明け方に湿度100%で無風に近い状態が必要で、ベストシーズンは10月〜11月で、お目にかかるのが難しい自然現象です。相模原市から、秩父までは車で2〜3時間かかるので、そう簡単に見に行くわけにはいきません。そこで、いつ秩父の雲海を見に行けばいいのかといろいろ調べていたところ、秩父雲海ライブカメラがあり、この画像から雲海を機械学習させて、通知するようにすれば、寒空の中で延々待たなくとも済む、と思ったのがきっかけです。
4.2リジェクトの原因
実は、今回、2日前に iPhone/iPad 版の秩父雲海検出を先行して申請し、承認されていました。4.2でリジェクトされたのは、iPhone/iPad 版と全く同一機能の macOS 版でした。このため、4.2リジェクトされた理由は、レビューワーの違いによる、考え方の違いが原因だろうと想像がつきました。もし、最初から、4.2リジェクトをくらっていたら、余計な機能を追加するような迷路に陥ったかもしれません。
レビューワーの視点に立ってみると、なぜ4.2でリジェクトするのか、次のポイントが容易に想像できます。
- アプリの利用者の対象地域が狭く、埼玉県秩父市近郊のみ
- ライブカメラの画像を表示して、雲海が発生した時に通知を出すだけの単機能
- レビュー時には、ほぼ間違いなく雲海は発生しておらず、秩父市の風景と時計が表示されているだけ
- UIに表示されている秩父市の風景はデフォルトで10分間更新されない
つまり、利用シーンを知らなければ、レビューワーにしてみれば、限定地域だし、ウェブカメラで代替できるし、見ていても変化なくって面白くない、となるのも尤もな話です。
また、アプリ申請時のレビューワーへのメッセージの項目には、どのような機能か、どのようにテストするのかを中心に記載しましたが、アプリの利用シーンやメリットは、あまり訴求していなかったのも原因の1つではないかと思っています。
4.2リジェクトへの対応
4.2リジェクトの原因が推定できたので、次の点を訴求するメッセージを送付しました。
- 秩父の雲海はとても綺麗で、多くの人が雲海を見に訪れる
- 雲海の発生は稀であり、雨の翌日、寒い夜に、風がなく、湿度が 100% の時だけに発生
- 多くの人が山の上の観測ポイントから、凍えながら雲海の発生を待っている
- このアプリがあれば、家で雲海の発生通知を待ってから、車で雲海を見に行けるので、とても便利
さらに、このことを補強するために、次のような画像のURLを複数添付し、秩父の煌めく雲海はとても美しく、多くの人が訪れることを強調しました。
Copyright 荻原修司さん @osamuchichibuun
Copyright 荻原修司さん @osamuchichibuun結果
結果として、メッセージ送付後、30分ほどでレビュー再開となり、さらに3分後に承認されました。時系列は次の通りです。
- 2020/11/23 03:24 iPhone, iPad版: 承認
この時点で、iPhone, iPad版は承認されていた。
- 2020/11/25 01:01 macOS版: レビュー開始
- 2020/11/25 01:50 macOS版: 4.2でリジェクト
- 2020/11/25 10:17 macOS版: 反論のメッセージを送付
- 2020/11/25 10:39 macOS版: レビュー再開のメッセージ
- 2020/11/25 10:42 macOS版: 承認メッセージ
おわりに
アプリの申請の際には、レビューワーへのメッセージに、アプリの利用シーンやメリットも訴求しておいた方が良さそうです。また、4.2リジェクトをとなってしまった場合にも、なぜリジェクトになってしまったのか、レビューワーの立場で考えてみると、解決策が見えてくるかもしれません。
- 投稿日:2020-11-28T17:01:27+09:00
生成しないenum
Qiita Advent Calendar 2020 Swift 2日目のエントリーです。
1日目「Swiftに搭載予定のC++相互運用機能」 は、
C++コードをSwiftコードとして呼び出せるようになる夢のような話でした。
ますますSwiftの時代ですね。書いたこと
case値を持たないenumは生成することが出来ません。
その特性を利用して、enumは以下のような使い方も出来ます。
- 名前空間としてのenum
- 演算子の型を表すenum
名前空間としてのenum
SwiftはObjective-C同様、名前空間は正式サポートされていませんが、標準スタンダードライブラリーで用意されているUnicodeのようにUtility的な機能や定数をまとめる際に、名前空間としてenumが使用されます。
enum内部では以下の宣言が出来ます。
typealias
struct
/enum
/class
static let
/static val
/static func
使い所としては、以下のような場面が想定されます。
- 定数をまとめる
- Utility的な機能をまとめる
例1: 定数をまとめる
enumで定数をまとめる.swiftenum ? { static let name = "Mike" static let age = 32 static let gender = 0 } print(?.name);例2: Utility的な関数をまとめる
enumでUtility的な関数をまとめる.swiftenum Qiita { // URLを返す static func commentURL(_ commentId: String) -> URL { return URL(string: "\(scheme)://\(host)/\(Path.comments)/\(commentId)")! } // 定数をまとめる private static let scheme = "https" private static let host = "qiita" private enum Path { static let comments = "comments" static let users = "users" static let tags = "tags" } } print(Qiita.commentURL("14159265359"))演算子の型定義
Arrayの添字に、
...
演算子を渡すことが出来ます。Arrayはカスタムな演算子を添え字に渡せる.swiftlet fruits = ["?", "?", "?", "?", "?"] let sliced = fruits[...] // ArraySlice<String> ["?", "?", "?", "?", "?"]これは以下のようにメソッドが定義されているためです。
array.swiftsubscript(x: (UnboundedRange_) -> ()) -> ArraySlice<Element> { get set }この
UnboundedRange_
がenumです。UnboundedRange_.swiftpublic enum UnboundedRange_ { public static postfix func ... (_: UnboundedRange_) -> () { fatalError("uncallable") } }
UnboundedRange_
内で、(UnboundedRange_) -> ()
型が返すカスタム演算子...
が定義されています。これによって
(UnboundedRange_) -> ()
で宣言された引数に、...
演算子を渡すことが出来ます。まとめ
正式に名前空間がサポートされているC++、C#、PHPでは、名前空間を省略するための機能も用意されています。
(C++,C#ならば、using
、PHPならば、use
)Swiftではどちらかというと上記の役割はモジュールが担っているところがあり、複雑になりかねないためサポートに踏み切れない理由がありそうです。
参照
以下の記事をまとめました。
https://qiita.com/takasek/items/3497188559fbf717751b
→ 「なぜ名前空間としてenumが使われるのか」について詳細に解説されています。https://qiita.com/ysn/items/29363f0f6cc78d1d6561
→ カスタムの演算子の型の作り方を解説しています。https://qiita.com/YOCKOW/items/dd409b9588f4be72f58f
→ UnboundedRange_型についてはこちらで詳しく解説されています。Array[...]の存在はこちらの記事でしりました。
- 投稿日:2020-11-28T16:46:08+09:00
【備忘録】Swift 実践入門について 第3章
Swift 実践入門のまとめ。
分からない部分の抜粋も記載し、解決できたら随時更新していきます。
なお、ここに記載している以外でも「わけわからん…」となっている部分も多々ありますが、
今は必要ない、と言い聞かせて飛ばしています。第3章
型(Intなど)
理解度:80%くらいか3.2
Bool型の論理積…true or falseの世界で、2つのBool型がともにtrueの場合のみtrueと考えること。&&で記入する。
Bool型の論理和…true or falseの世界で、2つのBool型のどちらかがtrueの場合trueと考えること。||で記入する。3.3
数値型は以下のように変更することができる。
let a : Float = 1.0
let b : Double = Double(a) //このDouble(a)の部分をイニシャライザと呼んでいる。
これはString型でもString(a)で“1.0”のように可能。3.4
文字列リテラルでは特殊文字を使うことで改行などを可能にしている。
\n これで改行 などなど3.5
Optional型は何となく理解している感じがする。これって「こんな設定を切り分けて作っていくのかスゲー!」って感動しそうだけど、そこまでなってない。実践で実感がわかないとダメかな。
アンラップ…値の取り出し→if-let文、??演算子、強制アンラップ
プロパティ名?、メソッド名?は、値があれば?以降のプロパティやメソッドへアクセスし、
nilならアクセスしない。3.6
Any型について。どんな型も取れるが、四則演算などのその型でできることができなくなる。3.7
タプル型について。複数の型を指定することができて、値を取り出すことも容易。
let tpl = (123, "ABC", true)
print(tpl.0) //123
print(tpl.1) //"ABC"
print(tpl.2) //true3.8
キャストについて。ある型を別の型として扱う操作をキャストという。Any→IntやInt→Anyなど。
アップキャスト(具体から抽象へ)…123 as Any
ダウンキャスト(抽象から具体へ)…any as? Int もしくは any as! Int3.9
値の比較のためのプロトコル
プロトコルとは…プロパティやメソッドなどが持つ最低限のルール。
ここでは==とComparableプロトコル(<>などの比較)安全性を保つためにそれぞれの値にはそれぞれの型がある。Any型をどこで使うかは実践が必要。
- 投稿日:2020-11-28T16:24:12+09:00
Apollo iOS チュートリアルにて「Command PhaseScriptExecution failed with a nonzero exit code」のエラーが出た際の対応
Apollo iOS チュートリアル(1)を実施したときに発生したエラーの対応
Apollo iOS チュートリアル(1)を実施している途中、「9. Xcodeでのコード生成 - API.swift」の(2)以前にコメント化したコード(codegen:generateを含む)のコメントアウトを解除を実施したところで、「Command PhaseScriptExecution failed with a nonzero exit code」とエラーになりました。
「Build Phases」の「Apollo CLI」にあるSwift Package Manager Run Scriptのコメントアウトを解除しますが、そのままではエラーになります。解決
issueにあるようにスクリプトを変更することでビルドが通りました。
"${SCRIPT_PATH}"/check-and-run-apollo-cli.sh codegen:generate --target=swift --includes=.//*.graphql --localSchemaFile="schema.json" API.swift↓
"${SCRIPT_PATH}"/run-bundled-codegen.sh codegen:generate --target=swift --includes=.//*.graphql --localSchemaFile="schema.json" API.swift
- 投稿日:2020-11-28T16:08:12+09:00
Swiftって何?特徴は?
本記事は、swiftって何をするための言語?swiftを使うメリットは?
というプログラミング初心者向けの記事になります。<目次>
- 1. swift とは
- 2. swift の特徴
1. Swift とは
SwiftはiOS、macOS向けアプリケーションの開発言語として
Appleが発表したプログラミング言語です。
swiftの発表後に登場したwatchOSやiPadOSにも対応しています。iOS
-> iPhoneに搭載されているOS(オペレーティングシステム)
macOS
-> macに搭載されているOS(オペレーティングシステム)
世の中には、他にもWindows や Linux 、 Android などといった様々なOSが存在します。2. Swift の特徴
swiftは、様々な人や企業が利用しています。
当たり前ですが、利用される多くの理由があるからです。swiftには様々な特徴が存在するのでそれについて紹介していきます。
2-1. 静的型付き言語
静的型付き言語とは、コンパイル時などの実行前の段階で変数や定数の型を決定するプログラミング言語のことです。
もちろん実行するまで変数の型が決まっていない動的型付き言語も存在します。もちろんどちらの言語体系にもメリット・デメリットは存在しますが、
今回は静的型付き言語のメリットのみ紹介します。静的型付き言語の最大のメリットといえば,
誤った型の値の代入などがコンパイルエラーとして検出されるため、
実行時のエラーの一部を未然に防げるというところです。var a: Int //変数はInt型 a = 1000 //Int型の代入はコンパイルエラーは発生しない。 a = "こんにちは" //String型の代入はコンパイルエラーが発生する。上記のようなわかりやすいミスはほぼ起こりえませんが、
コードを書いている上で型の違いは稀に起こります。実行後にエラーが発生し、アプリが強制終了してから型の不一致に気づくようでは、
機能が完成してから間違いに気づくようなものです。型が違うだけで多くのコードを書き直さなければならない可能性も出てきます。
そうなると機能の再設計をしなければならない場合も...。それを未然に防ぐことが出来るのが静的型付き言語の最大の特徴とも言えると思っています。
2-2. nilの許容性をコントロールすることが出来る
まずはじめに、nilとは何も値が存在しないことを示すものです。
別のプログラミング言語だとnullやnoneのような、別の名前で存在することもあります。多くのプログラミング言語では、
値が存在しない状態や参照先が存在しない状態を表す際に使われてきました。一方で、nilが存在するが故に実行時エラーを招いてしまうという問題もありました。
(nilは厄介なことに、コンパイル時にエラーが発生しませんでした。)しかし、そのような問題を回避するために、
Swiftでは基本的にはnilを変数や定数に代入出来ないようになっています。ですが、nilを許容する特別な型の変数や定数にのみnilを代入出来る仕様になっています。
nilを代入出来る型と、代入出来ない型の違いは次のようになります。
var a: Int //nilを代入するとエラーが起きる var b: Int! //nilを代入してもエラーが起きない var c: Int? //nilを代入してもエラーが起きないこのように型の最後に ! や ? を付けることによりnilを許容するInt型になります。
オプショナル型などと呼ばれるのですが、それについては後日記事にします。この状態で、print()というメソッドを使用すると下記のようになります。
print(a) //nilを許容出来ないInt型なのに値が入っておらずコンパイルエラーが発生する print(b) // nil print(c) // nil一時的に変数の中に値を入れたくない場合などは ! や ? をつけておくのがいいかもしれません。
2-3. 型推理による簡潔な記述が可能
この機能は個人的にかなり便利だなと思いました。
swiftの変数や定数には、Int型やString型など様々な方があるのですが、
型推理という仕組みが導入されているため明示的に型を宣言する必要がありません。つまり、どういうことかというと
var a: Int // Int型 var b = 10 // Int型 var c = "こんにちは" // String型このように、aのように明示的にInt型と宣言しても平気ですし、
bのように10という値(Int型の値)のみを入れても自動的にInt型ということになります。2-4. ジェネリクスによる汎用的な記述が可能
ジェネリクス・・・?
ワタシ、ヨコモジ、キライ・・・。少し調べてみたのですが、
なんでも特定の型に制限されない汎用的なプログラミングを書くための機能だとか。通常のプログラミングでは、関数の引数は
Int型やString型などの予め決められた型以外の引数を渡すことは出来ません。しかし、ジェネリクスを使用したプログラミングなら
様々な型を引数として渡すことが可能になります。今回は引数でもらった値をprintで出力する関数を作成したとします。
func printParameters<T>(_ x: T,_ y: T){ print(x) print(y) } printParameters(1,22) // 1 と 22 が表示されます。 printParameters("こんにちは","さようなら") // こんにちは と さようなら が表示されます。 printParameters(10,"こんにちは") // 型引数:T は同じ型を受け取るので Int型とString型を引数にするとエラーが発生するこのように、同じ関数でも様々な型を使用することが出来るのがジェネリクスになります。
また、_(アンダースコア)には引数名を使用しないという特別な意味があります。2-5. Objective-Cと連携可能
iOS,macOS向けのアプリケーション開発には、今まではObjective-Cが採用されていました。
アプリケーションを開発するために使用するフレームワークは、
macOS向けのCocoa、iOS, watchOS, tvOS向けのCocoa Touch が提供されています。Cocoaの大部分はObjective-Cで記載されているため、
Objective-Cと高い互換性を持っているswiftはCocoaの利用も可能となっています。これまで使用してきたObjective-Cのコードはほぼ全てswiftで使用可能なので、
Objective-Cでサービスを作った企業もswiftへの段階的な移行は楽ですね!これから新しい言語が出た時は、
swiftから楽に移行できる仕様になっていると嬉しいですね!以上、最後まで読んでいただきありがとうございました。
- 投稿日:2020-11-28T16:08:12+09:00
Swiftがこんなに使われる理由
本記事は、swiftって何をするための言語?swiftを使うメリットは?
というプログラミング初心者向けの記事になります。<目次>
- 1. swift とは
- 2. swift の特徴
1. Swift とは
SwiftはiOS、macOS向けアプリケーションの開発言語として
Appleが発表したプログラミング言語です。
swiftの発表後に登場したwatchOSやiPadOSにも対応しています。iOS
-> iPhoneに搭載されているOS(オペレーティングシステム)
macOS
-> macに搭載されているOS(オペレーティングシステム)
世の中には、他にもWindows や Linux 、 Android などといった様々なOSが存在します。2. Swift の特徴
swiftは、様々な人や企業が利用しています。
当たり前ですが、利用される多くの理由があるからです。swiftには様々な特徴が存在するのでそれについて紹介していきます。
2-1. 静的型付き言語
静的型付き言語とは、コンパイル時などの実行前の段階で変数や定数の型を決定するプログラミング言語のことです。
もちろん実行するまで変数の型が決まっていない動的型付き言語も存在します。もちろんどちらの言語体系にもメリット・デメリットは存在しますが、
今回は静的型付き言語のメリットのみ紹介します。静的型付き言語の最大のメリットといえば,
誤った型の値の代入などがコンパイルエラーとして検出されるため、
実行時のエラーの一部を未然に防げるというところです。var a: Int //変数はInt型 a = 1000 //Int型の代入はコンパイルエラーは発生しない。 a = "こんにちは" //String型の代入はコンパイルエラーが発生する。上記のようなわかりやすいミスはほぼ起こりえませんが、
コードを書いている上で型の違いは稀に起こります。実行後にエラーが発生し、アプリが強制終了してから型の不一致に気づくようでは、
機能が完成してから間違いに気づくようなものです。型が違うだけで多くのコードを書き直さなければならない可能性も出てきます。
そうなると機能の再設計をしなければならない場合も...。それを未然に防ぐことが出来るのが静的型付き言語の最大の特徴とも言えると思っています。
2-2. nilの許容性をコントロールすることが出来る
まずはじめに、nilとは何も値が存在しないことを示すものです。
別のプログラミング言語だとnullやnoneのような、別の名前で存在することもあります。多くのプログラミング言語では、
値が存在しない状態や参照先が存在しない状態を表す際に使われてきました。一方で、nilが存在するが故に実行時エラーを招いてしまうという問題もありました。
(nilは厄介なことに、コンパイル時にエラーが発生しませんでした。)しかし、そのような問題を回避するために、
Swiftでは基本的にはnilを変数や定数に代入出来ないようになっています。ですが、nilを許容する特別な型の変数や定数にのみnilを代入出来る仕様になっています。
nilを代入出来る型と、代入出来ない型の違いは次のようになります。
var a: Int //nilを代入するとエラーが起きる var b: Int! //nilを代入してもエラーが起きない var c: Int? //nilを代入してもエラーが起きないこのように型の最後に ! や ? を付けることによりnilを許容するInt型になります。
オプショナル型などと呼ばれるのですが、それについては後日記事にします。この状態で、print()というメソッドを使用すると下記のようになります。
print(a) //nilを許容出来ないInt型なのに値が入っておらずコンパイルエラーが発生する print(b) // nil print(c) // nil一時的に変数の中に値を入れたくない場合などは ! や ? をつけておくのがいいかもしれません。
2-3. 型推理による簡潔な記述が可能
この機能は個人的にかなり便利だなと思いました。
swiftの変数や定数には、Int型やString型など様々な方があるのですが、
型推理という仕組みが導入されているため明示的に型を宣言する必要がありません。つまり、どういうことかというと
var a: Int // Int型 var b = 10 // Int型 var c = "こんにちは" // String型このように、aのように明示的にInt型と宣言しても平気ですし、
bのように10という値(Int型の値)のみを入れても自動的にInt型ということになります。2-4. ジェネリクスによる汎用的な記述が可能
ジェネリクス・・・?
ワタシ、ヨコモジ、キライ・・・。少し調べてみたのですが、
なんでも特定の型に制限されない汎用的なプログラミングを書くための機能だとか。通常のプログラミングでは、関数の引数は
Int型やString型などの予め決められた型以外の引数を渡すことは出来ません。しかし、ジェネリクスを使用したプログラミングなら
様々な型を引数として渡すことが可能になります。今回は引数でもらった値をprintで出力する関数を作成したとします。
func printParameters<T>(_ x: T,_ y: T){ print(x) print(y) } printParameters(1,22) // 1 と 22 が表示されます。 printParameters("こんにちは","さようなら") // こんにちは と さようなら が表示されます。 printParameters(10,"こんにちは") // 型引数:T は同じ型を受け取るので Int型とString型を引数にするとエラーが発生するこのように、同じ関数でも様々な型を使用することが出来るのがジェネリクスになります。
また、_(アンダースコア)には引数名を使用しないという特別な意味があります。2-5. Objective-Cと連携可能
iOS,macOS向けのアプリケーション開発には、今まではObjective-Cが採用されていました。
アプリケーションを開発するために使用するフレームワークは、
macOS向けのCocoa、iOS, watchOS, tvOS向けのCocoa Touch が提供されています。Cocoaの大部分はObjective-Cで記載されているため、
Objective-Cと高い互換性を持っているswiftはCocoaの利用も可能となっています。これまで使用してきたObjective-Cのコードはほぼ全てswiftで使用可能なので、
Objective-Cでサービスを作った企業もswiftへの段階的な移行は楽ですね!これから新しい言語が出た時は、
swiftから楽に移行できる仕様になっていると嬉しいですね!以上、最後まで読んでいただきありがとうございました。
- 投稿日:2020-11-28T15:42:36+09:00
【超基礎】Swiftを触ったことの無い方が、「調べながらやれば形にできる」ようにする記事
なぜ書くか
Swiftを最近学習し始めたのですが、
「まずXcodeも使った事がねえし、なにからすればいいんだ?」
「いきなり「ボタンをつけて」なんて書かれてもつけ方が分からねぇよ」
っていう状態に陥り、苦しかったので、僕のように情報収集よりも先に手を動かしてしまうような横着な方のために、
「これさえわかればまぁあとは調べたら少しずつは進めるんじゃ無いの?」
と、思えるような記事を書いてみたいと思います。今回は例として「カウントアップアプリ」を作成してみます。
特定の数字になったらページ遷移をして、その特定の数字を次のページにも表紙するというのを仕様とします。まだインストールされていない方は「Apple Store」から「Xcode」をインストールしましょう。
その1 「インストールしてみて開いたもののこれどうやって開発始めるの?」
情報収集せずにいきなり作ろうとすると誰しもこの状態であると、僕は信じています。
イケイケが一番ですよ。素晴らしい。この記事を見る横着なあなたはガンガン進めていきたいはずなので、必要な部分(僕がやったことある事)だけ抜粋してお伝えします。
まずApple Storeからインストールしたアプリを開くと、こんなのが出てくると思います。
この項目の中の一番上、「Create a new Xcode Project」をクリックしてください。
次に、こんなのが出てくると思います。
とりあえず、なにも考えず「ios」の「App」を選択し、「Next」を押しましょう。
僕自身結構悩んでしまったのですが、「Single View Applicationを選択して下さい」という情報が、結構調べるとヒットするのですが、そんなもんねーよとなった方もいらっしゃると思います。そういう際は「App」を選んでしまってなにも問題はありません。「Next」を押すと次はこんなのが出てきます。
なんだかすごく面倒くさそうで、嫌いですねこういうの。まぁ意外とすぐに素っ飛ばせます。
このような感じで入力してください。(矢印のついてないところはマルパクリでOKです。)
細かい部分が気になってしまうなら、一度調べてみてください。入力して「Next」をポチッと。
そうすると、どこのディレクトリ(フォルダ)に、プロジェクトファイルを置くかを選択する画面が出てくるので、自分の好きな場所に突っ込んでください。ディレクトリを選ぶと、下記の画面に遷移します。
めでたしめでたし。
ここからやっと開発を始められます。その2 「どこになにを書けばいいの?は?」
わかります。一緒に頑張りましょう。
まずはそうですね、UIから始めましょう。画面左の、ディレクトリ一覧の中から「Main.storyboard」をポチッと。
ちなみにですが、ビューを構築する上で、土台となる機種は画面左上の、左から四番目のボタンを押すといろいろ機種を変えられます。今回は「iPhone 12 Pro」にしようかな。(なんでもいいですよ)
この「Main.storyboard」上で、様々な要素を配置したり、画面遷移の流れを作ったり、本当に簡単に様々な事ができてしまいます。基本的にボタンを配置したりテキストをおいたりするのはここでやればいいと思います。
さて次に、ロジックを書いたり(今のところは)する場所ですね。
画面左側のディレクトリ階層の中から、「ViewController.swift」をポチッと。
はい。ここに当面はロジックを書いていけばいいと思います。
ちなみに、この「ViewController」は画面一つにつき1ファイルが基本です。その3 「んで、ボタンやらラベルやらの、要素はどうやって配置するの?」
次にこれですね。「HTML&CSSを何処かに置くのかな?」なんて思っていましたがそんなことする必要もなかったです。
そしてディスプレイ(iPhoneやらiPadやら)が表示されていると思いますので、ディスプレイをクリックしたのちに、画面右上の「+」ボタンをタップしてみて下さい。
するとこんな画面が出現したと思います。
あとはなんとこれをドラッグ&ドロップするだけなのです...超簡単でしょう。
ひとまず、ボタン二つとラベル一つを置いてみました。
その4 「ラベルやらボタンやらのテキストや、見た目は、どうやって変えるんや?」
はいそうですね。おっしゃる通りです。
実はXcodeでは、テキストの色だったり、背景色だったりをコードを一切書かずに簡単に変える事ができてしまいます。まずはラベルのテキストやボタンの見た目を変えてみましょう。
先ほど追加したボタンの左側をタップして下さい。
下記のような画面が右側に表示されたかと思います。まずは字が小さくて見にくいので、字の大きさを変えていきましょう。
字の大きさを変えるには、右側のサイドバーの中の、「Font」の数値を変えるだけです。とりあえず、大きさは30くらいにしておきます。
続いてテキストを変更します。
テキストを変更するのは、主に2種類の方法があり
要素をダブルクリックして、入力モードにしてから入力するか、右側のサイドバーで変更することもできます。今回はサイドバーで変更してみます。(どちらの方法でも構いません)
今回は「+」という文字を入れました。
左側も同じ大きさにして、「ー」をテキストにしてみましょう。
このようになったかと思います。
Xcodeではこんなにいとも簡単に、テキストを変えたり、見た目を変化させたりできます。
その5 「んで、カウントアップする数字をどうやってラベルに反映するの?」
さていよいよここから基礎の中でも特に大切な部分です。
UIを作成する上で、一切コードを書いていないが故に、ロジックをビューに反映するという事がイメージつきづらいのでは無いでしょうか?ここでは実際に「+」ボタンを押せばカウントアップ、「ー」ボタンを押せばカウントダウンするロジックを組み、それをラベルに反映させていきたいと思います。
それでは「Main.storybord」を開いて、ラベルとボタンが表示されているディスプレイをクリックして下さい。
そして下記のボタンをクリックして下さい。そうすると、下記の画面が出現すると思いますので、「Assistant」をクリックして下さい。
すると上記のように、「Main.storybord」の画面とそれに対応する「ViewController.swift」が画面を二分し表示されたと思います。
今後は主にこの状態で作業をしていきます。まず、カウントアップするためのロジックを書きたいのですが、今回作るカウントアップアプリは「+」ボタンを押せばカウントアップし、「ー」を押せばカウントダウンするという仕様のため、まず双方のボタンのクリックをメソッド化しなければなりません。
まずクリックされた際に発動するメソッドの大枠を作成します。
ドラッグ&ドロップでクリックされた際のアクションの名前をつければOKです。
ViewController.swiftimport UIKit class ViewController: UIViewController { //変数として「countNumber」を用意し「0」を代入 var countNumber = 0 override func viewDidLoad() { super.viewDidLoad() } @IBAction func countUpButton(_ sender: UIButton) { } }これで「+」ボタンを押された際に走る処理の大枠ができました。
+ボタンと同じよう要領で「ー」ボタンにも「countDownButton」という名前をつけ大枠を作りましょう。ここで一気にロジックを書いていきます。
分からない方は都度調べながら実装していって下さい。ViewController.swiftimport UIKit class ViewController: UIViewController { //変数として「countNumber」を用意し「0」を代入 var countNumber = 0 override func viewDidLoad() { super.viewDidLoad() } @IBAction func countUpButton(_ sender: UIButton) { //「+」ボタンを押された際に走る処理 countNumber += 1 //ログに出力し確認する print(countNumber) } @IBAction func countDownButton(_ sender: UIButton) { //「ー」ボタンを押された際に走る処理 countNumber -= 1 //ログに出力し確認する print(countNumber) } }ここまでできちんと「countNumber」の値が増減しているか確認しましょう。
下記のViewをクリック
そして「Debug Area」の中の「Activate Console」をクリック
するとページ下部にフィールドができたと思います。ここにログが吐き出されます。
では早速シュミレーターを立ち上げ確認してみましょう。
画面上部の下記ボタンをクリックしてください。シュミレーターが立ち上がりますので少々お待ちを。
シュミレーターが立ち上がったら早速ボタンをクリックしてみましょう。
うまくいっていればログに「countNumber」の値が表示されるはずです。バッチリ変数が増減してますね。
では次に本筋であるラベルに「countNumber」の値を反映させていきます。
まずは+ボタンやーボタンのようにラベルも名前をつけViewControllerにつなげていきます。
ラベルを繋げる際の注意点として必ず「viewDidLoad()」メソッドよりも上に持ってくるようにしてください。
そして増減する「countNumber」の値を、「countNumberLabel」にはめ込んでいくロジックを書いていきます。ViewController.swiftimport UIKit class ViewController: UIViewController { //変数として「countNumber」を用意し「0」を代入 var countNumber = 0 @IBOutlet weak var countNumberLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() countNumberLabel.text = String(countNumber) } @IBAction func countUpButton(_ sender: UIButton) { //「+」ボタンを押された際に走る処理 countNumber += 1 //countNumberLabelに変数の値を代入する countNumberLabel.text = String(countNumber) } @IBAction func countDownButton(_ sender: UIButton) { //「-」ボタンを押された際に走る処理 countNumber -= 1 //countNumberLabelに変数の値を代入する countNumberLabel.text = String(countNumber) } }ここまでかけたら早速シュミレーターを立ち上げボタンを押してみましょう。
いかがでしょう。下記のようになれば成功です。
その6 「ボタンを押してのページ移動ってどうやってやるの?」
これがですね、衝撃的に簡単です。
ではまずページ移動するためのボタンを作成します。こんな感じで。色とかはマジでなんでもいいです。
次に移動先のviewを作成します。ページそのものもこのようにドラッグ&ドロップで簡単に追加できます。
ここに次のページに遷移したことがわかるよう、ラベルをおきましょう。こんな感じで。
では、ついに先ほど1ページ目に置いたボタンをクリックすると、2ページ目に遷移する流れを実装していきます
これがびっくりするほど簡単なのです。下記をご覧ください。
必殺ドラッグandドロップで終了です(笑)
早速シュミレーターで確認しましょう。バッチリですねぇ(Xcode恐ろしや)
その7 「じゃあ例えば、「カウントが10になったらページ移動する」みたいなのはどうするんや?」
ごもっともです。早速やりましょう。
まずはカウントが10になったことを取得します。
先ほどの画面遷移は「ボタンを押せば」という単純な画面遷移だったためにボタンを次の画面に繋ぐだけで遷移できました。
しかし今回は、カウントが10になった時になった時に遷移する、という少々複雑な条件のため先ほどとは少し違うやり方をします。まずは画面と画面を繋ぎますが少し繋ぎ方は先ほどとは変わります。
デバイスの上部をクリックし、一番左のボタンの「View Controller」と表示されるボタンを「control」押しながら、ドラッグ&ドロップで繋ぎます。
これでいわゆる遷移の準備、道が整備できました。次に、「countNumber」が10になったことを取得します。
下記のように書いてください。
ViewController.swiftimport UIKit class ViewController: UIViewController { //変数として「countNumber」を用意し「0」を代入 var countNumber = 0 @IBOutlet weak var countNumberLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() countNumberLabel.text = String(countNumber) } @IBAction func countUpButton(_ sender: UIButton) { //「+」ボタンを押された際に走る処理 countNumber += 1 //カウントが10を超えた時に{}の中の処理が実行される if countNumber >= 10 { } //countNumberLabelに変数の値を代入する countNumberLabel.text = String(countNumber) } @IBAction func countDownButton(_ sender: UIButton) { //「-」ボタンを押された際に走る処理 countNumber -= 1 //countNumberLabelに変数の値を代入する countNumberLabel.text = String(countNumber) } }上記の中で、
//カウントが10を超えた時に{}の中の処理が実行される if countNumber >= 10 { }この部分が新たに追加されました。
この中に画面遷移をする処理を、コードにして書いていきます。その前にまずその道を識別できるよう道に名前をつけます。
「toNextViewController」という名前を道につけます。
さて、ついに「countNumber」が10になった時に画面遷移をするコードを先ほどのif文の中に書いていきます。
コードは次のようになります。//カウントが10を超えた時に{}の中の処理が実行される if countNumber >= 10 { //下記一行を追加 self.performSegue(withIdentifier: "toNextViewController", sender: nil) }これでシュミレーターを立ち上げてカウントを10にしてみましょう。
カウントが10になったらページ遷移する動きが実現できていると思います。
その8 「あれ?2ページ目のロジックはどこに書くんや!?ファイルってどうやって追加するんや!?どうやってページとファイルを繋げるんや!?」
はいではまずファイルを作っていきます。
下記の場所を、controlキーを押しながらクリックしてください。そうするといろいろ選択肢が出るので、「New File...」を選んでください。
そうすると下記のような画面が出てくると思いますので、「Cocoa Touch Class」を選んでNextを押してください。
次にClass名を入力する画面が出てきますので好きなクラス名をつけてください。
私は「NextViewController」とつけます。はいこれで2ページ目のファイルを用意することができました。が!これでは不十分です。
実はこのファイルまだ、先ほどのに遷移先のビュートは繋がっていないのです。
ですので実際につないでいきます。Main.storybordを開いて、遷移先のページをクリックしてください。
すると右側にサイドバーが出てきますので下記のボタンをクリックしてください。そしてその中のClassという欄に先ほど設定したクラス名を入力してください。
これで完了です。さあ繋がっているか確かめましょう。
2ページ目をクリックして...Assistantボタンをクリック!
先ほど作成したファイルが表示されれば完璧です!
その9 「ページ遷移する前のページのデータを次のページに表示したいときはどうするんや?」
これ僕もかなり行き詰りましたし、やり方も結構多岐に渡るので、ここでは一つのやり方をご紹介します。
先ほどのカウントが10になった時に画面遷移して次の画面にカウントを渡してみます。まず、遷移先のページにラベルを配置し、データを表示するためのフィールドを用意します。
こんな感じでいいと思います。2桁表示の際のことも考え、少し横幅は広めに用意しましょう。
次に遷移先のページに、1ページ目からのデータを受け取るための変数を用意します。
NextViewController.swiftimport UIKit class NextViewController: UIViewController { //遷移元のページから値を受け取るための変数 var countNumber: Int? override func viewDidLoad() { super.viewDidLoad() } }はいこれで受け取る準備はOKです。
次にラベルとControllerを繋げ、受け取った変数をラベルに表示できるようにします。次に受け取った値を、ラベルに反映させます。
NextViewController.swiftimport UIKit class NextViewController: UIViewController { //遷移元のページから値を受け取るための変数 var countNumber: Int? @IBOutlet weak var countNumberLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() //ラベルのテキストに値を反映させる countNumberLabel.text = String(countNumber!) } }これで遷移先の準備はOKです。
次に、遷移元のページに値を渡しながら遷移するコードを書いていきます。
ViewController.swiftを表示してください。
早速処理を書きます。ViewController.swiftimport UIKit class ViewController: UIViewController { //変数として「countNumber」を用意し「0」を代入 var countNumber = 0 @IBOutlet weak var countNumberLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() countNumberLabel.text = String(countNumber) } @IBAction func countUpButton(_ sender: UIButton) { //「+」ボタンを押された際に走る処理 countNumber += 1 //カウントが10を超えた時に{}の中の処理が実行される if countNumber >= 10 { self.performSegue(withIdentifier: "toNextViewController", sender: nil) } //countNumberLabelに変数の値を代入する countNumberLabel.text = String(countNumber) } @IBAction func countDownButton(_ sender: UIButton) { //「-」ボタンを押された際に走る処理 countNumber -= 1 //countNumberLabelに変数の値を代入する countNumberLabel.text = String(countNumber) } //追加したメソッド //画面遷移をする際に呼ばれるメソッド override func prepare(for segue: UIStoryboardSegue, sender: Any?) { //値を渡したいコントローラーかチェックする if segue.identifier == "toNextViewController" { //遷移先のコントローラーをインスタンス化する let nextVC = segue.destination as! NextViewController //インスタンス化した遷移先のコントローラーの用意した変数に渡す nextVC.countNumber = countNumber } } }最下部のメソッドが今回実際に値渡しを行うメソッドです。
遷移先のページをインスタンス化し、そのページの用意している変数に値を入れています。では実際に値が渡っているかチェックしましょう。
しっかり遷移先にページが渡っていますね。めでたしめでたし
その10 「大体これで調べたら、進めそうだわ、最後に戻るボタンつけたいんだけど、どうすればいい??」
戻るボタン。大事ですね。実装しましょう。
まずボタンを配置します。
はいこんな感じで。(かっこいい...)
では作成したボタンとControllerをつないでいきます。
いつものドラッグアンドドロップで。
この中に元のページに戻るためのコードを書いていきます。
追加するのは一行だけです。NextViewControllerdismiss(animated: true, completion: nil)先ほどのメソッドの中に追記してください。
では挙動を確認しましょう。戻る動き、そして、10を超えているカウントである限り変更されるたびに画面遷移する動きが実現できていますね。
まとめ
シンプルにめちゃくちゃ使いやすいですねXcode
初めはどうしても「調べたらロジックを組める段階」に持っていくまでが苦痛だったので、この記事を読み、僕のようにせっかちの方が、快適に開発を始められるようになることを願っています?
- 投稿日:2020-11-28T12:52:42+09:00
【備忘録】Swift 実践入門について 第2章
Swift 実践入門のまとめ。
分からない部分の抜粋も記載し、解決できたら随時更新していきます。
なお、ここに記載している以外でも「わけわからん…」となっている部分も多々ありますが、
今は必要ない、と言い聞かせて飛ばしています。第2章
変数・定数
理解度:80%くらいか
2.1~2.3まではまだOK。2.4 式の組み立て…変数・定数に何を入れることができるのか
メンバー式…変数aに値を代入し、a.countなどで表すこと。変数のcountなどのことをプロパティ。
クロージャ式…1つの処理のカタマリ。関数と異なり関数名などが不要。本書では以下の例で示されていた。
let original = [1, 2, 3]
let doubled = original.map({value in value * 2})
doubled //[2, 4, 6]
この例では定数に処理のカタマリも持ってこれますよ、という紹介か。
- 投稿日:2020-11-28T09:47:07+09:00
Swift 動画再生のまとめ
動画を再生する方法、二通りを試しましたのでメモしておきます。
・AVPlayerViewControllerをそのまま使用
ソースコード量が少ない、便利
・AVPlayerでDIY実装
再生/停止ボタン、ProgressBar、Silder、TimeすべてDIY可能実装する前に、httpのURL動画を扱うために、info.plistをOpen as source codeで開いて
の箇所で以下を追加します。<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict>・AVPlayerViewController
AVKitをImportする必要がある
URLはネット動画のURL
ローカル動画のfilePathを使います。let videoURL = URL(fileURLWithPath: "/Users/yourname/Library/Developer/CoreSimulator/Devices/BB015206-A61D-43F8-8A6B-9C8AD07B827A/data/Media/DCIM/100APPLE/IMG_0008.MP4")let videoURL = URL(string: "http://0000.mp4") let player = AVPlayer(url: videoURL!) let playerViewController = AVPlayerViewController() playerViewController.player = player self.present(playerViewController, animated: true) { playerViewController.player!.play() }AVPlayerでDIYする場合、以下三つのパーツが欠かせません。
・AVPlayerItem どの動画を再生するか、対象やデータを管理したりする。
・AVPlayer 再生、停止などの操作系。
・AVPlayerLayer 動画を表示する役割。いろいろなパーツをDIYすることを考えると、PlayerViewを作り、
ViewControllerでPlayerViewを使うようにします。では、まずPlayerViewを作ります。
Layoutはxibファイル内で制御します。import UIKit import AVFoundation class PlayerView: UIView { var playerLayer:AVPlayerLayer? override func layoutSubviews() { super.layoutSubviews() playerLayer?.frame = self.bounds } }次にStoryBoardでViewControllerを作り、新規ViewをPlayerViewタイプに指定します。
AVPlayerItem、AVPlayer、AVPlayerLayerも作ります。class ViewController: UIViewController { var playerItem:AVPlayerItem! var avplayer:AVPlayer! var playerLayer:AVPlayerLayer! @IBOutlet var playerView: PlayerView! }次にViewDidLoad(画面表示してからすぐ再生)の中にどんどん追加していきます。
override func viewDidLoad() { super.viewDidLoad() let url = URL(fileURLWithPath: "/Users/name/Library/Developer/CoreSimulator/Devices/BB015206-A61D-43F8-8A6B-9C8AD07B827A/data/Media/DCIM/100APPLE/IMG_0008.MP4") playerItem = AVPlayerItem(url: url) //誰を再生するか決める // 状態監視 playerItem.addObserver(self, forKeyPath: "loadedTimeRanges", options: NSKeyValueObservingOptions.new, context: nil) playerItem.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.new, context: nil) self.avplayer = AVPlayer(playerItem: playerItem) playerLayer = AVPlayerLayer(player: avplayer) // 表示モードの設定 playerLayer.videoGravity = AVLayerVideoGravity.resizeAspect playerLayer.contentsScale = UIScreen.main.scale self.playerView.playerLayer = self.playerLayer self.playerView.layer.insertSublayer(playerLayer, at: 0) } //画面消える時にremove deinit{ playerItem.removeObserver(self, forKeyPath: "loadedTimeRanges") playerItem.removeObserver(self, forKeyPath: "status") } //監視イベント //Unknown 、ReadyToPlay 、 Failed状態があり、readyToPlayの際のみ再生 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { guard let playerItem = object as? AVPlayerItem else { return } if keyPath == "loadedTimeRanges"{ // TODO }else if keyPath == "status"{ if playerItem.status == AVPlayerItem.Status.readyToPlay{ self.avplayer.play() }else{ print("error") } } }これで再生できるはずです。でも、ボタンもProgressBarも何もないので引き続き実装に進みます。
●Time
PlayerViewにてCurrentTimeとTotalTimeを表示するtimeLabelを作成
ViewControllerにて秒数をStringへ変換するメソッドを作成func formatPlayTime(secounds:TimeInterval)->String{ if secounds.isNaN{ return "00:00" } let Min = Int(secounds / 60) let Sec = Int(secounds.truncatingRemainder(dividingBy: 60)) return String(format: "%02d:%02d", Min, Sec) }常に時間の変化を表示するため、CADisPlayLinkを生成して使います。
動画のコマごとにCADisPlayLinkが呼ばれます。self.link = CADisplayLink(target: self, selector: #selector(update)) self.link.add( to: RunLoop.main, forMode: RunLoop.Mode.default)updateの中身はこのように書きます。これで時間が表示できるようになります。
@objc func update(){ let currentTime = CMTimeGetSeconds(self.avplayer.currentTime()) let totalTime = NSTimeInterval(playerItem.duration.value) / NSTimeInterval(playerItem.duration.timescale) let timeStr = "\(formatPlayTime(currentTime))/\(formatPlayTime(totalTime))" playerView.timeLabel.text = timeStr }●ProgressBar
次に再生の進捗によってProgressBarも動くようにUISilderを使います。
StoryBoardでUISilderを追加したら、以下のように設定します。SilderのPinのImageを変えるため、以下を書きます。
@IBOutlet weak var slider: UISlider!{ didSet{ slider.setThumbImage(UIImage(systemName: "bolt.fill"), for: UIControl.State.normal) } }Pinが動くようにUpdateメソッドに以下を追加します。
self.testPlayerView.slider.value = Float(currentTime/totalTime)●ProgressBarの動作
この時点ではProgressBarをドラッグしたりできませんので次に動作の処理をします。
awakeFromNibの中で追加します。念のため指を離した時は三つイベントを監視します。// タップした時 slider.addTarget(self, action: #selector(sliderTouchDown), for: UIControl.Event.touchDown) // 指を離した時 slider.addTarget(self, action: #selector(sliderTouchUpOut), for: UIControl.Event.touchUpOutside) slider.addTarget(self, action: #selector(sliderTouchUpOut), for: UIControl.Event.touchUpInside) slider.addTarget(self, action: #selector(sliderTouchUpOut), for: UIControl.Event.touchCancel)ドラッグしている間では動画の再生進捗を変え続けたくないので、sliding を追加します。
ここでProtocalが登場します。protocol TestPlayerViewDelegate:NSObjectProtocol { func testPlayer(playerView:PlayerView,sliderTouchUpOut slider:UISlider) }PlayerViewの中でdelegateを使います。
weak var delegate: TestPlayerViewDelegate?前のsliderTouchDown、sliderTouchUpOutのメソッドを書きます。
@objc func sliderTouchDown(slider:UISlider){ self.sliding = true } @objc func sliderTouchUpOut(slider:UISlider){ delegate?.testPlayer(playerView: self, sliderTouchUpOut: slider) }そしてViewControllerの中で実際に指を離した時の処理を書きます。
extension ViewController: TestPlayerViewDelegate{ func testPlayer(playerView: PlayerView, sliderTouchUpOut slider: UISlider) { let duration = slider.value * Float(CMTimeGetSeconds(self.avplayer.currentItem!.duration)) let seekTime = CMTimeMake(value: Int64(duration), timescale: 1) // 位置を特定 self.avplayer.seek(to: seekTime, completionHandler: { (b) in // sliding状態を戻す playerView.sliding = false }) } }ViewControllerのviewDidLoadでdelegateを忘れずに
self.playerView.delegate = selfUpdateメソッドのSilderが動く処理にもsliding状態の判断を追加します。
これでProgressBarはドラッグできるようになります。if !self.playerView.sliding{ // 播放进度 self.playerView.slider.value = Float(currentTime/totalTime) }●PlayAndPauseボタン
最後に再生/停止ボタンの処理を書きます。
Sliderの処理と似ていています。StoryBaordでボタンを設置、Layoutを設定します。
awakeFromNibの中で以下を追加します。playAndPauseBtn.addTarget(self, action: #selector(playAndPause) , for: UIControl.Event.touchUpInside)メソッドを追加します。
再生と停止の処理もDelegateの中で書きます。@objc func playAndPause(btn:UIButton){ let tmp = !playing playing = tmp if playing { playAndPauseBtn.setImage(UIImage(systemName: "stop.circle"), for: UIControl.State.normal) }else{ playAndPauseBtn.setImage(UIImage(systemName: "play.circle"), for: UIControl.State.normal) } delegate?.testPlayer(playerView: self, playAndPause: btn) }protocol TestPlayerViewDelegate:NSObjectProtocol { func testPlayer(playerView:PlayerView,sliderTouchUpOut slider:UISlider) func testPlayer(playerView:PlayerView,playAndPause playBtn:UIButton) }ViewControllerでメソッドを書きます。これで再生や停止ができるようになります。
func testPlayer(playerView: PlayerView, playAndPause playBtn: UIButton) { if !playerView.playing{ self.avplayer.pause() }else{ if self.avplayer.status == AVPlayer.Status.readyToPlay{ self.avplayer.play() } } }
- 投稿日:2020-11-28T09:47:07+09:00
Swift 動画再生をAVPlayerでDIYする
動画を再生する方法、二通りを試しましたのでメモしておきます。
・AVPlayerViewControllerをそのまま使用
ソースコード量が少ない、便利
・AVPlayerでDIY実装
再生/停止ボタン、ProgressBar、Silder、TimeすべてDIY可能実装する前に、httpのURL動画を扱うために、info.plistをOpen as source codeで開いて
の箇所で以下を追加します。<key>NSAppTransportSecurity</key> <dict> <key>NSAllowsArbitraryLoads</key> <true/> </dict>・AVPlayerViewController
AVKitをImportする必要がある
URLはネット動画のURL
ローカル動画のfilePathを使います。let videoURL = URL(fileURLWithPath: "/Users/yourname/Library/Developer/CoreSimulator/Devices/BB015206-A61D-43F8-8A6B-9C8AD07B827A/data/Media/DCIM/100APPLE/IMG_0008.MP4")let videoURL = URL(string: "http://0000.mp4") let player = AVPlayer(url: videoURL!) let playerViewController = AVPlayerViewController() playerViewController.player = player self.present(playerViewController, animated: true) { playerViewController.player!.play() }AVPlayerでDIYする場合、以下三つのパーツが欠かせません。
・AVPlayerItem どの動画を再生するか、対象やデータを管理したりする。
・AVPlayer 再生、停止などの操作系。
・AVPlayerLayer 動画を表示する役割。いろいろなパーツをDIYすることを考えると、PlayerViewを作り、
ViewControllerでPlayerViewを使うようにします。では、まずPlayerViewを作ります。
Layoutはxibファイル内で制御します。import UIKit import AVFoundation class PlayerView: UIView { var playerLayer:AVPlayerLayer? override func layoutSubviews() { super.layoutSubviews() playerLayer?.frame = self.bounds } }次にStoryBoardでViewControllerを作り、新規ViewをPlayerViewタイプに指定します。
AVPlayerItem、AVPlayer、AVPlayerLayerも作ります。class ViewController: UIViewController { var playerItem:AVPlayerItem! var avplayer:AVPlayer! var playerLayer:AVPlayerLayer! @IBOutlet var playerView: PlayerView! }次にViewDidLoad(画面表示してからすぐ再生)の中にどんどん追加していきます。
override func viewDidLoad() { super.viewDidLoad() let url = URL(fileURLWithPath: "/Users/name/Library/Developer/CoreSimulator/Devices/BB015206-A61D-43F8-8A6B-9C8AD07B827A/data/Media/DCIM/100APPLE/IMG_0008.MP4") playerItem = AVPlayerItem(url: url) //誰を再生するか決める // 状態監視 playerItem.addObserver(self, forKeyPath: "loadedTimeRanges", options: NSKeyValueObservingOptions.new, context: nil) playerItem.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.new, context: nil) self.avplayer = AVPlayer(playerItem: playerItem) playerLayer = AVPlayerLayer(player: avplayer) // 表示モードの設定 playerLayer.videoGravity = AVLayerVideoGravity.resizeAspect playerLayer.contentsScale = UIScreen.main.scale self.playerView.playerLayer = self.playerLayer self.playerView.layer.insertSublayer(playerLayer, at: 0) } //画面消える時にremove deinit{ playerItem.removeObserver(self, forKeyPath: "loadedTimeRanges") playerItem.removeObserver(self, forKeyPath: "status") } //監視イベント //Unknown 、ReadyToPlay 、 Failed状態があり、readyToPlayの際のみ再生 override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) { guard let playerItem = object as? AVPlayerItem else { return } if keyPath == "loadedTimeRanges"{ // TODO }else if keyPath == "status"{ if playerItem.status == AVPlayerItem.Status.readyToPlay{ self.avplayer.play() }else{ print("error") } } }これで再生できるはずです。でも、ボタンもProgressBarも何もないので引き続き実装に進みます。
●Time
PlayerViewにてCurrentTimeとTotalTimeを表示するtimeLabelを作成
ViewControllerにて秒数をStringへ変換するメソッドを作成func formatPlayTime(secounds:TimeInterval)->String{ if secounds.isNaN{ return "00:00" } let Min = Int(secounds / 60) let Sec = Int(secounds.truncatingRemainder(dividingBy: 60)) return String(format: "%02d:%02d", Min, Sec) }常に時間の変化を表示するため、CADisPlayLinkを生成して使います。
動画のコマごとにCADisPlayLinkが呼ばれます。self.link = CADisplayLink(target: self, selector: #selector(update)) self.link.add( to: RunLoop.main, forMode: RunLoop.Mode.default)updateの中身はこのように書きます。これで時間が表示できるようになります。
@objc func update(){ let currentTime = CMTimeGetSeconds(self.avplayer.currentTime()) let totalTime = NSTimeInterval(playerItem.duration.value) / NSTimeInterval(playerItem.duration.timescale) let timeStr = "\(formatPlayTime(currentTime))/\(formatPlayTime(totalTime))" playerView.timeLabel.text = timeStr }●ProgressBar
次に再生の進捗によってProgressBarも動くようにUISilderを使います。
StoryBoardでUISilderを追加したら、以下のように設定します。SilderのPinのImageを変えるため、以下を書きます。
@IBOutlet weak var slider: UISlider!{ didSet{ slider.setThumbImage(UIImage(systemName: "bolt.fill"), for: UIControl.State.normal) } }Pinが動くようにUpdateメソッドに以下を追加します。
self.testPlayerView.slider.value = Float(currentTime/totalTime)●ProgressBarの動作
この時点ではProgressBarをドラッグしたりできませんので次に動作の処理をします。
awakeFromNibの中で追加します。念のため指を離した時は三つイベントを監視します。// タップした時 slider.addTarget(self, action: #selector(sliderTouchDown), for: UIControl.Event.touchDown) // 指を離した時 slider.addTarget(self, action: #selector(sliderTouchUpOut), for: UIControl.Event.touchUpOutside) slider.addTarget(self, action: #selector(sliderTouchUpOut), for: UIControl.Event.touchUpInside) slider.addTarget(self, action: #selector(sliderTouchUpOut), for: UIControl.Event.touchCancel)ドラッグしている間では動画の再生進捗を変え続けたくないので、sliding を追加します。
ここでProtocalが登場します。protocol TestPlayerViewDelegate:NSObjectProtocol { func testPlayer(playerView:PlayerView,sliderTouchUpOut slider:UISlider) }PlayerViewの中でdelegateを使います。
weak var delegate: TestPlayerViewDelegate?前のsliderTouchDown、sliderTouchUpOutのメソッドを書きます。
@objc func sliderTouchDown(slider:UISlider){ self.sliding = true } @objc func sliderTouchUpOut(slider:UISlider){ delegate?.testPlayer(playerView: self, sliderTouchUpOut: slider) }そしてViewControllerの中で実際に指を離した時の処理を書きます。
extension ViewController: TestPlayerViewDelegate{ func testPlayer(playerView: PlayerView, sliderTouchUpOut slider: UISlider) { let duration = slider.value * Float(CMTimeGetSeconds(self.avplayer.currentItem!.duration)) let seekTime = CMTimeMake(value: Int64(duration), timescale: 1) // 位置を特定 self.avplayer.seek(to: seekTime, completionHandler: { (b) in // sliding状態を戻す playerView.sliding = false }) } }ViewControllerのviewDidLoadでdelegateを忘れずに
self.playerView.delegate = selfUpdateメソッドのSilderが動く処理にもsliding状態の判断を追加します。
これでProgressBarはドラッグできるようになります。if !self.playerView.sliding{ // 播放进度 self.playerView.slider.value = Float(currentTime/totalTime) }●PlayAndPauseボタン
最後に再生/停止ボタンの処理を書きます。
Sliderの処理と似ていています。StoryBaordでボタンを設置、Layoutを設定します。
awakeFromNibの中で以下を追加します。playAndPauseBtn.addTarget(self, action: #selector(playAndPause) , for: UIControl.Event.touchUpInside)メソッドを追加します。
再生と停止の処理もDelegateの中で書きます。@objc func playAndPause(btn:UIButton){ let tmp = !playing playing = tmp if playing { playAndPauseBtn.setImage(UIImage(systemName: "stop.circle"), for: UIControl.State.normal) }else{ playAndPauseBtn.setImage(UIImage(systemName: "play.circle"), for: UIControl.State.normal) } delegate?.testPlayer(playerView: self, playAndPause: btn) }protocol TestPlayerViewDelegate:NSObjectProtocol { func testPlayer(playerView:PlayerView,sliderTouchUpOut slider:UISlider) func testPlayer(playerView:PlayerView,playAndPause playBtn:UIButton) }ViewControllerでメソッドを書きます。これで再生や停止ができるようになります。
func testPlayer(playerView: PlayerView, playAndPause playBtn: UIButton) { if !playerView.playing{ self.avplayer.pause() }else{ if self.avplayer.status == AVPlayer.Status.readyToPlay{ self.avplayer.play() } } }