20200518のiOSに関する記事は8件です。

【Swift】Optional型を安全に扱う5つの方法

Optional型とは

nilを代入できる型のこと。

let a: Int? = Int("42") // Optionalで宣言
let b: Optional<Int> = Int("42") // 上と同義

ただし他の型の変数に代入したりする場合、unwrapという処理が必要になる。
その際、nilが代入されている可能性があるので慎重に扱わなければなりません。
ここではOptional型を安全にunwrapして扱うための5つの方法をまとめました。

1. Force Unwrapping

変数の中身がなんであれとにかくunwrapする。
もしnilだった場合はruntime errorとなってしまうため、nilでないことが確かな場合に使うと良い。
"Unconditional Unwrapping"とも言うらしい。

let myOptioal: String? // Optional<String>で宣言
myOptional = "Angela" // 文字列を代入
print(myOptional!) // "Angela"が出力される
let myOptioal: String? // Optional<String>で宣言
myOptional = nil // nilを代入
print(myOptional!) // runtime errorとなる

2. Check for nil value

if statementでunwrapしようとしている変数の中身がnilかどうか判定し、trueの場合の処理でunwrapをする。

let myOptioal: String? // Optional<String>で宣言
myOptional = nil // nilを代入

if myOptional != nil {
  print(myOptional!) // nilでない場合のみunwrapするので安全
} else {
  print("myOptional was found to be nil") // nilだった場合はこちらの処理が実行される
}

unwrapする前にnilかどうか判定するので安全な一方で、毎回unwrapしなくてはならないので面倒。そんなときは次の"Optional Binding"が有効。

let myOptioal: String? // Optional<String>で宣言
myOptional = nil // nilを代入

if myOptional != nil {
  let text1: String = myOptional!
  let text2: String = myOptional!
  let text3: String = myOptional! // 毎回unwrapする必要がある
} else {
  print("myOptional was found to be nil")
}

3. Optional Binding

if statementで新たな定数にOptional型の変数を代入する。nilでない場合はif let safeOptional = myOptionalの判定はtrueになり、nilの場合は判定の結果がfalseになる。ブロックの中では非OptionalのString型であるsafeOptionalが使えるのでunwrapは不要となるので便利。

let myOptioal: String? // Optional<String>で宣言
myOptional = nil // nilを代入

if let safeOptional = myOptional { 
  // myOptionalがnilでない場合はtrueになりここの処理が実行される
  let text1: String = safeOptional
  let text2: String = safeOptional
  let text3: String = safeOptional //条件の箇所で宣言したsafeOptionalは非OptionalのStringなのでunwrapは不要
} else {
  // myOptionalがnilの場合はfalseになりここの処理が実行される
  print("myOptional was found to be nil")
}

4. Nil Coalescing Operator

Optional型の変数の中身がnilの場合にデフォルト値を与えたい場合に有効です。

COALESCEといえばSQLの関数にもありますね。"NULL以外の最初の引数を返す"というものです。
SwiftのNil Coalescing Operatorという演算子も同様の考え方が使えます。

optional ?? defaultValue // optionalがnilでない場合はoptinalを、nilの場合はdefaultValueを返す
let myOptioal: String? // Optional<String>で宣言
myOptional = "Angela" // 文字列を代入

let text: String = myOptional ?? "I am the default value"
print(text) // "Angela"が出力される
let myOptioal: String? // Optional<String>で宣言
myOptional = nil // nilを代入

let text: String = myOptional ?? "I am the default value"
print(text) // "I am the default value"が出力される

5. Optional Chaining

これまではOptional型のStringなどを見てきましたがstructのインスタンスの場合はどうでしょうか。Optional型のユーザー定義のstructを格納する変数や定数が宣言されている場合にも安全にunwrapしたいです。これにはまた別の方法が用意されています。

struct MyOptional {
  var property = 123
  func method() {
    print("I am the struct's method.")
  }
}

let myOptional: MyOptional? // Optional<MyOptional>で宣言
myOptional = MyOptional() // MyOptionalのインスタンスを代入

print(myOptional?.property) // 123が出力される
myOptional?.method() // "I am the struct's method."が出力される
struct MyOptional {
  var property = 123
  func method() {
    print("I am the struct's method.")
  }
}

let myOptional: MyOptional? // Optional<MyOptional>で宣言
myOptional = nil // nilを代入

print(myOptional?.property) // myOptionalはnilなのでpropertyにはアクセスしない
myOptional?.method() // myOptionalはnilなのでmethodは実行しない

まとめ

Optional型はSwiftの便利な点であると同時に混乱することが多い箇所だと思うので、これらを状況によって使い分けることが大事ですね。

参考

https://developer.apple.com/documentation/swift/optional
https://www.udemy.com/course/ios-13-app-development-bootcamp/

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

【Xcode】差分(ファイル名の横のMとかRとか)が表示されない

GitHubでよくやるリポジトリ作成から開発までのフロー

  1. GitHubでリポジトリを新規作成する
  2. cloneする
  3. cloneしたディレクトリ内にXcodeのプロジェクトを作成
  4. git push
  5. 実装を進めていく

Xcodeでよくファイル名の隣にでる差分のアイコンがでない

最初に上記のフローで開発を始めたら、よく見るファイル名の隣の差分アイコンが表示されませんでした。
スクリーンショット 2020-05-18 19.03.22.png
ローカルブランチとの差分をみようとしても見れない。

解決策

メニュー>Source Control>Create Git Repositories...を選択してcreateすればOK!
スクリーンショット 2020-05-18 19.05.50.png

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

[AndroidStudio][iOS] Flutterデバッグ中にHDの容量が食われてMacが落ちる問題について

発生した問題

Android StudioでFlutterアプリを実装中、iOSを実機に繋いでデバッグしていたらなんかのタイミングでMacのHDをめちゃくちゃ食うようになってしまいにはMacが落ちるという問題が発生した。
Macを再起動後、ゴミ箱にRecoverd filesが作成され大量の(A Document Being Saved By xcdevice)が。。。

スクリーンショット 2020-05-18 15.12.43.png

発生したバージョン

  • Mac: macOS Catalina 10.15.4
  • XCode: 11.4(11E146)
  • AndroidStudio: 3.6.3
  • Flutter: 1.17.0
  • Dart: 2.8.1

解決策

以下のURLにたどり着いた。
https://apple.stackexchange.com/questions/387634/temporary-dyld-shared-cache-taking-a-lot-of-space

XCodeを起動し、メニューの「Window」→「Devices and Simulators」をクリック。
接続しているiPhoneを選択し「Copying cache files from device」が表示されてるのでそのプロセスが完了すれば発生しなくなる。

試してないが、XCodeをアップデートしたら治るのかも。。

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

[iOS] UIWebView APIが含まれているライブラリを調べる方法

前置き

本記事執筆時点の2020年5月現在、
新規アプリはUIWebView APIが含まれているとすでに受付停止されており、
アップデートは2020年12月から受付停止です。

参考リンク:
[iOS] UIWebViewがいよいよヤバいらしい("ITMS-90809: Deprecated API Usage"メールが届いた件)
[速報] [iOS] UIWebViewが使えなくなる最終期限が告知されました

使用ライブラリの中にUIWebView APIが含まれているかどうかの調査、
特に、手動で導入しているサードパーティ製ライブラリ(すなわちGitHub等でPublicな情報が出ていないもの)の調査は少々面倒かと思います。

以下、私たちのチームでUIWebView APIが含まれているライブラリを調査した方法をシェアします

本題

Macのターミナルで、以下のコマンドを実行

nm {ライブラリ導入フォルダ}/{ライブラリ名}.framwork/{ライブラリ名} | grep UIWeb
および(または)
nm {ライブラリ導入フォルダ}/{ライブラリ名}.a | grep UIWeb

そうすると、該当ライブラリのシンボル内にUIWebViewに関連する箇所がある場合には以下の様に出力されます。

U OBJC_CLASS$_UIWebView

UIWebView APIが含まれていなければ、何も出力されません。

以上、簡単ですが、これから同様の調査をされる方に、何かの参考になればと思います。

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

【Alamofire】Module 'Alamofire' has no member named 'request' というエラーが出る

Alamofire.request(route).responseJSONDecodable { response in }

これを書いて普通にAlamofireを使おうと思ったら

Module 'Alamofire' has no member named 'request'

そんなん無いよと怒られました。

Alamofireは5系からの変更に注意

Alamofireは5系からメソッドの命名とかが結構変わっています。
こういう変更は勘弁してもらいたい。

基本的に「Alamofire」と記述していた部分は「AF」に変わったみたいです。
上記の例だと

AF.request(route).responseJSONDecodable { response in }

こうすれば動くようになります。

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

【iOS】iOSアプリ開発入門~ レイアウト編2~

前回はStoryBoard上にボタンを配置する方法、ボタンの色変更方法について投稿しました。
レイアウト編1:https://qiita.com/euJcIKfcqwnzDui/items/ba37adae94e07c89d404

今回はUIの配置やサイズの調整方法について解説していきます。

AutoLayoutとは

iOSアプリのUIの配置、サイズ指定には必ずといっていいほどAutoLayoutというXcodeならではの概念を使う必要があります。

ではAutoLayoutとは何でしょうか。
iPhone、iPadのを想像してもらえるとわかりますが機種によって画面の大きさが異なります。
参考:https://qiita.com/tomohisaota/items/f8857d01f328e34fb551

この多岐にわたる画面サイズに適応させるための仕組みをAutoLayoutと言います。

何を言っているかイメージしづらいかと思います。
AutoLayoutで組んだレイアウトを一度シミュレータで見てみましょう。
前回使用したプロジェクトでMain.storyboardを開いてください。

まず差をわかりやすくするためにstoryboard上でのViewサイズを変更します。
(Viewとは今白く表示されている端末画面のような部分ととりあえず認識してもらえればいいです。)
storyboardの下の方にある[View as:iPhone〜〜]となっている部分を選択し[Device]を選択してください。
スクリーンショット 2020-05-17 20.30.36.png

すると端末の種類をプルダウン形式で選択できます。
これはstoryboard上でViewサイズを変更するメニューです。
今回は[iPhone 11 Pro]を選択してください。ViewのサイズがiPhone 11 Proのサイズになります。

次に以下のように配置されているボタンをViewの右上に配置してください。
ドラッグアンドドロップで移動させることができます。
スクリーンショット 2020-05-17 20.39.56.png

この状態でiPhone 11 Proのシミュレータで実行します。
(シミュレータの選択方法は以前説明しているので参照してください。)
ボタンの位置はどうでしょう?
storyboardと同じ位置に配置されています。
スクリーンショット 2020-05-17 21.19.25.png

今度はこのままiPhone 11 Pro Maxのシミュレータで実行してください。
どうでしょうか?
スクリーンショット 2020-05-17 20.48.39.png

ボタン右側に余白ができてしまったかと思います。
原因はiPhone 11 ProとiPhone 11 Pro Maxの端末サイズが異なるためです。

iOSの座標系は画面の左上を原点として取り、右方向にX軸の正、下方向にY軸の正を取ります。
またその単位はdpです。
この座標の1dpあたりの大きさは端末毎に変わらず一定です。
AutoLayoutを使用しなければUIは配置された座標に表示されます。
この差により右側に余白ができてしまいます。
スクリーンショット 2020-05-17 21.15.12.png

この差分を吸収するためにAutoLayoutを使用します。

AutoLayoutを設定する

それではAutoLayoutを使って右上にボタンを配置していきます。

ボタンを選択した状態で下の方にあるアイコンのうち真ん中のアイコンを選択します。
先程Viewサイズを変更したあたりの右の方です。
スクリーンショット 2020-05-17 21.39.06.png

選択するとメニューが表示されます。
まずメニューの一番上に上下左右に数字が出ています。
これは上下左右にどれだけのマージンを取るかという設定です。
とりあえず右上に寄せたいので上と右に0と入力します。
真ん中あたりのマークが濃い赤にならなければマークをクリックして濃い赤に変えてください。
その後[Add 2 Constraints]をクリックします。
するとボタンの上と右に0dpのマージンが設定されます。
このようなUI設定するマージン等のことを制約(Constraint)と呼びます。
スクリーンショット 2020-05-17 21.51.03.png

それではこの状態でiPhone 11 Pro、iPhone 11 Pro Maxでそれぞれ実行してください。
機種に依存せずボタンが右上に表示になったかと思います。

この追加した制約はUIのsrotyboardに追加されています。
スクリーンショット 2020-05-17 22.06.07.png

画面左の階層構造にButton.top = Safe Area.topとあります。
Safe Areaは少し難しいかもしれないのでとりあえずは大元となるビュー、画面自体というように認識しておいてください。
Buttonというのは配置しているボタンのことです。ボタンの名前を変えてあげると自動的に反映されます。
topというのはそのまま上部という意味です。
つまりこの制約は「Safe Areaとボタンの上部が同じ位置にある」ということを示します。
【補足】

  • UIのリネーム方法:UIを選択した状態でもう一度クリックする
  • 制約の意味
    • top:上
    • bottom:下
    • leading:左
    • trailing:右

設定したマージンを変更することもできます。一度試してみましょう。
ボタンのtopマージンを選択するとInspectorにtopマージンの設定が表示されます。
Attribute InspectorのConstraintの値が0になっています。
これが現在設定しているtopマージンの値です。
スクリーンショット 2020-05-18 1.37.14.png

これを100に変更してみます。
topマージンに100dpが設定され、ボタンが100dp下方向に動いたかと思います。

このようにして制約を追加し、端末間の差分をなくすための機能がAuto Layoutです。

複数のUIを配置しにマージンを設定する

実際のアプリではこのようなボタン1つではなく様々なUIが配置され、その分制約が増えAuto Layoutの設定は複雑になります。
こればかりは慣れるしかありません。
一度練習としてもう一つUIを配置してみます。

Text Fieldを追加します。
(このテキストフィールドはキーボードから文字を入力するためのUIです)

ボタンを追加したときのように[+]ボタンからUIのメニューを開きます。
[Text Field]を選択しビューの適当な位置にドラッグアンドドロップします。
するとビューにText Fieldが追加されました。
スクリーンショット 2020-05-18 1.54.23.png

説明した通りこの状態では端末毎に位置が異なります。
制約を追加してあげましょう。
今回はボタンと同じ高さに左寄せで配置してあげます。
同様の手順でテキストフィールドを選択し、leadingマージン:0, topマージン:100と設定してあげます。
スクリーンショット 2020-05-18 2.01.01.png

これでボタンと同じ高さにテキストフィールドを配置することができました。
ですが配置されたテキストフィールドを見ると小さくなっていませんか?
制約を追加すると自動的にサイズ調整がされるようです。
それではサイズの制約を追加してあげましょう。
制約追加のメニューに[Width]、[Height]の項目があります。
これらにチェックを入れ任意のサイズを設定します。
スクリーンショット 2020-05-18 2.07.28.png

これでテキストフィールドのサイズ指定ができました。
もちろん同様の方法でボタン等他のUIでもサイズの制約を追加することができます。

これで複数のUIを追加することができましたが、考えてみてください。
今回テキストフィールドの位置はボタンの位置と合わせるためtopマージンに100を設定しました。
ではもしボタンの位置を調整したいとなったときテキストフィールドのtopマージンも合わせてあげる必要があります。
今は2つなのでそれほど問題にはなりませんが、それが5個、10個となるとそれだけ手間になりますし修正漏れが発生する可能性があります。

そこでテキストフィールドの高さを相対的に指定してあげます。

一度テキストフィールドのtopマージンを削除してください。topマージンを選択しキーボードからバックスペースを入力するだけです。
下図のようにテキストフィールドが赤くなっていればOKです。
余談ですがこの赤くなっている状態はAuto Layoutのエラーです。話が逸れるので次項で簡単に説明します。
スクリーンショット 2020-05-18 2.22.40.png

次にcommandキーを押しながらボタンとテキストフィールドを選択します。
スクリーンショット 2020-05-18 2.27.38.png

下の方にあるタブの右から4つ目を選択します。(先程制約を追加する際に選択したタブの左側)
スクリーンショット 2020-05-18 2.28.37.png

このメニューからは選択した2つ以上のUIに相対的な制約を追加することができます。
今回はtopマージンを相対的に指定したいので[Top Edges]にチェックを入れ、0を入力し[Add 1 Constraint]を選択します。
これは選択された複数のUI間のtop間の差は0dpです、という制約です。
スクリーンショット 2020-05-18 2.34.43.png

追加するとAuto Layoutのエラーが消えボタンとテキストフィールドのtopが等しくなりました。
スクリーンショット 2020-05-18 2.38.53.png

ではこれで本当に相対的な制約を追加できたのか確認します。
前項で説明した手順でボタンのtopマージンを変更してください。

どうですか?
一緒にテキストフィールドの位置も変わったのではないでしょうか?

このようにして複数のUI間に制約を与えていき複雑なUIを組み上げていきます。
そのためそれぞれのUIが影響しあい、1つでも制約を間違えていると大きくレイアウトが崩れたりエラーが発生したりします。
本項冒頭でも言いましたがこれは慣れていくしかありません。
トライアンドエラーで頑張ってください。

Auto Layoutのエラー

前項の途中でAuto Layoutのエラーが発生しました。
これについて簡単に解説します。
スクリーンショット 2020-05-18 2.27.38.png

このエラーはtopマージンを削除したときに発生しました。
上の方に赤い矢印が出ていますがこれをクリックするとエラーの内容を確認できます。
エラー内容は以下です。
スクリーンショット 2020-05-18 2.56.07.png
Need constraints for: Y position
Y座標に対する制約が必要ですよとのこと。

Auto Layoutでは1つでも制約を追加すると完璧に位置を決定できるように制約を追加してあげる必要があります。
削除前はビューからマージンとしてtop:100dp、leading:0dpと指定していました。
その状態だと(x,y) = (0, 100)の位置に表示すればいい、ということがInterface Builderが認識してくれます。
ですが削除すると(x,y) = (0,?)という状態になりInterface Builderが理解できなくなったためエラーが発生しました。

その後ボタンとテキストフィールドの間に制約を設定したため
ボタンとビューのtopマージン:100
=> ボタンとテキストフィールドのY座標の差:0
=> テキストフィールドのY座標:100
という流れでInterface Builderが理解できるようになりエラーがなくなったというわけです。

エラーが発生したら足りてない制約はないか?どうすればInterface Builderが計算できるようになるかを考えていく必要があります。

最後に

今回は端末間の表示差をなくすためのAuto Layoutについて説明しました。
何度か言ってますがこれは習うより慣れろです。筋トレのようなものだと思って諦めてください。
Auto Layoutはアプリ開発の第一関門です。覚えてしまえば格段に楽になります。

とはいえAuto Layoutの内容は量が多く、この連載だけでは説明しきれない部分が多くあります。
本を買って勉強するなりして身につけていきましょう。

以上で今回の説明は終わりです。
次回はUIとSwiftの接続方法について説明してます。
SwiftとUIの接続編1:https://qiita.com/euJcIKfcqwnzDui/items/f794ee8b996e6d650027

本連載ではプログラミング未経験からiOSアプリ開発が行えるようになることを目的としています。
今までの投稿をまとめていますのでこちらもご覧ください。
アジェンダ:https://qiita.com/euJcIKfcqwnzDui/items/0b480e96166e88945684

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

[Swift][Eureka]"onCellSelection"でButtonRowは怖くない

UIを実装するためのライブラリEurekaにはrowそのものがbuttonになっているButtonRowというものがあります。
onCellSelectionを使えばButtonRowに好きな処理を持たせることができます。

<<< ButtonRow(){
    $0.onCellSelection{_,_ in
        //ここに任意の処理を書く
    }
}

これでできます。

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

【Swift】RxSwiftでボタンのタップイベントを拾う

RxSwiftはボタンのタップイベントとTableView CollecrionViewの実装が格段に楽になるので大好きです。
今回はボタンのタップイベントの実装をいくつかご紹介します。

subscribe(_ on:)を使う

subscribe(_ on:)の説明

Subscribes an event handler to an observable sequence.
- parameter on: Action to invoke for each event in the observable sequence.
- returns: Subscription object used to unsubscribe from the observable sequence.

つまり監視できるもののイベントを拾えるのです。
これを使ってButtonのタップイベントを拾うとこんな感じになります。

button.rx.tap.subscribe({ [weak self] _ in
    // ボタンタップでキックしたいアクションを記述
    }).disposed(by: disposeBag)

[weak self][unowned self]でももちろんOKですが、私は管理が面倒なので[weak self]を使うことが多いです。
循環参照一度起こすと結構調査が面倒だし、平気でクラッシュするから脳筋weakしちゃうんですよね。。

[weak self]と[unowned self]についてはこちらの記事が面白かったです

bind(to observers:)を使う

私は普段この実装方法を取り入れていることが多いです。

private func setButton() {
    button.rx.tap.bind(to: buttonTapBinder).disposed(by: disposeBag)
}

private var buttonTapBinder: Binder<()> {
    return Binder(self) { base, _  in
        base.button.isSelected = !base.button.isSelected
    }
}

Binderを使用しているので、循環参照は自ずと防がれるので結構使ってます。
subscribeの実装もそうですが、どちらもボタンのタップだけではなく、他のイベントの監視もできるのでぜひ応用してみてください!

RxSwiftに関する記事

【Swift】RxSwiftを使ったTableViewの実装(Delegate/reloadDataは使わない!)

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