1.导航栏按钮
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = UIBarButtonItem(imageName: "navigationbar_friendattention", target: self, action: Selector("leftBtnClick"))
navigationItem.rightBarButtonItem = UIBarButtonItem(imageName: "navigationbar_pop", target: self, action: Selector("rightBtnClick"))
}
// MARK: - 内部控制方法
@objc private func leftBtnClick()
{
NJLog("")
}
@objc private func rightBtnClick()
{
NJLog("")
}
UIBarButtonItem+Extension.swift文件
import UIKit
extension UIBarButtonItem
{
// 1.用于快速创建一个对象
// 2.依赖于指定构造方法
convenience init(imageName: String, target: AnyObject?, action: Selector)
{
let btn = UIButton()
btn.setImage(UIImage(named: imageName), forState: UIControlState.Normal)
btn.setImage(UIImage(named: imageName + "_highlighted"), forState: UIControlState.Highlighted)
btn.sizeToFit()
btn.addTarget(target, action: action, forControlEvents: UIControlEvents.TouchUpInside)
self.init(customView: btn)
}
}
2.标题按钮
TitleButton.swift
import UIKit
class TitleButton: UIButton {
// override func titleRectForContentRect(contentRect: CGRect) -> CGRect {
// return CGRectZero
// }
// override func imageRectForContentRect(contentRect: CGRect) -> CGRect {
// return CGRectZero
// }
// 通过纯代码创建时调用
// 在Swift中如果重写父类的方法, 必须在方法前面加上override
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
// 通过XIB/SB创建时调用
required init?(coder aDecoder: NSCoder) {
// 系统对initWithCoder的默认实现是报一个致命错误
// fatalError("init(coder:) has not been implemented")
super.init(coder: aDecoder)
setupUI()
}
fileprivate func setupUI()
{
setImage(UIImage(named: "navigationbar_arrow_down"), for: UIControlState())
setImage(UIImage(named: "navigationbar_arrow_up"), for: UIControlState.selected)
setTitleColor(UIColor.darkGray, for: UIControlState())
sizeToFit()
}
override func setTitle(_ title: String?, for state: UIControlState) {
// ?? 用于判断前面的参数是否是nil, 如果是nil就返回??后面的数据, 如果不是nil那么??后面的语句不执行
super.setTitle((title ?? "") + " ", for: state)
}
override func layoutSubviews() {
super.layoutSubviews()
/*
// offsetInPlace 方法用于设置控件的偏移位
titleLabel?.frame.offsetInPlace(dx: -imageView!.frame.width * 0.5, dy: 0)
imageView?.frame.offsetInPlace(dx: titleLabel!.frame.width * 0.5, dy: 0)
*/
// 和OC不太一样, Swift语法允许我们直接修改一个对象的结构体属性的成员
titleLabel?.frame.origin.x = 0
imageView?.frame.origin.x = titleLabel!.frame.width
}
}
// 2.添加标题按钮
let titleButton = TitleButton()
titleButton.setTitle("小码哥", for: UIControlState())
titleButton.addTarget(self, action: #selector(HomeTableViewController.titleBtnClick(_:)), for: UIControlEvents.touchUpInside)
navigationItem.titleView = titleButton
@objc fileprivate func titleBtnClick(_ btn: TitleButton)
{
btn.isSelected = !btn.isSelected
}
3.弹出菜单
// 2.显示菜单
// 2.1创建菜单
let sb = UIStoryboard(name: "Popover", bundle: nil)
guard let menuView = sb.instantiateInitialViewController() else
{
return
}
// 自定义专场动画
// 设置转场代理
menuView.transitioningDelegate = animatorManager
// 设置转场动画样式
menuView.modalPresentationStyle = UIModalPresentationStyle.custom
// 2.2弹出菜单
present(menuView, animated: true, completion: nil)
// MARK: - 懒加载
fileprivate lazy var animatorManager: XMGPresentationManager = {
let manager = XMGPresentationManager()
manager.presentFrame = CGRect(x: 100, y: 45, width: 200, height: 300)
return manager
}()
XMGPresentationManager.swift
import UIKit
/// 自定转场展现
let XMGPresentationManagerDidPresented = "XMGPresentationManagerDidPresented"
/// 自定义转场消失
let XMGPresentationManagerDidDismissed = "XMGPresentationManagerDismissed"
class XMGPresentationManager: NSObject , UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning
{
/// 定义标记记录当前是否是展现
fileprivate var isPresent = false
/// 保存菜单的尺寸
var presentFrame = CGRect.zero
// MARK: - UIViewControllerTransitioningDelegate
// 该方法用于返回一个负责转场动画的对象
// 可以在该对象中控制弹出视图的尺寸等
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController?
{
let pc = XMGPresentationController(presentedViewController: presented, presenting: presenting)
pc.presentFrame = presentFrame
return pc
}
// 该方法用于返回一个负责转场如何出现的对象
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?
{
isPresent = true
// 发送一个通知, 告诉调用者状态发生了改变
NotificationCenter.default.post(name: Notification.Name(rawValue: XMGPresentationManagerDidPresented), object: self)
return self
}
// 该方法用于返回一个负责转场如何消失的对象
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?
{
isPresent = false
// 发送一个通知, 告诉调用者状态发生了改变
NotificationCenter.default.post(name: Notification.Name(rawValue: XMGPresentationManagerDidDismissed), object: self)
return self
}
// MARK: - UIPresentationController
// 告诉系统展现和消失的动画时长
// 暂时用不上
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
{
return 0.5
}
// 专门用于管理modal如何展现和消失的, 无论是展现还是消失都会调用该方法
func animateTransition(using transitionContext: UIViewControllerContextTransitioning)
{
// 0.判断当前是展现还是消失
if isPresent
{
// 展现
willPresentedController(transitionContext)
}else
{
// 消失
willDismissedController(transitionContext)
}
}
/// 执行展现动画
fileprivate func willPresentedController(_ transitionContext: UIViewControllerContextTransitioning)
{
// 1.获取需要弹出视图
// 通过ToViewKey取出的就是toVC对应的view
guard let toView = transitionContext.view(forKey: UITransitionContextViewKey.to) else
{
return
}
// 2.将需要弹出的视图添加到containerView上
transitionContext.containerView.addSubview(toView)
// 3.执行动画
toView.transform = CGAffineTransform(scaleX: 1.0, y: 0.0)
// 设置锚点
toView.layer.anchorPoint = CGPoint(x: 0.5, y: 0.0)
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { () -> Void in
toView.transform = CGAffineTransform.identity
}, completion: { (_) -> Void in
// 注意: 自定转场动画, 在执行完动画之后一定要告诉系统动画执行完毕了
transitionContext.completeTransition(true)
})
}
/// 执行消失动画
fileprivate func willDismissedController(_ transitionContext: UIViewControllerContextTransitioning)
{
// 1.拿到需要消失的视图
guard let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from) else
{
return
}
// 2.执行动画让视图消失
UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { () -> Void in
// 突然消失的原因: CGFloat不准确, 导致无法执行动画, 遇到这样的问题只需要将CGFloat的值设置为一个很小的值即可
fromView.transform = CGAffineTransform(scaleX: 1.0, y: 0.00001)
}, completion: { (_) -> Void in
// 注意: 自定转场动画, 在执行完动画之后一定要告诉系统动画执行完毕了
transitionContext.completeTransition(true)
})
}
}
XMGPresentationController.swift
import UIKit
class XMGPresentationController: UIPresentationController{
/// 保存菜单的尺寸
var presentFrame = CGRect.zero
/*
1.如果不自定义转场modal出来的控制器会移除原有的控制器
2.如果自定义转场modal出来的控制器不会移除原有的控制器
3.如果不自定义转场modal出来的控制器的尺寸和屏幕一样
4.如果自定义转场modal出来的控制器的尺寸我们可以自己在containerViewWillLayoutSubviews方法中控制
5.containerView 非常重要, 容器视图, 所有modal出来的视图都是添加到containerView上的
6.presentedView() 非常重要, 通过该方法能够拿到弹出的视图
*/
override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
}
// 用于布局转场动画弹出的控件
override func containerViewWillLayoutSubviews()
{
// 设置弹出视图的尺寸
presentedView?.frame = presentFrame //CGRect(x: 100, y: 45, width: 200, height: 200)
// 添加蒙版
containerView?.insertSubview(coverButton, at: 0)
coverButton.addTarget(self, action: #selector(XMGPresentationController.coverBtnClick), for: UIControlEvents.touchUpInside)
}
// MARK: - 内部控制方法
@objc fileprivate func coverBtnClick()
{
// NJLog(presentedViewController)
// NJLog(presentingViewController)
// 让菜单消失
presentedViewController.dismiss(animated: true, completion: nil)
}
// MARK: - 懒加载
fileprivate lazy var coverButton: UIButton = {
let btn = UIButton()
btn.frame = UIScreen.main.bounds
btn.backgroundColor = UIColor.clear
return btn
}()
}
4.通知
/// 自定义转场消失
let XMGPresentationManagerDidDismissed = "XMGPresentationManagerDismissed"
// 发送一个通知, 告诉调用者状态发生了改变
NotificationCenter.default.post(name: Notification.Name(rawValue: XMGPresentationManagerDidDismissed), object: self)
NotificationCenter.default.addObserver(self, selector: #selector(HomeTableViewController.titleChange), name: NSNotification.Name(rawValue: XMGPresentationManagerDidDismissed), object: animatorManager)
deinit
{
// 移除通知
NotificationCenter.default.removeObserver(self)
}
5.扫描二维码
import UIKit
import AVFoundation
class QRCodeViewController: UIViewController {
/// 扫描容器
@IBOutlet weak var customContainerView: UIView!
/// 底部工具条
@IBOutlet weak var customTabbar: UITabBar!
/// 结果文本
@IBOutlet weak var customLabel: UILabel!
/// 冲击波视图
@IBOutlet weak var scanLineView: UIImageView!
/// 容器视图高度约束
@IBOutlet weak var containerHeightCons: NSLayoutConstraint!
/// 冲击波顶部约束
@IBOutlet weak var scanLineCons: NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
// 1.设置默认选中
customTabbar.selectedItem = customTabbar.items?.first
// 2.添加监听, 监听底部工具条点击
customTabbar.delegate = self
// 3.开始扫描二维码
scanQRCode()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
startAnimation()
}
// MARK: - 内部控制方法
fileprivate func scanQRCode()
{
// 1.判断输入能否添加到会话中
if !session.canAddInput(input)
{
return
}
// 2.判断输出能够添加到会话中
if !session.canAddOutput(output)
{
return
}
// 3.添加输入和输出到会话中
session.addInput(input)
session.addOutput(output)
// 4.设置输出能够解析的数据类型
// 注意点: 设置数据类型一定要在输出对象添加到会话之后才能设置
output.metadataObjectTypes = output.availableMetadataObjectTypes
// 5.设置监听监听输出解析到的数据
output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
// 6.添加预览图层
view.layer.insertSublayer(previewLayer, at: 0)
previewLayer.frame = view.bounds
// 7.添加容器图层
view.layer.addSublayer(containerLayer)
containerLayer.frame = view.bounds
// 8.开始扫描
session.startRunning()
}
/// 开启冲击波动画
fileprivate func startAnimation()
{
// 1.设置冲击波底部和容器视图顶部对齐
scanLineCons.constant = -containerHeightCons.constant
view.layoutIfNeeded()
// 2.执行扫描动画
UIView.animate(withDuration: 2.0, animations: { () -> Void in
UIView.setAnimationRepeatCount(MAXFLOAT)
self.scanLineCons.constant = self.containerHeightCons.constant
self.view.layoutIfNeeded()
})
}
@IBAction func photoBtnClick(_ sender: AnyObject) {
// 打开相册
// 1.判断是否能够打开相册
/*
case PhotoLibrary 相册
case Camera 相机
case SavedPhotosAlbum 图片库
*/
if !UIImagePickerController.isSourceTypeAvailable(UIImagePickerControllerSourceType.photoLibrary)
{
return
}
// 2.创建相册控制器
let imagePickerVC = UIImagePickerController()
imagePickerVC.delegate = self
// 3.弹出相册控制器
present(imagePickerVC, animated: true, completion: nil)
}
@IBAction func closeBtnClick(_ sender: AnyObject) {
dismiss(animated: true, completion: nil)
}
// MARK: - 懒加载
/// 输入对象
fileprivate lazy var input: AVCaptureDeviceInput? = {
let device = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)
return try? AVCaptureDeviceInput(device: device)
}()
/// 会话
fileprivate lazy var session: AVCaptureSession = AVCaptureSession()
/// 输出对象
fileprivate lazy var output: AVCaptureMetadataOutput = {
let out = AVCaptureMetadataOutput()
// 1.获取屏幕的frame
let viewRect = self.view.frame
// 2.获取扫描容器的frame
let containerRect = self.customContainerView.frame
let x = containerRect.origin.y / viewRect.height;
let y = containerRect.origin.x / viewRect.width;
let width = containerRect.height / viewRect.height;
let height = containerRect.width / viewRect.width;
// 3.设置输出对象解析数据时感兴趣的范围
out.rectOfInterest = CGRect(x: x, y: y, width: width, height: height)
return out
}()
/// 预览图层
fileprivate lazy var previewLayer: AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer(session: self.session)
/// 专门用于保存描边的图层
fileprivate lazy var containerLayer: CALayer = CALayer()
}
extension QRCodeViewController: UINavigationControllerDelegate, UIImagePickerControllerDelegate
{
// 过时
// func imagePickerController(picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String : AnyObject]?) {
//
// }
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
// NJLog(info)
// 1.取出选中的图片
guard let image = info[UIImagePickerControllerOriginalImage] as? UIImage else
{
return
}
guard let ciImage = CIImage(image: image) else
{
return
}
// 2.从选中的图片中读取二维码数据
// 2.1创建一个探测器
let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: [CIDetectorAccuracy: CIDetectorAccuracyLow])
// 2.2利用探测器探测数据
let results = detector?.features(in: ciImage)
// 2.3取出探测到的数据
for result in results!
{
NJLog((result as! CIQRCodeFeature).messageString)
}
// 注意: 如果实现了该方法, 当选中一张图片时系统就不会自动关闭相册控制器
picker.dismiss(animated: true, completion: nil)
}
}
extension QRCodeViewController: AVCaptureMetadataOutputObjectsDelegate
{
/// 只要扫描到结果就会调用
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!)
{
// 1.显示结果
customLabel.text = (metadataObjects.last as AnyObject).stringValue
clearLayers()
// 2.拿到扫描到的数据
guard let metadata = metadataObjects.last as? AVMetadataObject else
{
return
}
// 通过预览图层将corners值转换为我们能识别的类型
let objc = previewLayer.transformedMetadataObject(for: metadata)
// 2.对扫描到的二维码进行描边
drawLines(objc as! AVMetadataMachineReadableCodeObject)
}
/// 绘制描边
fileprivate func drawLines(_ objc: AVMetadataMachineReadableCodeObject)
{
// 0.安全校验
guard let array = objc.corners else
{
return
}
// 1.创建图层, 用于保存绘制的矩形
let layer = CAShapeLayer()
layer.lineWidth = 2
layer.strokeColor = UIColor.green.cgColor
layer.fillColor = UIColor.clear.cgColor
// 2.创建UIBezierPath, 绘制矩形
let path = UIBezierPath()
var point = CGPoint.zero
var index = 0
point = CGPoint.init(dictionaryRepresentation: (array[index] as! CFDictionary))!
index = index + 1
// 2.1将起点移动到某一个点
path.move(to: point)
// 2.2连接其它线段
while index < array.count
{
point = CGPoint.init(dictionaryRepresentation: (array[index] as! CFDictionary))!
index = index + 1
}
// 2.3关闭路径
path.close()
layer.path = path.cgPath
// 3.将用于保存矩形的图层添加到界面上
containerLayer.addSublayer(layer)
}
/// 清空描边
fileprivate func clearLayers()
{
guard let subLayers = containerLayer.sublayers else
{
return
}
for layer in subLayers
{
layer.removeFromSuperlayer()
}
}
}
extension QRCodeViewController: UITabBarDelegate
{
func tabBar(_ tabBar: UITabBar, didSelect item: UITabBarItem) {
// 根据当前选中的按钮重新设置二维码容器高度
containerHeightCons.constant = (item.tag == 1) ? 150 : 300
view.layoutIfNeeded()
// 移除动画
scanLineView.layer.removeAllAnimations()
// 重新开启动画
startAnimation()
}
}
6.生成二维码
import UIKit
class QRCodeCreateViewController: UIViewController {
/// 二维码容器
@IBOutlet weak var customImageVivew: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
// 1.创建滤镜
let filter = CIFilter(name: "CIQRCodeGenerator")
// 2.还原滤镜默认属性
filter?.setDefaults()
// 3.设置需要生成二维码的数据到滤镜中
// OC中要求设置的是一个二进制数据
filter?.setValue("极客江南".data(using: String.Encoding.utf8), forKeyPath: "InputMessage")
// 4.从滤镜从取出生成好的二维码图片
guard let ciImage = filter?.outputImage else
{
return
}
// customImageVivew.image = UIImage(CIImage: ciImage)
customImageVivew.image = createNonInterpolatedUIImageFormCIImage(ciImage, size: 500)
}
/**
生成高清二维码
- parameter image: 需要生成原始图片
- parameter size: 生成的二维码的宽高
*/
fileprivate func createNonInterpolatedUIImageFormCIImage(_ image: CIImage, size: CGFloat) -> UIImage {
let extent: CGRect = image.extent.integral
let scale: CGFloat = min(size/extent.width, size/extent.height)
// 1.创建bitmap;
let width = extent.width * scale
let height = extent.height * scale
let cs: CGColorSpace = CGColorSpaceCreateDeviceGray()
let bitmapRef = CGContext(data: nil, width: Int(width), height: Int(height), bitsPerComponent: 8, bytesPerRow: 0, space: cs, bitmapInfo: 0)!
let context = CIContext(options: nil)
let bitmapImage: CGImage = context.createCGImage(image, from: extent)!
bitmapRef.interpolationQuality = CGInterpolationQuality.none
bitmapRef.scaleBy(x: scale, y: scale);
bitmapRef.draw(bitmapImage, in: extent);
// 2.保存bitmap到图片
let scaledImage: CGImage = bitmapRef.makeImage()!
return UIImage(cgImage: scaledImage)
}
}