最近項(xiàng)目不算緊,于是就學(xué)了學(xué) Swift ,看了一大神寫的項(xiàng)目https://github.com/hrscy/DanTang,很受益,感謝開(kāi)源!另外自己也寫了一些基礎(chǔ)代碼,分享出來(lái),第一是希望得到同行前輩的指導(dǎo),第二是希望對(duì)需要的朋友有所幫助。
先分享一些學(xué)習(xí)資料:
-
學(xué)習(xí)網(wǎng)站:
蘋果官方為開(kāi)發(fā)者提供的 Swift 學(xué)習(xí)資源: https://developer.apple.com/swift/resources/
官方的 API Design: https://swift.org/documentation/api-design-guidelines/
-
學(xué)習(xí)書(shū)籍:
TheSwiftProgrammingLanguage(Swift3): 鏈接:http://pan.baidu.com/s/1jIopBwi 密碼:dqho
The Swift Programming Language 中文版: 鏈接:http://pan.baidu.com/s/1slpxtTj 密碼:xay1
-
其它學(xué)習(xí)資料
練習(xí)的 demo 地址: https://github.com/liuzhongning/NNMintFurniture
- 主要包括以下功能:
- 多頁(yè)面滑動(dòng)視圖(分頁(yè)控制器)
- 圖片輪播
- 導(dǎo)航欄漸變
- 瀑布流練習(xí)
- UIScrollView 練習(xí)
- 照相功能,更換頭像
- 二維碼掃描及識(shí)別
- 隨機(jī)圖片驗(yàn)證碼封裝
- 圓形輸入框封裝
- 第三方庫(kù) SnapKit 用法
- ............
練習(xí) demo 的簡(jiǎn)單介紹,前方高能預(yù)警,大量圖片請(qǐng)注意手機(jī)流量!
一、多頁(yè)面滑動(dòng)視圖(分頁(yè)控制器)
- 核心代碼
// MARK: 點(diǎn)擊了標(biāo)簽欄
func titlesClick(button: UIButton) {
selectedButton!.isEnabled = true
selectedButton!.setTitleColor(UIColor.gray, for: .normal)
button.isEnabled = false
selectedButton = button
selectedButton?.setTitleColor(UIColor.red, for: .normal)
var offset = contentView!.contentOffset
offset.x = CGFloat(button.tag) * (contentView?.frame.size.width)!
contentView!.setContentOffset(offset, animated: true)
}
// MARK: 點(diǎn)擊了右邊搜索框
func rightBarButtonClick() {
navigationController?.pushViewController(NNSearchController(), animated: true)
}
// MARK: 點(diǎn)擊了箭頭
func arrowButtonClick(button: UIButton) {
UIView.animate(withDuration: 0.25) {
button.imageView?.transform = button.imageView!.transform.rotated(by: CGFloat(Double.pi))
}
}
// MARK: - UIScrollViewDelegate 代理
// MARK: scrollViewDidEndScrollingAnimation
func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) {
let index = Int(scrollView.contentOffset.x / scrollView.frame.size.width)
// 取出子控制器
let vc = childViewControllers[index]
vc.view.frame.origin.x = scrollView.contentOffset.x
vc.view.frame.origin.y = 0
// 設(shè)置控制器的 view 的 height 值為整個(gè)屏幕的高度
vc.view.frame.size.height = scrollView.frame.size.height
scrollView.addSubview(vc.view)
}
// MARK: scrollViewDidEndDecelerating
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
scrollViewDidEndScrollingAnimation(scrollView)
// 當(dāng)前索引
let index = Int(scrollView.contentOffset.x / scrollView.frame.size.width)
// 點(diǎn)擊 Button
let button = titlesView.subviews[index] as! UIButton
titlesClick(button: button)
}
二、圖片輪播
- 核心代碼:
輪播圖的封裝
// 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
}()
通過(guò)代理處理圖片的點(diǎn)擊事件
// MARK: - 輪播代理方法,處理輪播圖的點(diǎn)擊事件
extension NNItemTableViewController: NNShufflingFigureViewDelegate {
func addShufflingFigureView(addShufflingFigureView: NNShufflingFigureView, iconClick index: NSInteger) {
print(index)
}
}
三、導(dǎo)航欄漸變
- 核心代碼
頁(yè)面滾動(dòng)時(shí)調(diào)用
// MARK: - UIScrollViewDelegate 滾動(dòng)頁(yè)面時(shí)調(diào)用
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
}
}
}
}
四、瀑布流練習(xí)
- 核心代碼
基礎(chǔ)設(shè)置
// 布局
let layout = NNItemCollectionViewFlowLayout()
// 創(chuàng)建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 對(duì)應(yīng)的布局屬性
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 練習(xí)
- 核心代碼
// MARK: - 放大縮小
// MARK: 放大
func amplificationBtnClick() {
var zoomScale = scrollView.zoomScale // 當(dāng)前縮放
zoomScale += 0.1
if zoomScale >= scrollView.maximumZoomScale {
return
}
self.scrollView.setZoomScale(zoomScale, animated: true)
}
// MARK: 縮小
func narrowDownBtnClick() {
var zoomScale = scrollView.zoomScale // 當(dāng)前縮放
zoomScale -= 0.1
if zoomScale <= scrollView.minimumZoomScale {
return
}
self.scrollView.setZoomScale(zoomScale, animated: true)
}
// MARK: - NNItemBtnViewDelegate 上下左右點(diǎn)擊代理
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)
}
}
六、照相功能,更換頭像(建議用真機(jī))
- 核心代碼
// MARK: - 點(diǎn)擊頭像按鈕,更換頭像
func changePicture() {
let alertcontroller = UIAlertController(title: "請(qǐng)選擇相片", message: nil, preferredStyle: .actionSheet)
let alertaction = UIAlertAction(title: "從相冊(cè)選取", 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("設(shè)備不支持相機(jī)")
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)
}
七、二維碼掃描及識(shí)別(建議用真機(jī))
- 核心代碼
// MARK: - 掃描設(shè)備設(shè)置
func setupScanSession() {
do {
// 設(shè)置捕捉設(shè)備
let device = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
// 設(shè)置設(shè)備輸入輸出
let input = try AVCaptureDeviceInput(device: device)
let output = AVCaptureMetadataOutput()
output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
// 設(shè)置會(huì)話
let scanSession = AVCaptureSession()
scanSession.canSetSessionPreset(AVCaptureSessionPresetHigh)
if scanSession.canAddInput(input) {
scanSession.addInput(input)
}
if scanSession.canAddOutput(output) {
scanSession.addOutput(output)
}
// 設(shè)置掃描類型(二維碼和條形碼)
output.metadataObjectTypes = [
AVMetadataObjectTypeQRCode,
AVMetadataObjectTypeCode39Code,
AVMetadataObjectTypeCode128Code,
AVMetadataObjectTypeCode39Mod43Code,
AVMetadataObjectTypeEAN13Code,
AVMetadataObjectTypeEAN8Code,
AVMetadataObjectTypeCode93Code]
// 預(yù)覽圖層
let scanPreviewLayer = AVCaptureVideoPreviewLayer(session:scanSession)
scanPreviewLayer!.videoGravity = AVLayerVideoGravityResizeAspectFill
scanPreviewLayer!.frame = view.layer.bounds
view.layer.insertSublayer(scanPreviewLayer!, at: 0)
// 設(shè)置掃描區(qū)域
NotificationCenter.default.addObserver(forName: NSNotification.Name.AVCaptureInputPortFormatDescriptionDidChange, object: nil, queue: nil, using: { (noti) in
output.rectOfInterest = (scanPreviewLayer?.metadataOutputRectOfInterest(for:self.scanImageView.frame))!
})
// 保存會(huì)話
self.scanSession = scanSession
} catch {
// 攝像頭不可用
return
}
}
掃描完成后調(diào)用
// 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 = "掃描結(jié)果:" + resultObj.stringValue
}
}
}
}
識(shí)別驗(yàn)證碼,從相冊(cè)中選擇
// MARK: - UIImagePickerControllerDelegate, UINavigationControllerDelegate
extension NNScanCodeController : UIImagePickerControllerDelegate , UINavigationControllerDelegate {
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
// 判斷是否能取到圖片
guard let image = info[UIImagePickerControllerOriginalImage] as? UIImage else {
return
}
// 轉(zhuǎn)成ciimage
guard let ciimage = CIImage(image: image) else {
return
}
// 從選中的圖片中讀取二維碼
// 創(chuàng)建探測(cè)器
let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: [CIDetectorAccuracy : CIDetectorAccuracyLow])
let resoult = (detector?.features(in: ciimage))!
scanResult.text = "無(wú)法識(shí)別"
for result in resoult {
guard (result as! CIQRCodeFeature).messageString != nil else {
return
}
scanResult.text = "掃描結(jié)果:" + (result as! CIQRCodeFeature).messageString!
}
picker.dismiss(animated: true, completion: nil)
}
}
八、隨機(jī)圖片驗(yàn)證碼封裝
- 核心代碼
// MARK: - 繪制圖形驗(yàn)證碼
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()
}
}
OC版本:iOS開(kāi)發(fā) - 隨機(jī)圖片驗(yàn)證碼封裝-http://www.lxweimin.com/p/936d2e06fd26
九、圓形輸入框封裝
- 核心代碼
// MARK: - 監(jiān)聽(tīng)文本輸入 核心操作
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: 監(jiān)聽(tīng)輸入框
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
}
OC版本:iOS開(kāi)發(fā) - 圓形驗(yàn)證碼(或密碼)輸入框的封裝-http://www.lxweimin.com/p/fce6bd4038eb
十、第三方庫(kù) 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 是(50,50)
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 上對(duì)齊
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 左對(duì)齊;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 的底部對(duì)齊;yellowView 與 blackView 左對(duì)齊;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,距離父視圖四條邊的距離分別是(30,10,30,10)
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))
}
}