20200310のSwiftに関する記事は5件です。

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

編集途中です、、

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

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 時に ParsableArgumentsfunc 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 に対応してないというコンパイルエラーが出ていました。ParsableCommandParsableArguments に準拠しています。さらに ParsableArgumentsDecodable に準拠しています。よって、 ParsableCommandDecodable である必要があるのです。ただし、別に Decodable である必要性がなかったプロパティだったので CodingKey を定義して除外できるようにしました。

するとさっきまで動いていたバージョン番号の出力が引数がないと怒られるようになりました。

ずっと謎だったのですが、最終的に分かったことは CodingKey に列挙している case がプロパティの定義順通りになっていなかったからでした。

このせいで結構な時間が溶けていました。

まとめ

プロパティの定義順は

  • パース処理の順番
  • Usage や Help の出力の順番
  • CodingKey を定義する場合はプロパティの定義順に揃える

ということでした。

常に順番を意識してコードを書きましょう!

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

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:$PATH

Android 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それぞれの開発ツールにて開き実行すると

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

【翻訳】あなたの(多分)まだ知らない iOS パフォーマンスアドバイス(元アップルエンジニアから)

本記事はこの記事の日本語訳です。翻訳許可をいただいております

以下翻訳:


もし Cocoa 開発やソフトウェアビジネスのブートストラップについての最新の記事を常にキャッチアップしたいなら、ぜひ Twitter で私をフォローするかメールリストを購読してください。

陸上トラック

開発者として、パフォーマンスの良さは我々のユーザにワクワクと嬉しさを与えるのに評価しきれないほど貴重なものです。iOS ユーザの目は非常に高く、そのためもしあなたのアプリが動作がモサモサしたり、すぐにメモリプレッシャーでクラッシュしたりすると、彼らはあなたのアプリを削除するか、最悪悪いレビューまで残してしまうでしょう。

私はアップルに 6 年間を在籍し、その歳月を Cocoa フレームワークやファーストパーティーのアプリに費やしてきましいた。私が手掛けたものには SpotlightiCloudapp extensions、そして最近は Files などがあります

そして私は 20% だけの時間を使って 80% のパフォーマンス向上が達成できるとても手軽なパターンがあることに気づきました。

さてここからこれらのパフォーマンスについてのアドバイスを書きますので、あなたのお役に立てたら幸いです。

1. UILabel のコストはあなたの想像を超える

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 ではこのように表示されるのです:

Time Profiler トレース

編集:これはせいぜい微々たる最適化にすぎません。私が伝えたいことは:1)驚くべきことに、-[UIView tag] は Objective-C の associated objects に依存しているのと、2)これはパフォーマンスに厳しいコードに多用する時だけインパクトを与えます。

終わりに

あなたがこれらのアドバイスを読み終わったら、今日新しい知見を持ち帰れたと祈ります。いつもと同じ、必ずパフォーマンス調整する前に必ず計測してください。

何か質問?他のパフォーマンスアドバイスを共有したい?ぜひコメントで私に知らせてください!

追伸

ここで私の素敵な Mac ユーティリティを確認できます。

編集

UICollectionView/UITableView を利用した時のラベルの nil 代入の内容について修正を入れてくれた Paul Hudson に感謝します。


訳者後書:私に本記事についての内容について問い合わせても、努力はしますが必ずしも答えられるわけではありませんのでご了承ください。英語で直接本文著者に問い合わせることをお勧めします。


  1. Quality of Service。GCD が制御するキューの優先順位を表す型です。詳しくは公式資料をご参照ください。 

  2. 原文が全部大文字になっているためちょっと微妙に紛らわしいように見えますが、よく見てください。Swift の構文に直すと、メインキューの QoS は .userInteractive であり、作られた DB キューの QoS は .utility.userInitiated です。Interactive と Initiated の違いです。 

  3. サンドボックスをアクセスするなど、プロセス間のコミュニケーションを安全に行うための管理ライブラリーです。詳しくは公式資料をご参照ください。 

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

SwiftのMVCアーキテクチャにおけるコードレイアウトのベストプラクティスを模索&解説してみた

どうも〜〜〜〜
横文字まみれでルー大柴みたいなタイトルですね。

iOS開発でアーキテクチャ何使ってますか?
僕はもっぱらMVCです。

先日、趣味でフレームワークを入れて遊んでいたのですが、Xibを使用すると警告が出る為コードレイアウトで実装したれ!と思ったわけです。

と言うことでサンプル作りました。
ボタン押すと遷移してテーブルセルの機種のかな文字を表示します。

Animated GIF-downsized_large.gif

githubに対象プロジェクトあげてます。
ぎっとはぶ

コードレイアウトで実装する上で何に困ったか

SwiftでMVC実装する場合大体UIViewクラスにXibをくっ付けて実装するので描画関係はほとんど気にしなくて済む訳です。

ですが、コードレイアウトの場合どこでレイアウトを指定したら良いのかが読む文献によって違うわけです。

と言うことでコードレイアウトでの実装方法を考えてみました。
一つ一つ解説するので全体のコードは読み飛ばしてもらっていいです。

SMCLLabelView.swift
class 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)
    }

}

実際このコードを走らせると以下のような画面が描画されます。
Simulator Screen Shot - iPhone 8 - 2020-03-10 at 00.06.20.png

白い画面に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でのオートレイアウト設定だとこの設定と同一になります。
スクリーンショット 2020-03-10 0.24.55.png

次に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 = true

Xibでのオートレイアウト設定だとこの設定と同一になります。
スクリーンショット 2020-03-10 0.37.48.png

そして固定値で横幅120,高さ30を指定しているのがこちらになります。

transitionButton.widthAnchor.constraint(equalToConstant: 120).isActive = true
transitionButton.heightAnchor.constraint(equalToConstant: 30).isActive = true

先ほどの設定と合わせてXibでのオートレイアウト設定だとこの設定と同一になります。
スクリーンショット 2020-03-10 0.40.45.png

以下VCでの実装になります。

SMCLLabelViewController.swift
class 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)
    }


}

実際このコードを走らせると以下のような画面が描画されます。
Simulator Screen Shot - iPhone 8 - 2020-03-10 at 01.55.23.png

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の場合このような設定になります。
スクリーンショット 2020-03-10 1.51.06.png

左、上、右の余白を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の場合このような設定になります。
スクリーンショット 2020-03-10 2.01.15.png

出来たスペースいっぱいにビューを伸ばしているのでテーブルビューのサイズが変わった場合それに比例して高さが伸びるレイアウトになります。

最後にボタンです

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

buttonViewの中心に配置、
高さは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表示ライフサイクルを理解する

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