20210114のiOSに関する記事は7件です。

iOSのlaunchImageNameで大ハマりしたメモ

iOSでローカル通知に画像を表示したい時に、launchImageNameを使用してくださいという記事がいくつもある。

しかし、実際は動かないし、使い方を書いてある記事は一つも無かった。

https://developer.apple.com/documentation/usernotifications/unnotificationcontent/1649870-launchimagename

僕と同じようにハマってほしくないので、launchImageNameは使えないよ。とメモをここに残します。

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

【Swift】動的な画面でのパフォーマンスを考慮したUIImageViewの使用

はじめに

動的な画面ではUIImageViewを高速に使用する場合、CALayerの使用方法によっては処理が重くなってしまう場合があります。
コメント欄などでのパフォーマンスを考慮した角丸やBorderを使用したUIImageViewの使用について書きます。

一般的な実装方法

一般的にフチのある丸画像を使用する際は

ViewController.swift
imageView.layer.cornerRadius = imageView.frame.height / 2
imageView.layer.borderWidth = 4
imageView.layer.borderColor = UIColor.orange.cgColor

このようにCALayerにたいして指定し、実装するかと思います。

問題点

しかしCALayerに指定して実装した場合は、コンテンツをスクロールしたとき、毎フレームで画像処理のためのオフスクリーンレンダリングが走ります。
画像の変更がない場合や関係ない画像に関係ないViewの動きに対してもオフスクリーンレンダリング発生してしまう場合があります。
そのためCPUへの負荷が非常に高くなってしまいます。
また、画像が一瞬四角に表示されるなど、意図せぬ挙動をしてしまう場合があります。
よってLayerのcornerRadiusは、高速な処理をする画面での使用には向きません。

そのため

  • 角丸などの加工済みの画像を用意する
  • ライブラリなどで使用前に画像を加工する
  • 角丸のViewを上から重ねる などで対策する必要があります。

実装方法については// TODOということでまた書きます()

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

【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 = hogehoge

Public
別モジュールから呼び出せるが継承やオーバーライドが不可能
Private
同スコープ内からのみ呼び出せる
fileprivate
同じfile内であれば呼び出せる

private let hoge = hogehoge

private var hoge = hogehoge
public let hoge = hogehoge

public var hoge = hogehoge
fileprivate let hoge = hogehoge

fileprivate var hoge = hogehoge

参考になりましたでしょうか?よければLGTMくれると僕がめっちゃ喜びます!

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

Swift UITextViewのplaceholder

UITextViewにplaceholderがない!?

twitter風アプリを作ろうと思った時にtextfieldと同じようにtextView.placeholder = ""って感じでコードを書くと
Value of type 'UITextView' has no member 'placeholder'と怒られてしまいました。
え、textViewってplaceholderないの...
ということで、なんとかしてtextViewplaceholderを実装していきましょう。

今回はUITextViewを継承したTextViewというclassを作成してそのTextViewの中にUILabeladdSubViewしていきます。

TextView.swift
class 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を定義しました。

あとはViewControllertextViewを表示するだけですね!

ViewController.swift
class 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の文字が重なってしまいますね。
んー、どうしよっかと考えた結果UITextViewDelegatetextViewDidChange(_ textView: UITextView)を使うことにしました。

ViewController.swift
extension 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を実装するだけで結構コード書かないといけないですね笑
もっと簡単に実装できる方法があればどなたか教えてください!

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

swiftのErrorとNSErrorの違いがよく分からないので自分なりにまとめてみた

はじめに

いつも、swiftで開発しているのですが、Error・NSErrorの2つのエラーオブジェクトが出てきて
いつも何で分けられてるの?と疑問に思いながらErrorからNSErrorに型キャストしたりしてました。

よく分からないままだと流石にまずいなと思い、Qiitaに備忘録として残そうと思った感じです。

ErrorとNSErrorの違いとは

そもそも、この2つの違いは何でしょうか?

調べてみると

・NSErrorはObjective-Cのエラーオブジェクトでクラスとして定義されている。

・Errorはswiftのエラーオブジェクトでプロトコルとして定義されている。

という感じでした。

更に、深く調べてみましょう。

NSError

NSErrorObjective-Cで使われていたエラーオブジェクトでエラー状態に関する情報が含まれています。

どんな情報が含まれているのかというと
domaincodeuserInfoという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).message

Errorからエラーメッセージを取得したい場合は、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のエラー処理についてざっくりとまとめてみた

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

iOSのバックアップで、FirebaseのAuthは機種変更では引き継がれない

はじめに

タイトル通りですが、Firebaseの認証情報 Auth.auth().currentUser は、あるiOS端末のバックアップを取って、他のiOS端末で復元したとしても認証情報を引き継ぐことはできません。

iOS端末Aのバックアップを取って、端末Bで復元する

この場合、復元された端末Bでアプリを動作させてもcurrentUserはnilです。

iOS端末Aのバックアップを取って、再度端末Aで復元する

この場合、端末AのcurrentUserのuidはバックアップを取った時と同一です。つまり同一端末での復元ではFirebaseの認証情報は復元されます

まとめ

このようにバックアップの復元先が元の端末かそうでないかでFirebaseの認証情報が復元されるかどうかが変わってきます。
特にUserDefaultsやキーチェーンなど、復元される項目と組み合わせて情報を組み立てている場合、バグの温床となります。
お気をつけください。

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

Swift よくつまずくdelegateについて

delegateとは

Swiftを勉強しているとdelegateというのがよく出てきます。
例えば以下の例

ViewController.swift
tableView.delegate = self
tableview.dataSource = self

delegateを和訳すると委譲や委任らしいですが日本語の意味は理解できても、「ほぉ、それで?」と思いますよね。
てことで、どういう使われ方をしているか見ていきましょう。

delegateの使い方

そのクラスができない処理を他のクラスに代わりにしてもらうというものなんですが、すこしコードを見てみましょう
今回はUITextViewについてです。

ViewController.swift
textView.delegate = self
ViewController.swift
extension ViewController: UITextViewDelegate {
    func textViewDidChange(_ textView: UITextView) {
        // textViewのtextに変更が会った時の処理
        if !textView.text.isEmpty {
            print(textView.text)
        }
    }
}

こんな感じでtextViewだけじゃできない処理をViewControllerに代わりにやってもらうことができます。
他にもpresentで画面を表示したりするのはViewControllerじゃないとできないので、タップして画面表示させたいときとかに使います。

delegateを実装

delegateはprotocolで宣言します。
: AnyObjectの部分はdelegateをclassでしか使えなくするためらしい??
weakを使えるようにするためらしいです。
詳しくは以下を参照してください。

Swift で Class-Only Protocol を定義する

protocol CustomViewDelegate: AnyObject {
    public func customView()
}
CustomView.swift
class CustomView {
    weak var delegate: CustomViewDelegate?
    @IBOutlet var button: UIButton!
    @IBAction func button() {
        delegate.customView()
    }
}

delegate変数の宣言の時のweakはメモリリークを避けるためのなんたら〜らしいです。
追記:weakについてコメントしてくださっているのでそちらも見てください。

【Swift】weakやunownedなどの参照についてまとめてみた

まとめ

なんやかんや書いてきましたが、Twitterとかのプロフィール画像をタップした時に相手のプロフィール画面に飛ぶ時とかにつかえるってことですね!

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