20200514のSwiftに関する記事は7件です。

JSONを使用してJava ServletからiPhoneアプリへ値を受け渡してみる【初心者】

 会社の同期に誘われてiPhoneアプリを作成している最中、WebサーバーとiPhoneアプリ間の値の受渡しをどうするか悩んでいました。その時、どうやらJSONを使用して、値を受け渡すらしい!ということを知りました。
 そこで、色々調べた結果、無事にJSONを使用した値の受渡しができたので、まとめます。

 新人研修&自己学習で得た知識で書いてるので、間違っている箇所やより良い方法があれば、コメントください、、、、

作成するアプリ

 画面としては、こんなかんじです。
スクリーンショット 2020-05-12 22.47.32.png

 流れとしてはこんな感じです。
 ① 「JSONを取得する」ボタンを押し、JavaServletへランダムな数値を渡す。
 ② JavaServletは、受け取った数値に応じたJSONをレスポンスする。
 ③ Swiftは、受け取ったJSONオブジェクトを変換し、値を画面へ出力する。

 赤枠で囲まれている箇所に、JavaServletから得たJSONを出力しています。
 また、すべてローカル環境で実装します。

環境

 XcodeとEclipseを使用し、開発しました。 
 
 JSONを使用するにあたって、GSONかJacksonで迷いましたが、今回はJacksonを使用しました。

実際のコード

Xcode

JSONを取得する画面

ViewController.swift
import 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.swift
import 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.java
 package 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.java
package 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記事と書籍を参考にしました。

拙い内容でしたが、閲覧いただきありがとうございました!

マークダウン記法、めちゃめちゃ書きやすいですね。

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

JSONを使用してJava ServletからiPhoneアプリへ値を受け渡してみる

 会社の同期に誘われてiPhoneアプリを作成している最中、WebサーバーとiPhoneアプリ間の値の受渡しをどうするか悩んでいました。その時、どうやらJSONを使用して、値を受け渡すらしい!ということを知りました。
 そこで、色々調べた結果、無事にJSONを使用した値の受渡しができたので、まとめます。

 新人研修&自己学習で得た知識で書いてるので、間違っている箇所やより良い方法があれば、コメントください、、、、

作成するアプリ

 画面としては、こんなかんじです。
スクリーンショット 2020-05-12 22.47.32.png

 流れとしてはこんな感じです。
 ① 「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.swift
import 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.swift
import 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.java
 package 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.java
package 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記事と書籍を参考にしました。

拙い内容でしたが、閲覧いただきありがとうございました!

マークダウン記法、めちゃめちゃ書きやすいですね。

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

5 月 LeetCode 挑戰, W2D6, Remove K Digits (Greedy), in Swift

資料結構與演算法: Greedy Stack

這題稍微想了一下下自己該怎麼解釋多花了一點時間 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 - 123120 比,我們比較想要 120 ,所以我們移除 3 , k - 1 之後剩下 2
  • 120 - 1210 比,我們比較想要 120 ,所以我們移除 2 , k - 1 之後剩下 1
  • 10 - 10 比,我們比較想要 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 為空為止

統整演算法

這題的思考步驟我自己是覺得有點複雜,需要考慮的邊界案例也比較多,在這邊來統整整個流程要做哪些事情。

  1. 先導處理 - 如果 k 的初始值大於 num 的長度的話必定全部扣完,因此直接回傳 "0" 。
  2. 核心 Greedy - 逐位走訪,只要高一位數字比自己還要大,如果還可以移除 (k > 0) 我們就移除高一位的數字
  3. k 有剩餘的話,扣除最後位數
  4. 移除 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

image

我用的測試案例

"1432219"
3
"100200"
1
"10"
2
"112"
1
"1111111"
3
"12340"
4

結語

做完之後去找別人的文章或影片去看別人怎麼解釋,發現有些人都直接跳到「這題就是要用 stack」而沒有說為什麼該用 stack 。所以這篇我就多紀錄了原先的寫法和如何怎麼走到 stack 的想法。

又不小心有點長了 XDDDDD

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

SwiftUIでQRコードを表示してみる

はじめに

QRコードを生成するアプリを作ろうと思い、せっかくなのでSwiftUIで作成しました。

コードの解説

QRコードの生成

こちらは、よく検索すると出てくるコードです。いくつか検索していて気付いたのですが、CIContext().createCGImage()を実行していないコードがあります。
outputImageでciImageが生成されているのでGPUによるレンダリングする必要があるので、長いですが実装が必要なのではないかと思います。

あとハマったのは、謝り訂正レベルを指定するパラメーター"inputCorrectionLevel"には、"L"/"M"/"Q"/"H" いずれかを指定する必要があります。(数字だと思い込んでしまった・・・)

QRCodeMaker.swift
    func 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())
スクリーンショット 2020-05-14 9.59.38.png

Spacer()を用いないと中央によってしまいます。
●Spacer()を用いていない画面
スクリーンショット 2020-05-14 10.03.32.png スクリーンショット 2020-05-14 10.03.47.png

また、起動時はQRコード画像がありません。これはif文でImageを表示非表示を切り替えています。

ContentView.swift
            if qrImage != nil {
                Image(uiImage: qrImage!)
            }

終わりに

サンプルコードは、githubにアップしています。
参考にどうぞ。

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

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-bleaconblenoというライブラリをラップしたものなのですが、どうやらメンテがされておらず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.swift
import 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

アプリを起動するとこんな画面が出てきます。
IMG_0942.PNG

右下の設定アイコンをタップして発信側で設定しているUUIDを入力します。
IMG_0943.PNG

保存を押し、リスト画面に戻るとiPhoneから発信されているBeacon情報が確認できます。
IMG_0944.PNG

補足

・BeaconRegionを作成する際にUUIDが必要になります。ターミナルで

$ uuidgen

を実行してUUIDを作成してください。
また、majorminor情報も必要となります。これは識別情報のひとつでmajorはグループ(例:フロアや階層)、minorは個々(例:置かれている部屋や場所)を識別するために使います。Beaconが複数あったときにどれが発信しているものかわかるようにするためですね。

・この機能はBluetoothを使用するので、info.plistPrivacy - Bluetooth Always Usage Description(xlmファイルで開いている場合は<key>NSBluetoothAlwaysUsageDescription</key>)の設定をするようにしてください。これがないとアプリを実行することができません。

感想

最初はサンプルコードが動かず、Beacon発信器もないためあきらめかけていましたが、iPhoneに発信機能を実装することで引き続きサンプルを試すことができそうです。
常に感じていることですが、まだプログラミング経験の浅い初学者はサンプルコードが動く書籍を買うことをお勧めします。そのためにも書籍の発刊日やアップデートに対応しているをチェックしましょう。
なお不満が多く批判的に聞こえているかもしれませんが、本書はクラウドネイティブな構成に必要なサービスの知識及び例が豊富に含まれているので、私を含めて事故解決できるひとでデザインパターンを知りたい方にはおすすめです。

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

マイナンバーカードのパスワード残り試行回数を調べる

本記事は 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...」に進みます。
001.png
「Search or enter package repository URL」に「TRETJapanNFCReader」と入力します。
002.png

記事執筆時点ではマイナンバーカードに関する機能は Release version に含まれていないので、master ブランチを選択します。
003.png
本来であれば、マイナンバーカードの読み取りに関する Product のみにチェックを入れればよいのですが名称が長くて全て表示されないので、どれにもチェックを入れずに「Finish」します。
004.png
Target の「Frameworks, Libraries, and Embedded Content」にて、「TRETJapanNFCReader-MIFARE-IndividualNumber」を「Add」します。
005.png

※個人番号カード(マイナンバーカード)は英語で「Individual Number Card」となっています。

これで正しくマイナンバーカードに関する Product のみを取り込めました。
006.png

Capability と Entitlements の設定

これ以降は基本的に treastrain/TRETJapanNFCReader の README に示されたとおり進めていくだけです。
まず、Signing & Capabilities で「Near Field Communication Tag Reading」を有効にします。これにより、「Near Field Communication Tag Reader Session Formats」が entitlements ファイルに含まれます。
007.png

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

008.png

コーディング

以上でもろもろの設定はおしまいです。続いてコーディングしていきます。
すべて 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.swift
public 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:) を呼び出します。completionInt? に残り試行回数が含まれています。エラーが発生した場合は 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 {
        // 取得エラー
    }
}

実行結果

それでは実行してみましょう。以下のように残り試行回数がコンソールに表示されれば成功です。
009.png

もしよろしければ 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)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【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.swift
import 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)
        }
    }
}

動作

RPReplay_Final1589396086

おわりに

SwiftUI 2では痒いところに手が届くように色々期待です。

参考

https://developer.apple.com/documentation/swiftui/textfield/3338359-init

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