スプラッシュのViewを取得する方法【Swift】

■はじめに

以下のようにスプラッシュのViewを取得したい場合があります。(たまに)

  • スプラッシュ表示後も一定時間スプラッシュを表示したい
  • 特定の画面でローディングダイアログなどの代わりにスプラッシュを表示したい(裏で画面遷移するなど)

その場合に、スプラッシュと同じような構成でViewを作成してもいいですが、二度手間になります。

ここでは、簡単にスプラッシュからViewを取得して使用する方法を記載します。

■手順

以下のように記載することで、スプラッシュのViewを取得できます。

let splashStoryboardName = Bundle.main.object(forInfoDictionaryKey: "UILaunchStoryboardName") as? String ?? ""
let storyboard = UIStoryboard(name: splashStoryboardName, bundle: nil)
let viewController = storyboard.instantiateInitialViewController()
let splashView = viewController?.view ?? UIView()

まずは以下でSplashで使用しているStoryboardの名前を取得します。

基本は「LaunchScreen」になっていると思いますが、変更されても問題ないようにInfo.plistから取得しています。

Info.plistの「Launch screen interface file base name」がSplashのストーリーボード名です。

let splashStoryboardName = Bundle.main.object(forInfoDictionaryKey: "UILaunchStoryboardName") as? String ?? ""

上記で取得したStoryboard名から、Storyboardのインスタンスを取得します。

let storyboard = UIStoryboard(name: splashStoryboardName, bundle: nil)

取得したStoryboardの「Is Initial View Controller」にチェックされているViewControllerを取得します。

let viewController = storyboard.instantiateInitialViewController()

該当のViewControllerの一番親のViewを取得します。

※このViewがスプラッシュのViewになります。

let splashView = viewController?.view ?? UIView()

■おまけ

以下のようにすることで、どのような画面でも、一番上にスプラッシュのViewを表示することが可能です。

let splashStoryboardName = Bundle.main.object(forInfoDictionaryKey: "UILaunchStoryboardName") as? String ?? ""
let storyboard = UIStoryboard(name: splashStoryboardName, bundle: nil)
let viewController = storyboard.instantiateInitialViewController()
let splashView = viewController?.view ?? UIView()
let window = UIApplication.shared.keyWindow!
window.addSubview(splashView);

任意のタイミングで、以下のようにViewを破棄することで、スプラッシュ表示前の画面に戻せます。

splashView.removeFromSuperview()

キーチェーンの共通クラスの作成【Swift】

■はじめに

ライブラリは使用しないで、キーチェーンを使用する共通処理を記載します。

なるべく保守性の高いクラスにしようとして作成しました。

(今後、修正するかも。。。)

あくまで、参考までに。

■手順

「KeyChain.swift」クラスを作成する。

以下を丸々コピペで「KeyChain.swift」に貼り付けてください。

※KeyChainの一意のKeyはBundleIDにしています。

import Foundation
class KeyChain {
// KeyChainクラスのインスタンス取得
public static let shared = KeyChain()
// KeyChainの一意のKey情報(BundleID)
fileprivate let privateKey: String = Bundle.main.bundleIdentifier ?? ""
// 保存
func setKeyChain(_ value: String?, key: String) {
let data = value?.data(using: .utf8)
guard let _data = data else {
deleteKeyChain(key: key)
return
}
let dic: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrGeneric as String: key,
kSecAttrAccount as String: privateKey,
kSecValueData as String: _data]
var itemAddStatus: OSStatus?
// 保存データが存在するかの確認
let matchingStatus = SecItemCopyMatching(dic as CFDictionary, nil)
if matchingStatus == errSecItemNotFound {
// 保存
itemAddStatus = SecItemAdd(dic as CFDictionary, nil)
} else if matchingStatus == errSecSuccess {
// 更新
itemAddStatus = SecItemUpdate(dic as CFDictionary, [kSecValueData as String: _data] as CFDictionary)
} else {
print("保存失敗1")
}
// 保存・更新ステータス確認
if itemAddStatus == errSecSuccess {
print("正常終了")
} else {
print("保存失敗")
}
}
// 取得
func getKeyChain(key: String) -> String? {
let dic: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrGeneric as String: key,
kSecAttrAccount as String: privateKey,
kSecReturnData as String: kCFBooleanTrue]
var data: AnyObject?
let matchingStatus = withUnsafeMutablePointer(to: &data){
SecItemCopyMatching(dic as CFDictionary, UnsafeMutablePointer($0))
}
if matchingStatus == errSecSuccess {
print("取得成功")
if let getData = data as? Data,
let getStr = String(data: getData, encoding: .utf8) {
return getStr
}
print("取得失敗: Dataが不正")
return nil
} else {
print("取得失敗")
return nil
}
}
// 削除
func deleteKeyChain(key: String) {
// 削除するqueryを設定
let dic: [String: Any] = [kSecClass as String: kSecClassGenericPassword,
kSecAttrGeneric as String: key,
kSecAttrAccount as String: privateKey]
if SecItemDelete(dic as CFDictionary) == errSecSuccess {
print("削除成功")
} else {
print("削除失敗")
}
}
}

このクラスの使用方法は以下です。

(なるべく、UserDefaultsに近い書き方ができるようにしています。)

// 値の取得
print(KeyChain.shared.getKeyChain(key: "key"))
// 値の設定
KeyChain.shared.setKeyChain("値", key: "key")
// 値の削除(以下のどちらでも可)
KeyChain.shared.deleteKeyChain(key: "kkkk")
KeyChain.shared.setKeyChain(nil, key: "key")

SQLite3でINSERT時にPrepared Statementを複数使用する方法【Swift】

■はじめに

以下のサイトを参考に、SQLite3は使用できるようにしておいてください。(基本的なことは飛ばして説明します。)

www.reigle.info

■手順

まずは、ソースコードの全量は以下

func insert() {
var stmt: OpaquePointer?
let queryString = "INSERT INTO sampleTable (name, age) VALUES (?, ?)"
// クエリを準備する
if sqlite3_prepare(db, queryString, -1, &stmt, nil) != SQLITE_OK{
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("error preparing insert: \(errmsg)")
return
}
// prepared statementを複数定義するために非定常の定義
let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)
// 1つめの?の定義
if sqlite3_bind_text(stmt, 1, "ひとつめ", -1, SQLITE_TRANSIENT) != SQLITE_OK{
let errmsg = String(cString: sqlite3_errmsg(self.db)!)
print("failure binding name: \(errmsg)")
return
}
// 2つめの?の定義
if sqlite3_bind_text(stmt, 2, "ふたつめ", -1, SQLITE_TRANSIENT) != SQLITE_OK{
let errmsg = String(cString: sqlite3_errmsg(self.db)!)
print("failure binding name: \(errmsg)")
return
}
// クエリを実行する
if sqlite3_step(stmt) != SQLITE_DONE {
let errmsg = String(cString: sqlite3_errmsg(db)!)
print("failure inserting hero: \(errmsg)")
return
}
print("データが登録されました")
}

重要なのは、以下の定義です。

これは、複数のPrepared Statementを使用するための「おまじない」だと思ってください。

let SQLITE_TRANSIENT = unsafeBitCast(-1, to: sqlite3_destructor_type.self)

上記のおまじないの定数を「sqlite3_bind_text」の第五引数に設定することで、複数のPrepared Statementに値をそれぞれ設定できます。

TextFieldに英数記号のキーボードのみを表示する方法【Swift】

■はじめに

ここでは、以下の要望への対応方法を記載します。

  • TextFieldにフォーカスを当てた時のキーボードを、英数記号のみにして欲しい
  • ユーザーの設定関係なく、英数記号のキーボードにして欲しい。
  • キーボードの切り替えを許さないで欲しい
  • パスワード入力以外のTextFieldに使いたい(●は出さないで欲しい)

iOSの事を知らない人はこんな無茶苦茶な要望してくる人もいるかもしれません。

(実際にされた人を知っていますw)

上記は、iOSのデフォルト機能だけでは実現することができません。

「●」は出さないで欲しい。が一番の原因ですね。。。

■手順

最終的にこんな感じのキーボードが表示されます。

(パスワード用のキーボードだとスクショに映らないので、写真で取りました。。。見辛くてすみません。。。)

まずは、結論から。

CustomTextField.swiftを作成して以下をコピペしてください。

import UIKit
class CustomTextField: UITextField, UITextFieldDelegate {
// パスワード入力フィールド(画面上には表示しない)
var passText = UITextField()
/// 初期化処理
/// - Parameter frame: frame description
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
/// 初期化処理
/// - Parameter aDecoder: aDecoder description
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
commonInit()
}
/// 初期化処理で呼び出す共通処理
func commonInit() {
// 非表示のテキストフィールドをパスワード入力フィールドに設定
passText.isSecureTextEntry = true
// キーボードの上のiCloudから入力する「パスワード」の部分の非表示
if #available(iOS 12.0, *) {
passText.textContentType = .oneTimeCode
}
// TextFieldのデリゲートでフォーカス時、値入力時の制御を行う
self.delegate = self
passText.delegate = self
// パスワード入力テキストフィールドを非表示状態でViewに追加
passText.frame = CGRect(x: 0, y: 0, width: 0, height: 0)
self.addSubview(passText)
}
/// フォーカス時処理
/// - Parameter textField: textField description
func textFieldDidBeginEditing(_ textField: UITextField) {
// フォーカスが当たったあと、一瞬遅延させる(即時にフォーカス切り替えが動かないので)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
// フォーカス制御はメインスレッドで
DispatchQueue.main.async {
// パスワード入力TextFIeldにフォーカスを当てる
self.passText.becomeFirstResponder()
}
}
}
/// 値入力(削除)時処理
/// - Parameters:
///   - textField: textField description
///   - range: range description
///   - string: string description
/// - Returns: description
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// 値をTextFieldに反映
self.text = textField.text
// 一瞬遅延
DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) {
// メインスレッドで処理
DispatchQueue.main.async {
// パスワード入力TextFieldの値を表示されているTextFieldに設定
self.text = self.passText.text
}
}
return true
}
}

使い方は以下のような感じで、使ってください。(普通のUITextFieldと同じです)

let text = CustomTextField()
text.frame = CGRect(x: 50, y: 160, width: 100, height: 30)
text.layer.borderColor = UIColor.blue.cgColor
text.layer.borderWidth = 1
self.view.addSubview(text)

この方法のポイントとしては、「passText」です。

これを画面に見えないように精製して、TextFieldにフォーカスされたタイミングで「passText」にフォーカスさせています。

で、「passText」に値が入力された際にTextFieldに反映させています。

結果、TextFieldに値が入力されているようにみえます。

他に方法があれば、是非ご連絡ください。

「kCBAdvDataManufacturerData」の操作【Swift】

■はじめに

Bluetoothで「kCBAdvDataManufacturerData」の値を取得してどうのこうのしたい時があります。

その方法で結構詰まったので、メモ。

■手順

「kCBAdvDataManufacturerData」の値を以下とします。

"kCBAdvDataManufacturerData": <68656c6c 6f20776f 726c64>

以下のExtensionを用意します。

//// Data extension
extension Data {
var dataToHexString: String {
return reduce("") {$0 + String(format: "%02x", $1)}
}
}

以下の通り、実装することで、値が取得できます。(具体的な値はコメントに記載。)

func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber){
let aaa = advertisementData["kCBAdvDataManufacturerData"] as? NSData {
let publicData = Data(bytes: aaa.bytes, count: Int(aaa.length))
let str = publicData.dataToHexString
print(str) // 68656c6c6f20776f726c64
}
}

以下の通りにすることで、文字列に復元することもできます。

let str = publicData.dataToHexString
var bytes = [UInt8]()
for idx in stride(from: 0, to: str.count, by: 2) {
let hex = str[str.index(str.startIndex, offsetBy: idx)..<str.index(str.startIndex, offsetBy: idx + 2)]
bytes.append(UInt8(hex, radix: 16)!)
}
print(String(bytes: bytes, encoding: .utf8)!) // Hello World

ソースコード全量

func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber){
let aaa = advertisementData["kCBAdvDataManufacturerData"] as? NSData {
let publicData = Data(bytes: aaa.bytes, count: Int(aaa.length))
let str = publicData.dataToHexString
print(str) // 68656c6c6f20776f726c64
var bytes = [UInt8]()
for idx in stride(from: 0, to: str.count, by: 2) {
let hex = str[str.index(str.startIndex, offsetBy: idx)..<str.index(str.startIndex, offsetBy: idx + 2)]
bytes.append(UInt8(hex, radix: 16)!)
}
print(String(bytes: bytes, encoding: .utf8)!) // Hello World
}
}
//// Data extension
extension Data {
var dataToHexString: String {
return reduce("") {$0 + String(format: "%02x", $1)}
}
}

UserDefaultsの処理をまとめて保守性が高いUserDefaultsの書き方【Swift】

■はじめに

UserDefaultsの処理を保守性を高く記載しようと思って、色々と試行錯誤しました。

ここで紹介する方法は一例ですので、参考にしてください。

(Keyを定数クラスで管理するなども考えましたが、保守するクラスが増えるだけでしたので断念。)

■手順

UserDefaultsの処理をまとめるクラス「UserDefaults+MyApp.swift」を作成してください。(ファイル名は任意)

まずは、一例として、以下のコードを丸々コピペで「UserDefaults+MyApp.swift」に貼り付けてください。

import Foundation
// UserDefaultsの拡張クラス
extension UserDefaults {
// UserDefaultsのKeyをまとめたenum
private enum UDKey: String {
case keyOne
case keyTwo
}
// データの全件削除処理
func removeAllObject() {
removeObject(forKey: UDKey.keyOne.rawValue)
removeObject(forKey: UDKey.keyTwo.rawValue)
}
// 「keyOne」のセッターゲッターの処理
var keyOne: String? {
get {
return string(forKey: UDKey.keyOne.rawValue)
}
set(val) {
set(val, forKey: UDKey.keyOne.rawValue)
}
}
// 「keyTwo」のセッターゲッターの処理
var keyTwo: Bool {
get {
return bool(forKey: UDKey.keyTwo.rawValue)
}
set(val) {
set(val, forKey: UDKey.keyTwo.rawValue)
}
}
}

上記のサンプルは「String」「Bool」の2種類の型のUserDefaultsの定義をしています。

UserDefaultsの「Key」は「UDKey」のenumにまとめて定義しています。

setterとgetterでKeyの処理を書いているので、以下のように利用できます。

// 取得する場合
print(UserDefaults.standard.keyOne)
print(UserDefaults.standard.keyTwo)
// 値を設定する場合
UserDefaults.standard.keyOne = "きー1"
UserDefaults.standard.keyTwo = true
// データを全件削除する場合
UserDefaults.standard.removeAllObject()

APIKitのサンプル(郵便番号検索)【Swift】

■はじめに

ここでは、「APIKit」というライブラリのサンプルを記載します。
APIKitの詳細に関しては、GitHubhttps://github.com/ishkawa/APIKit)を参照してください。
APIKitの詳細を簡単に説明すると、タイプセーフにAPIを利用するためのライブラリです。
※タイプセーフとは、型変換の時に別の型を渡せない様になっているみたいなことです。(String型にInt型を設定するなどはできない)
※CocoaPodsなどを用いて、APIKitをプロジェクトに組み込んでおいてください。(ここでは組込方法は割愛します。)

また、以下のサンプルでは、郵便番号検索APIを使用したサンプルになっていますが、郵便番号検索APIには「zipcloud」(http://zipcloud.ibsnet.co.jp/doc/api)を利用しています。

■手順

まずは、APIのレスポンスを入れるためのEntityを作成します。
郵便番号検索APIのレスポンスを入れるため、以下の様に記載してください。

import Foundation
class Entity {
var address1: String = ""
var address2: String = ""
var address3: String = ""
var kana1: String = ""
var kana2: String = ""
var kana3: String = ""
var prefcode: String = ""
var zipcode: String = ""
init(object: [String: AnyObject]?) throws {
guard
let status: Int = object?["status"] as? Int,
status == 200,
let results: NSArray = object?["results"] as? NSArray
else { return }
if let data = results[0] as? [String: AnyObject] {
self.address1 = data["address1"] as? String ?? ""
self.address2 = data["address2"] as? String ?? ""
self.address3 = data["address3"] as? String ?? ""
self.kana1 = data["kana1"] as? String ?? ""
self.kana2 = data["kana2"] as? String ?? ""
self.kana3 = data["kana3"] as? String ?? ""
self.prefcode = data["prefcode"] as? String ?? ""
self.zipcode = data["zipcode"] as? String ?? ""
}
}
}

次に、APIの本体となるDataStoreクラスを作成します。
以下の様に記載してください。
ここで、APIのURLやパラメータやHTTPメソッドなどの指定をしています。

import Foundation
import APIKit
struct DataStore: Request {
typealias Response = Entity
let zipcode: String
var baseURL: URL {
return URL(string: "http://zipcloud.ibsnet.co.jp")!
}
var method: HTTPMethod {
return .get
}
var path: String {
return "/api/search"
}
var parameters: Any? {
return ["zipcode": zipcode]
}
func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Entity {
return try Entity(object: object as? [String : AnyObject])
}
}

ここまで用意できたら以下の様に呼び出すだけです。
以下は、千代田区の郵便番号を利用しています。(100-0000なんて、キリがいい郵便番号だったので!)

// 東京都千代田区の郵便番号「100-0000」
let request = DataStore(zipcode: "1000000")
Session.send(request) { result in
switch result {
case .success(let target):
print("=====SUCCESS=====")
print(target.address1)
print(target.address2)
print(target.address3)
print(target.kana1)
print(target.kana2)
print(target.kana3)
print(target.prefcode)
print(target.zipcode)
case .failure(let error):
print("error: \(error)")
}
}

ライブラリを使用せずにJWT(JSON Web Token)をデコードする方法【Swift】

■はじめに

APIなどでJWTを取得する事があります。
その際にそのJWTをデコードして値を操作する必要があるので、ここでは「ライブラリを使用せずに」JWTをデコードする方法を記載します。
※ライブラリ使ってはいけないプロジェクトもあるので。。。

■手順

以下の関数を定義します。
細かい内容はコメントをみてください!!

/// JWT(Json Web Token)のデコード処理
/// - Parameters:
///   - token: JWT
///   - type: デコードタイプ(0: ヘッダーの取得, 1: ペイロードの取得)※引数に指定しなければペイロードが取得される
func jwtDecode(_ token: String, _ type: Int = 1) -> [String: AnyObject]? {
// JWTを「.」で分割して取得したい項目を取得する(0:Header, 1:Payload)
let toDecode = token.components(separatedBy: ".")[type] as String
// JWTでエンコードする際にキャストされる文字列(「-」「+」)をキャスト前に戻す
var stringtoDecode: String = toDecode.replacingOccurrences(of: "-", with: "+").replacingOccurrences(of: "_", with: "/")
// 文字数に応じて末に「=」を追加する
switch stringtoDecode.utf16.count % 4 {
case 2:
stringtoDecode += "=="
case 3:
stringtoDecode += "="
default:
break
}
// JWTを辞書型にキャストする
guard
let dataToDecode = Data(base64Encoded: stringtoDecode, options: []),
let base64DecodedString = NSString(data: dataToDecode, encoding: String.Encoding.utf8.rawValue),
let data = base64DecodedString.data(using: String.Encoding.utf8.rawValue, allowLossyConversion: true),
let value = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: AnyObject]
else {
// キャストが失敗したらnilを返す
return nil
}
// キャストが正常に完了したらそのデータを返す
return value
}

使い方の説明をする前に、テストでは以下のJWTを利用します。
※こちらのサイト様(https://jwt.io/)のデフォルト値のデータを拝借(感謝!)

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

以下、JWTの値(PAYLOAD)の取得方法です。

let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
print(jwtDecode(jwt))

ちなみに、以下の様に記載しても同じです!!
※第二引数に「1」を入れました(デフォルト値が1なので入れなくてもいいですが。。。)

let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
print(jwtDecode(jwt, 1))

JWTのHEADERを取得したい場合には以下のように記載してください。
※第二引数が「0」です!

let jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c"
print(jwtDecode(jwt, 0))

注意ですが、第二引数に「0」「1」以外を入れても「nil」が返ってくるだけです。

KeyChainの操作(CRUD)の方法【Swift】

■はじめに

アプリ内のデータを保存する際にUserDefaultsなど様々な方法で保存ができます。
ここで紹介するKeyChainへの保存は、データを強固に保存することができます。
また、同じAppleIDを利用する事で別端末へ引き継いだりもできます。

■手順

KeyChainの操作はゴチャゴチャしていてわかりづらいので、共通定義を用意しました。
「KeyChain.swift」を作成して以下を丸々コピペしてください。

import Foundation
/// 継承不可のKeyChain操作クラス
struct KeyChain {
// MARK: - 保存 / 更新
/// String型のデータの保存処理
/// - Parameters:
///   - key: 保存する対象のキー
///   - value: 保存する対象のString型の値
static func setKeyChain(key: String, value: String) {
guard let data = value.data(using: .utf8) else { return }
setKeyChainCommon(key: key, data: data)
}
/// Int型のデータの保存処理
/// - Parameters:
///   - key: 保存する対象のキー
///   - value: 保存する対象のInt型の値
static func setKeyChain(key: String, value: Int) {
setKeyChain(key: key, value: String(value))
}
/// Double型のデータの保存処理
/// - Parameters:
///   - key: 保存する対象のキー
///   - value: 保存する対象のDouble型の値
static func setKeyChain(key: String, value: Double) {
setKeyChain(key: key, value: String(value))
}
/// Float型のデータの保存処理
/// - Parameters:
///   - key: 保存する対象のキー
///   - value: 保存する対象のFloat型の値
static func setKeyChain(key: String, value: Float) {
setKeyChain(key: key, value: String(value))
}
/// Bool型のデータの保存処理
/// - Parameters:
///   - key: 保存する対象のキー
///   - value: 保存する対象のBool型の値
static func setKeyChain(key: String, value: Bool) {
setKeyChain(key: key, value: String(value))
}
/// Any型のデータの保存処理(配列や辞書などの関数が用意されていないものはこの関数を利用する)
/// - Parameters:
///   - key: 保存する対象のキー
///   - value: 保存する対象のAny型の値
static func setKeyChain(key: String, value: Any) {
if let archiveData = try? NSKeyedArchiver.archivedData(withRootObject: value, requiringSecureCoding: true) {
setKeyChainCommon(key: key, data: archiveData)
}
}
/// データの保存共通処理
/// - Parameters:
///   - key: 保存する対象のキー
///   - data: 保存するデータのData型の値
fileprivate static func setKeyChainCommon(key: String, data: Data) {
deleteKeyChain(key: key)
SecItemAdd([kSecClass: kSecClassGenericPassword,
kSecValueData: data,
kSecAttrAccount: Bundle.main.bundleIdentifier ?? "",
kSecAttrService: key] as CFDictionary, nil)
}
// MARK: - 取得
/// String型のデータ取得処理
/// - Parameter key: 取得データのキー
static func getStringKeyChain(key: String) -> String? {
guard let targetData = getKeyChainCommon(key: key) else { return nil }
return String(data: targetData, encoding: .utf8)
}
/// Int型のデータ取得処理
/// - Parameter key: 取得データのキー
static func getIntKeyChain(key: String) -> Int? {
return Int(getStringKeyChain(key: key) ?? "")
}
/// Double型のデータ取得処理
/// - Parameter key: 取得データのキー
static func getDoubleKeyChain(key: String) -> Double? {
return Double(getStringKeyChain(key: key) ?? "")
}
/// Float型のデータ取得処理
/// - Parameter key: 取得データのキー
static func getFloatKeyChain(key: String) -> Float? {
return Float(getStringKeyChain(key: key) ?? "")
}
/// Bool型のデータ取得処理
/// - Parameter key: 取得データのキー
static func getBoolKeyChain(key: String) -> Bool? {
return Bool(getStringKeyChain(key: key) ?? "")
}
/// Any型のデータ取得処理(配列や辞書などの関数が用意されていないものはこの関数を利用する)
/// - Parameter key: 取得データのキー
static func getAnyKeyChain(key: String) -> Any? {
guard let data: Data = getKeyChainCommon(key: key) else { return nil }
return try? NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(data)
}
/// データ取得処理の共通処理
/// - Parameter key: 取得データのキー
fileprivate static func getKeyChainCommon(key: String) -> Data? {
var queryResult: AnyObject?
_ = withUnsafeMutablePointer(to: &queryResult) {
SecItemCopyMatching([kSecClass: kSecClassGenericPassword,
kSecAttrAccount: Bundle.main.bundleIdentifier ?? "",
kSecAttrService: key,
kSecReturnAttributes: kCFBooleanTrue as Any,
kSecMatchLimit: kSecMatchLimitOne] as CFDictionary, UnsafeMutablePointer($0))
}
// パスワード以外のキーを取得する
guard var valueQuery = queryResult as? [AnyHashable: AnyHashable] else { return nil }
valueQuery[kSecClass] = kSecClassGenericPassword
valueQuery[kSecReturnData] = kCFBooleanTrue
// パスワードを取得する
var valueQueryResult: AnyObject?
_ = withUnsafeMutablePointer(to: &valueQueryResult){
SecItemCopyMatching(valueQuery as CFDictionary, UnsafeMutablePointer($0))
}
return valueQueryResult as? Data
}
// MARK: - 削除
/// 削除処理
/// - Parameter key: 削除対象のキー
static func deleteKeyChain(key: String) {
SecItemDelete([kSecClass: kSecClassGenericPassword,
kSecAttrAccount: Bundle.main.bundleIdentifier ?? "",
kSecAttrService: key] as CFDictionary)
}
}

これで、簡単にKeyChainを使用する準備ができました。
以下の様に使用する事で、KeyChainにデータを保存することができます。
※以下では、「value」をString型で記載していますが、その他のデータ型でも同様です。
※「key」に関してはString型で定義する必要があります。
※既に保存されているKeyの場合には、データが上書きされます。(この動きでデータを更新します。)

KeyChain.setKeyChain(key: "key", value: "VALUE")

以下の様に使用する事で、KeyChainからデータを取得することができます。
※データ型によって、取得方法を変更する必要があるので、メソッド名は「get[データ型]KeyChain」の様にしてください。
※「key」に関してはString型で定義する必要があります。
※配列型や辞書型などは「Any」で取得した後にキャストする必要があります。

KeyChain.getStringKeyChain(key: "key")

以下の様に使用する事で、KeyChainからデータを削除することができます。
※「key」に関してはString型で定義する必要があります。

KeyChain.deleteKeyChain(key: "key")

Sign In with Appleを実装する方法【Swift】

■はじめに

Appleから、Googleログインなどのソーシャルログインを追加する場合にはAppleのソーシャルログインを追加するようにレビューガイドラインが更新されました。

そのため、ここでは、Appleのソーシャルログイン(Sign In with Apple)の実装方法を記載します。

このログインはiOS13から利用できますので、ターゲットバージョンを13以上にしてください。

■手順

プロジェクトを作成し、「Capability」で「Sign In with Apple」を追加する。

ViewControllerに「AuthenticationServices」のインポートを追加する。

import AuthenticationServices

Delegateに「ASAuthorizationControllerDelegate」を追加する。

class ViewController: UIViewController, ASAuthorizationControllerDelegate {

「viewDidLoad」で「Sign In」のボタンを作成する。

override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
let authorizationButton = ASAuthorizationAppleIDButton()
authorizationButton.frame = CGRect(x: 10, y: 100, width: UIScreen.main.bounds.size.width - 20, height: 50)
authorizationButton.addTarget(self, action: #selector(authorizationAppleID), for: .touchUpInside)
authorizationButton.cornerRadius = 10
self.view.addSubview(authorizationButton)
}

ボタン押下時の処理を追加する。

@objc func authorizationAppleID() {
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.performRequests()
}

Delegateのメソッドを追加する。

func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
print(appleIDCredential.user)
print(appleIDCredential.fullName ?? "")
print(appleIDCredential.email ?? "")
}
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
// エラー処理
}

これで、実装が完了しました。

ビルドして確認してみてください。

ソースコードの全量は以下です。

import UIKit
import AuthenticationServices
class ViewController: UIViewController, ASAuthorizationControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = .white
let authorizationButton = ASAuthorizationAppleIDButton()
authorizationButton.frame = CGRect(x: 10, y: 100, width: UIScreen.main.bounds.size.width - 20, height: 50)
authorizationButton.addTarget(self, action: #selector(authorizationAppleID), for: .touchUpInside)
authorizationButton.cornerRadius = 10
self.view.addSubview(authorizationButton)
}
@objc func authorizationAppleID() {
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = self
authorizationController.performRequests()
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
print(appleIDCredential.user)
print(appleIDCredential.fullName ?? "")
print(appleIDCredential.email ?? "")
}
}
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
// エラー処理
}
}