- 投稿日:2019-04-15T21:53:25+09:00
URLResposne.allHeaderFieldsでレスポンスヘッダーの値を取得しようとしたらつまずいた
環境
xcode10.1
iOS12.1
Swift4.2まえがき
商品の検索結果をTableViewで表示する際にページングを行いたく、レスポンスヘッダーに含まれる
X-Total-Pages
の値を取得していろいろ使おうとしていた。
https://developer.apple.com/documentation/foundation/httpurlresponse/1417930-allheaderfields
でレスポンスヘッダーが取得できるらしい。api.swift(APIKit使用)func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response { // 検索結果がない場合は0が入るため、forced unwrappingにしていた let totalPages = urlResponse.allHeaderFields["X-Total-Pages"] as! String }といった実装をしていて、しばらく問題がなかったが、ある日リリースビルドしたアプリをTestFlight上で確認したところ、検索を行った際にクラッシュしていて、原因を調べてみたらこの箇所が
nil
になっていた。レスポンスを確認してみても0またはそれ以上の値が入っているし、サーバー担当に確認してみても、ここが空になることはないという。
原因1: サーバーのレスポンスヘッダーに含まれているキーが小文字になっていた
リリースビルドの方
x-total-pages 64
開発の方
X-Total-Pages 64
そのため
api.swift(APIKit使用)func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response { // "X-Total-Pages"というキーがないためリリースビルドではnilになっていた let totalPages = urlResponse.allHeaderFields["X-Total-Pages"] as! String }自分が調べたところによると、ネットワークによっては小文字を返す場合がある(らしい)、サーバーのバージョン違い、アプリのバージョン違いなど考えられるそうです。
調べた分だとバージョン違いに関してはなさそうでした。
なぜ小文字になったのか、原因が特定できていないため、ご存知の方がいたら教えていただけるとありがたいです。原因2: Swiftのバグ (仕様?)
大文字小文字の違いで値が取得できずnilになったとは言いましたが、
前提として、HTTPHeaderは大文字小文字を区別しないはずです。これはドキュメントの
https://developer.apple.com/documentation/foundation/httpurlresponse/1417930-allheaderfields
にも書かれています。(google翻訳したやつ)この辞書のキーは、サーバーから受け取ったヘッダーフィールド名です。一般的に使用されているHTTPヘッダーフィールドのリストについては、RFC 2616を参照してください。
HTTPヘッダーは大文字と小文字を区別しません。コードを単純化するために、特定のヘッダーフィールド名は標準形式に正規化されています。たとえば、サーバーがcontent-lengthヘッダーを送信すると、自動的にに調整されますContent-Length。とドキュメントにも書かれていますが、実際allheaderfieldsは大文字小文字を区別してしまっています。
これはSwiftのバグ(仕様?)で、ドキュメントも更新されずそのままのようです。
https://bugs.swift.org/browse/SR-24292016年ごろからあるらしいですね。
対策
URLResponseにすべて小文字として取得できるものをはやしました。
// https://stackoverflow.com/questions/40152483/httpurlresponse-allheaderfields-swift-3-capitalisation/40152676#40152676 extension HTTPURLResponse { func find(header: String) -> String? { let keyValues = allHeaderFields.map { (String(describing: $0.key).lowercased(), String(describing: $0.value)) } if let headerValue = keyValues.filter({ $0.0 == header.lowercased() }).first { return headerValue.1 } return nil } } // usage 存在しない場合nilが入るのでよしなに処理する let total = urlResponse.find(header: "X-Total-Pages")という暫定的な対策をとりました。
間違っていたり、原因が違っていた場合は教えてもらえるとありがたいです。
- 投稿日:2019-04-15T12:30:46+09:00
Promisesを使って、Swiftの非同期処理をスマートにする
Promisesの紹介
実際に使ってみて、評価した結果
あるモバイルアプリ開発プロジェクトで実際にこのPromisesを導入してみたところ、なかなか良好な使い勝手とパフォーマンスでした。今後は有力な選択肢になるかと思い、その基本機能について原典を元に参考資料としてまとめておくことにしました。
そもそもPromise(プロミス)とは
Promise(プロミス)とは、並列プログラミング言語における同期処理実行を構成するための概念の一つです。
一般的には、非同期タスクのイベント結果を返し、もしそのタスクが失敗した時はエラー原因を返すといった機構を表現するものです。類似した概念として、Futuresといったものもあります。
先行事例としては、既にJavascriptなどで実装されている例が多いと思います。Javascriptの世界においては、ECMAScript 2015(ES6)でpromise機能が導入されました。Promisesフレームワーク
Promisesは、SwiftとObjective-Cで、同期処理構造を実現するモダンなフレームワークです。
本稿では、Swiftに絞って、基本的な使い方を確認していきます。特徴
ざっくり言うと、RxSwiftなどの人気フレームワークに比較して、容量や処理性能が優れているのだそうです。ベンチマークを計測し、2倍以上バイナリサイズが小さい、かつ直列キュー、並列キュー上の処理性能が優れているなど、実測データが紹介されています。例えば、列キューに連結された処理ブロックに到達する平均時間は2.5倍程速いなど、色々良いと言われています。その他、注目すべき特徴は下記の通りです。
- シンプル: 既存コード上にも展開しやすい、直感的なAPIを持っている
- 互換性: Objective-CとSwiftの両方をサポートしている
- 軽量: GCDやコールバックとほぼ同等レベルのパフォーマンスを達成する最小のオーバーヘッド
- 柔軟: どのスレッドやキュー上でも発動するオブザーバー処理ブロックを使用できる
- 安全: 全ての処理ブロックは、GCDによって管理される
- テスト済み: 100%のコードカバレッジでテストされている
導入
今回はCocoaPodsでの導入方法を紹介します。
CocoaPodsで導入
podがインストール済み、Xcodeプロジェクト作成済みであることを前提として、Podfileに下記を追記します。
# targetは環境に応じて変更 target 'PromisesExample' do use_frameworks! pod 'PromisesSwift', '~> 1.2.7' endプロジェクトディレクトリで
pod install
を実行し、open PromisesExample.xcworkspace
でXcodeを起動します。Swiftコード上では、モジュールのインポートを行うと使えます。
一度ビルドを通してから追記するのが良いかもしれません。import Promisesその他の方法
Bazel, Swift Package Manager, Carthageで行う方法もありますが、これは割愛します。
基本的な使い方
プロミスの作成
非同期処理をするには、所定の作業ブロックを指定します。そしてfulfill()をパラメータ付きで発動し、成功処理、reject()でエラーオブジェクトを引数にとって失敗処理を行います。successはBool型のフラグ、someErrorは何らかのErrorオブジェクトとして次のような構文で記述します。
非同期処理の基本
Swift:
// 保留状態の処理を変数に格納 let promise = Promise<String>(on: .main) { fulfill, reject in // 指定のディスパッチキュー上で非同期処理される if success { // 成功処理 fulfill("Hello world.") } else { // エラー処理 reject(someError) } }非同期処理の省略形
Promisesフレームワークは、デフォルトでメインディスパッチキューを使用します。なので、 下記のコードも同様の処理となります。
Swift:
// 保留状態の処理を変数に格納 let promise = Promise<String> { fulfill, reject in // デフォルトキュー上で非同期処理される if success { // 成功した場合 fulfill("Hello world.") } else { // 失敗、エラーになった場合 reject(someError) } }doブロック 非同期処理の最小構文
さらに、doブロックでもっとも簡潔な記述をすると次のようにできます。
Swift:
let promise = Promise { () -> String in // デフォルトキュー上で非同期処理される guard success else { throw someError } return "Hello world" }保留する
非同期処理ブロックを指定しないでプロミスオブジェクトを保留するには、pending()関数を用います。
そして後段の処理で結果を返すことができます。Swift:
let promise = Promise<String>.pending() // ... if success { promise.fulfill("Hello world") } else { promise.reject(someError) }解決済みプロミス生成
既に結果を解決済みのプロミスを生成できると便利な場合があります。その時は、初期値またはエラーをプロミスのコンストラクタに渡してください。例えば、次のようになります。
Swift:
func data(at url: URL) -> Promise<Data?> { // 妥当性チェックなどの場面で、即座にnilを返すことができる if url.absoluteString.isEmpty { return Promise(nil) } return load(url) }成功処理の監視
プロミスからの結果通知を受け取るには、
then
演算子を使います。
様々な方法で保留プロミスの結果を処理することができます。
- プロミス上で
fulfill
メソッドを実行- 非同期処理ブロック内で
fulfill
メソッドを実行then
ブロックから戻り値を返却- エラーなしで、解決済みのプロミスを返却
Then
then演算子は引数に一つの処理ブロックを取ります。処理ブロックは、前段の処理結果を引数で受け取り、必要な処理を経由して、他のプロミスオブジェクトや値、エラーを戻すようにします。
Swift:
let numberPromise = Promise(42) // 別のプロミスを返す let chainedStringPromise = numberPromise.then { number in return self.string(from: number) } // 値を返す let chainedStringPromise = numberPromise.then { number in return String(number) } // エラーを返す let chainedStringPromise = numberPromise.then { number in throw NSError(domain: "", code: 0, userInfo: nil) } // Voidを返す let chainedStringPromise = numberPromise.then { number in print(number) // Implicit 'return number' here. }注意: chainedStringPromiseは、Voidを返す一つの例です。
thenの処理ブロックをメインスレッドでなく、別のキューで実行させたい場合は、次のように記述します。
numberPromise.then(on: backgroundQueue) { number in return String(number) }Then のパイプライン
そして、これが最も重要な機能です。非同期処理のプロミスをパイプラインでつないで、まとめて同期処理を行うところです。次のようなサンプルで実行します。
Swift:
// 処理1、文字列を返す非同期Promise func work1(_ string: String) -> Promise<String> { return Promise { return string } } // 処理2、数値を返す非同期Promise func work2(_ string: String) -> Promise<Int> { return Promise { return Int(string) ?? 0 } } // 処理3、数値を返す関数 func work3(_ number: Int) -> Int { return number * number } // 処理1が実行されたら、 work1("10").then { string in // 処理2を実行し、 return work2(string) }.then { number in // 処理3を実行して、 return work3(number) }.then { number in // 結果を出力して終了する print(number) // 100 } // Swiftでは上記の例をもっとシンプルに記述することができます。 work1("10").then(work2).then(work3).then { number in print(number) // 100 }失敗処理の監視
プロミスからのエラー、失敗通知を受け取るには、
catch
演算子を使います。
様々な方法でプロミスの失敗を処理することができます。
- プロミス上で
reject
メソッドを実行- 非同期処理ブロック(asyncやdoブロック)内で
reject
メソッドを実行then
ブロックからエラーを返却またはthrow- エラーありで、解決済みのプロミスを返却
Catch
catch演算子は一つの処理ブロックを取ります。処理ブロックは、プロミスの処理で失敗した時のエラーを受け取ります。
Swift:
number(from: "abc").catch { error in print("Cannot convert string to number: \(error)") }Catch のパイプライン
従来のコールバックのネストは、コードが複雑化してしまうなど、開発が困難になることもあります。予期せぬバグの温床となったり、メンテが大変になるなど、問題化しやすい方法と言えます。
本Promisesにおいては、エラーが発生した場合、自動的にパイプラインに伝播され、残りの処理ブロックは無視されて、エラーが通知される仕組みを持っています。catch
演算子は、処理連鎖のどこに配置されてもよく、柔軟にエラーハンドリングができるものです。Swift:
// エラーの定義 struct CustomError: Error {} // 処理1、文字列を返す非同期Promise func work1(_ string: String) -> Promise<String> { return Promise { return string } } // 処理2、数値を返す非同期Promise func work2(_ string: String) -> Promise<Int> { return Promise { () -> Int in guard let number = Int(string), number > 0 else { throw CustomError() } return number } } // 処理3、数値を返す関数 func work3(_ number: Int) -> Int { return number * number } // 処理1が実行されたら、 work1("abc").then { string in // 処理2を実行し、文字列を変換しようとしてエラーが発生 return work2(string) }.then { number in // 前段の処理でエラーになるので到達しない return work3(number) }.then { number in // 前段の処理でエラーになるので到達しない print(number) }.catch { error in // エラーが捕捉され、ログ出力して終了する print("Cannot convert string to number: \(error)") }拡張機能
このフレームワークで、async, do, then, catchといった基本の演算子を使っていれば、一般的な非同期処理には十分に実装することができます。
それでもやはり、より一層ハイレベルなパターンにも対応した拡張機能が多数提供されています。
各機能を列挙すると次のようになりますが、詳しくは原典をあたりましょう。通常は、基本パターンで間に合うはずです。All, Always, Any, Await, Delay, Race, Recover, Reduce, Retry, Timeout, Validate, Wrap
アンチパターン
やってはいけない、誤った使用方法があります。ごく簡単ですが、紹介しておきましょう。
破損したサイクル
正しいパイプラインとなっていないケース。
Swift:
func asyncCall() -> Promise<Data> { let promise = doSomethingAsync() promise.then(processData) return promise }プロミスのネスト
入れ子構造にするのはやめた方が良いとのこと。
Swift:
loadSomething().then { something in self.loadAnother().then { another in self.doSomething(with: something, and: another) } }まとめ
ということで、最後にまとめておきます。
async/await方式より簡潔な記述はできないが、十分に便利なライブラリになっていると思います。いわゆるコールバック地獄から開放されより見通しやすい非同期処理が実現できるでしょう。アプリ開発の場面で、しばらく採用していきたいと思いました。参考資料
- 投稿日:2019-04-15T12:18:47+09:00
unable to open file in targetのエラーが発生し、シミュレータービルドできない
概要
友人のプロジェクトを手伝うことになり、プロジェクトをgit cloneし、シミュレーターでビルドしようとしたところ
unable to open file in target
のエラーが発生し、シミュレーターでのビルドに失敗したので、対処方法について記述したいと思います。エラー画像
環境
Swift4.2
Xcode10.1対処方法
- Podfile.lockを削除する
pod install
を実行する- シミュレーターでビルドする
参考資料
- unable to open file in target xcode 10 https://stackoverflow.com/questions/53117077/unable-to-open-file-in-target-xcode-10
- 投稿日:2019-04-15T12:18:47+09:00
unable to open file in targetのエラーが発生しシミュレータービルドができない
概要
友人のプロジェクトを手伝うことになり、プロジェクトをgit cloneし、シミュレーターでビルドしようとしたところ
unable to open file in target
のエラーが発生し、シミュレーターでのビルドに失敗したので、対処方法について記述したいと思います。エラー画像
環境
Swift4.2
Xcode10.1対処方法
- Podfile.lockを削除する
pod install
を実行する- シミュレーターでビルドする
参考資料
- unable to open file in target xcode 10 https://stackoverflow.com/questions/53117077/unable-to-open-file-in-target-xcode-10
- 投稿日:2019-04-15T09:31:47+09:00
SwiftLintを導入してソースコードを整えたのでその知見を紹介します
久しぶりの投稿となります。
今月から現場が変わってメンテするプロジェクトが変わりました。
私のしばらくの業務は既存のソースコードに溜まっている技術的負債を返済するタスクになるそうです。で、実際にソースコードを確認したのですが、これを自分がメンテするにはまずは
SwiftLint
を導入しないとダメだなと思い先週からSwiftLint
の導入方法を調査していました。というのも今までは既に
SwiftLint
を導入しているプロジェクトに参画していたので自分では導入したことはなかったのですね。ということ今回はこの
SwiftLint
の調査と導入する過程で得られた知見を備忘録として残したいと思います。SwiftLintについて
ではそもそも
SwiftLint
ってなんぞやということから紹介します。簡単にいうとSwiftのソースコードで独自ルールを設定してそれに沿って既存のソースコードを自動修正していくツールです。ツールですが、実際にはライブラリですので導入にはCocoapodsを使います。
Githubページに移れば分かりますがモバイルアプリのDBでお世話になる
Realm
社が提供しているライブラリになります。このSwiftLintを既存コードに導入するとどのようなメリットがあるかというと
例えば、以下のソースコードがあったとします。
Sample.swiftimport UIKit import UIKit // 重複import です class MainViewController:UIViewController { @IBOutlet var button:UIButton! override func viewDidLoad(){ super.viewDidLoad() } func sampleMethod()->Int{ var count:Int = 1 count += 1 return count } }パッと見あんまり見たくないコードですよね。できればコードフォーマッタを使って
型
のスペース部分とかを修正したいかと思います。その気持ちがあれば十分だと思います。こういったクラスが2,3個だけならいいのですが、数百個レベルになると個人的には辛くなってくると思います。
そんなあなたにとってはSwiftLint
は大きな味方です。SwiftLintに既にあるデフォルトのルールを適用させるだけでも上記のコードを綺麗に整えることができます。
例えば、上記のコードは正確には
Sample.swiftimport UIKit class MainViewController: UIViewController { @IBOutlet var button: UIButton! override func viewDidLoad() { super.viewDidLoad() } func sampleMethod() -> Int { var count: Int = 1 count += 1 return count } }このように修正されます。
これがSwiftLintのパワーです。あなたがメンテしているコードで上記の部分が該当している場合にはチームリーダーに相談してSwiftLintを導入して見ましょう。
そんなSwiftLintの導入の仕方を紹介します。
SwiftLintの導入方法について
簡単にまとめると
- Cocoapodsに
Swiftlint
を追加するimportする- Run Scriptを追加してとあるコードを差し込む
- ルートディレクトリに
.swiftlint.yml
を追加して最初のルールを簡単に記載する- Terminalで
swiftlint autocorrect
を叩くたったこれだけの作業でSwiftLintを導入して既存コードを修正していくことができます。
ではそれぞれ説明していきます。
Cocoapodsに
SwiftLint
を追加してimportするgithubページの解説に従ってpodsを使ってライブラリをimportしましょう。
まずはMacに
Homebrew
がない場合はHomebrew
をインストールします。https://brew.sh/ のトップページにある下記のコマンドを叩いて
Homebrew
をインストールしましょう。/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
Homebrew
が無事にインストールできたら次のコマンドを叩きます。brew install swiftlint
これでMacにswiftlintがインポートされます。
それが完了したら
CocoaPods
を使ってSwiftLintをインポートします。pod 'SwiftLint'そしてterminalで
pod install
を叩くことで無事にSwiftLintがプロジェクトファイルに入ります。
Run Scriptを追加してとあるコードを差し込む
次にSwiftLintを正常に動かすためにプロジェクトファイルの
Targets
のところでBuild Phases
の部分で
Run Script
を追加します。そのRun Scriptを追加してシェル上に次のコードを差し込みます。
if which "${PODS_ROOT}/SwiftLint/swiftlint" >/dev/null; then ${PODS_ROOT}/SwiftLint/swiftlint else echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint" fiこれで基本的な設定が完了しました。
[注記]
@uhooi さんのコメントを参照ですが、if which "${PODS_ROOT}/SwiftLint/swiftlint" >/dev/null; then swiftlint autocorrect --format swiftlint else echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint" fiと記載するとコミット毎に自動修正が入るようになります。
こちらの方が運用上楽かと思います。ルートディレクトリに
.swiftlint.yml
を追加して最初のルールを簡単に記載するさてこれからソースコードに適用させるlintのルールを設定していきます。
このルールの把握に時間が取られました。流石にここはQiitaの次の記事を参考にして設定していきました。
非常にお世話になりましたのでこの場をお借りして感謝のお礼を申し上げます。
そして、続きの説明に入りますが、プロジェクトファイルのルートディレクトリ(簡単に言えばPodfileなどが入ってい階層のこと)に
.swiftlint.yml
ファイルを追加します。ターミナルでルートディレクトに移動したらviコマンドでファイルを追加します。
vi .swiftlint.ymlこれでvimが使えるようになるのでルールの詳細がわからない導入期は下記のコードをコピーペーストしましょう。
swiftlint.yml# 無効にするルール disabled_rules: # xcodeでviolation warningが表示されたり違反でXcodeがビルドできないルールを差し込んでいきます。 # デフォルトルール以外でopt-inから適用させるルールの設定 opt_in_rules: # ここに採用したいルールを追加していく # # SwiftLintの適用から外すディレクトリ # (UnitTestやUITestもある場合は修正されますので入れます) excluded: # cocoapodsを使っている場合# - Pods/ # Carthageを使っている場合# - Carthage/ # 1行あたりの文字数制限を300に変更 line_length: 300 # etcこれをペースとして
:wq
で保存します。
これでSwiftLintに適用させたいルールの設定が終わります。Terminalで
swiftlint autocorrect
を叩くここまでの設定が終わればあとはTerminalで次のコマンドを叩くだけです。
swiftlint autocorrectこれでちょっと汚いと思われていたコードの箇所が適用クラスに限り自動で修正されます。
自動修正自体はものの5秒程度で完了しますので本当にすぐです。これでXcodeでソースコードを見ていきましょう。
そしてXcode上で⌘ + B
でビルドして見ましょう。大体は感動の代わりに落胆になるかと思います。
少なくとも私は落胆しました。Swiftの標準的な書き方を踏襲していたらそんな違反になることはないと思いましたが私がやって見た限りは
ビルドエラーになってしまいました。はい。デフォルトルールに違反しているコードが多く存在していました。
例えば、メソッドの命名だったり定数・変数の命名で違反しているところが多かったです。既存のコードでviolationや違反があってビルドが失敗する場合の対処方法
そこでデフォルトのSwiftLintのルールに違反していてビルドが成功しなかった場合の対処方法を紹介します。
定数・変数レベルでビルドが失敗していた場合には
.swiftlint.yml
でルールを緩和していきます。
ただ、.swiftlint.yml
ファイルは通常は非表示ファイルなのでFinder上では見つけられません。どうするかというと、
ターミナル上で次のコマンドを叩きます。
defaults write com.apple.finder AppleShowAllFiles TRUEそして、次のコマンドでFinderを一度切ります。
killall Finder再度、Finderを開くと非表示ファイルが見えるようになります。
これで
.swiftlint.yml
をVScodeとかでひらけます。そして
disabled_rules
に次のようなルールを入れてそのルールを無効
にしていきますdisabled_rules: # warningが走るため無効にする # ビルドが失敗するルールを無効にする - variable_name - file_length - function_body_length - line_length - type_body_length - variable_name_max_length - variable_name_min_length - force_cast - force_try - shorthand_operator - empty_count - cyclomatic_complexity - type_name - implicit_getterこのあたりのルールを無効化したらSwiftLintが許してくれるようになりました(笑)。
結構、規約に無視したコードでも割とビルドに成功すると思います。全体的なファイルをもう一度記載すると
swiftlint.yml# 無効にするルール disabled_rules: # warningが走るため無効にする # ビルドが失敗するルールを無効にする - variable_name - file_length - function_body_length - line_length - type_body_length - variable_name_max_length - variable_name_min_length - force_cast - force_try - shorthand_operator - empty_count - cyclomatic_complexity - type_name - implicit_getter # デフォルトルール以外でopt-inから適用させるルールの設定 opt_in_rules: # ここに採用したいルールを追加していく # # SwiftLintの適用から外すディレクトリ # (UnitTestやUITestもある場合は修正されますので入れます) excluded: # cocoapodsを使っている場合# - Pods/ # Carthageを使っている場合# - Carthage/ # 1行あたりの文字数制限を300に変更 line_length: 300 # etcこんな感じにして再度
swiftlint autocorrect
を叩いてXcode上で⌘ + B
して見てください。
これよりひどい場合にはやっぱりビルドは失敗すると思いますが私の場合はこれでなんとかビルドが成功するようになりました。あとはコミットのたびに
swiftlint autocorrect
していけばSwiftLintを適用した快適なSwiftライフが送れるようになると思います。では以上で記事を終わります。
- 投稿日:2019-04-15T09:10:41+09:00
枠線・角丸を設定できるUIVIewクラスの作り方
1.はじめに
毎回角丸やら、枠線を設定するのは、面倒なので、IBDesignable、IBInspectableを用いてカスタムクラスを作っておくと便利です。
過去記事で散々書かれている内容ではありますが、今回はその作り方、使い方を備忘録として残します。IBDesignable、IBInspectableとは
今更ながらIBDesignableとIBInspectableについて
こちらの記事を読んでいただけるとわかるかと思いますので、詳しくは割愛しますが、ざっくりいうとIBDesignable
・Storyboard上にリアルタイムで反映される
IBInspectable
Storyboard上で属性変更できる
というものです。
ただこれらの修飾子を多用すると、これらを使用しているStoryboardやXibを開いた時に、自動読み込みが入る為、重くなる要因となります。
実際はとても便利ですが、PCのスペックや、UIコンポーネンツの設置数によっては、使わない方がいい時もありますので用法容量を守ってお使いください。2.プロトコルを作成する
まず角丸、枠線のプロトコルを定義します。
UIViewだけでなく、UIButtonや他のクラスでも使用できるようにこうしてプロトコルとしておくことであとあと便利になります。角丸
/// 角丸 public protocol Roundable: class { var cornerRadius: CGFloat { get set } var masksToBounds: Bool { get set } }枠線
/// 枠線 public protocol Borderable: class { var borderColor: UIColor { get set } var borderWidth: CGFloat { get set } }3.枠線・角丸を設定可能なUIViewの作成
UIView
/// 枠線・角丸を設定可能なUIView @IBDesignable public class IBDesignableView: UIView, Roundable, Borderable { @IBInspectable var borderColor: UIColor = UIColor.black { didSet { layer.borderColor = borderColor.cgColor } } @IBInspectable var borderWidth: CGFloat = 0.0 { didSet { layer.borderWidth = borderWidth } } @IBInspectable var cornerRadius: CGFloat = 5.0 { didSet { layer.cornerRadius = cornerRadius } } @IBInspectable var masksToBounds: Bool = true { didSet { layer.masksToBounds = masksToBounds } } }4.使い方
あとは簡単です。
Storyboard、またはXib上でUIViewを設置します。
その後右ペインのclassをIBDesignableViewに変更します。
すると自動ビルドが開始され先ほど定義されたコードが読み込まれて、右ペインの属性設定からいじれるようになります。
5.最後に
以上、簡単ではありますが、枠線、角丸設定ができるUIVIewの作り方、使い方でした。
お忙しい中ご覧いただきありがとうございました。ブログも始めました。
kazyblog
- 投稿日:2019-04-15T00:13:57+09:00
XcodeでTODO:などの警告を表示させる方法
はじめに
警告の表示方法についての備忘録です。
方法1
Swift4.2までは#warning("")などが使えなかったので、お決まりのスクリプトです。
使用方法はBuildPhaseのRunScriptに以下を追加。TAGS="TODO:|FIXME:" ERRORTAG="ERROR:" find "${SRCROOT}" \( -name "*.h" -or -name "*.m" -or -name "*.swift" \) -print0 | xargs -0 egrep --with-filename --line-number --only-matching "($TAGS).*\$|($ERRORTAG).*\$" | perl -p -e "s/($TAGS)/ warning: \$1/" | perl -p -e "s/($ERRORTAG)/ error: \$1/"使い方
// TODO:
// FIXME:
// ERROR:
を使用した場合に警告およびエラーがビルド実行時に表示されるようになります。
結果
方法2
エディタプレースホルダーを使用する強引なやり方です。
ビルドが通らないので使い方は限られますし、本来の使い方ではないですね。
予測変換で出したコードの引数とかに出てくるこれですね。生成方法は至って簡単。
以下のように文字を<#
#>
で挟めばエディタプレースホルダーとして変換されます。使い方
<#hogehoge#>結果
方法3
Swift4.2以降で使えるようになったやつです。
使い方
#warning("hogehoge") #error("fugafuga")結果
まとめ
個人的にはまだ方法1のやり方8割で、2割で方法3のやり方でやっています。
まず方法2はやらないですね。
警告内容も該当箇所いかないとわからないですし。
(なぜ書いたんだって話になりますが...)簡易的な備忘録でしたが、ご覧いただきありがとうございました。
ひっそりとブログも始めました。
kazyblog