- 投稿日:2020-03-10T17:17:12+09:00
FirebaseのTimeStampあれこれ[忘備録]
FirebaseのTimeStampをswiftであれこれ
func chancgeFirebaseTime(date:Date){ let formatter = DateFormatter() formatter.dateStyle = .medium formatter.timeStyle = .short formatter.locale = Locale(identifier: "ja_JP") print(formatter.string(from: date))//2020/00/00 00:00 }let serverTime = "firebaseで取得してきたTimeStamp型を配置".dateValue()//To date type chancgeFirebaseTime(date: serverTime)ちなみにカスタム出来ます↓
- .dateStyle
- .timeStyle
- .locale
編集途中です、、
- 投稿日:2020-03-10T09:28:48+09:00
Swift ArgumentParser におけるプロパティの定義順の重要性
今回はタイトル通り ArgumentParser を使っている時のプロパティの定義順の重要性について書きます。v0.0.2 で実際に経験していた範囲ですが、まだ ArgumentParser のソースコードを読んだ内容ではないこと、今後のバージョンで変わりうることをご了承ください。
前回のエントリのつづき
import ArgumentParser struct MyEcho: ParsableCommand { static var configuration = CommandConfiguration(commandName: "myEcho") struct Version: ParsableArguments { @Flag() var version: Bool func validate() throws { if version { throw CleanExit.message("1.0.0") } } } @OptionGroup() var version: Version @Argument() var text: String func run() throws { print(text) } } MyEcho.main()上記サンプルコードは前回の記事で紹介した、必須引数とバージョンフラグの両立をするためのコード例です。詳しい内容は前回のエントリを参照ください。
実はこの上記のコードでも前回解決した問題を再び発生させることが出来ます。
問題再燃サンプル
import ArgumentParser struct MyEcho: ParsableCommand { static var configuration = CommandConfiguration(commandName: "myEcho") struct Version: ParsableArguments { @Flag() var version: Bool func validate() throws { if version { throw CleanExit.message("1.0.0") } } } @Argument() var text: String @OptionGroup() var version: Version func run() throws { print(text) } } MyEcho.main()何が変わったかわかりますか?
text プロパティと version プロパティの定義順を変更して text プロパティを先に持ってきました。これだけで
% myEcho --version Error: Missing expected argument '<text>' Usage: myEcho <text> [--version]というエラーになります。
結論(経験上)
プロパティの定義順に処理が進むので先に解決して欲しいものは先に書くべし。
もしくは
@Argument()は必ず最後に書くべしとも言えます。
@OptionGroup()の Property Wrapper の init 時にParsableArgumentsのfunc validate() throwsが実行されます。上からプロパティを埋めていくように ArgumentParser は動作しているようなので、引数を処理している最中にstruct Versionのイニシャライズの段階でバリデーション処理でこける(バージョン番号を出力して正常扱いで終了)ために、@Argument() var text: Stringに到達しない。これが、期待する動作を手に入れていたころのコードでした。プロパティの定義順を text を先にすると、ArgumentParser が引数の処理をしているときに text プロパティに代入するべき値が存在しないため version プロパティの処理をする前に必須項目が欠如しているとしてエラーになります。
Help や Usage にも影響あり
% myEcho --version Error: Missing expected argument '<text>' Usage: myEcho <text> [--version]このエラー時の Usage の出力をみると引数 text の後ろに version フラグが書かれています。
ためしに前回のエントリの完成形コードを引数もフラグもなしで実行すると
Error: Missing expected argument '<text>' Usage: myEcho [--version] <text>このように version のあとに text が来ています。
プロパティの定義順によって出力される説明の順番にも影響を与えています。
さらに僕がハマった罠
僕が実際に書いていたコードで、外から引数として与えられはしない固定値としてプロパティに保持したいものがありました。今手元で再現できないのですが、たしか Decodable に対応してないというコンパイルエラーが出ていました。
ParsableCommandはParsableArgumentsに準拠しています。さらにParsableArgumentsはDecodableに準拠しています。よって、ParsableCommandはDecodableである必要があるのです。ただし、別に Decodable である必要性がなかったプロパティだったので CodingKey を定義して除外できるようにしました。するとさっきまで動いていたバージョン番号の出力が引数がないと怒られるようになりました。
ずっと謎だったのですが、最終的に分かったことは CodingKey に列挙している case がプロパティの定義順通りになっていなかったからでした。
このせいで結構な時間が溶けていました。
まとめ
プロパティの定義順は
- パース処理の順番
- Usage や Help の出力の順番
- CodingKey を定義する場合はプロパティの定義順に揃える
ということでした。
常に順番を意識してコードを書きましょう!
- 投稿日:2020-03-10T04:55:51+09:00
Cordova Mac環境設定の苦労話し
ハイブリッドアプリ作成の元のCORDOVAを以前より研究してしてみたいと思っていましたが、今一つピンと来なかったのでそのままにしていました。今回先ずは環境構築と言う事で取り掛かったら、何と中々モノに出来ず苦労したので、その辺も含め投稿してみました。もしかしたらハイブリッド今更なのかも知れませんが?
先ずはsdkのパスを通す!!
vi ~/.bash_profile export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-9.0.4.jdk/Contents/Home export JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk1.8.0_241.jdk/Contents/Home export ANDROID_HOME=/Users/myusername/Library/Android/sdk export PATH=$ANDROID_HOME/platform-tools:$PATH export PATH=$ANDROID_HOME/tools/bin:$PATHAndroid Debug Bridge(adb)コマンドでパスが通った事確認
adb version
android -h
現在のCORDOVAバージョンでは、Xcode10及びandroid studio3+が条件の様です。
cordova環境設定を下記の手順で。
Node.jsのインストール
cordovaのコマンドラインツールをインストール
npm install cordova -g下記のcommandにてproject生成
cordova create MyApp com.example MyApp
フォルダへ移動
cd MyApp
cordova platform add ios android
cordova build ios
下記の様なエラーが出るのでxcodeでMyApp内のplatformのios更にMyApp.xcworkspaceよりプロジェクト開き
Signing for “MyApp" requires a development team. Select a development team in the project editor.
Code signing is required for product type 'Application' in SDK 'iOS 10.2’
Teamを選択設定を実行し再度buildとrunを実行する。
cordova run ios
更にandroid studioを生成
ordova prepare android
cordova build android
上記処理後xcodeまたはandroid studioそれぞれの開発ツールにて開き実行すると
- 投稿日:2020-03-10T02:38:44+09:00
【翻訳】あなたの(多分)まだ知らない iOS パフォーマンスアドバイス(元アップルエンジニアから)
本記事はこの記事の日本語訳です。翻訳許可をいただいております。
以下翻訳:
もし Cocoa 開発やソフトウェアビジネスのブートストラップについての最新の記事を常にキャッチアップしたいなら、ぜひ Twitter で私をフォローするかメールリストを購読してください。
開発者として、パフォーマンスの良さは我々のユーザにワクワクと嬉しさを与えるのに評価しきれないほど貴重なものです。iOS ユーザの目は非常に高く、そのためもしあなたのアプリが動作がモサモサしたり、すぐにメモリプレッシャーでクラッシュしたりすると、彼らはあなたのアプリを削除するか、最悪悪いレビューまで残してしまうでしょう。
私はアップルに 6 年間を在籍し、その歳月を Cocoa フレームワークやファーストパーティーのアプリに費やしてきましいた。私が手掛けたものには Spotlight、iCloud、app extensions、そして最近は Files などがあります
そして私は 20% だけの時間を使って 80% のパフォーマンス向上が達成できるとても手軽なパターンがあることに気づきました。
さてここからこれらのパフォーマンスについてのアドバイスを書きますので、あなたのお役に立てたら幸いです。
1.
UILabelのコストはあなたの想像を超える我々はラベルはメモリ利用量的にとても軽いと想像しがちです。何せ、彼らは単純にテキストの表示だけですので。しかし、
UILabelは実はビットマップとして保存されており、そのためメモリ利用が簡単に何メガバイトにも達してしまいますありがたいことに、
UILabelの実装は非常に賢いです、彼は必要最低限のことしかしません
- もしあなたのラベルはモノクロなら、
UILabelは自動的にCALayerContentsFormatの設定をkCAContentsFormatGray8Uint(1 バイト/ピクセル)にし、それ以外(例えば"?it’s party time"のテキスト、もしくはマルチカラーなNSAttributedStringを表示するラベル)ならkCAContentsFormatRGBA8Uint(4 バイト/ピクセル)にします。つまり、モノクロのラベルなら最大
width * height * contentScale^2 * (1 バイト/ピクセル)のバイト数を必要とするが、カラーのラベルはそれの 4 倍:つまりwidth * height * contentScale^2 * (4 バイト/ピクセル)が必要とします。例えば、iPhone 11 Pro Max に、
414 * 100のポイントサイズのラベルはこれだけのメモリ量が必要とします:
- 414 * 100 * 3^2 * 1 = 372.6kB(モノクロ)
- 414 * 100 * 3^2 * 4 = ~1.49MB(カラー)
編集:
UIKit エンジニアとの Twitter でのディスカッションの末、注意書きを一つ足すことにしました:
必ず先にメモリ計測しましょう、そしてあなたのパフォーマンスの問題は本当にラベルによるメモリプレッシャーが起因としたものだと断定できる場合のみ下記の変更を行ってください。
UIKit の @Inferis から:
例として:仮に将来 UILabel のバッキングストアの(再)利用最適化がアップデートされた場合、あなたの今行った最適化は物事を(潜在的にかなり)悪くしてるだけ。
一つのよくあるアンチパターンは
UITableView/UICollectionViewのセルが再利用キューに入った時,セルラベルのテキスト内容をそのままにしてセルラベルを生かしておくことです。セルがリサイクルされた時、ラベルのテキスト内容が変わる可能性が非常に高いです、そのためラベルを保存するのは非常に無駄です。メガバイトレベルのメモリを空けるためには:
- ラベルを隠して、ほんのたまにしか表示しない時はラベルの
textを nil 代入しましょう。UITableView/UICollectionViewのセルに表示してるラベルは、下記のメソッドでtextを nil 代入しましょう:tableView(_:didEndDisplaying:forRowAt:) collectionView(_:didEndDisplaying:forItemAt:)2. 常にシリアルキューを利用し、コンカレントキューは最後の手段として保留せよ
一つのよくあるアンチパターンは、UI に影響しないブロックを、メインキューからグローバルなコンカレントキューにディスパッチすることです。
例えば:
func textDidChange(_ notification: Notification) { let text = myTextView.text myLabel.text = text DispatchQueue.global(qos: .utility).async { self.processText(text) } }もしここでアプリを中断すると:
あなたがブロックをコンカレントキューに
dispatch_asyncした時、GCD はスレッドプールからアイドリングしているスレッドを探し、そのスレッドでブロックを動かそうとします。そしてもしアイドリングなスレッドが見つからなかった時、仕方ないのでその仕事のために新しいスレッドを作るしかありません。そのため、速いペースでブロックをコンカレントキューにディスパッチすると、速いペースで新しいスレッドを作らなくてはならないことになりかねないです。忘れないでください:
- スレッドの作成はただではありません。もしあなたが出したブロックの仕事量が少ない(< 1ms)なら、新しいスレッドの作成は非常に無駄です、なぜならそれには実行コンテキスト、CPU サイクル、そして dirty メモリ等のスイッチングが発生するからです。
- GCD は喜んであなたにスレッドを作成しちゃいます、結果的にスレッド爆発が起こります。
一般的には、常に数の限られたシリアルキューからスタートすべきで、それぞれのキューにあなたのアプリのサブコンポーネント(DB キュー、テキスト処理キュー、などなど…)を現すべきです。そして自分自身のシリアルキューを持つ小さいオブジェクトには、
dispatch_set_target_queueを使って先ほどのサブコンポーネントキューにターゲットセットすればいいです。あなたのボトルネックが追加の並列化で解決できる時のみ、あなたが作ったコンカレントキュー(
dispatch_get_global_queueではなく)を使い、そしてdispatch_applyの利用を考えましょう。
dispatch_get_global_queueについての注意:あなたが
dispatch_get_global_queueから取得したコンカレントキューは、システムへの QoS1 情報の転送が苦手であり、回避すべきです。libdispatch の Pierre Habouzit からの引用:
dispatch_get_global_queue()は実践上 dispatch API が提供してるものの中に最悪の部類の一つです、なぜならランタイムがどれだけ尽力しても、実行時にあなたのオペレーション/アクター/などなど…についての情報が足りないため、あなたがやろうとしてることを理解できず最適化もできない。libdispatch の効率化アドバイスについてのもっと詳しい情報は、ぜひこちらの文章をご確認ください。
3. そう見えるほど、コードが悪くないかもしれない
さてあなたができる限りのメモリ利用量の最適化をしてきました、しかしそれでもしばらくアプリを使うとメモリ利用量が上がったまま下がりません。
焦らないでください、一部のシステムコンポーネントはメモリワーニングをもらわない限りメモリを解放しないだけです。
例えば、
UICollectionViewは(iOS 13 から)-didReceiveMemoryWarningに反応し、メモリが足りなくなったら再利用キューのメモリをクリアしますメモリワーニングをシミュレーションするには:
- iOS Simulator の場合、メニューから
Simulate Memory Warningを選びます- 実機の場合、このプライベート API を呼び出します(App Store には提出しないように)
[[UIApplication sharedApplication] performSelector:@selector(_performMemoryWarning)];4.
dispatch_semaphore_tを使って非同期作業を待たないようにここには一つよくあるアンチパターンがあります:
let sem = DispatchSemaphore(value: 0) makeAsyncCall { sem.signal() } sem.wait()これの問題は
makeAsyncCallを呼び出したスレッドから、実際の処理が行われているスレッドに優先度の情報が伝えられず、優先順位の逆転に繋がることです。
- 例えばメインキューから
makeAsyncCallを呼び出し、QoS がQOS_CLASS_UTILITYの DB キューに作業をディスパッチしたとします。makeAsyncCallがメインキューからdispatch_asyncを呼び出したおかげで、実際の DB キューの QoS はQOS_CLASS_USER_INITIATEDに上げられます。- セマフォでメインキューをブロックすると言うのは、メインキューは(自身の QoS である
QOS_CLASS_USER_INTERACTIVEよりも順位が低い)QOS_CLASS_USER_INITIATEDの仕事が終わるまで待たなくてはいけず2、すなわち優先順位の逆転が発生してしまいます。
XPC3 についての補足です:もしあなたが既に XPC を利用しており(macOS アプリか、もしくは
[NSFileProviderService](https://developer.apple.com/documentation/foundation/nsfileproviderservice)を利用しているなら)、そして同期な呼び出しを行いたい場合は、セマフォを使うのではなく、代わりに下記のコードを使って同期プロキシにあなたのメッセージを送りましょう:-[NSXPCConnection synchronousRemoteObjectProxyWithErrorHandler:]5.
UIViewの tag を使わないようにこれは悪い実践であり、臭うコードを示します。そしてパフォーマンス的にも悪いです。
私は最近こんなコードと出会いました、そのコードは一つのビューをタップしたら、そのビューの小ビューの色をそれらの tag の値に応じて変更するものです。
UIKit は tag の実装に
objc_get/setAssociatedObject()を使っています、それはつまりあなたが tag をゲット/セットする度に、実は辞書参照を行っており、実行中だと Instruments ではこのように表示されるのです:編集:これはせいぜい微々たる最適化にすぎません。私が伝えたいことは:1)驚くべきことに、
-[UIView tag]は Objective-C の associated objects に依存しているのと、2)これはパフォーマンスに厳しいコードに多用する時だけインパクトを与えます。終わりに
あなたがこれらのアドバイスを読み終わったら、今日新しい知見を持ち帰れたと祈ります。いつもと同じ、必ずパフォーマンス調整する前に必ず計測してください。
何か質問?他のパフォーマンスアドバイスを共有したい?ぜひコメントで私に知らせてください!
追伸
ここで私の素敵な Mac ユーティリティを確認できます。
編集
UICollectionView/UITableViewを利用した時のラベルの nil 代入の内容について修正を入れてくれた Paul Hudson に感謝します。
訳者後書:私に本記事についての内容について問い合わせても、努力はしますが必ずしも答えられるわけではありませんのでご了承ください。英語で直接本文著者に問い合わせることをお勧めします。
Quality of Service。GCD が制御するキューの優先順位を表す型です。詳しくは公式資料をご参照ください。 ↩
原文が全部大文字になっているためちょっと微妙に紛らわしいように見えますが、よく見てください。Swift の構文に直すと、メインキューの QoS は
.userInteractiveであり、作られた DB キューの QoS は.utility→.userInitiatedです。Interactive と Initiated の違いです。 ↩サンドボックスをアクセスするなど、プロセス間のコミュニケーションを安全に行うための管理ライブラリーです。詳しくは公式資料をご参照ください。 ↩
- 投稿日:2020-03-10T02:26:21+09:00
SwiftのMVCアーキテクチャにおけるコードレイアウトのベストプラクティスを模索&解説してみた
どうも〜〜〜〜
横文字まみれでルー大柴みたいなタイトルですね。iOS開発でアーキテクチャ何使ってますか?
僕はもっぱらMVCです。先日、趣味でフレームワークを入れて遊んでいたのですが、Xibを使用すると警告が出る為コードレイアウトで実装したれ!と思ったわけです。
と言うことでサンプル作りました。
ボタン押すと遷移してテーブルセルの機種のかな文字を表示します。githubに対象プロジェクトあげてます。
ぎっとはぶコードレイアウトで実装する上で何に困ったか
SwiftでMVC実装する場合大体UIViewクラスにXibをくっ付けて実装するので描画関係はほとんど気にしなくて済む訳です。
ですが、コードレイアウトの場合どこでレイアウトを指定したら良いのかが読む文献によって違うわけです。
と言うことでコードレイアウトでの実装方法を考えてみました。
一つ一つ解説するので全体のコードは読み飛ばしてもらっていいです。SMCLLabelView.swiftclass SMCLLabelView: UIView { weak var delegate: SMCLLabelViewDelegate? /**デバイスのかな情報を表示するラベル*/ var displayLabel = UILabel() override init(frame: CGRect) { super.init(frame: frame) setLayout() } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } /// レイアウトを設定します func setLayout() { setBackView() setTransitionButton() } /// 背景ビューを生成します func setBackView() { let backView: UIView = { let backView = UIView() backView.translatesAutoresizingMaskIntoConstraints = false backView.backgroundColor = .white return backView }() self.addSubview(backView) backView.topAnchor.constraint(equalTo: self.topAnchor, constant:0).isActive = true backView.leftAnchor.constraint(equalTo: self.leftAnchor, constant:0).isActive = true backView.rightAnchor.constraint(equalTo: self.rightAnchor, constant:0).isActive = true backView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant:0).isActive = true } /// 次の画面へ遷移するボタンを生成します func setTransitionButton() { let transitionButton: UIButton = { let transitionButton = UIButton() transitionButton.translatesAutoresizingMaskIntoConstraints = false transitionButton.setTitle("次の画面へ遷移", for: .normal) transitionButton.setTitleColor(.blue, for: .normal) transitionButton.addTarget(self, action: #selector(tappedTransitionButton), for: .touchUpInside) return transitionButton }() self.addSubview(transitionButton) transitionButton.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true transitionButton.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true transitionButton.widthAnchor.constraint(equalToConstant: 120).isActive = true transitionButton.heightAnchor.constraint(equalToConstant: 30).isActive = true displayLabel = UILabel() displayLabel.translatesAutoresizingMaskIntoConstraints = false displayLabel.text = "Labelです" self.addSubview(displayLabel) displayLabel.topAnchor.constraint(equalTo:transitionButton.bottomAnchor, constant:10.0).isActive = true displayLabel.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true } /// 次の画面へ遷移します /// - Parameter button: transitionButton @objc func tappedTransitionButton(_ button: UIButton) { delegate?.smclLabelView(self, tappedTransitionButton: button) } }白い画面にButtonとLabelが表示された簡単な構成です。
まず背景の白ビューの解説から
/// 背景ビューを生成します func setBackView() { let backView: UIView = { let backView = UIView() backView.translatesAutoresizingMaskIntoConstraints = false backView.backgroundColor = .white return backView }() self.addSubview(backView) backView.topAnchor.constraint(equalTo: self.topAnchor, constant:0).isActive = true backView.leftAnchor.constraint(equalTo: self.leftAnchor, constant:0).isActive = true backView.rightAnchor.constraint(equalTo: self.rightAnchor, constant:0).isActive = true backView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant:0).isActive = true }白いビューを生成しsuperViewの4方向にconstant:0を設定しています。
画像はわかりやすくするために紫にしています。
Xibでのオートレイアウト設定だとこの設定と同一になります。
次にButtonの制約ですがこちらになります。
/// 次の画面へ遷移するボタンを生成します transitionButton.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true transitionButton.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true transitionButton.widthAnchor.constraint(equalToConstant: 120).isActive = true transitionButton.heightAnchor.constraint(equalToConstant: 30).isActive = trueこちらの二行で対象のビュー(self)の中心に配置しています。
transitionButton.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true transitionButton.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = trueXibでのオートレイアウト設定だとこの設定と同一になります。
そして固定値で横幅120,高さ30を指定しているのがこちらになります。
transitionButton.widthAnchor.constraint(equalToConstant: 120).isActive = true transitionButton.heightAnchor.constraint(equalToConstant: 30).isActive = true先ほどの設定と合わせてXibでのオートレイアウト設定だとこの設定と同一になります。
以下VCでの実装になります。
SMCLLabelViewController.swiftclass SMCLLabelViewController: UIViewController { private let labelView = SMCLLabelView() private let model = SMCLLabelViewModel() override func loadView() { self.view = labelView labelView.delegate = self } override func viewDidLoad() { super.viewDidLoad() } }こちらの
self.view = labelView通過時にoverrideしたinitメソッドを通過するのでレイアウトの設定が行われます。
次の画面TableViewとボタンになります。
class SMCLTableView: UIView { weak var delegate: SMCLTableViewDelegate? /**データ表示テーブルビュー*/ var tableView: UITableView = { let table = UITableView() table.translatesAutoresizingMaskIntoConstraints = false return table }() override init(frame: CGRect) { super.init(frame: frame) setLayout() } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } func setLayout(){ setBackView() setTableView() setSendButton() } func setBackView(){ let backView: UIView = { let backView = UIView() backView.translatesAutoresizingMaskIntoConstraints = false backView.backgroundColor = .white return backView }() self.addSubview(backView) backView.topAnchor.constraint(equalTo: self.topAnchor, constant:0).isActive = true backView.leftAnchor.constraint(equalTo: self.leftAnchor, constant:0).isActive = true backView.rightAnchor.constraint(equalTo: self.rightAnchor, constant:0).isActive = true backView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant:0).isActive = true } func setTableView(){ self.addSubview(tableView) tableView.topAnchor.constraint(equalTo: self.topAnchor, constant:0).isActive = true tableView.leftAnchor.constraint(equalTo: self.leftAnchor, constant:0).isActive = true tableView.rightAnchor.constraint(equalTo: self.rightAnchor, constant:0).isActive = true tableView.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.9).isActive = true } func setSendButton(){ //内包するビューを作成 let buttonView: UIView = { let buttonView = UIView() buttonView.translatesAutoresizingMaskIntoConstraints = false buttonView.backgroundColor = .white return buttonView }() self.addSubview(buttonView) buttonView.topAnchor.constraint(equalTo: tableView.bottomAnchor, constant:0).isActive = true buttonView.leftAnchor.constraint(equalTo: self.leftAnchor, constant:0).isActive = true buttonView.rightAnchor.constraint(equalTo: self.rightAnchor, constant:0).isActive = true buttonView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant:0).isActive = true let button: UIButton = { let button = UIButton() button.translatesAutoresizingMaskIntoConstraints = false button.setTitle("追加", for: .normal) button.setTitleColor(.blue, for: .normal) button.backgroundColor = .white button.addTarget(self, action: #selector(tappedAddButton), for: .touchUpInside) return button }() buttonView.addSubview(button) button.centerXAnchor.constraint(equalTo: buttonView.centerXAnchor).isActive = true button.centerYAnchor.constraint(equalTo: buttonView.centerYAnchor).isActive = true button.heightAnchor.constraint(equalTo: buttonView.heightAnchor, multiplier: 0.8).isActive = true button.widthAnchor.constraint(equalTo: buttonView.widthAnchor, multiplier: 0.5).isActive = true } /// 追加ボタン押下時のメソッド /// - Parameter button: 追加ボタン @objc func tappedAddButton(_ button: UIButton) { delegate?.smclTableView(self, tappedAddButton: button) } }TableViewのレイアウトはこちらで設定しています。
func setTableView(){ self.addSubview(tableView) tableView.topAnchor.constraint(equalTo: self.topAnchor, constant:0).isActive = true tableView.leftAnchor.constraint(equalTo: self.leftAnchor, constant:0).isActive = true tableView.rightAnchor.constraint(equalTo: self.rightAnchor, constant:0).isActive = true tableView.heightAnchor.constraint(equalTo: self.heightAnchor, multiplier: 0.9).isActive = true }Xibを使用したautolayoutの場合このような設定になります。
左、上、右の余白を0にし高さを親ビューの9/10に設定しています。
次にボタンビューです
func setSendButton(){ //内包するビューを作成 let buttonView: UIView = { let buttonView = UIView() buttonView.translatesAutoresizingMaskIntoConstraints = false buttonView.backgroundColor = .white return buttonView }() self.addSubview(buttonView) buttonView.topAnchor.constraint(equalTo: tableView.bottomAnchor, constant:0).isActive = true buttonView.leftAnchor.constraint(equalTo: self.leftAnchor, constant:0).isActive = true buttonView.rightAnchor.constraint(equalTo: self.rightAnchor, constant:0).isActive = true buttonView.bottomAnchor.constraint(equalTo: self.bottomAnchor, constant:0).isActive = true }Xibを使用したautolayoutの場合このような設定になります。
出来たスペースいっぱいにビューを伸ばしているのでテーブルビューのサイズが変わった場合それに比例して高さが伸びるレイアウトになります。
最後にボタンです
buttonView.addSubview(button) button.centerXAnchor.constraint(equalTo: buttonView.centerXAnchor).isActive = true button.centerYAnchor.constraint(equalTo: buttonView.centerYAnchor).isActive = true button.heightAnchor.constraint(equalTo: buttonView.heightAnchor, multiplier: 0.8).isActive = true button.widthAnchor.constraint(equalTo: buttonView.widthAnchor, multiplier: 0.5).isActive = truebuttonViewの中心に配置、
高さはButtonView.height * 0.8
横幅はButtonView.width * 0.5と言う風になっております。
おそらくこれだけあればあとはこのパターンを組み合わせて大体のレイアウトが作れると思います。
最後に
まだまだペーペーでございまして、、、
この書き方がいいとかあったら教えてください、、、コードでのautolayoutはXibでの設定を完全に理解していないと難しいだろうと感じました。
なので一つ一つの制約に画像を添付していきましたがこれが皆様の助けとなれば幸いです。参考にさせていただいた記事
今更MVCとかでiOSアプリつくってみた(Swift)
【swift】MVCモデルの簡単な説明
iOS 9で追加されたNSLayoutAnchor使うと簡単にわかりやすく間違えずにNSLayoutConstraint(制約)が作れます【Auto Layout】
【Swift4】AutoLayoutをコードで実装する
UIViewにおけるレイアウトのライフサイクル
UIKitのView表示ライフサイクルを理解する







![-[UIView tag] はタッチイベントの処理に貴重なミリ秒を消費するのです。 Time Profiler トレース](https://qiita-user-contents.imgix.net/https%3A%2F%2Fwww.fadel.io%2Fblog%2Fpublic%2Ftag_time_profiler.jpg?ixlib=rb-1.2.2&auto=format&gif-q=60&q=75&s=70cf3f042d2eed883b1446b233d08283)







