20190415のSwiftに関する記事は7件です。

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-2429

2016年ごろからあるらしいですね。

対策

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") 

という暫定的な対策をとりました。

間違っていたり、原因が違っていた場合は教えてもらえるとありがたいです。

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

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方式より簡潔な記述はできないが、十分に便利なライブラリになっていると思います。いわゆるコールバック地獄から開放されより見通しやすい非同期処理が実現できるでしょう。アプリ開発の場面で、しばらく採用していきたいと思いました。

参考資料

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

unable to open file in targetのエラーが発生し、シミュレータービルドできない

概要

友人のプロジェクトを手伝うことになり、プロジェクトをgit cloneし、シミュレーターでビルドしようとしたところunable to open file in targetのエラーが発生し、シミュレーターでのビルドに失敗したので、対処方法について記述したいと思います。

エラー画像

スクリーンショット 2019-04-15 11.18.41.png

環境

Swift4.2
Xcode10.1

対処方法

  1. Podfile.lockを削除する
  2. pod installを実行する
  3. シミュレーターでビルドする 

参考資料

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

unable to open file in targetのエラーが発生しシミュレータービルドができない

概要

友人のプロジェクトを手伝うことになり、プロジェクトをgit cloneし、シミュレーターでビルドしようとしたところunable to open file in targetのエラーが発生し、シミュレーターでのビルドに失敗したので、対処方法について記述したいと思います。

エラー画像

スクリーンショット 2019-04-15 11.18.41.png

環境

Swift4.2
Xcode10.1

対処方法

  1. Podfile.lockを削除する
  2. pod installを実行する
  3. シミュレーターでビルドする 

参考資料

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

SwiftLintを導入してソースコードを整えたのでその知見を紹介します

久しぶりの投稿となります。
今月から現場が変わってメンテするプロジェクトが変わりました。
私のしばらくの業務は既存のソースコードに溜まっている技術的負債を返済するタスクになるそうです。

で、実際にソースコードを確認したのですが、これを自分がメンテするにはまずはSwiftLintを導入しないとダメだなと思い先週からSwiftLintの導入方法を調査していました。

というのも今までは既にSwiftLintを導入しているプロジェクトに参画していたので自分では導入したことはなかったのですね。

ということ今回はこのSwiftLintの調査と導入する過程で得られた知見を備忘録として残したいと思います。

SwiftLintについて

ではそもそもSwiftLintってなんぞやということから紹介します。

簡単にいうとSwiftのソースコードで独自ルールを設定してそれに沿って既存のソースコードを自動修正していくツールです。ツールですが、実際にはライブラリですので導入にはCocoapodsを使います。

SwiftLint(Githubリポジトリ)

Githubページに移れば分かりますがモバイルアプリのDBでお世話になるRealm社が提供しているライブラリになります。

このSwiftLintを既存コードに導入するとどのようなメリットがあるかというと

例えば、以下のソースコードがあったとします。

Sample.swift
import 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.swift
import 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の導入方法について

簡単にまとめると

  1. CocoapodsにSwiftlintを追加するimportする
  2. Run Scriptを追加してとあるコードを差し込む
  3. ルートディレクトリに.swiftlint.ymlを追加して最初のルールを簡単に記載する
  4. 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ライフが送れるようになると思います。

では以上で記事を終わります。

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

枠線・角丸を設定できるUIVIewクラスの作り方

1.はじめに

毎回角丸やら、枠線を設定するのは、面倒なので、IBDesignableIBInspectableを用いてカスタムクラスを作っておくと便利です。
過去記事で散々書かれている内容ではありますが、今回はその作り方、使い方を備忘録として残します。

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を設置します。
スクリーンショット 2019-04-15 9.02.17.png

その後右ペインのclassをIBDesignableViewに変更します。
スクリーンショット 2019-04-15 9.02.48.png

すると自動ビルドが開始され先ほど定義されたコードが読み込まれて、右ペインの属性設定からいじれるようになります。
スクリーンショット 2019-04-15 9.02.57.png

5.最後に

以上、簡単ではありますが、枠線、角丸設定ができるUIVIewの作り方、使い方でした。
お忙しい中ご覧いただきありがとうございました。

ブログも始めました。
kazyblog

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

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:

を使用した場合に警告およびエラーがビルド実行時に表示されるようになります。

結果

スクリーンショット 2019-04-14 23.42.59.png

方法2

エディタプレースホルダーを使用する強引なやり方です。
ビルドが通らないので使い方は限られますし、本来の使い方ではないですね。
予測変換で出したコードの引数とかに出てくるこれですね。

スクリーンショット 2019-04-15 0.02.06.png

生成方法は至って簡単。
以下のように文字を<# #>で挟めばエディタプレースホルダーとして変換されます。

使い方

<#hogehoge#>

結果

スクリーンショット 2019-04-14 23.51.58.png

方法3

Swift4.2以降で使えるようになったやつです。

使い方

 #warning("hogehoge")
 #error("fugafuga")

結果

スクリーンショット 2019-04-14 23.56.27.png

まとめ

個人的にはまだ方法1のやり方8割で、2割で方法3のやり方でやっています。
まず方法2はやらないですね。
警告内容も該当箇所いかないとわからないですし。
(なぜ書いたんだって話になりますが...)

簡易的な備忘録でしたが、ご覧いただきありがとうございました。

ひっそりとブログも始めました。
kazyblog

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