- 投稿日:2020-05-14T23:15:35+09:00
JSONを使用してJava ServletからiPhoneアプリへ値を受け渡してみる【初心者】
会社の同期に誘われてiPhoneアプリを作成している最中、WebサーバーとiPhoneアプリ間の値の受渡しをどうするか悩んでいました。その時、どうやらJSONを使用して、値を受け渡すらしい!ということを知りました。
そこで、色々調べた結果、無事にJSONを使用した値の受渡しができたので、まとめます。新人研修&自己学習で得た知識で書いてるので、間違っている箇所やより良い方法があれば、コメントください、、、、
作成するアプリ
流れとしてはこんな感じです。
① 「JSONを取得する」ボタンを押し、JavaServletへランダムな数値を渡す。
② JavaServletは、受け取った数値に応じたJSONをレスポンスする。
③ Swiftは、受け取ったJSONオブジェクトを変換し、値を画面へ出力する。赤枠で囲まれている箇所に、JavaServletから得たJSONを出力しています。
また、すべてローカル環境で実装します。環境
XcodeとEclipseを使用し、開発しました。
JSONを使用するにあたって、GSONかJacksonで迷いましたが、今回はJacksonを使用しました。実際のコード
Xcode
JSONを取得する画面
ViewController.swiftimport UIKit class ViewController: UIViewController { //表示用の文言 var textId = "" var textName = "" //タプル配列の宣言 var studentList:[(id:String , name:String)] = [] override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } @IBAction func getJson(_ sender: Any) { self.performSegue(withIdentifier: "goResultVC", sender: nil) } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { //リクエストURL JSONを返すサーブレットを指定 guard let req_url = URL(string: "http://localhost:8080/servlet_test/JsonServlet") else{return} //リクエストに必要な情報を生成 var req = URLRequest(url: req_url //0~2のランダムな数値を取得 let id = Int.random(in: 0...2) //データ転送を管理するためのセッションを作成 let session = URLSession(configuration: .default, delegate: nil, delegateQueue: OperationQueue.main) //JavaServletへ渡す情報(ID)をBodyへ設定する req.httpMethod = "POST" req.httpBody = "id=\(id)".data(using: .utf8) //リクエストをタスクとして登録 let task = session.dataTask(with: req, completionHandler: { (data, response ,error) in //セッションの終了 session.finishTasksAndInvalidate() do{ //取得したJSONを変換する let decoder = JSONDecoder() let json = try decoder.decode(StudentJson.self, from: data!) self.textId = json.id! self.textName = json.name! //「JSONを取得する」ボタンに紐づくセグエ if segue.identifier == "goResultVC" { let nextVC = segue.destination as! ResultViewVontroller nextVC.jsonId = self.textId nextVC.jsonName = self.textName } }catch{ print(error) print("エラーがでました") } }) //ダウンロード開始 task.resume() } //JSONのデータ構造 struct StudentJson: Codable { let id: String? let name: String? } }JSONを出力する画面
ResultViewVontroller.swiftimport UIKit class ResultViewVontroller: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } //出力メッセージのフィールド var jsonId = "" var jsonName = "" //出力ラベル @IBOutlet weak var resultJsonId: UILabel! @IBOutlet weak var resultJsonName: UILabel! override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(true) resultJsonId.text = jsonId resultJsonName.text = jsonName } }Eclipse
Java Servlet
JsonServlet.javapackage servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import bean.JsonBean; /** * Servlet implementation class JsonServlet */ @WebServlet("/JsonServlet") public class JsonServlet extends HttpServlet { private static final long serialVersionUID = 1L; /** * @see HttpServlet#HttpServlet() */ public JsonServlet() { super(); // TODO Auto-generated constructor stub } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { JsonBean jsonBeanList[] = new JsonBean[3]; //Javaオブジェクトに値をセット JsonBean jsonBean = new JsonBean(); jsonBean.setId("101"); jsonBean.setName("tanaka"); JsonBean jsonBean2 = new JsonBean(); jsonBean2.setId("102"); jsonBean2.setName("yamada"); JsonBean jsonBean3 = new JsonBean(); jsonBean3.setId("103"); jsonBean3.setName("satou"); jsonBeanList[0] = jsonBean; jsonBeanList[1] = jsonBean2; jsonBeanList[2] = jsonBean3; // String str = request.getParameter("id"); int requestId = Integer.parseInt(str); System.out.println(requestId); ObjectMapper mapper = new ObjectMapper(); try { //JavaオブジェクトからJSONに変換 String testJson = mapper.writeValueAsString(jsonBeanList[requestId]); //JSONの出力 response.getWriter().write(testJson); //出力されるJSONの確認 System.out.println(testJson); } catch (JsonProcessingException e) { e.printStackTrace(); } } }Bean
JsonBean.javapackage bean; import java.util.List; import com.fasterxml.jackson.annotation.JsonProperty; public class JsonBean { @JsonProperty("id") private String id; @JsonProperty("name") private String name; public void setId(String id) { this.id = id; } public void setName(String name) { this.name = name; } }JsonServletの動きを確認するには、Chromeの拡張機能であるTalend API Testerがおすすめです!
Talend API Testerまとめ
これで、フロントエンドとバックエンド間の通信ができるようになりました。
次は、AWS上に乗っけてみます。また、今回の実装をするにあたり、多くのQiita記事と書籍を参考にしました。
拙い内容でしたが、閲覧いただきありがとうございました!
マークダウン記法、めちゃめちゃ書きやすいですね。
- 投稿日:2020-05-14T23:15:35+09:00
JSONを使用してJava ServletからiPhoneアプリへ値を受け渡してみる
会社の同期に誘われてiPhoneアプリを作成している最中、WebサーバーとiPhoneアプリ間の値の受渡しをどうするか悩んでいました。その時、どうやらJSONを使用して、値を受け渡すらしい!ということを知りました。
そこで、色々調べた結果、無事にJSONを使用した値の受渡しができたので、まとめます。新人研修&自己学習で得た知識で書いてるので、間違っている箇所やより良い方法があれば、コメントください、、、、
作成するアプリ
流れとしてはこんな感じです。
① 「JSONを取得する」ボタンを押し、JavaServletへランダムな数値を渡す。
② JavaServletは、受け取った数値に応じたJSONをレスポンスする。
③ Swiftは、受け取ったJSONオブジェクトを変換し、値を画面へ出力する。赤枠で囲まれている箇所に、JavaServletから得たJSONを出力しています。
また、すべてローカル環境で実装します。環境
XcodeとEclipseを使用し、開発しました。
JSONを使用するにあたって、GSONかJacksonで迷いましたが、今回はJacksonを使用しました。
EclipceでのJackson のセットアップ方法は以下を参考にしました。
https://tech.pjin.jp/blog/2020/03/09/jackson_setup/実際のコード
Xcode
JSONを取得する画面
ViewController.swiftimport UIKit class ViewController: UIViewController { //表示用の文言 var textId = "" var textName = "" //タプル配列の宣言 var studentList:[(id:String , name:String)] = [] override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } @IBAction func getJson(_ sender: Any) { self.performSegue(withIdentifier: "goResultVC", sender: nil) } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { //リクエストURL JSONを返すサーブレットを指定 guard let req_url = URL(string: "http://localhost:8080/servlet_test/JsonServlet") else{return} //リクエストに必要な情報を生成 var req = URLRequest(url: req_url //0~2のランダムな数値を取得 let id = Int.random(in: 0...2) //データ転送を管理するためのセッションを作成 let session = URLSession(configuration: .default, delegate: nil, delegateQueue: OperationQueue.main) //JavaServletへ渡す情報(ID)をBodyへ設定する req.httpMethod = "POST" req.httpBody = "id=\(id)".data(using: .utf8) //リクエストをタスクとして登録 let task = session.dataTask(with: req, completionHandler: { (data, response ,error) in //セッションの終了 session.finishTasksAndInvalidate() do{ //取得したJSONを変換する let decoder = JSONDecoder() let json = try decoder.decode(StudentJson.self, from: data!) self.textId = json.id! self.textName = json.name! //「JSONを取得する」ボタンに紐づくセグエ if segue.identifier == "goResultVC" { let nextVC = segue.destination as! ResultViewVontroller nextVC.jsonId = self.textId nextVC.jsonName = self.textName } }catch{ print(error) print("エラーがでました") } }) //ダウンロード開始 task.resume() } //JSONのデータ構造 struct StudentJson: Codable { let id: String? let name: String? } }JSONを出力する画面
ResultViewVontroller.swiftimport UIKit class ResultViewVontroller: UIViewController { override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. } //出力メッセージのフィールド var jsonId = "" var jsonName = "" //出力ラベル @IBOutlet weak var resultJsonId: UILabel! @IBOutlet weak var resultJsonName: UILabel! override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(true) resultJsonId.text = jsonId resultJsonName.text = jsonName } }Eclipse
Java Servlet
JsonServlet.javapackage servlet; import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import bean.JsonBean; /** * Servlet implementation class JsonServlet */ @WebServlet("/JsonServlet") public class JsonServlet extends HttpServlet { private static final long serialVersionUID = 1L; /** * @see HttpServlet#HttpServlet() */ public JsonServlet() { super(); // TODO Auto-generated constructor stub } /** * @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response) */ protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { } /** * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response) */ protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { JsonBean jsonBeanList[] = new JsonBean[3]; //Javaオブジェクトに値をセット JsonBean jsonBean = new JsonBean(); jsonBean.setId("101"); jsonBean.setName("tanaka"); JsonBean jsonBean2 = new JsonBean(); jsonBean2.setId("102"); jsonBean2.setName("yamada"); JsonBean jsonBean3 = new JsonBean(); jsonBean3.setId("103"); jsonBean3.setName("satou"); jsonBeanList[0] = jsonBean; jsonBeanList[1] = jsonBean2; jsonBeanList[2] = jsonBean3; // String str = request.getParameter("id"); int requestId = Integer.parseInt(str); System.out.println(requestId); ObjectMapper mapper = new ObjectMapper(); try { //JavaオブジェクトからJSONに変換 String testJson = mapper.writeValueAsString(jsonBeanList[requestId]); //JSONの出力 response.getWriter().write(testJson); //出力されるJSONの確認 System.out.println(testJson); } catch (JsonProcessingException e) { e.printStackTrace(); } } }Bean
JsonBean.javapackage bean; import java.util.List; import com.fasterxml.jackson.annotation.JsonProperty; public class JsonBean { @JsonProperty("id") private String id; @JsonProperty("name") private String name; public void setId(String id) { this.id = id; } public void setName(String name) { this.name = name; } }JsonServletの動きを確認するには、Chromeの拡張機能であるTalend API Testerがおすすめです!
Talend API Testerまとめ
これで、フロントエンドとバックエンド間の通信ができるようになりました。
次は、AWS上に乗っけてみます。また、今回の実装をするにあたり、多くのQiita記事と書籍を参考にしました。
拙い内容でしたが、閲覧いただきありがとうございました!
マークダウン記法、めちゃめちゃ書きやすいですね。
- 投稿日:2020-05-14T13:22:44+09:00
5 月 LeetCode 挑戰, W2D6, Remove K Digits (Greedy), in Swift
資料結構與演算法:
Greedy
Stack
- 挑戰頁面 - Remove K Digits
- 原題目頁面 - 402. Remove K Digits
這題稍微想了一下下自己該怎麼解釋多花了一點時間 XDDDD
思考方式
核心是 Greedy ,分析範例
1432219, k = 3
之後,可以推測出核心的演算法是:
- 從最高位開始走訪, 因為我們要取最小值(題目要求) ,所以只要高一位數字比自己還要大,如果還可以移除 (k > 0) 我們就移除高一位的數字
例如走到
143
,比起14
我們更希望是13
我們把4
移除後加上3
,所以這個步驟的結果是13
。test case 1
接著用這個核心演算法來看比較特殊的 test case :
1230, k = 3
,解為0
這是一個一路升冪,只有最後一個降到 0 的一個數列。
- 一路會走到最後一個 0 ,一位一路上都沒有符合「高一位比自己大」的條件
1230
-123
和120
比,我們比較想要120
,所以我們移除 3 , k - 1 之後剩下2
120
-12
和10
比,我們比較想要120
,所以我們移除 2 , k - 1 之後剩下1
10
-1
和0
比,我們比較想要0
,所以我們移除 1 , k - 1 之後剩下0
- 結果為
0
test case 2 - 無降冪
111111, k = 3
解應為111
但是這個測試案例就算走完整個字串還是無法扣掉,因此我們要加上一個步驟:
- 當走訪完字串 k 卻沒有為 0 的話,就把最後的 k (剩餘值) 位都移除掉
因此
111111
移除最後的 3 位就是111
test case 3 - 高位數有 0
100200, k = 1
解應為200
走完核心演算法之後,會剩下
00200
,而題目並不希望回傳的答案裡面會有高位數的 0 (leading zeros)所以為了處理這個情況,在回傳之前,就必須再加上這個步驟
- 如果 num[0] 的內容是
0
就移除他,移除到 num[0] 不是0
或是 num 為空為止統整演算法
這題的思考步驟我自己是覺得有點複雜,需要考慮的邊界案例也比較多,在這邊來統整整個流程要做哪些事情。
- 先導處理 - 如果 k 的初始值大於 num 的長度的話必定全部扣完,因此直接回傳 "0" 。
- 核心 Greedy - 逐位走訪,只要高一位數字比自己還要大,如果還可以移除 (k > 0) 我們就移除高一位的數字
- k 有剩餘的話,扣除最後位數
- 移除 leading zeros
程式碼 (未優化)
class Solution { func removeKdigits(_ num: String, _ k: Int) -> String { // ① 排除 k > num 長度 if k >= num.count { return "0" } // 例行公事, Swift 不擅長處理字串因此把它轉成 [Character] var characters = Array(num) // 轉換成 mutable var count = k // ② 核心 Greedy 演算法 var index = 0 while index < characters.count && count > 0 { while count > 0 && index > 0 && characters[index - 1] > characters[index] { characters.remove(at: index - 1) count -= 1 // 目前的位數因為前一位被移除掉所以往前移,因此要 -1 index -= 1 } index += 1 } // ③ 用掉剩下的 k characters.removeLast(count) // ④ 移除在高位數的 0 while stack.first == "0" { characters.remove(at: 0) } return characters.isEmpty ? "0" : String(characters) } }複雜度分析
n 為 num 的長度
- 時間複雜度: O(n)
- 空間複雜度: O(n)
- 陣列化的字串
執行結果
Runtime: 36 ms (beats 48.15) Memory Usage: 21.4 MB由於在執行時間上不是非常漂亮,我們就再來優化一下。
程式碼 (Stack 優化)
- 原先的寫法問題:當 num 的長度非常非常長的時候, 移除其中一個元素之後,他後面要重新配置(往前移動)記憶體空間所需要耗費的時間越多
因此為了不要讓尾巴不要成為我們的包袱,我們把處理好的另外的 stack 中,num 就只負責被走訪,而不用被執行移除的動作。這樣應該就可以大大改善執行時間。
Stack 則照往常直接拿 array 來用。
class Solution { func removeKdigits(_ num: String, _ k: Int) -> String { // ① 排除 k > num 長度 if k >= num.count { return "0" } var count = k var stack = [Character]() // ② 核心 Greedy 演算法 for character in num { while count > 0, let top = stack.last, top > character { stack.removeLast() count -= 1 } stack.append(character) } // ③ 用掉剩下的 k stack.removeLast(count) // ④ 移除在高位數的 0 while stack.first == "0" { stack.removeFirst() } return stack.isEmpty ? "0" : String(stack) } }複雜度分析
- 時間複雜度: O(n)
- 空間複雜度: O(n)
- 用在 Stack
執行結果
Runtime: 24 ms (beats 100.00%) Memory Usage: 21.5 MB我用的測試案例
"1432219" 3 "100200" 1 "10" 2 "112" 1 "1111111" 3 "12340" 4結語
做完之後去找別人的文章或影片去看別人怎麼解釋,發現有些人都直接跳到「這題就是要用 stack」而沒有說為什麼該用 stack 。所以這篇我就多紀錄了原先的寫法和如何怎麼走到 stack 的想法。
又不小心有點長了 XDDDDD
- 投稿日:2020-05-14T10:08:14+09:00
SwiftUIでQRコードを表示してみる
はじめに
QRコードを生成するアプリを作ろうと思い、せっかくなのでSwiftUIで作成しました。
コードの解説
QRコードの生成
こちらは、よく検索すると出てくるコードです。いくつか検索していて気付いたのですが、
CIContext().createCGImage()
を実行していないコードがあります。
outputImage
でciImageが生成されているのでGPUによるレンダリングする必要があるので、長いですが実装が必要なのではないかと思います。あとハマったのは、謝り訂正レベルを指定するパラメーター"inputCorrectionLevel"には、"L"/"M"/"Q"/"H" いずれかを指定する必要があります。(数字だと思い込んでしまった・・・)
QRCodeMaker.swiftfunc make(message:String , level:Int) -> UIImage? { guard let data = message.data(using: .utf8) else { return nil } guard let qr = CIFilter(name: "CIQRCodeGenerator", parameters: ["inputMessage": data, "inputCorrectionLevel": getCorrectionText(index: level)]) else { return nil } let sizeTransform = CGAffineTransform(scaleX: 10, y: 10) guard let ciImage = qr.outputImage?.transformed(by: sizeTransform) else { return nil } guard let cgImage = CIContext().createCGImage(ciImage, from: ciImage.extent) else { return nil } let image = UIImage(cgImage: cgImage) return image }QRコード生成するコードは
QRCodeMaker.swift
として別ファイルに分離しました。SwiftUI
基本は、VStackを用いて縦にパーツを配置しています。
QRコードの画像は中央、テキスト入力と誤り訂正レベルの選択肢は上部、生成ボタンは下部にしたいので、赤枠の箇所をSpacer()
を入れることによって実現しています。
●画面レイアウト(赤枠がSpacer()
)
Spacer()
を用いないと中央によってしまいます。
●Spacer()を用いていない画面
また、起動時はQRコード画像がありません。これは
if
文でImage
を表示非表示を切り替えています。ContentView.swiftif qrImage != nil { Image(uiImage: qrImage!) }終わりに
サンプルコードは、githubにアップしています。
参考にどうぞ。
- 投稿日:2020-05-14T06:08:40+09:00
iPhoneにBeacon発信機能を実装してみた
はじめに
先日アーキテクチャのデザインのレパートリーを増やしたいこともあり気になっていた「Amazon Web Services クラウドネイティブ・アプリケーション開発技法」という本を買いました。本書はAWSを活用したアプリケーションのサンプルがいくつか取り上げられているのですが、その中にある「iBeaconと連携する勤怠管理アプリケーション」の項目に惹かれたのが理由です。
Beaconと呼ばれるBluetooth4.0以降に搭載されているBLE(Bluetooth Low-Energy)の技術を使った位置情報を特定できるデバイスを使った実装例があるとのことで、IoT技術に関心を寄せている私は早速サンプルコードを動かしてみようと取り掛かりました。問題:サンプルコードが動かない
まあ予想はしてました。なんせ出版されたのが2016年なんですもの。
近年のコンピュータ技術は発展がものすごく早いので、少し前に出版された本のサンプルが動くだなんて期待してはいけません。
ということで少し現状を整理しながら原因を探っていくことにしました。仕組み
このiBeaconを使ったサンプルの仕組みですが、ざっくり言えばBeaconを受信する勤怠アプリがBeaconを受信したらAPI経由でDynamoDB上に作成された勤怠情報を格納するようになっています。肝心のBeaconの発信側ですが
node-bleacon
と呼ばれるライブラリを使うことでPCで代用できるようになっております。調査
で、原因ですが、node-bleaconはNode.jsの現行バージョン(ver 14.x)に非対応だからでした。
node-bleacon
はblenoというライブラリをラップしたものなのですが、どうやらメンテがされておらずver9.xまでしか使えない様子でした。
代わりとなるライブラリを探してみたものの、node-beacon-scanner
といった受信機能のあるものはありましたが、発信機能があるものはみつかりませんでした。
iBeaconを使ったサンプルなのにBeaconが発信できないとなると流石に困りました。
とは言え流石にサンプルを動かすためだけにラズパイやBeaconモジュールを買うわけにもいかず、すぐにでも試したかったので他の手段がないか探しました。スマホを発信器として使う
よく考えてみたら受信端末として使おうとしているこのiPhoneを発信器として利用できるのではと思い、調べてみたらちゃんとできるようです。
公式ドキュメント:https://developer.apple.com/documentation/corelocation/turning_an_ios_device_into_an_ibeacon_device
Beaconで使われてるBLEの通信にはそれぞれ役割があり、受信側を
Central
、発信側をPeripheral
と呼ぶそうです。
というわけでサンプルコードを動かすためにまずはBeacon発信機能をiPhoneに実装することにしました。実装
以下に作成したコードを載せておきます。
最小限の実装で済ませるため、ここではアプリを起動すると画面には何も表示されず、アプリを落とすまでBeaconを発信し続けるようにしております。appDelegate.swiftimport UIKit import CoreLocation import CoreBluetooth @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate, CLLocationManagerDelegate, CBPeripheralManagerDelegate { var localBeacon : CLBeaconRegion!; var beaconPeripheralData: NSDictionary!; var peripheralManager: CBPeripheralManager! let BEACON_ID: String = "BEACON" let uuid: UUID = UUID(uuidString: "01234567-89AB-CDEF-0123-456789ABCDEF")! // 事前にuuidを用意しておく let MAJOR: CLBeaconMajorValue = 10 let MINOR: CLBeaconMinorValue = 20 // アプリケーションの起動完了後に実行される処理 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool { self.initBeacon() return true } // Beaconを起動 func initBeacon() { if localBeacon != nil { stopLocalBeacon() } localBeacon = createBeaconRegion()! beaconPeripheralData = localBeacon.peripheralData(withMeasuredPower: nil) peripheralManager = CBPeripheralManager(delegate: self, queue: nil, options: nil) } // Beaconを停止 func stopLocalBeacon() { peripheralManager.stopAdvertising() peripheralManager = nil beaconPeripheralData = nil localBeacon = nil } // Bluetoothの電源が切り替わった際に実行される処理 func peripheralManagerDidUpdateState(_ peripheral: CBPeripheralManager) { if peripheral.state == .poweredOn { peripheralManager.startAdvertising(beaconPeripheralData as? [String: Any]) } else if peripheral.state == .poweredOff { peripheralManager.stopAdvertising() } } func createBeaconRegion() -> CLBeaconRegion? { return CLBeaconRegion( proximityUUID: self.uuid, major: self.MAJOR, minor: self.MINOR, identifier: self.BEACON_ID ) }動作確認
動作を確認するには先ほど作成したアプリ以外に、Beaconの受信機能とその端末が必要になります。最初の方に話した
node-beacon-scanner
などを使って開発することもできますが、そんな余裕はなかったのですでに出回っているもの、ここではBeacon Scan
というアプリを使って確認することにします。なお端末は自分のiPadを使用しました。https://apps.apple.com/us/app/beacon-scan/id1011834079
右下の設定アイコンをタップして発信側で設定しているUUIDを入力します。
保存を押し、リスト画面に戻るとiPhoneから発信されているBeacon情報が確認できます。
補足
・BeaconRegionを作成する際にUUIDが必要になります。ターミナルで
$ uuidgenを実行してUUIDを作成してください。
また、major
とminor
情報も必要となります。これは識別情報のひとつでmajor
はグループ(例:フロアや階層)、minorは個々(例:置かれている部屋や場所)を識別するために使います。Beaconが複数あったときにどれが発信しているものかわかるようにするためですね。・この機能はBluetoothを使用するので、
info.plist
にPrivacy - Bluetooth Always Usage Description
(xlmファイルで開いている場合は<key>NSBluetoothAlwaysUsageDescription</key>
)の設定をするようにしてください。これがないとアプリを実行することができません。感想
最初はサンプルコードが動かず、Beacon発信器もないためあきらめかけていましたが、iPhoneに発信機能を実装することで引き続きサンプルを試すことができそうです。
常に感じていることですが、まだプログラミング経験の浅い初学者はサンプルコードが動く書籍を買うことをお勧めします。そのためにも書籍の発刊日やアップデートに対応しているをチェックしましょう。
なお不満が多く批判的に聞こえているかもしれませんが、本書はクラウドネイティブな構成に必要なサービスの知識及び例が豊富に含まれているので、私を含めて事故解決できるひとでデザインパターンを知りたい方にはおすすめです。
- 投稿日:2020-05-14T04:38:52+09:00
マイナンバーカードのパスワード残り試行回数を調べる
本記事は Qrunch とのクロス投稿です。
マイナンバーカードのパスワード残り試行回数を調べる | Qrunch(クランチ)
最近話題のマイナンバーカード。マイナポータルを用いた申請等で必要なカードで、このカード1つに4つのパスワードがそれぞれ設定されています。
パスワードの設定は各々がマイナンバーカードを受け取る際に設定したはずですが…
これらのパスワードはそれぞれ規定回数以上連続で間違えてしまうとロックされ、このロック解除のために役所へ行かないといけなくなります。そしてこの度、Japan NFC Reader では Ver 1.0.5 で「マイナンバーカードのパスワードの残り試行回数」を確認できるようになりました。
また、それに伴い Japan NFC Reader のコア部分を MIT Licence で公開しているライブラリ、treastrain/TRETJapanNFCReader でもマイナンバーカードへの対応作業を開始し、このパスワードの残り試行回数を取得する機能も提供しています。今回はこちらを使って、実際にマイナンバーカードからパスワードの残り試行回数を取得するところまでを試してみます。
なお、この記事を書くために作成したプロジェクトはまるごと GitHub 上で公開しています。もしよろしければ参考にしてください。
treastrain/MyNumberPINRemainingChecker実機の用意
実際のマイナンバーカードからデータを読み取るので、シミュレーターでは動作しません。iOS 13.0 以降を搭載した「リーダーモード対応NFC」が使える iPhone が必要です。
Swift Package Manager でライブラリを導入する
treastrain/TRETJapanNFCReader はマイナンバーカードの他にも、Suica、PASMO といった交通系ICカード、各種電子マネー、運転免許証の読み取りにも対応しており、少しずつ大きなライブラリとなっています。
今回はマイナンバーカードの読み取り機能のみ利用できればよいので、Swift Package Manager を用いて部分的にライブラリを導入します。Xcode で iOS App のプロジェクトを作成したら、「File」>「Swift Packages」>「Add Package Dependency...」に進みます。
「Search or enter package repository URL」に「TRETJapanNFCReader」と入力します。
記事執筆時点ではマイナンバーカードに関する機能は Release version に含まれていないので、master ブランチを選択します。
本来であれば、マイナンバーカードの読み取りに関する Product のみにチェックを入れればよいのですが名称が長くて全て表示されないので、どれにもチェックを入れずに「Finish」します。
Target の「Frameworks, Libraries, and Embedded Content」にて、「TRETJapanNFCReader-MIFARE-IndividualNumber」を「Add」します。
※個人番号カード(マイナンバーカード)は英語で「Individual Number Card」となっています。
これで正しくマイナンバーカードに関する Product のみを取り込めました。
Capability と Entitlements の設定
これ以降は基本的に treastrain/TRETJapanNFCReader の README に示されたとおり進めていくだけです。
まず、Signing & Capabilities で「Near Field Communication Tag Reading」を有効にします。これにより、「Near Field Communication Tag Reader Session Formats」が entitlements ファイルに含まれます。
Info.plist の設定
Info.plist に「Privacy - NFC Scan Usage Description」と「ISO7816 application identifiers for NFC Tag Reader Session」を追加します。
「Privacy - NFC Scan Usage Description」には NFC を何のために使用するのかについての説明を、「ISO7816 application identifiers for NFC Tag Reader Session」の配下には以下の AID を記述します。
- Item 0:
D392F000260100000001
- Item 1:
D3921000310001010408
- Item 2:
D3921000310001010100
- Item 3:
D3921000310001010401
コーディング
以上でもろもろの設定はおしまいです。続いてコーディングしていきます。
すべてViewController.swift
に記述します。全文を見たい場合は GitHub 上の treastrain/MyNumberPINRemainingChecker を見たほうが便利かもしれません。ライブラリをインポートする
まずはライブラリをインポートします。
import UIKit import TRETJapanNFCReader_MIFARE_IndividualNumber
IndividualNumberReaderSessionDelegate
に準拠させる
IndividualNumberReaderSessionDelegate
プロトコルに準拠させます。
individualNumberReaderSession(didRead:)
はマイナンバーカードからの情報の読み取りが終わると呼ばれるものになります。しかし、今回の目的である「パスワードの残り試行回数を調べる」際には特に使用しません。
japanNFCReaderSession(didInvalidateWithError:)
は NFC 通信でのエラーハンドリングに使用します。今回は省略します。class ViewController: UIViewController, IndividualNumberReaderSessionDelegate { // ... func individualNumberReaderSession(didRead individualNumberCardData: IndividualNumberCardData) { // ... } func japanNFCReaderSession(didInvalidateWithError error: Error) { // ... } // ... }
IndividualNumberReader
を初期化するマイナンバーカードの読み取りには
IndividualNumberReader
を使用します。var reader: IndividualNumberReader! override func viewDidLoad() { super.viewDidLoad() self.reader = IndividualNumberReader(delegate: self) // ... }パスワードの種別を指定する
treastrain/TRETJapanNFCReader では、マイナンバーカードの4つのパスワードの残り試行回数の取得に対応しています。それは
IndividualNumberCardPINType
に次のように定義されています。TRETJapanNFCReader/Sources/MIFARE/IndividualNumber/IndividualNumberCardPINType.swiftpublic enum IndividualNumberCardPINType { /// 署名用電子証明書(公的個人認証 署名用) case digitalSignature /// 利用者証明用電子証明書(公的個人認証 利用者証明用) case userAuthentication /// 券面事項入力補助用 case cardInfoInputSupport /// 個人番号カード用(住民基本台帳事務用) case individualNumber }ここでは、署名用電子証明書のパスワードを取得します。
// ... self.reader = IndividualNumberReader(delegate: self) let pinType = IndividualNumberCardPINType.digitalSignature // ...
IndividualNumberReader.lookupRemainingPIN(pinType:completion:)
を呼び出す以上の準備ができたら、
IndividualNumberReader.lookupRemainingPIN(pinType:completion:)
を呼び出します。completion
のInt?
に残り試行回数が含まれています。エラーが発生した場合はnil
となり、NFC 通信に起因するエラーの場合は先述のjapanNFCReaderSession(didInvalidateWithError:)
にエラー内容が送られてきます。// ... self.reader = IndividualNumberReader(delegate: self) let pinType = IndividualNumberCardPINType.digitalSignature self.reader.lookupRemainingPIN(pinType: pinType) { (remaining) in if let remaining = remaining { print(pinType.description, "残り試行回数:", remaining) } else { // 取得エラー } }実行結果
それでは実行してみましょう。以下のように残り試行回数がコンソールに表示されれば成功です。
もしよろしければ GitHub にて Star をしていただけると嬉しいです…!
- treastrain/TRETJapanNFCReader : 今回使用したライブラリ
- treastrain/MyNumberPINRemainingChecker : 今回の記事のために作成したサンプルプロジェクト環境
- 開発
- Xcode Version 11.4.1 (11E503a)
- Apple Swift version 5.2.2 (swiftlang-1103.0.32.6 clang-1103.0.32.51)
- macOS Catalina 10.15.4 (19E287)
- 実機
- iPhone X (A1902、MQAY2J/A)
- iOS 13.3.1 (17D50)
- iPhone 11 Pro (A2215、MWCC2J/A)
- iOS 13.5 (17F5065a)
- 投稿日:2020-05-14T04:20:17+09:00
【SwiftUI】入力開始時に TextField の Placeholder の文字列を非表示にする
はじめに
SwiftUI ではデフォルトでは入力開始時に
TextField
の
Placeholder の文字列は表示されたままですが,
入力開始時に Placeholder を非表示にできるのかについて
検証をする機会があったので備忘録です。通常はデフォルトのままで大丈夫だとは思います。
実装
結果的に Placeholder の文字列を定数にせず,
バインドすることで実現できました。
TextField
を生成する際に下記を使って入力開始/終了時の処理で
placeholder の文字列を変更しています。init<S>(_ title: S, text: Binding<String>, onEditingChanged: @escaping (Bool) -> Void = { _ in }, onCommit: @escaping () -> Void = {}) where S : StringProtocolサンプルコードはこちらです。
1つ目のTextField
がデフォルトのシンプルなもの,
2つ目のTextField
が入力時に非表示になるものです。ContentView.swiftimport SwiftUI struct ContentView: View { @State private var firstTextFieldStr = "" @State private var secondTextFieldStr = "" @State private var placeholderStr = "文字列を入力" var body: some View { NavigationView { VStack(alignment: .leading) { TextField("文字列を入力", text: $firstTextFieldStr) .textFieldStyle(RoundedBorderTextFieldStyle()) .padding(.vertical, 16.0) TextField(self.placeholderStr, text: $secondTextFieldStr, onEditingChanged: { (hasChanged) in // 編集を始めたらtrue,終えたらfalseになる if hasChanged && self.secondTextFieldStr.isEmpty { // 編集開始時に文字列が入力されていなければplaceholderを空文字に self.placeholderStr = "" } else if !hasChanged && self.secondTextFieldStr.isEmpty { // 編集終了時に文字列が入力されていなければplaceholderをデフォルトに self.placeholderStr = "文字列を入力" } }) { // returnキーが押された際の処理 } .textFieldStyle(RoundedBorderTextFieldStyle()) Spacer() } .padding(.horizontal, 20.0) .navigationBarTitle("TextField Placeholder test", displayMode: .inline) } } }動作
おわりに
SwiftUI 2では痒いところに手が届くように色々期待です。
参考
https://developer.apple.com/documentation/swiftui/textfield/3338359-init