- 投稿日:2021-01-14T20:48:51+09:00
【Swift】動的な画面でのパフォーマンスを考慮したUIImageViewの使用
はじめに
動的な画面ではUIImageViewを高速に使用する場合、CALayerの使用方法によっては処理が重くなってしまう場合があります。
コメント欄などでのパフォーマンスを考慮した角丸やBorderを使用したUIImageViewの使用について書きます。一般的な実装方法
一般的にフチのある丸画像を使用する際は
ViewController.swiftimageView.layer.cornerRadius = imageView.frame.height / 2 imageView.layer.borderWidth = 4 imageView.layer.borderColor = UIColor.orange.cgColorこのようにCALayerにたいして指定し、実装するかと思います。
問題点
しかしCALayerに指定して実装した場合は、コンテンツをスクロールしたとき、毎フレームで画像処理のためのオフスクリーンレンダリングが走ります。
画像の変更がない場合や関係ない画像に関係ないViewの動きに対してもオフスクリーンレンダリング発生してしまう場合があります。
そのためCPUへの負荷が非常に高くなってしまいます。
また、画像が一瞬四角に表示されるなど、意図せぬ挙動をしてしまう場合があります。
よってLayerのcornerRadiusは、高速な処理をする画面での使用には向きません。そのため
- 角丸などの加工済みの画像を用意する
- ライブラリなどで使用前に画像を加工する
- 角丸のViewを上から重ねる などで対策する必要があります。
実装方法については// TODOということでまた書きます()
- 投稿日:2021-01-14T18:56:24+09:00
【Swift】アクセス修飾子を理解して使い分ける
はじめに
Swiftでコーディングする際、アクセス修飾子を適当に使っちゃってないですか?
雰囲気で使いわけてレビューで突っ込まれてないですか?なぜ定数(変数)を使うのか、なぜその修飾子を使うのか理解して使うために自分なりにまとめたので、よかったら参考にしてください!
変数と定数
let(定数)
値を変更する可能性がない場合に使う
static let
全インスタンスから共通で利用できる
var(変数)
後から値を変更することができるlet hoge = hogehoge static let hoge = hogehoge var hoge = hogehogeアクセス修飾子
Set
入れた値を使って他のプロパティの値に渡すことができる
Get
他のプロパティの値を受け取るのことができる追記: getterは同一モジュール内、setterは同一スコープ内からアクセスできる
(@takehito-koshimizu さんありがとうございます?)private(set) var hoge = hogehogePublic
別モジュールから呼び出せるが継承やオーバーライドが不可能
Private
同スコープ内からのみ呼び出せる
fileprivate
同じfile内であれば呼び出せるprivate let hoge = hogehoge private var hoge = hogehogepublic let hoge = hogehoge public var hoge = hogehogefileprivate let hoge = hogehoge fileprivate var hoge = hogehoge参考になりましたでしょうか?よければLGTMくれると僕がめっちゃ喜びます!
- 投稿日:2021-01-14T18:49:47+09:00
Swift UITextViewのplaceholder
UITextViewにplaceholderがない!?
twitter風アプリを作ろうと思った時にtextfieldと同じように
textView.placeholder = ""
って感じでコードを書くと
Value of type 'UITextView' has no member 'placeholder'
と怒られてしまいました。
え、textView
ってplaceholder
ないの...
ということで、なんとかしてtextView
にplaceholder
を実装していきましょう。今回は
UITextView
を継承したTextView
というclass
を作成してそのTextView
の中にUILabel
をaddSubView
していきます。TextView.swiftclass TextView: UITextView { public let placeholderLabel: UILabel = { let label = UILabel() label.textColor = .lightGray label.font = .systemFont(ofSize: 18) label.numberOfLines = 0 return label }() override init(frame: CGRect, textContainer: NSTextContainer?) { super.init(frame: frame, textContainer: textContainer) addSubview(placeholderLabel) placeholderLabel.translatesAutoresizingMaskIntoConstraints = false let cons = [ placeholderLabel.topAnchor.constraint(equalTo: topAnchor, constant: 8), placeholderLabel.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 5), placeholderLabel.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -5), ] NSLayoutConstraint.activate(cons) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } }こんな感じでTextViewを定義しました。
あとは
ViewController
でtextView
を表示するだけですね!ViewController.swiftclass ViewController: UIViewController { let textView: TextView = { let textView = TextView() textView.placeholderLabel.text = "placeholderだよー" textView.font = .systemFont(ofSize: 18) return textView }() override func viewDidLoad() { super.viewDidLoad() view.addSubview(textView) textView.frame = .init(x: 0, y: 100, width: view.frame.size.width, height: view.frame.size.height-200) } }これでいい感じに表示されました!
でも、このままだと入力した文字とplaceholderLabel
の文字が重なってしまいますね。
んー、どうしよっかと考えた結果UITextViewDelegate
のtextViewDidChange(_ textView: UITextView)
を使うことにしました。ViewController.swiftextension ViewController: UITextViewDelegate { func textViewDidChange(_ textView: UITextView) { if textView.text.isEmpty { self.textView.placeholderLabel.alpha = 1 } else { self.textView.placeholderLabel.alpha = 0 } } }こんな感じです。
textView.text
が空だったらplaceholderLabel
を表示し、そうでなかったらplaceholderLabel
を非表示にする。まとめ
textViewにplacehoderを実装するだけで結構コード書かないといけないですね笑
もっと簡単に実装できる方法があればどなたか教えてください!
- 投稿日:2021-01-14T18:30:19+09:00
【Swift】アプリとFirebaseの連携方法
はじめに
アプリを作成するにあたり、ログインの機能はよく使用します。
AppleIDを使ったログインやTwitter、Facebookを使ったログインの方法があります。今回は、それらの実装の簡略化やログイン後のアカウント管理を
行うことができるFirebaseと、アプリを連携するまでの手順を記載していきます。実際のログインの実装は別記事で紹介しています。
・FirebaseUIを使ってTwitterログイン機能を実装する環境
・Swift version 5.3
・XCode version 12.3
CocoaPods version 1.10.1実装方法
Qiitaでは一ヶ月に投稿できる画像の容量が決まっているらしいので、
なるべく節約した最小限の画像にしたいと思います。申し訳ございません・・・。アプリ作成
ログイン機能を実装するアプリを作成します。
今回は、SampleAppleLoginというプロジェクト名で作成しました。画像のBundle Identifierをコピーしておいてください。
Firebase 新規プロジェクト作成
Firebaseで新規プロジェクトを作成します。
Firebase consoleにアクセスしたら、
+ プロジェクトを追加
をクリック
-> プロジェクト名を指定して「続行」をクリック
->このプロジェクトで Google アナリティクスを有効にする
は無効にしてプロジェクトを作成
->プロジェクトを作成できました。
と表示されたら「続行」をクリックFirebaseとアプリの連携
プロジェクトの概要
からiOSのボタンをクリックしてアプリを追加します。
①アプリの登録にて
iOS バンドル ID
を入力するフィールドがあるので、
こちらに先ほど作成したアプリのBundle Identifierをコピペしてください。その他の入力は任意なので「次へ」をクリックします。
GoogleService-Info.plist
をクリックしてダウンロードして「次へ」をクリック
以降の項目の設定は後ほど行うので、
「次へ」 -> 「次へ」 -> 「コンソールへ進む」と進んでください。これでFirebase側にアプリの登録をすることができたので、
アプリ側でFirebaseと連携するための処理を行います。画面右上の虫眼鏡マークをクリックしてください。(もしくはcommand + スペースキー)
検索欄が出てくるので「ターミナル」と入力Enterを押します。まず、ディレクトリの移動を行います。
cdコマンドでディレクトリを移動するのですが、
移動先は先ほど作成したアプリのPATHを入力してください。
私の場合は下記のようなコマンドになります。よくわからない方は、作成したアプリのフォルダをドラッグ&ドロップしてください。
$ cd Desktop/project/SampleAppleLogin次にPodfileの作成を行います。
$ pod init念のためPodfileが作成されていることを確認します。
私の場合は、Podfileが作成され下記の状態になりました。$ ls Podfile SampleAppleLogin SampleAppleLogin.xcodeprojPodfileの編集を行います。
$ vi Podfileインストール対象を追記します。
追記するものは下記の2つになります。pod 'FirebaseUI' pod 'Firebase'まず編集モードにする必要があるので、
i
を押します。
すると左下に-- INSERT --
と表紙されると思います。その状態ですと文字を入力することができます。
追記すると次のようになると思います。
(バージョンによって記載内容は多少異なります。)# Uncomment the next line to define a global platform for your project # platform :ios, '9.0' target 'SampleAppleLogin' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! # Pods for SampleAppleLogin pod 'FirebaseUI' // 追加 pod 'Firebase' // 追加 end追記後にファイルを保存する必要があるので、
esc
を押し編集モードを解除後、:wq
を順に押し保存します。その後、インストールを行います。
$ pod installエラーが発生しなければOKです!
発生した場合はエラー内容でググってみてください。
(基本的にエラーは出ないはずです。)Xcodeを一度閉じて、Finderからアプリを開くのですが、
この時プロジェクト名.xcworkspace
のファイルを開いてください。次に、Firebaseでプロジェクトを作成する際にダウンロードした、
GoogleService-Info.plist
をアプリ内に置きます。ViewController.swiftなどと同じ階層に置きます。
GoogleService-Info.plist
クリックすると真ん中の画面が変わると思います。
その中の、REVERSED_CLIENT_ID
の行のvalueの列の値をコピーしてください。
com.googleuser 〜
といった内容だと思います。一番上の階層のプロジェクト名をクリックすると真ん中の画面が変わると思います。
タグの中からInfoを選択してください。
一番下のURl Types
を開き、+
を押して追加します。右上のURL Schemesに先ほどコピーしたものを貼り付けます。
次にAppDelegate.swiftを開き、下記のように編集します。
※ application( )メソッドは複数あるので間違えないようにしてください。AppDelegate.swiftimport Firebase // 追加 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { // Override point for customization after application launch. FirebaseApp.configure() // 追加 return true }さいごに
本記事は、FirebaseUIでログイン機能を実装する予定でしたので、
CocoaPodsでFirebaseUIをインストールしました。別の方法でログインする場合はインストールする必要がない可能性があるので、
その方法にしたがってください。また、ここまででは連携までしか終わっていないので、
実際のログイン機能の実装を行う必要があります。そちらに関しての記事は下記になります。
・FirebaseUIを使ってTwitterログイン機能を実装する以上、最後までご覧いただきありがとうございました。
- 投稿日:2021-01-14T17:08:19+09:00
swiftのErrorとNSErrorの違いがよく分からないので自分なりにまとめてみた
はじめに
いつも、swiftで開発しているのですが、Error・NSErrorの2つのエラーオブジェクトが出てきて
いつも何で分けられてるの?と疑問に思いながらErrorからNSErrorに型キャストしたりしてました。よく分からないままだと流石にまずいなと思い、Qiitaに備忘録として残そうと思った感じです。
ErrorとNSErrorの違いとは
そもそも、この2つの違いは何でしょうか?
調べてみると
・NSErrorは
Objective-C
のエラーオブジェクトでクラス
として定義されている。・Errorは
swift
のエラーオブジェクトでプロトコル
として定義されている。という感じでした。
更に、深く調べてみましょう。
NSError
NSErrorは
Objective-C
で使われていたエラーオブジェクトでエラー状態に関する情報
が含まれています。どんな情報が含まれているのかというと
domain
・code
・userInfo
という3つの情報が含まれています。分かりやすようにまとめてみました↓
情報 意味 domain エラーの種類を識別するための文字列 code エラーの種類を識別するための整数値 userInfo エラーに関する付加情報 swiftのErrorはNSErrorに常に
キャスト
できるようになっており、それらの情報を参照
することができます。実際にコードを書いてみるとこんな感じです↓
func manager(error: Error) { // ErrorからNSErrorにキャスト let nsError = error as NSError // 欲しい情報を参照する print(nsError.domain) print(nsError.code) print(nsError.userInfo) }例えば何かのマネージャークラスのデリゲートメソッドがあって
エラーが発生したら呼び出されるメソッドがあるとします。そしたら、そのエラーを
NSError
にキャストして欲しい情報を参照
していくという流れですね。NSErrorの使いどころとは
swiftでは
Objective-C
との互換性を保つために存在していますが、swiftのエラーハンドリングが強力なのもあって、NSErrorで使っていたdomainやcodeを使う機会は減ってしまったそうです。ただ、
2つの言語が混在しているプロジェクト
であればNSErrorを意識する場面に直面するそうですね。私は、全くObjective-Cを触ったことがないので分かりませんが自分の開発しているアプリでは
domainやcodeの情報が欲しい時があるので割とキャストして使っています。Error
最初の方で述べたように
swift
で使われているエラーオブジェクトでプロトコル
として定義されています。そんなswiftのErrorは
準拠した型がエラーを表現する型として扱えることを示すためのプロトコル
であって
準拠するために必要な実装はないです。つまり、swiftのErrorは
エラー情報を表現するプロトコル(規約)
なんですね。因みに余談ですが、swift2ではErrorは
ErrorType
という名前だったそうで
swift3からError
という名前に変わったそうです。Errorの使いどころとは
このErrorプロトコルに準拠する型は、
列挙型(enum)
に準拠するのが一般的で
発生するエラーをまとめて記述できる
というメリットがあるからです。更に、プログラム全体で起こり得るあらゆるエラーを一つの列挙型で定義せずに
エラーの種類によって別の型を定義する
のが常だそうですね。では実際に、Errorプロトコルを準拠して列挙型で定義してみましょう。
今回は、API通信を行う時に発生するであろうエラーを列挙型で定義してみます。
enum ApiError: Error { case networkError case decodeFailed case responceFailed case unknown }このように定義することができます。
後は
引数
を持たせて、このような使い方が出来ます。
以下の例では、computedプロパティを使って、ケースごとにメッセージを返すようにしています↓enum ApiError: Error { case networkError case decodeFailed(Error) case responceFailed(Error?) case unknown var message: String { switch self { case .networkError: return "通信エラーが発生しました" case .decodeFailed(let error): return "デコードに失敗しました \(error.localizedDescription)" case .responceFailed(let error): return "レスポンスの取得に失敗しました \(error?.localizedDescription ?? "error")" case .unknown: return "不明なエラーが発生しました" } } } let networkErrorMessage = ApiError.networkError.message let decodeErrorMessage = ApiError.decodeFailed(error).messageErrorからエラーメッセージを取得したい場合は、localizedDescriptionプロパティを使う方法があります。
このプロパティは元々、NSErrorが定義しているプロパティなので、swiftのErrorが持っているプロパティではないのですがFoundationフレームワークをリンクしていると使えるようになります。
なぜ使えるのかというと、そもそも
Foundation
フレームワークはObjective-Cで作られており、Objective-CではFoundationフレームワークに含まれるNSErrorクラスを使ってエラーハンドリングしていたからです。なので、swiftでも
FoundationをインポートすればNSErrorクラスが使える
ということです。他にも
Int型
やString型
なども引数に持たせることが可能で
エラーに付随する情報
を表現することが出来ます。enum DatabaseError { case networkError case invalidEntry(reson: String) } let invalidEntryReson = DatabaseError.invalidEntry(reson: "~が理由で無効です")おわり
ErrorとNSErrorの違いをまとめてみました。
swiftしか触ったことがないので、かなりswift寄りの記事になってしまいましたね。もし、間違っている部分があればコメントして下さると有り難いです。
最後に下記にて参考資料を載せておきます。
参考資料
Swift3時代のErrorとNSErrorに関するいくつかの実験
Swift 4.0 エラー処理入門
[Swift] Swiftのエラー処理についてざっくりとまとめてみた
- 投稿日:2021-01-14T15:24:47+09:00
[コピペ用] Resultのためのテスト関数群 [XCTest]
僕のためのコピペ用です。
func M_XCTAssertResultIsSuccess<T, E>(_ result: @autoclosure () -> Result<T, E>, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { switch result() { case .success: () case .failure: XCTFail("M_XCTAssertResultIsSuccess faild: Reuslt is not Success.", file: file, line: line) } } func M_XCTAssertResultIsFailure<T, E>(_ result: @autoclosure () -> Result<T, E>, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { switch result() { case .failure: () case .success: XCTFail("M_XCTAssertResultIsFailure faild: Reuslt is not Failure.", file: file, line: line) } } /// func M_XCTAssertResultEqualValue<T: Equatable, E>(_ result: @autoclosure () -> Result<T, E>, _ value: @autoclosure () -> T, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { switch result() { case let .success(r): if r != value() { XCTFail("M_XCTAssertResultEqualValue faild: \(r) is not equal to \(value()).", file: file, line: line) } case .failure: XCTFail("M_XCTAssertResultEqualValue faild: result is Failure.", file: file, line: line) } } func M_XCTAssertResultEqualError<T, E: Equatable>(_ result: @autoclosure () -> Result<T, E>, _ value: @autoclosure () -> E, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { switch result() { case let .failure(e): if e != value() { XCTFail("M_XCTAssertResultEqualError faild: \(e) is not equal to \(value()).", file: file, line: line) } case .success: XCTFail("M_XCTAssertResultEqualError faild: result is Success.", file: file, line: line) } } /// func M_XCTAssertResultGreaterThan<T: Comparable, E>(_ result: @autoclosure () -> Result<T, E>, _ value: @autoclosure () -> T, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { switch result() { case let .success(r): if r <= value() { XCTFail("M_XCTAssertResultGreaterThan faild: \(r) is not greater than \(value()).", file: file, line: line) } case .failure: XCTFail("M_XCTAssertResultGreaterThan faild: result is Failure.", file: file, line: line) } } func M_XCTAssertResultGreaterThanOrEqual<T: Comparable, E>(_ result: @autoclosure () -> Result<T, E>, _ value: @autoclosure () -> T, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { switch result() { case let .success(r): if r < value() { XCTFail("M_XCTAssertResultGreaterThanOrEqual faild: \(r) is not greater than or equal to \(value()).", file: file, line: line) } case .failure: XCTFail("M_XCTAssertResultGreaterThanOrEqual faild: result is Failure.", file: file, line: line) } } func M_XCTAssertResultLessThan<T: Comparable, E>(_ result: @autoclosure () -> Result<T, E>, _ value: @autoclosure () -> T, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { switch result() { case let .success(r): if r >= value() { XCTFail("M_XCTAssertResultLessThan faild: \(r) is not less than \(value()).", file: file, line: line) } case .failure: XCTFail("M_XCTAssertResultLessThan faild: result is Failure.", file: file, line: line) } } func M_XCTAssertResultLessThanOrEqual<T: Comparable, E>(_ result: @autoclosure () -> Result<T, E>, _ value: @autoclosure () -> T, _ message: @autoclosure () -> String = "", file: StaticString = #filePath, line: UInt = #line) { switch result() { case let .success(r): if r > value() { XCTFail("M_XCTAssertResultLessThanOrEqual faild: \(r) is not less than or equal to \(value()).", file: file, line: line) } case .failure: XCTFail("M_XCTAssertResultLessThanOrEqual faild: result is Failure.", file: file, line: line) } }