2021年6月7日 星期一

TouchID 或 FaceID 的基本應用

 今天來玩玩裝置生物識別的功能,

也就是TouchID or FaceID

雖然現在主要大多數有應用的APP基本上都是金融業居多。

但也是有部分的APP有引入此功能,

假設您的APP非一次性登入加入驗證生物識別可大大提升使用者體驗...

下面直接就來寫重點拉!

從基本設定開始,第一步先開啟info.plist

加入Privacy - Face ID Usage Description 後面字串就自行發揮吧!

例如:使用TouchID/FaceID進行登入

此加入僅限於FaceID,TouchID是不用加入該授權的。

接下來在要引用的ViewController加入import LocalAuthentication

然後宣告一個變數 並init. var context = LAContext()

接下來就是實作了,網路上我看過的範例多數都是直接寫一個Action執行,

我則是另外寫了一個方法進行,你可以依照你習慣的方式去進行不同的改變。

首先先檢查裝置是否可使用生物識別

 if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) { 

//省略

        } else {

            if let error = error {

               errorCode(error)

            }

            

        }

如果error直接跳錯誤訊息,成功則進行生物辨識,如下:

            //此文字會顯示於辨識時訊息

            let reason = "使用TouchID或FaceID進行驗證"

            // 使用哪種方法進行生物驗證

            context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) { (success, error) in

                if success {

                    DispatchQueue.main.async { [unowned self] in

                        //成功直接執行login

                        showAlert(title: "成功", "您已驗證成功")

                    }

                } else {

                    DispatchQueue.main.async { [unowned self] in

                        if let error = error {

                            errorCode(error)

                        }

                        

                    }

                }

            }

reason所使用文字建議你們使用一個通用的文字..

順帶一提官方提供的生物辨識有兩種方法,deviceOwnerAuthenticationWithBiometrics

deviceOwnerAuthentication  而我引用後者,金融業都是使用前者。兩種方法玩起來不一樣,我引用的當您失敗過多次後仍然可透過裝置所設定密碼執行驗證,前者則無法...這邊再由各位自己去玩看看吧!

而驗證可直接寫在檢查裝置是否可使用生物識別的判斷內,也可跟我一樣拆開寫。看當下案子需求能做不同的變化。

在此再附上我的errorCode(錯誤有很多種可以自己完)基本上我的error因使用的是deviceOwnerAuthentication的方法照理說都用不太到,

要研究error建議使用deviceOwnerAuthenticationWithBiometrics

 func errorCode(_ error:Error) {

        switch error {

        case LAError.authenticationFailed:

            print("Authentication failed")

        case LAError.passcodeNotSet:

            showAlert(title: "錯誤", "您未設定密碼")

        case LAError.systemCancel:

            showAlert(title: "錯誤", "授權過程中被系統取消")

        case LAError.userCancel:

            showAlert(title: "錯誤", "授權被用戶取消")

        case LAError.userFallback:

            showAlert(title: "提示", "用戶取消生物辨識使用密碼驗證")

        case LAError.biometryNotAvailable:

            if context.biometryType == .faceID {

                showAlert(title: "提示", "您已關閉FaceID授權,請至系統設定中開啟。")

            } else if context.biometryType == .touchID {

                showAlert(title: "提示", "您已關閉TouchID授權,請至系統設定中開啟。")

            } else if context.biometryType == .none {

                showAlert(title: "提示", "您未授權TouchID或FaceID於APP中,請至系統設定中開啟。")

            }

        case LAError.biometryNotEnrolled:

            showAlert(title: "提示", "沒有登入生物識別,無法使用")

        case LAError.biometryLockout:

            showAlert(title: "錯誤", " 您已多次登入失敗,請使用一般登入。")

        case LAError.appCancel:

            print("APP取消驗證")

        default:

            print(error.localizedDescription)

        }

    }

錯誤訊息內你有注意到有個biometryType嗎?此方法是用來判斷它屬於哪種生物辨識驗證,至於none我個人覺得現在應該是不太會發生,因為iPhone 5s後的機種不是touchID就是FaceID所以照理說不會執行到none

以上很廢的分享文,提供您參考...希望能幫助到剛好有要使用沒玩過的朋友。


   

2020年12月17日 星期四

TextField判斷是否輸入Emoji

 最近剛好遇到一個需求就是禁止在TextField內輸入Emoji的需求,

當Google後發現方式滿多的,可是都不是那麼的完美(包含原生Emoji判斷),

說不完美是因為可能需求在於地址,可是iPhone的User可能習慣連動打字,

不完美的原因在於如果使用編碼判斷的方式會造成輸入時只能一個字一個字輸入...

這樣對於使用者體驗個人是覺得很差的...

所以最後使用了原生的方法 請參考:官方文件

裡面總共有以下四個判斷Emoji的方法

而我一開始使用isEmoji發現他連聲符都擋掉了...可是他所以的Emoji都可以判斷出來

最後使用isEmojiPresentation,聲符沒擋了,但是部分的Emoji是可以輸入的...

所以上方才會提說原生判斷也不是那麼完美。

以下提供我所寫的方法

func containsEmoji() -> Bool {

        for scalar in unicodeScalars {

            if !scalar.properties.isEmojiPresentation {

                continue

            }

            return true

        }

        return false

    }

而我們會在text的delegate去判斷

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

        

        if textField == txtAddressDetail {

            if string.containsEmoji() {

                return false

            }

        }

}

當判斷是Emoji直接return false就不會進入輸入欄位了

另外附上正則式的寫法如下:

func hasEmoji() -> Bool {

        let pattern = "[^\\u0020-\\u007E\\u00A0-\\u00BE\\u2E80-\\uA4CF\\uF900-\\uFAFF\\uFE30-\\uFE4F\\uFF00-\\uFFEF\\u0080-\\u009F\\u2000-\\u201f\\u2026\\u2022\\u20ac\r\n]"

        let pred = NSPredicate(format: "SELF MATCHES %@",pattern)

        return pred.evaluate(with: self)

    }

基本上用法是一樣的,但是不完美的就是上方所提的輸入問題,只能一個字一個字輸入。

再來就是每年可能都會有新的Emoji,Unicode要每年都去更新...

以上跟大家方享我目前所用的方法跟想法,如果您有更好的方法可以完美的呈現歡迎交流唷^^

2020年10月28日 星期三

swift 各國國家列表資料(NSLocale)

今天要來寫簡單的各國資料列表取得,顯示方式就看個人或公司需求為主了,今天只做列表資料的簡單說明。基本上我是用最簡單的方式去取得,取得方式排序等等有很多方法再由個人自行探討摟~

首先寫作一個model(如果你是練習懶得做就換方式做吧)

struct countryModel {

    var name:String

    var countryCode:String

    var currencyCode:Any?

    var currencySymbol:String

    

    

    

    init(name:String, countryCode:String, currencyCode:Any?, currencySymbol:String) {

        self.name = name

        self.countryCode = countryCode

        self.currencyCode = currencyCode

        self.currencySymbol = currencySymbol

    }

}

然後是取得資料,要寫在哪也是看個人摟~

 let countryCode = NSLocale.isoCountryCodes

        // 取得使用者 iOS 設定的語系

        let preferredLanguages:String = NSLocale.preferredLanguages.first!

        for code in countryCode  {

            let id = NSLocale.localeIdentifier(fromComponents: [NSLocale.Key.countryCode.rawValue: code])

            //帶入本機語系代碼取得該國國名

            let name = NSLocale(localeIdentifier: preferredLanguages).displayName(forKey: NSLocale.Key.identifier, value: id)

            

            let locale = NSLocale.init(localeIdentifier: id)

            //該國國名英文縮寫

            let countryCode:String = locale.object(forKey: NSLocale.Key.countryCode) as! String

            let currencyCode = locale.object(forKey: NSLocale.Key.currencyCode)

            //該國貨幣代碼或符號

            let currencySymbol = locale.object(forKey: NSLocale.Key.currencySymbol) as! String

            let countryM = countryModel(name: name!, countryCode: countryCode, currencyCode: currencyCode, currencySymbol: currencySymbol)

            country.append(countryM)

          }

顯示效果如下:(因我沒刻意做資料排序,取出直接就引用了,你們可以先做排序在使用)

英文:



中文:





 

2020年10月22日 星期四

Swift 當APP至背景系統自動截圖防止個資洩漏方法 ->xCode12

 今天要用xCode12來進行預防iOS本身自動截圖用於背景開啟未關閉App所顯示的預覽圖

方法其實最簡單的做法就是建立一個新的UIImage或是UIView看您個人需求或是公司需求為主,

我這邊分享一般最簡單的做法,就是直接用一個UIView設定背景色為紅色,然後在SceneDelegate內的

func sceneWillResignActive(_ scene: UIScene)

將您建立的UIView執行addView如下

hideView.frame = self.window!.rootViewController!.view.bounds

self.window?.rootViewController?.view.addSubview(hideView)

這樣當用戶將App退到背景時會先蓋上我們的UIView,此時系統自動截圖當前畫面會截圖到我們所新蓋上去的UIView

然後當用戶從背景回來時再將該view移除即可

 func sceneDidBecomeActive(_ scene: UIScene) {

        if (hideView.superview != nil) {

            hideView.removeFromSuperview()

        }

    }

這樣就能預防一些個資或特殊資訊有洩漏的問題~

大致上顯示結果如下:

1.到背景已截圖為我心覆蓋的紅色


2.多開其他APP做比較,確定我們的是有多蓋一層View


3.回到App內確定該View有被移除掉


以上基本上您可使用Image放入您需要的圖片作為截圖也是很棒的選擇,而且比較美觀唷!


swift 螢幕截圖警示語 -> xcode12

 又好久沒有跟大家分享一些小功能方法拉!

今天要跟大家分享的是用戶執行螢幕截圖時進行提醒,

例如有用戶個人基本資料畫面進行截圖,我們可能會提醒有個資外流疑慮等等...

基本上使用的方法是NotificationCenter內的原生name就可以執行了。

我們僅需寫自己想執行的selecotr動作

範例如下:

NotificationCenter.default.addObserver(self, selector: #selector(showSomeThing), name: UIApplication.userDidTakeScreenshotNotification, object: nil)

先執行觀察(監聽)動作加入,這方法可以加入在你要提醒的頁面viewdidload,記得在離開頁面時做釋放將其remove掉。

今天我是做在SceneDelegate內,主要是方便測試各個頁面等等...假如您覺得你大多數頁面都需要做這件事情就可以像我一樣寫在SceneDelegate ->  func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions)

如果你是舊有專案只有AppDelegate的話就直接加在

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool

接下來就是執行方法

@objc func showSomeThing() {

        let alertController = UIAlertController(title: "提醒:", message: "螢幕截圖可能會造成隱私資訊洩露", preferredStyle: .alert)

        let alertAction = UIAlertAction(title: "確定", style: .default, handler: nil)

        alertController.addAction(alertAction)

        self.window?.rootViewController?.present(alertController, animated: true, completion: nil)

    }

Selector的用法這邊我就不多做解說了,該寫哪就寫哪...


執行後的結果如下





2018年10月28日 星期日

UIButton倒數計時並開啟觸發

有時候有些小功能可能需要倒數計時後才能在觸發,例如簡訊驗證碼發送。要避免伺服器一直重複處理同一個門號發送,所以我們可能再送出後限制一分鐘後才能重新發送,這時就需要倒數計時顯示給用戶看並且是無法觸發的。
這功能不會太複雜所以我也不細說直些附上完整的程式碼跟git連結
https://github.com/h81061678/Button-countdown

貼心小提醒,UIButton的Type要設定為Custom,這樣數字再變更時才不會有閃爍的問題


import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var label: UILabel!
    @IBOutlet weak var sendButton: UIButton!
    var timeStop:Int!
    var timer:Timer?
    var counter = 20
    
    override func viewDidLoad() {
        super.viewDidLoad()
        sendButton.layer.cornerRadius = 5;
        label.text = "還沒處發";
    }
    
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        if self.timer != nil{
            self.timer?.invalidate()
        }
    }
    
    func timerEanbled(){
        sendButton.backgroundColor = UIColor(named: "reColor");
        sendButton.layer.borderWidth = 2
        sendButton.layer.borderColor = UIColor.red.cgColor
        self.timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(resendButton), userInfo: nil, repeats: true)
    }
    
    @objc func resendButton(){
        if (counter > 1){
            counter = counter - 1
            label.text = "已觸發";
            sendButton.setTitle(" 於\(counter)秒後可觸發 ", for: .normal)
        }else{
            label.text = "可重新觸發";
            sendButton.layer.borderWidth = 0
            sendButton.isEnabled = true
            sendButton.setTitleColor(.white, for: .normal)
            sendButton.backgroundColor = UIColor(named: "myColor");
            self.timer?.invalidate()
            sendButton.setTitle("重新發送", for: .normal)
            counter = 20
        }
    }

    @IBAction func sendAction(_ sender: UIButton) {
        sendButton.isEnabled = false;
        self.timerEanbled()
    }
    
}


封裝UIAlertController成為Extension

每次使用AlertController都要寫個好幾行是不是覺得很煩躁呢?
又或者要使用時沒有顯示問題?
通常要present一個Alert的前提是要在當前最上方的ViewController,今天我來分享我寫的Extension讓大家參考。

首先我會建立三個新的文件檔並且impotr UIKit ,您也可以只建立一個文件檔即可。
然後最先要處理的是取得當前最上方的ViewController

extension UIViewController {
    //取得當前uiviewcontroller
    class func getCurrentVC() -> UIViewController?{
        var result:UIViewController?
        var window = UIApplication.shared.keyWindow
        if window?.windowLevel != UIWindow.Level.normal{
            let windows = UIApplication.shared.windows
            for tmpWin in windows{
                if tmpWin.windowLevel == UIWindow.Level.normal{
                    window = tmpWin
                    break
                }
            }
        }
        let fromView = window?.subviews[0]
        if let nextRespnder = fromView?.next{
            if nextRespnder.isKind(of: UIViewController.self){
                result = nextRespnder as? UIViewController
            }else{
                result = window?.rootViewController
            }
        }
        return result
    }

}

再來就可以進行UIAlertController及UIAlertAction的Extension 如下:

extension UIAlertController {
    class func  showAlertController(title:String,msg:String,style:UIAlertController.Style,actions:[UIAlertAction]) {
        let VC = UIViewController.getCurrentVC()
        let alertController = UIAlertController(title: title, message: msg, preferredStyle: style);
        for action in actions{
            alertController.addAction(action)
        }
        
        VC?.present(alertController, animated: true, completion: nil);
    }
    

}

我故意將UIAlertAction封裝成一個Array,並讓他跑for迴圈去加入AlertController,因為每次需求都不同,可能只需要一個Action,有時需要兩個或以上的Action,所以使用Array搭配for迴圈就可以輕鬆許多。

extension UIAlertAction {
    class func addAction(title:String,style:UIAlertAction.Style,handler:((UIAlertAction) -> Void)?) -> UIAlertAction{
        let alertAction = UIAlertAction(title: title, style: style, handler: handler);
        return alertAction
    }

}

你可能會好奇為什麼style為什麼不寫死,因為顧慮到每個不同需求時可能會使用不同的style所以這方法跟官方所提供沒有太大差別,但會比原本寫官方的簡化許多。
而Action其實就只有在用到時宣告並呼叫就能夠使用了,下方附上呼叫時的程式碼。
請自行寫個Button去觸發執行


 let one = UIAlertAction.addAction(title: "oneAction", style: .default) {
            (action) in
            print("one")
        }
        let two = UIAlertAction.addAction(title: "twoAction", style: .default) {
            (action) in
            print("two")
        }
        let cancel = UIAlertAction.addAction(title: "cancel", style: .cancel) {
            (action) in
            print("cencel")
        }
        let actions = [one,two,cancel];
        

       UIAlertController.showAlertController(title: "展示", msg: "三個Action的實作", style: .alert, actions: actions)

最後效果如下

希望能夠幫助到有需要用到的朋友們
範例https://github.com/h81061678/AlertController_Extension
另附上OC版https://github.com/h81061678/OC_AlertController_Extension