1. 程式人生 > >Swift 全面系統的學習(持續更新...)

Swift 全面系統的學習(持續更新...)

最近專案不算緊,於是就學了學 Swift ,看了一大神寫的專案https://github.com/hrscy/DanTang,很受益,感謝開源!另外自己也寫了一些基礎程式碼,分享出來,第一是希望得到同行前輩的指導,第二是希望對需要的朋友有所幫助。

先分享一些學習資料:
  • 主要包括以下功能:
    • 圖片輪播
    • 導航欄漸變
    • 瀑布流練習
    • UIScrollView 練習
    • 照相功能,更換頭像
    • 二維碼掃描及識別
    • 隨機圖片驗證碼封裝
    • 圓形輸入框封裝
    • 第三方庫 SnapKit 用法
    • …………

練習 demo 的簡單介紹,前方高能預警,大量圖片請注意手機流量!

一、圖片輪播

圖片輪播

  • 核心程式碼:

輪播圖的封裝

    // MARK: - 懶載入輪播檢視
    private lazy var shufflingFigureView : NNShufflingFigureView = {
        let frame = CGRect(x: 0, y: 0, width: NNScreenWidth, height: 180)
        let imageView = ["shuffling1", "shuffling2", "shuffling3", "shuffling4"]
        let shufflingFigureView = NNShufflingFigureView(frame: frame, images: imageView as
NSArray, autoPlay: true, delay: 3, isFromNet: false) shufflingFigureView.delegate = self return shufflingFigureView }()

通過代理處理圖片的點選事件

// MARK: - 輪播代理方法,處理輪播圖的點選事件
extension NNItemTableViewController: NNShufflingFigureViewDelegate {
    func addShufflingFigureView(addShufflingFigureView: NNShufflingFigureView, iconClick index
: NSInteger) { print(index) } }

二、導航欄漸變

導航欄漸變

  • 核心程式碼

頁面滾動時呼叫

// MARK: - UIScrollViewDelegate 滾動頁面時呼叫
extension NNItemTableViewController {
    override func scrollViewDidScroll(_ scrollView: UIScrollView) {
        if type == tableViewType.haveHeader {
            return
        }
        currentPostion = scrollView.contentOffset.y
        if currentPostion > 0 {
            if currentPostion - lastPosition >= 0 {
                if topBool {
                    topBool = false
                    bottomBool = true
                    stopPosition = currentPostion + 64
                }
                lastPosition = currentPostion
                navigationController?.navigationBar.alpha = 1 - currentPostion / 500
            } else {
                if bottomBool {
                    bottomBool = false
                    topBool = true
                    stopPosition = currentPostion + 64
                }
                lastPosition = currentPostion
                navigationController?.navigationBar.alpha = (stopPosition - currentPostion) / 200
            }
        }
    }
}

三、瀑布流練習

瀑布流練習

  • 核心程式碼

基礎設定

        // 佈局
        let layout = NNItemCollectionViewFlowLayout()
        // 建立collectionView
        let collectionView = UICollectionView.init(frame: view.bounds, collectionViewLayout: layout)
        view.addSubview(collectionView)
        collectionView.dataSource = self
        collectionView.delegate = self
        collectionView.backgroundColor = UIColor.white
        collectionView.register(UICollectionViewCell.self, forCellWithReuseIdentifier: NNItemCollectionViewControllerID)

自定義 UICollectionViewFlowLayout

    // MARK: - 更新佈局
    override func prepare() {
        super.prepare()
        // 清除所有的佈局屬性
        attrsArray.removeAll()
        columnHeightsAry.removeAll()

        for _ in 0 ..< columnCountDefault {
            columnHeightsAry.append(edgeInsetsDefault.top)
        }

        let sections : Int = (collectionView?.numberOfSections)!
        for num in 0 ..< sections {
            let count : Int = (collectionView?.numberOfItems(inSection: num))!
            for i in 0 ..< count {
                let indexpath : NSIndexPath = NSIndexPath.init(item: i, section: num)
                let attrs = layoutAttributesForItem(at: indexpath as IndexPath)!
                attrsArray.append(attrs)
            }
        }
    }

    // MARK: - cell 對應的佈局屬性
    override func layoutAttributesForItem(at indexPath: IndexPath) -> UICollectionViewLayoutAttributes? {

        let attrs = UICollectionViewLayoutAttributes.init(forCellWith: indexPath)
        let collectionWidth = collectionView?.frame.size.width
        // 獲得所有 item 的寬度
        let itemW = (collectionWidth! - edgeInsetsDefault.left - edgeInsetsDefault.right - CGFloat(columnCountDefault-1) * columnMargin) / CGFloat(columnCountDefault)
        let itemH = 50 + arc4random_uniform(100)

        // 找出高度最短那一列
        var dextColum : Int = 0
        var minH = columnHeightsAry[0]
        for i in 1 ..< columnCountDefault{
            // 取出第 i 列的高度
            let columnH = columnHeightsAry[i]

            if minH > columnH {
                minH = columnH
                dextColum = i
            }
        }

        let x = edgeInsetsDefault.left + CGFloat(dextColum) * (itemW + columnMargin)
        var y = minH
        if y != edgeInsetsDefault.top{
            y = y + itemMargin
        }
        attrs.frame = CGRect(x: x, y: y, width: itemW, height: CGFloat(itemH))
        // 更新最短那列高度
        columnHeightsAry[dextColum] = attrs.frame.maxY
        return attrs
    }

四、UIScrollView 練習

UIScrollView 練習

  • 核心程式碼
    // MARK: - 放大縮小
    // MARK: 放大
    func amplificationBtnClick() {
        var zoomScale = scrollView.zoomScale // 當前縮放
        zoomScale += 0.1
        if zoomScale >= scrollView.maximumZoomScale {
            return
        }
        self.scrollView.setZoomScale(zoomScale, animated: true)
    }

    // MARK: 縮小
    func narrowDownBtnClick() {
        var zoomScale = scrollView.zoomScale // 當前縮放
        zoomScale -= 0.1
        if zoomScale <= scrollView.minimumZoomScale {
            return
        }
        self.scrollView.setZoomScale(zoomScale, animated: true)
    }

// MARK: - NNItemBtnViewDelegate 上下左右點選代理
extension NNItemScrollView {
    // MARK: 向左
    func leftBtnClickDelegate() {
        var point = self.scrollView.contentOffset
        point.x += 100
        point.x = point.x >= self.scrollView.contentSize.width ? 0 : point.x
        scrollView.setContentOffset(point, animated: true)
    }

    // MARK: 向右
    func rightBtnClickDelegate() {
        var point = self.scrollView.contentOffset
        point.x -= 100
        point.x = point.x <= -NNScreenWidth ? 0 : point.x
        scrollView.setContentOffset(point, animated: true)
    }

    // MARK: 向上
    func topBtnClickDelegate() {
        var point = self.scrollView.contentOffset
        point.y += 50
        point.y = point.y >= self.scrollView.contentSize.height ? 0 : point.y
        scrollView.setContentOffset(point, animated: true)
    }

    // MARK: 向下
    func bottomBtnClickDelegate() {
        var point = self.scrollView.contentOffset
        point.y -= 50
        point.y = point.y <= -NNScreenHeight ? 0 : point.y
        scrollView.setContentOffset(point, animated: true)
    }
}

五、照相功能,更換頭像(建議用真機)

照相功能,更換頭像

  • 核心程式碼
// MARK: - 點選頭像按鈕,更換頭像
    func changePicture() {
        let alertcontroller = UIAlertController(title: "請選擇相片", message: nil, preferredStyle: .actionSheet)
        let alertaction = UIAlertAction(title: "從相簿選取", style: .destructive) { (action) in
            let imagePicker = UIImagePickerController()
            imagePicker.delegate = self
            imagePicker.sourceType = .photoLibrary
            imagePicker.allowsEditing = true
            self.present(imagePicker, animated: true, completion: nil)
        }

        let alertaction2 = UIAlertAction(title: "拍照", style: .destructive) { (action) in
            if (!UIImagePickerController.isSourceTypeAvailable(.camera)) {
                print("裝置不支援相機")
                return
            }
            let imagePicker = UIImagePickerController()
            imagePicker.delegate = self
            imagePicker.sourceType = .camera
            imagePicker.allowsEditing = true
            self.present(imagePicker, animated: true, completion: nil)
        }

        let alertAction3 = UIAlertAction(title: "取消", style: .cancel) { (action) in
            print("取消")
        }

        alertcontroller.addAction(alertaction)
        alertcontroller.addAction(alertaction2)
        alertcontroller.addAction(alertAction3)
        present(alertcontroller, animated: true, completion: nil)
    }

    // MARK: - UIImagePickerControllerDelegate
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        let image = info[UIImagePickerControllerOriginalImage] as! UIImage
        imageView.image = image
        self.dismiss(animated: true, completion: nil)
    }

六、二維碼掃描及識別(建議用真機)

二維碼掃描及識別

  • 核心程式碼
// MARK: - 掃描裝置設定
    func setupScanSession() {
        do {
            // 設定捕捉裝置
            let device = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
            // 設定裝置輸入輸出
            let input = try AVCaptureDeviceInput(device: device)
            let output = AVCaptureMetadataOutput()
            output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)

            // 設定會話
            let  scanSession = AVCaptureSession()
            scanSession.canSetSessionPreset(AVCaptureSessionPresetHigh)

            if scanSession.canAddInput(input) {
                scanSession.addInput(input)
            }

            if scanSession.canAddOutput(output) {
                scanSession.addOutput(output)
            }

            // 設定掃描型別(二維碼和條形碼)
            output.metadataObjectTypes = [
                AVMetadataObjectTypeQRCode,
                AVMetadataObjectTypeCode39Code,
                AVMetadataObjectTypeCode128Code,
                AVMetadataObjectTypeCode39Mod43Code,
                AVMetadataObjectTypeEAN13Code,
                AVMetadataObjectTypeEAN8Code,
                AVMetadataObjectTypeCode93Code]

            // 預覽圖層
            let scanPreviewLayer = AVCaptureVideoPreviewLayer(session:scanSession)
            scanPreviewLayer!.videoGravity = AVLayerVideoGravityResizeAspectFill
            scanPreviewLayer!.frame = view.layer.bounds

            view.layer.insertSublayer(scanPreviewLayer!, at: 0)

            // 設定掃描區域
            NotificationCenter.default.addObserver(forName: NSNotification.Name.AVCaptureInputPortFormatDescriptionDidChange, object: nil, queue: nil, using: { (noti) in
                output.rectOfInterest = (scanPreviewLayer?.metadataOutputRectOfInterest(for:self.scanImageView.frame))!
            })
            // 儲存會話
            self.scanSession = scanSession

        } catch {
            // 攝像頭不可用
            return
        }
    }

掃描完成後呼叫

// MARK: - AVCaptureMetadataOutputObjectsDelegate 掃描捕捉完成
extension NNScanCodeController : AVCaptureMetadataOutputObjectsDelegate {
    func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
        // 停止掃描
        scanLine.layer.removeAllAnimations()
        scanSession!.stopRunning()

        // 掃完完成
        if metadataObjects.count > 0 {
            if let resultObj = metadataObjects.first as? AVMetadataMachineReadableCodeObject {
                print(resultObj.stringValue)
                scanResult.text = "掃描結果:" + resultObj.stringValue
            }
        }
    }
}

識別驗證碼,從相簿中選擇

// MARK: - UIImagePickerControllerDelegate, UINavigationControllerDelegate
extension NNScanCodeController : UIImagePickerControllerDelegate , UINavigationControllerDelegate {
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {

        // 判斷是否能取到圖片
        guard let image = info[UIImagePickerControllerOriginalImage] as? UIImage else {
            return
        }
        // 轉成ciimage
        guard let ciimage = CIImage(image: image) else {
            return
        }
        // 從選中的圖片中讀取二維碼
        // 建立探測器
        let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: [CIDetectorAccuracy : CIDetectorAccuracyLow])
        let resoult = (detector?.features(in: ciimage))!
        scanResult.text = "無法識別"
        for result in resoult {
            guard (result as! CIQRCodeFeature).messageString != nil else {
                return
            }

            scanResult.text = "掃描結果:" + (result as! CIQRCodeFeature).messageString!
        }
        picker.dismiss(animated: true, completion: nil)
    }
}

七、隨機圖片驗證碼封裝

隨機圖片驗證碼封裝

  • 核心程式碼
    // MARK: - 繪製圖形驗證碼
    override func draw(_ rect: CGRect) {
        if charString.isEmpty {
            return;
        }

        let textString:String = charString
        let charSize = textString.substring(to: textString.startIndex).size(attributes: [NSFontAttributeName : UIFont.systemFont(ofSize: 14)])
        let width = rect.size.width / CGFloat(charCount) - charSize.width - 15;
        let hight = rect.size.height - charSize.height;
        var point: CGPoint

        var pointX: CGFloat
        var pointY: CGFloat
        for i in 0..<textString.characters.count {
            let char = CGFloat(i)
            pointX = (CGFloat)(arc4random() % UInt32(Float(width))) + rect.size.width / (CGFloat)(textString.characters.count) * char
            pointY = (CGFloat)(arc4random() % UInt32(Float(hight)))
            point = CGPoint(x: pointX, y: pointY)
            let charStr = textString[textString.index(textString.startIndex, offsetBy:i)]
            let string = String(charStr)
            string.draw(at: point,withAttributes:([NSFontAttributeName : UIFont.systemFont(ofSize: 14)]))
        }
        drawLine(rect)
    }

    // MARK: - 繪製干擾線
    func drawLine(_ rect: CGRect) {
        let context = UIGraphicsGetCurrentContext()
        context!.setLineWidth(1.0)
        var pointX = 0.0
        var pointY = 0.0
        for _ in 0..<lineCount {
            context!.setStrokeColor(randomColor().cgColor)
            pointX = Double(arc4random() % UInt32(Float(rect.size.width)))
            pointY = Double(arc4random() % UInt32(Float(rect.size.height)))
            context?.move(to: CGPoint(x: pointX, y: pointY))
            pointX = Double(CGFloat(arc4random() % UInt32(Float(rect.size.width))))
            pointY = Double(CGFloat(arc4random() % UInt32(Float(rect.size.height))))
            context?.addLine(to: CGPoint(x: pointX, y: pointY))
            context!.strokePath()
        }
    }

八、圓形輸入框封裝

圓形輸入框封裝

  • 核心程式碼
    // MARK: - 監聽文字輸入 核心操作
    func textFieldDidChange(_ textField: UITextField) {
        let i = textField.text?.characters.count
        if i! > labelCount {
            return
        }
        if i == 0 {
            ((labelArr.object(at: 0)) as! UILabel).text = ""
            ((labelArr.object(at: 0)) as! UILabel).layer.borderColor = defaultColor.cgColor
        } else {
            ((labelArr.object(at: (i! - 1))) as! UILabel).text = (textField.text! as NSString).substring(with: NSMakeRange(i! - 1, 1))
            ((labelArr.object(at: (i! - 1))) as! UILabel).layer.borderColor = changedColor.cgColor
            ((labelArr.object(at: (i! - 1))) as! UILabel).textColor = changedColor
            if labelCount > i! {
                ((labelArr.object(at: (i!))) as! UILabel).text = ""
                ((labelArr.object(at: (i!))) as! UILabel).layer.borderColor = defaultColor.cgColor
            }
        }
    }

    // MARK: - setupUI
    func setupUI() {
        setupTextField()
        var labelX = CGFloat()
        let labelY : CGFloat = 0.0
        let labelWidth = self.width / CGFloat(labelCount)
        let sideLength = labelWidth < self.height ? labelWidth : self.height

        for i in 0..<labelCount {
            if i == 0 {
                labelX = 0
            } else {
                labelX = CGFloat(i) * (sideLength + labelDistance)
            }
            let label = UILabel(frame: CGRect(x: labelX, y: labelY, width: sideLength, height: sideLength))
            self.addSubview(label)
            label.textAlignment = NSTextAlignment.center
            label.layer.borderColor = UIColor.black.cgColor
            label.layer.borderWidth = 1.0
            label.layer.cornerRadius = sideLength / 2.0
            labelArr.add(label)
        }
    }

    // MARK: - UITextFieldDelegate
    // MARK: 監聽輸入框
    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        // 允許刪除
        if (string.characters.count == 0) {
            return true
        } else if (textField.text?.characters.count)! >= labelCount {
            return false
        } else {
            return true
        }
    }
    // MARK: 回車收起鍵盤
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        textField.resignFirstResponder()
        return false
    }

九、第三方庫 SnapKit 用法

第三方庫 SnapKit 用法

  • 示例程式碼
    func addRedView() {
        redView.backgroundColor = UIColor.red
        view.addSubview(redView)

        // redView 距離父檢視四條邊的距離都是 50
        redView.snp.makeConstraints { (make) in
            make.edges.equalTo(view).inset(50)
        }
    }

    func addBlueView() {
        blueView.backgroundColor = UIColor.blue
        view.addSubview(blueView)

        // blueView 左邊距離父檢視為 0;上邊距離父檢視為 0;size 是(5050)
        blueView.snp.makeConstraints { (make) in
            make.left.equalTo(0)
            make.top.equalTo(0)
            make.size.equalTo(CGSize(width: 50, height: 50))
        }
    }

    func addBlackView() {
        blackView.backgroundColor = UIColor.black
        view.addSubview(blackView)

        // blackView 左邊和 redView 的右邊距離為 0;大小與 blueView 相同;且與 blueView 上對齊
        blackView.snp.makeConstraints { (make) in
            make.left.equalTo(redView.snp.right)
            make.size.equalTo(blueView)
            make.top.equalTo(blueView)
        }
    }

    func addCyanView() {
        cyanView.backgroundColor = UIColor.cyan
        view.addSubview(cyanView)

        // cyanView 與 blueView 左對齊;cyanView 的頂部距離 redView 的底部 10;cyanView 的高是40;cyanView 與 blueView 等寬
        cyanView.snp.makeConstraints { (make) in
            make.trailing.equalTo(blueView)
            make.top.equalTo(redView.snp.bottom).offset(10)
            make.height.equalTo(40)
            make.width.equalTo(blueView)
        }
    }

    func addYellowView() {
        yellowView.backgroundColor = UIColor.yellow
        view.addSubview(yellowView)

        // yellowView 頂部與 redView 的底部對齊;yellowView 與 blackView 左對齊;yellowView 與 blueView 相同大小
        yellowView.snp.makeConstraints { (make) in
            make.top.equalTo(redView.snp.bottom)
            make.trailing.equalTo(blackView)
            make.size.equalTo(blueView)
        }
    }

    func addWhiteView() {
        whiteView.backgroundColor = UIColor.white
        redView.addSubview(whiteView)

        // whiteView 的父檢視是 redView,距離父檢視四條邊的距離分別是(30103010)

        whiteView.snp.makeConstraints { (make) in
            // 第一種方式
            make.edges.equalTo(redView).inset(UIEdgeInsets(top: 30, left: 10, bottom: 30, right: 10))
            // 第二種方式
//            make.top.equalTo(redView).offset(30)
//            make.left.equalTo(redView).offset(10)
//            make.bottom.equalTo(redView).offset(-30)
//            make.right.equalTo(redView).offset(-10)
            // 第三種方式
//            make.top.left.bottom.right.equalTo(redView).inset(UIEdgeInsets(top: 30, left: 10, bottom: 30, right: 10))
        }
    }
詳情程式碼,請移步到 https://github.com/liuzhongning/NNMintFurniture 中檢視,如有疑問或有建議的地方,歡迎討論。另外程式碼中有一個名為 guide.swift 的類,簡單標出了程式碼的結構,更方便閱讀。
會持續更新……