20190708のiOSに関する記事は5件です。

iOSのFIDO対応についての考察

はじめに

FIDOは2019年に入り、AndroidやWindowsで対応が進む一方iOSやmacOSはそのような話は出てきていなかった。しかし、WWDC2019にてSafariのFIDO対応が発表された。今後のiOSやmacOSでのFIDOの対応について考察をしてみる。

FIDOとは

  • Fast IDentity Online(素早いオンライン認証)の略語
  • 従来のパスワード認証に変わる、生態認証ベースの認証技術
  • 簡単に説明すると、鍵の作成とサービスへの鍵の登録というフェーズで構成される
  • 「鍵」は厳密にはPKIの秘密鍵であり、登録は公開鍵を登録する

FIDOの何が嬉しいのか?

メリット

  • パスワードを介在しないので、パスワード管理から開放される
  • パスワードを介在しないので、総当たり攻撃のリスクが無い

デメリット

  • 秘密鍵を作成したデバイスを紛失したらログインできなくなる
  • 1ユーザが複数デバイスを使ってログインしたい場合、その分だけ公開鍵の登録が必要になる

FIDOの全体像

FIDO

  • UAF :パスワードの代わりに生体認証を使う方式(モバイルアプリ向け)
  • U2F :簡易なパスワードと生体認証を組み合わせる方式

FIDO2

  • WebAuthn :JavaScriptによりWebブラウザから生体認証を呼びだす仕組み
  • CTAP  :生体認証が無いデバイスが、生体認証を持つデバイスを利用するためのデバイス間プロトコル

それぞれの方式の評価

方式 パスワード入力 外部デバイスによる生体認証 サービスへのアクセス方法
UAF 不要 NG 専用アプリ
U2F 必要 OK Webブラウザ
WebAuthn 不要 NG Webブラウザ
WebAuthn + CTAP 不要 OK Webブラウザ

ブラウザの対応状況

IBMの調査によると各種ブラウザの2019/5/22時点の対応状況は以下の通り

ブラウザ U2F WebAuthn CTAP
Chrome Desktop 対応済 対応済 対応済
Chrome Android 対応済 対応済 対応済
Firefox 開発中 対応済 対応済
Edge 未対応 対応済 対応済
Safari macOS 未対応 開発中 開発中
Safari iOS 未対応 未対応 未対応

WWDC2019での対応公表

WWDC2019のセッションWhats new in AuthenticationにてmacOS10.15のSafariでFIDOに対応することを公表した。

yubicoのiOSサポート開始発表

2019年7月に入りセキュリティキー大手のyubicoがiOSのサポートを開始したと発表

iOSとmacOSの今後についての考察

macOS10.15でFIDOの対応が発表され、yubicoもiOSをサポートすることを発表したため近い将来iOSでもU2FとWebAuthnが利用できるようになる可能性が高い。しかし、iOSでCTAPが使えるようになるのかは不明(デベロッパーのユースケースとしてはiPhoneを認証機器として使うという利用方法が多いはずなので早く対応して欲しいところ)

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

iPadのWifiがたまに切れる機種がある場合の対処

iPadのWifiモデルを使用していると、同じ機種、環境下でもたまにWifiが切れる機種がある。

環境

iPad Air2 Wi-fiモデル

手順

システムサービスの「Wi-Fiネットワーク」を オフ にする
設定→プライバシー→位置情報サービス→システムサービス→Wi-fiネットワークを
オンからオフに変更する。

この方法にたどり着かず、色々な方法を試して半日ほどつぶした。

image.png

image.png

image.png

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

lazyプロパティ内でstaticメソッドを使用すると、2回目以降のアクセスでも処理が走る場合がある

2019/07/09 追記

@nukka123 さんのコメントより、Swiftのlazyプロパティという仕様どんな処理を呼ぼうが異なるスレッドからアクセスすれば初期化処理が1回に止まる保証はないことが判明してます。

https://qiita.com/m-yamada1992/items/d77adc52b75f6d44c3f6#comment-f24b8fc4241d8b918ce3

実行環境

Xcode 10.2.1
Swift 5

ViewController.swift
class ViewController: UIViewController {

    private lazy var label: UILabel = {
        return ViewController.createLabel()
    }()

    init() {
        super.init(nibName: nil, bundle: nil)
        self.addSubview(label)
    }

    func setup(text: String) {
        label.text = text
    }

    static func createLabel() {
        let label = UILabel()
        return label
    }
}

init() で初回のアクセスをしたタイミングでlabelプロパティが生成され、2回目以降にsetup(text:)メソッドでアクセスした際には生成済みのインスタンスにアクセスされることを想定していた。
が、ViewController.createLabel()が2回目以降も走っていることが発覚。

解決策

lazyプロパティ内の定義をいずれかに変更

要は、staticメソッドさえ使わなければ想定通りの遅延初期化プロパティな挙動をする。

直接生成

    private lazy var label: UILabel = {
        let label = UILabel()
        return label
    }()

インスタンスメソッドを用いて生成

    private lazy var label: UILabel = {
        return self.createLabel()
    }()

    static func createLabel() {
        let label = UILabel()
        return label
    }

lazyではなくlet宣言にする

    private let label: UILabel

    init() {
        self.label = ViewController.createLabel()

        super.init(nibName: nil, bundle: nil)
        self.addSubview(label)
    }

    static func createLabel() {
        let label = UILabel()
        return label
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

SwiftでWiFi電波強度を取得する

SwiftでWifiの電波強度を取得する方法です。
検索して調べたやり方で実装してみたところ、私の手元の環境ではうまく動きませんでした。
試行錯誤の末、自分なりに辿り着いた解決方法を記載します。

動作確認環境

機種 iOSのバージョン
iPhone XS 12.3.1
iPad 第5世代 12.1.4

WiFi情報はどこから取得するのか

参考サイトによると、ステータスバーの中にSignalに関する情報が入っているようです。
また、iPhoneX以降とそれ以前とではステータスバーの取得方法が異なるようです。

まずは試してみる

参考サイトのコードを真似して実装してみました。

iPhoneX以降用

private func getWifiNumberOfActiveBars() -> Int? {
    let app = UIApplication.shared
    var numberOfActiveBars: Int?
    guard let containerBar = app.value(forKey: "statusBar") as? UIView else { return nil }
    guard let statusBarMorden = NSClassFromString("UIStatusBar_Modern"), containerBar .isKind(of: statusBarMorden), let statusBar = containerBar.value(forKey: "statusBar") as? UIView else { return nil }

    guard let foregroundView = statusBar.value(forKey: "foregroundView") as? UIView else { return nil }

    for view in foregroundView.subviews {
        for v in view.subviews {
            if let statusBarWifiSignalView = NSClassFromString("_UIStatusBarWifiSignalView"), v .isKind(of: statusBarWifiSignalView) {
                if let val = v.value(forKey: "numberOfActiveBars") as? Int {
                    numberOfActiveBars = val
                    break
                }
            }
        }
        if let _ = numberOfActiveBars {
            break
        }
    }

    return numberOfActiveBars
}

手元のiPhoneで確認してみたところ、こちらは想定通りの結果を得ることができました:ok_woman:

iPhoneX以前用

private func getWiFiRSSI() -> Int? {
    let app = UIApplication.shared
    var rssi: Int?

      guard let statusBar = app.value(forKey: "statusBar") as? UIView else { return nil }
      if let statusBarMorden = NSClassFromString("UIStatusBar_Modern"), statusBar .isKind(of: statusBarMorden) { return nil }

      guard let foregroundView = statusBar.value(forKey: "foregroundView") as? UIView else { return nil  }

      for view in foregroundView.subviews {
          if let statusBarDataNetworkItemView = NSClassFromString("UIStatusBarDataNetworkItemView"), view .isKind(of: statusBarDataNetworkItemView) {
              if let val = view.value(forKey: "wifiStrengthRaw3") as? Int {
                  rssi = val
                  break
              }
          }
      }

    return rssi
}

手元のiPadで確認してみたところ、常にnilが返されました・・・:no_good:

原因

一行一行デバッグしてみたところ、6行目のif let statusBarMorden = NSClassFromString("UIStatusBar_Modern"), statusBar .isKind(of: statusBarMorden) { return nil } の部分でreturnされていることがわかりました。

ホームボタンのない機種はUIStatusBar_Modern が使われていて、それ以外の機種はUIStatusBar_Modern が使われていないのだと勝手解釈していたのですが、違ったみたい。。
この辺イマイチわかっていないので、詳しい方教えて欲しいです。:bow:

色々ためしてみる

iPadでもUIStatusBar_Modern が取れるのなら、実がiPhoneX以降用のやり方で電波強度を取得できるのかなと思い、先ほど記載したiPhoneX以降用の関数を呼んでみました。

結果は・・・ダメでした:sob:

どうやらStatusbarの階層構造が違うようです。

iPhoneXSだと、以下のようにforegroundView > subview > subview の階層に_UIStatusBarWifiSignalView がいるのに対し、

foregroundView.subviewsの中身
<UIView: 0x1066024c0; frame = (13.3333 14.6667; 66.6667 13.6667); layer = <CALayer: 0x280b6cbe0>>
foregroundView.subviews.subviewsの中身
<_UIStatusBarWifiSignalView: 0x105602b00; frame = (22 2.66667; 15.3333 11); userInteractionEnabled = NO; layer = <CALayer: 0x280b660a0>>

iPadだと、以下のようにforegroundView > subview の階層に_UIStatusBarWifiSignalView がいました。

foregroundView.subviewsの中身
<_UIStatusBarWifiSignalView: 0x15de17910; frame = (672.5 5; 14 10); userInteractionEnabled = NO; layer = <CALayer: 0x28278a0e0>>

苦肉の策

iPhoneX以前用の関数内で、UIStatusBar_Modern がある場合とない場合とで処理を分けてみました。

static func getWifiRSSI() -> Int? {
    let app = UIApplication.shared
    var numberOfActiveBars: Int?

    guard let containerBar = app.value(forKey: "statusBar") as? UIView else { return 0 }

    if let statusBarMorden = NSClassFromString("UIStatusBar_Modern"), containerBar.isKind(of: statusBarMorden) {

        guard let statusBar = containerBar.value(forKey: "statusBar") as? UIView else { return 0 }
        guard let foregroundView = statusBar.value(forKey: "foregroundView") as? UIView else { return 0 }

        for view in foregroundView.subviews {
            if let statusBarWifiSignalView = NSClassFromString("_UIStatusBarWifiSignalView"), view
                .isKind(of: statusBarWifiSignalView) {
                if let val = view.value(forKey: "numberOfActiveBars") as? Int {
                    numberOfActiveBars = val
                    break
                }
            }
            if let _ = numberOfActiveBars {
                break
            }
        }
    } else {
        guard let foregroundView = containerBar.value(forKey: "foregroundView") as? UIView else { return 0 }
        for view in foregroundView.subviews {
            if let statusBarDataNetworkItemView = NSClassFromString("UIStatusBarDataNetworkItemView"), view .isKind(of: statusBarDataNetworkItemView) {
                if let val = view.value(forKey: "wifiStrengthRaw") as? Int {
                    numberOfActiveBars = val
                }
            }
        }
    }
    return numberOfActiveBars
}

これで無事にiPadでもWifiの電波強度を取得することができました。

では、else にいくのはどういう時か

UIStatusBar_Modern 存在有無の条件分岐を入れてみたものの、ここでelse にくるのは、どういったケースなのかが謎でした。
周りにいるiPhoneユーザの方々に協力してもらったところ、iPhoneSEやiPhone5の場合にelse の処理に入ってくることがわかりました。
そしてこのソースでiPhoneSEでも無事に電波強度を取得することができたのですが、デバッグしていて一つ疑問が残りました。

残された疑問

手元のiPhoneX以降・iPadでは、返される電波強度は「0〜4」の数値(ステータスバーの扇の数)であるのに対して、iPhoneSEでは「-67」とか「-73」のように、dBmと思われる値が返されました。
この辺は端末の仕様によるものなのでしょうか。。。

私は4段階での数値が必要だったため、ここでもケース分けしなければならず、かなり汚いソースになってしまいました:fearful:

一応想定通り動いているとはいえ、なんとなくスッキリしないので、もっと良い方法ご存知の方いらっしゃればぜひご教示いただきたいです:bow:

参考サイト

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

UITableViewCell内のパーツに自身のIndexPathをわからせる手段たち

こんにちは。インターネットねこです。
最近はSwiftUIが話題ですが、あんまりそっちを追えていないので今回は表題に関する内容を書き残しておきます。散々こすられたネタかもしれませんが...

概要

ある程度複雑なTableViewを作成しようと思った時に、セル内のパーツ(UIButton, UISwitch, UITextField...)がIndexPathの情報を必要とする場合が出てきます。その対応策をこの記事では

  1. tagを使う
  2. カスタムクラスを作る
  3. extensionを使う
  4. パーツの座標からIndexPathを求める

の4つのパターンに分けて紹介していきたいと思います。個人的には 4. パーツの座標からIndexPathを求める を推していますが、ご自身の用途に適した方法を採用してください。

今回は、例として以下の画像のようなセルを使用します。対応するクラスも一応載せておきます。イメージとしては、ボタンを押した時に行う処理でIndexPathを必要とするような状況を想定しています。
スクリーンショット 2019-06-21 12.26.30.png

class ButtonCell: UITableViewCell {
    @IBOutlet weak var button: UIButton!
}

1. tagを使う

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "ButtonCell", for: indexPath)
    cell.button.tag = indexPath.row
    return cell
}

これは単純な方法で簡単に使えますが、複雑なテーブルビューにはオススメできません。複数セクションに対応させるのが面倒だという点と、可読性や保守性を損なうという点があるからです。

まず、上記のコードは、IndexPathのrowしか記録していないため、複数のセクションには対応していません。もしtagを使ってセクションも記録したいのであれば、indexPath.row の代わりにindexPath.section*1000 + indexPath.rowなどと記述してあげればsectionもわかるようになります。もちろん、記録したindexPathを使う場面になったらsectionとrowに分解する処理をいれてあげないといけません。これはイケてなさそうですね。

また、可読性, 保守性の観点からも良くなさそうです。tagがIndexPathを表しているということは、実装した本人しかわかりませんし、tagって名前も微妙です。結論として、tagを使用する方法はあまり良い手ではないように思います。まあ、サクっと作れるアプリなら全然よさそうですけどね。特に1セクション限定でセルの数も少ない場面なら十分有効な手段だと思います。

2. カスタムクラスを作る

final class CustomButton: UIButton {
    var indexPath: IndexPath?
}

tagがダメなら専用のプロパティをもたせればいいじゃない!ということで、カスタムクラスを作ってみます。IndexPathを記録しておくプロパティさえあれば、「1. tagを使う」で出た2つの問題である、複数セクションと可読性問題を解決できそうです。この方法を使う場合は、例で使用しているCellを以下のように書き換えて、

class ButtonCell: UITableViewCell {
    @IBOutlet weak var button: CustomButton!
}

tableView(_:cellForRowAt:)を以下のように書いてあげれば良さそうです。

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "ButtonCell", for: indexPath) as! ButtonCell
    cell.button.indexPath = indexPath
    return cell
}

この方法で無事に目的を果たせたような気もしますが、たかがIndexPathを保存するためにクラスを作るのは過剰な気もしますよね?しませんか?
僕はしますので、今度はextensionを使ってみます!

3. extensionを使う

extensionでプロパティを追加するのは黒魔術的要素を含んでいるので、あんまり良い手段とは思いませんが、せっかく調べたので一応載せておきます。

var IndexPathKey: Int = 0

extension UIButton {
    var indexPath: IndexPath? {
        get {
            guard let object = objc_getAssociatedObject(self, &IndexPathKey) as? IndexPath else {
                return nil
            }
            return object
        }

        set {
            objc_setAssociatedObject(self, &IndexPathKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

extensionでプロパティ(的なもの)を追加しようとすると上記のようなコードになってしまい黒魔術感が凄いですが、使うときは UIButton.indexPathを使えばいいのでシンプルですね。この黒魔術も記事のテーマを解決する有効な手段だと思います。

4. パーツの座標からIndexPathを求める

これまでに3つの手段を紹介してきましたが、もっとシンプルな方法がありました。それが、パーツの座標からIndexPathを求める方法です。

extension UITableView {
    func indexPath(of view: UIView) -> IndexPath? {
        let location = view.convert(CGPoint.zero, to: self)
        return indexPathForRow(at: location)
    }
}

今回はセル側ではなく、テーブルビュー側を拡張しています。たったこれだけでテーブルビュー側から特定のパーツのIndexPathを取れるようになります。IndexPathが欲しいときは以下のように書いてあげればよいです。

tableView.indexPath(of: button) // tableView内でのbuttonのIndexPathを取得

この方法は、1~3と違ってパーツ自体がIndexPathの情報を持っているわけではなく、テーブルビュー側に問い合わせる形になっているので、テーマとは少し意味合いが変わっていますが、結構シンプルに書けて個人的には気に入っています。問い合わせ先のテーブルビューのインスタンスを知っている前提で書いていますがご了承ください。

とまあ、以上4つ紹介してみました。自分に合ったやり方でやってもらえればいいとおもいます。何か他にもいい方法をご存知の方は教えてください!(文章書くの疲れて段々投げやりになってきてしまうヤツ)

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