0

Image Description

荆文征

Zhidu Inc.


你好,再见

手势密码解锁界面

  • 小酒馆老板
  • /
  • 2018/6/14 13:46:0

休陪产假了,在家里实在是没什么事儿干了。每天都是洗尿布… 不过每天早上都是6.7点起床。哇。。。每一天的时间被拉长了呢

在家里的时候偶然想要创建一个 锁屏界面,下面就是我创建的额手势密码的一个例子。


Image Description
手势密码预览


H2

废话不多说

接下来就是全部的代码,使用方法,复制该代码到Xcode中,使用

init(frame:)
//or
required init?(coder aDecoder)

手写代码和Storyboard,两种方式。其中,FingerPasswordView 宽高建议为相同的数值。

//
//  FingerPasswordView.swift
//  FingerPassWord
//
//  Created by 荆文征 on 2018/6/14.
//  Copyright © 2018年 com.baimaodai. All rights reserved.
//

import UIKit

let FingerPasswordColor = UIColor(red: 168.0/255, green: 216.0/255, blue: 185.0/255, alpha: 1)

class FingerPasswordView: UIView {

    /// 配置手指滑动的路径的 绘制
    private let fingerPasswordLayer = CAShapeLayer()

    /// 手势动作
    private let fingerPasswordPan = FingerPasswordPanGestureRecognizer()

    override init(frame: CGRect) {

        super.init(frame: frame)

        __init()
    }

    required init?(coder aDecoder: NSCoder) {

        super.init(coder: aDecoder)

        __init()
    }

    private var collectionView:UICollectionView!

    private func __init(){

        self.isUserInteractionEnabled = true

        fingerPasswordPan.delegate = self
        fingerPasswordPan.addTarget(self, action: #selector(_fingerPasswordPanHandleMethod(pan:)))
        addGestureRecognizer(fingerPasswordPan)

        let viewLayout = UICollectionViewFlowLayout()
        viewLayout.scrollDirection = .vertical

        collectionView = UICollectionView(frame: .zero, collectionViewLayout: viewLayout)
        collectionView.isScrollEnabled = false
        collectionView.isUserInteractionEnabled = false
        self.addSubview(collectionView)
        collectionView.allowsMultipleSelection = true
        collectionView.backgroundColor = UIColor.clear
        collectionView.dataSource = self
        collectionView.delegate = self
        collectionView.register(FingerPasswordCollectionCell.self, forCellWithReuseIdentifier: "cell")
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        self.addConstraints([
            NSLayoutConstraint(item: collectionView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0),
            NSLayoutConstraint(item: collectionView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0),
            NSLayoutConstraint(item: collectionView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1, constant: 0),
            NSLayoutConstraint(item: collectionView, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1, constant: 0),
            ])

        self.fingerPasswordLayer.fillColor = UIColor.clear.cgColor
        self.fingerPasswordLayer.lineWidth = 3
        self.fingerPasswordLayer.lineJoin = kCALineCapRound
        self.fingerPasswordLayer.lineCap = kCALineCapRound
        self.fingerPasswordLayer.strokeColor = FingerPasswordColor.cgColor
        self.layer.addSublayer(self.fingerPasswordLayer)
    }

    private var path = UIBezierPath()
    private var movePanPoint:CGPoint!
    private var throughPanCell = [FingerPasswordCollectionCell](){
        didSet{

            if let first = self.throughPanCell.first {

                let path = UIBezierPath()
                path.move(to: first.center)

                for (index,cell) in self.throughPanCell.enumerated() {
                    if index == 0 { continue }
                    path.addLine(to: cell.center)
                }

                self.path = path
            }
        }
    }
}

// MARK: - UICollectionViewDataSource
extension FingerPasswordView: UICollectionViewDataSource {

    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {

        return 9
    }

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)

        return cell
    }
}

// MARK: - UICollectionViewDelegateFlowLayout
extension FingerPasswordView: UICollectionViewDelegateFlowLayout {

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {

        return CGSize(width: (frame.width-20)/3, height: (frame.width-20)/3)
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumLineSpacingForSectionAt section: Int) -> CGFloat {

        return 10
    }

    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {

        return 10
    }
}

// MARK: - UIPanGestureRecognizer Handle Extension
extension FingerPasswordView {

    /// Pan GetstureRecongnizer Handle Method
    @objc fileprivate func _fingerPasswordPanHandleMethod(pan:UIPanGestureRecognizer){

        switch pan.state {
        case .began: break
        case .changed: _movePointMethod(point: pan.location(in: pan.view))
        default: _movePointMethod(point: nil)
        }

//        print(pan.location(in: pan.view).x)
    }

    /// 手势滑动对象 滑动 处理方法
    ///
    /// - Parameter point: point
    private func _movePointMethod(point:CGPoint?){

        if let cell = identifyFootprintsMethod() {

            appendFingerCellMethod(cell: cell)
        }

        let _path = path.copy() as! UIBezierPath

        if let _point = point {

            _path.addLine(to: _point)
        }

        self.fingerPasswordLayer.path = _path.cgPath
    }
}


extension FingerPasswordView: UIGestureRecognizerDelegate{

    /// 手势 是否开始处理
    ///
    /// - Parameter gestureRecognizer: 手势处理
    /// - Returns: 是否开始处理该手势
    override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {

        if let cell = identifyFootprintsMethod() {

            appendFingerCellMethod(cell: cell)

            return true
        }

        return false
    }

    /// 辨识足迹 根据Point 进行相对应的数据处理
    ///
    /// - Returns: 当前的足迹 是否涉及到了某个 Cell
    func identifyFootprintsMethod() -> FingerPasswordCollectionCell?{

        let localPoint = self.fingerPasswordPan.location(in: fingerPasswordPan.view)

        for _cell in self.collectionView.visibleCells where ((_cell as? FingerPasswordCollectionCell) != nil) {

            let cell = _cell as! FingerPasswordCollectionCell

            let frame = self.convert(cell.spotView.frame, from: cell)

            if frame.contains(localPoint) {

                return cell
            }
        }

        return nil
    }

    /// 经过点新增 一个 Cell
    ///
    /// - Parameter cell: Cell
    func appendFingerCellMethod(cell:FingerPasswordCollectionCell){

        guard !self.throughPanCell.contains(cell) else {  return }

        self.throughPanCell.append(cell)

        self.collectionView.selectItem(at: self.collectionView.indexPath(for: cell), animated: true, scrollPosition: UICollectionViewScrollPosition.bottom)
    }
}



import UIKit.UIGestureRecognizerSubclass

/// UIPanGestureRecognizer do something immediately when touched
/// Is there a way to increase the size of a puzzle piece once the finger has touched the puzzle piece and then continue with the pan gesture.
///
/// source: https://stackoverflow.com/a/19145354/4242817
/// Make appropriate modifications
class FingerPasswordPanGestureRecognizer: UIPanGestureRecognizer {

    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
        if (self.state == UIGestureRecognizerState.began) { return }
        super.touchesBegan(touches, with: event)
        self.state = UIGestureRecognizerState.began
    }
}





/// SubView
class FingerPasswordCollectionCell: UICollectionViewCell {

    let spotView = UIView()
    private let apertureView = UIView()

    private let lable = UILabel()

    override init(frame: CGRect) {
        super.init(frame: frame)

        self.contentView.addSubview(apertureView)
        self.contentView.addSubview(spotView)

        spotView.translatesAutoresizingMaskIntoConstraints = false
        self.contentView.addConstraints([
            NSLayoutConstraint(item: spotView, attribute: .centerX, relatedBy: .equal, toItem: self.contentView, attribute: .centerX, multiplier: 1, constant: 0),
            NSLayoutConstraint(item: spotView, attribute: .centerY, relatedBy: .equal, toItem: self.contentView, attribute: .centerY, multiplier: 1, constant: 0),
            NSLayoutConstraint(item: spotView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 20),
            NSLayoutConstraint(item: spotView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 20),
            ])

        apertureView.translatesAutoresizingMaskIntoConstraints = false
        self.contentView.addConstraints([
            NSLayoutConstraint(item: apertureView, attribute: .centerX, relatedBy: .equal, toItem: self.contentView, attribute: .centerX, multiplier: 1, constant: 0),
            NSLayoutConstraint(item: apertureView, attribute: .centerY, relatedBy: .equal, toItem: self.contentView, attribute: .centerY, multiplier: 1, constant: 0),
            NSLayoutConstraint(item: apertureView, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 40),
            NSLayoutConstraint(item: apertureView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 40),
            ])

        spotView.layer.cornerRadius = 10
        apertureView.layer.cornerRadius = 20

        spotView.clipsToBounds = true
        apertureView.clipsToBounds = true

        self.isSelected = false
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    override var isSelected: Bool{

        didSet{

            UIView.animate(withDuration: 0.3) {

                if self.isSelected {

                    self.spotView.backgroundColor = FingerPasswordColor
                    self.apertureView.backgroundColor = FingerPasswordColor.withAlphaComponent(0.3)
                }else{

                    self.spotView.backgroundColor = UIColor.gray.withAlphaComponent(0.3)
                    self.apertureView.backgroundColor = UIColor.clear
                }
            }
        }
    }
}

H2

创建一个 九宫格

本来打算使用九个UIView去创建一个九宫格视图,但是最后还是打算使用UICollectionView来创建,主要在于他针对于后期的一些维护会很方便,比如当想要选中展示错误的时候,我们可以根据选中的Cell处理如下:

self.throughPanCell.forEach { (cell) in

    // .. cell configure method
}

// - OR

self.throughPanCell.map{ cell as? CUSTOMCELL }.filter{ $0 != nil }.map{ cell as! CUSTOMCELL }.forEach{(cell) in

    // .. cell.wrong()
}

我们其实还可以抽象一个配置对象,将这些代码发布出去,但是主要还是不知道如何处理,自定义cell和 注册 identifier的问题。总觉得每一个自定义的视图都注册的话,会觉的很麻烦。


H2

使用 UICollectionView的好处

因为 ColletionViewCell 本身就存在 Selected 状态,所以不需要额外配置很多方法。

只需要创建如下方法就可以实现

override var isSelected: Bool{

    didSet{

        UIView.animate(withDuration: 0.3) {

            if self.isSelected {

                  self.spotView.backgroundColor = FingerPasswordColor                      self.apertureView.backgroundColor = FingerPasswordColor.withAlphaComponent(0.3)
            }else{

                self.spotView.backgroundColor = UIColor.gray.withAlphaComponent(0.3)
                self.apertureView.backgroundColor = UIColor.clear
            }
        }
    }
}
专栏: IOS
标签: ios 手势密码