Commit 2a3c7c0b by Sarkizz

添加各种工具类

parent 2f74944f
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:MRFramework.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
//
// Data+crypto.swift
// MRFramework
//
// Created by Sarkizz on 2019/8/27.
// Copyright © 2019 sarkizz. All rights reserved.
//
import Foundation
import CommonCrypto
extension Data {
public var md5: String {
let hash = self.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) -> [UInt8] in
var hash = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
CC_MD5(bytes.baseAddress, CC_LONG(self.count), &hash)
return hash
}
return hash.map { String(format: "%02x", $0) }.joined()
}
}
//
// Dictionary+util.swift
// MRFramework
//
// Created by Sarkizz on 2019/8/23.
// Copyright © 2019 sarkizz. All rights reserved.
//
import Foundation
extension Dictionary where Key == String, Value == String {
public func cookie(_ custom: ((_ each: String) -> String)? = nil) -> String {
guard let custom = custom else {
return map({key, value in "\(key)=\(value)"}).joined(separator: ";")
}
return map({key, value in custom("\(key)=\(value)")}).joined(separator: ";")
}
}
//
// NSMutableURLRequest+util.swift
// MRFramework
//
// Created by Sarkizz on 2019/8/23.
// Copyright © 2019 sarkizz. All rights reserved.
//
import Foundation
extension NSMutableURLRequest {
public func setCookie(_ cookie: [String: String]) {
setValue(cookie.cookie(), forHTTPHeaderField: "Cookie")
}
}
//
// String+util.swift
// MRFramework
//
// Created by Sarkizz on 2019/8/27.
// Copyright © 2019 sarkizz. All rights reserved.
//
import Foundation
import CommonCrypto
extension String {
public var md5: String {
let data = Data(self.utf8)
let hash = data.withUnsafeBytes { (bytes: UnsafeRawBufferPointer) -> [UInt8] in
var hash = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
CC_MD5(bytes.baseAddress, CC_LONG(data.count), &hash)
return hash
}
return hash.map { String(format: "%02x", $0) }.joined()
}
}
//
// CAGradientLayer+util.swift
// MRFramework
//
// Created by Sarkizz on 2019/8/23.
// Copyright © 2019 sarkizz. All rights reserved.
//
import Foundation
extension CAGradientLayer {
public struct RenderColor {
var color: UIColor
// 颜色所在位置,0.0~1.0
var location: CGFloat
init(color: UIColor, location: CGFloat) {
self.color = color
self.location = location
}
}
/// 渐变方向枚举,其中参数表示填充的比例,0.0~1.0
///
/// - horizontal: 水平方向
/// - vertical: 垂直方向
/// - adjectiveFromLeft: 从左上到右下对角线斜方向
/// - adjectiveFromRight: 从右上到坐下对角线斜方向
public enum RenderDirection {
case horizontal(CGFloat)
case vertical(CGFloat)
case adjectiveFromLeft(CGFloat)
case adjectiveFromRight(CGFloat)
var startPoint: CGPoint {
switch self {
case .horizontal, .vertical, .adjectiveFromLeft:
return CGPoint(x: 0, y: 0)
case .adjectiveFromRight:
return CGPoint(x: 1, y: 1)
}
}
var endPoint: CGPoint {
switch self {
case .horizontal(let f):
return CGPoint(x: f, y: 0)
case .vertical(let f):
return CGPoint(x: 0, y: f)
case .adjectiveFromLeft(let f):
return CGPoint(x: f, y: f)
case .adjectiveFromRight(let f):
return CGPoint(x: 1 - f, y: 1 - f)
}
}
}
}
extension CAGradientLayer {
public class func layer(with colors: [RenderColor], direction: RenderDirection = .vertical(1)) -> CAGradientLayer {
let renderColors = colors.map({ $0.color.cgColor })
let renderLocations = colors.map({ NSNumber(value: Float($0.location)) })
let layer = CAGradientLayer()
layer.colors = renderColors
layer.locations = renderLocations
layer.startPoint = direction.startPoint
layer.endPoint = direction.endPoint
return layer
}
}
//
// UIButton+util.swift
// MRFramework
//
// Created by Sarkizz on 2019/8/26.
// Copyright © 2019 sarkizz. All rights reserved.
//
import Foundation
extension UIButton {
/// 图片位置
public enum ImagePosition {
case left
case right
case top
case bottom
}
/// 调整图片位置,返回调整后所需要的size
/// 调用本方法前,请先确保imageView和titleLabel有值。
@discardableResult
public func adjustImage(position: ImagePosition, spacing: CGFloat) -> CGSize {
guard imageView != nil && titleLabel != nil else {
return CGSize.zero
}
let imageSize = self.imageView!.intrinsicContentSize
let titleSize = self.titleLabel!.intrinsicContentSize
// 布局
switch position {
case .left:
imageEdgeInsets = UIEdgeInsets(top: 0, left: -spacing / 2, bottom: 0, right: spacing / 2)
titleEdgeInsets = UIEdgeInsets(top: 0, left: spacing / 2, bottom: 0, right: -spacing / 2)
contentEdgeInsets = UIEdgeInsets(top: 0, left: spacing / 2, bottom: 0, right: spacing / 2)
case .right:
imageEdgeInsets = UIEdgeInsets(top: 0, left: (titleSize.width + spacing / 2), bottom: 0, right: -(titleSize.width + spacing / 2))
titleEdgeInsets = UIEdgeInsets(top: 0, left: -(imageSize.width + spacing / 2), bottom: 0, right: (imageSize.width + spacing / 2))
contentEdgeInsets = UIEdgeInsets(top: 0, left: spacing / 2, bottom: 0, right: spacing / 2)
case .top, .bottom:
let imageOffsetX = (imageSize.width + titleSize.width) / 2 - imageSize.width / 2
let imageOffsetY = imageSize.height / 2 + spacing / 2
let titleOffsetX = (imageSize.width + titleSize.width / 2) - (imageSize.width + titleSize.width) / 2
let titleOffsetY = titleSize.height / 2 + spacing / 2
let changedWidth = titleSize.width + imageSize.width - max(titleSize.width, imageSize.width)
let changedHeight = titleSize.height + imageSize.height + spacing - max(imageSize.height, imageSize.height)
if position == .top {
imageEdgeInsets = UIEdgeInsets(top: -imageOffsetY, left: imageOffsetX, bottom: imageOffsetY, right: -imageOffsetX)
titleEdgeInsets = UIEdgeInsets(top: titleOffsetY, left: -titleOffsetX, bottom: -titleOffsetY, right: titleOffsetX)
self.contentEdgeInsets = UIEdgeInsets(top: imageOffsetY, left: -changedWidth / 2, bottom: changedHeight - imageOffsetY, right: -changedWidth / 2)
} else {
imageEdgeInsets = UIEdgeInsets(top: imageOffsetY, left: imageOffsetX, bottom: -imageOffsetY, right: -imageOffsetX)
titleEdgeInsets = UIEdgeInsets(top: -titleOffsetY, left: -titleOffsetX, bottom: titleOffsetY, right: titleOffsetX)
self.contentEdgeInsets = UIEdgeInsets(top: changedHeight - imageOffsetY, left: -changedWidth / 2, bottom: imageOffsetY, right: -changedWidth / 2)
}
}
return self.intrinsicContentSize
}
}
//
// UICollectionView+reusable.swift
// MRFramework
//
// Created by Sarkizz on 2019/8/26.
// Copyright © 2019 sarkizz. All rights reserved.
//
import Foundation
extension UICollectionView: NamespaceWrappable{}
extension TypeWrapperProtocol where WrappedType == UICollectionView {
public func registerCell<T>(_ cellType: T.Type) where T: UICollectionViewCell {
wrappedValue.register(cellType, forCellWithReuseIdentifier: "\(cellType)")
}
public func dequeueCell<T>(_ cellType: T.Type, for indexPath: IndexPath) -> T where T: UICollectionViewCell {
return wrappedValue.dequeueReusableCell(withReuseIdentifier: "\(cellType)", for: indexPath) as? T ?? T()
}
}
extension TypeWrapperProtocol where WrappedType == UICollectionView {
public func registerHeader<T>(_ viewType: T.Type) where T: UICollectionReusableView {
wrappedValue.register(viewType, forSupplementaryViewOfKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "\(viewType)")
}
public func dequeueHeader<T>(_ viewType: T.Type, for indexPath: IndexPath) -> T where T: UICollectionReusableView {
return wrappedValue.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionHeader, withReuseIdentifier: "\(viewType)", for: indexPath) as? T ?? T()
}
}
extension TypeWrapperProtocol where WrappedType == UICollectionView {
public func registerFooter<T>(_ viewType: T.Type) where T: UICollectionReusableView {
wrappedValue.register(viewType, forSupplementaryViewOfKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "\(viewType)")
}
public func dequeueFooter<T>(_ viewType: T.Type, for indexPath: IndexPath) -> T where T: UICollectionReusableView {
return wrappedValue.dequeueReusableSupplementaryView(ofKind: UICollectionView.elementKindSectionFooter, withReuseIdentifier: "\(viewType)", for: indexPath) as? T ?? T()
}
}
//
// UIColor+util.swift
// MRFramework
//
// Created by Sarkizz on 2019/8/26.
// Copyright © 2019 sarkizz. All rights reserved.
//
import Foundation
extension UIColor {
public convenience init(red: Int, green: Int, blue: Int, alpha: CGFloat = 1) {
self.init(
red: CGFloat(red) / 255.0,
green: CGFloat(green) / 255.0,
blue: CGFloat(blue) / 255.0,
alpha: alpha
)
}
public convenience init(_ hex: Int, alpha: CGFloat = 1) {
self.init(
red: (hex >> 16) & 0xFF,
green: (hex >> 8) & 0xFF,
blue: hex & 0xFF,
alpha: alpha
)
}
public convenience init(_ hexStr: String) {
let hex = strtoul(hexStr, nil, 16)
self.init(Int(hex))
}
public var image: UIImage? {
let rect = CGRect(x: 0, y: 0, width: 1, height: 1)
UIGraphicsBeginImageContext(rect.size)
let context = UIGraphicsGetCurrentContext()
context?.setFillColor(self.cgColor)
context?.fill(rect)
let image = UIGraphicsGetImageFromCurrentImageContext()
return image
}
}
......@@ -8,6 +8,9 @@
import Foundation
import UIKit
import Photos
public typealias UIImageSaveToAlbumCompleteBlock = (_ image: UIImage, _ saveURL: URL?, _ error: Error?) -> Void
extension UIImage {
public enum SaveConfig {
......@@ -17,6 +20,16 @@ extension UIImage {
}
extension UIImage {
public func thumbImage(_ size: CGSize) -> UIImage? {
let size = sizeForFit(size) * scale
UIGraphicsBeginImageContextWithOptions(size, true, scale)
draw(in: CGRect(origin: CGPoint.zero, size: size))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
public func save(to url: URL, name: String, config: UIImage.SaveConfig = .default,
complate: ((_ fullImgURL: URL?, _ thumbImgURL: URL?, _ error: Error?) -> Void)?) {
DispatchQueue.global().async {
......@@ -64,17 +77,39 @@ extension UIImage {
}
}
}
public func saveToAlbum(_ complate: UIImageSaveToAlbumCompleteBlock?) {
var localId: String?
PHPhotoLibrary.shared().performChanges({
let rs = PHAssetChangeRequest.creationRequestForAsset(from: self)
let placeholder = rs.placeholderForCreatedAsset
localId = placeholder?.localIdentifier
}) { (isSuccessed, error) in
if isSuccessed {
if let id = localId {
let rs = PHAsset.fetchAssets(withBurstIdentifier: id, options: nil)
if let asset = rs.firstObject {
let options = PHContentEditingInputRequestOptions()
options.canHandleAdjustmentData = { data in
return true
}
asset.requestContentEditingInput(with: options, completionHandler: { (input, info) in
complate?(self, input?.fullSizeImageURL, nil)
})
} else {
complate?(self, nil, self.error(code: -2, msg: "获取图片资源失败"))
}
} else {
complate?(self, nil, self.error(code: -1, msg: "保存图片缺失localIdentifier"))
}
} else {
complate?(self, nil, error)
}
}
}
}
extension UIImage {
private func thumbImage(_ size: CGSize) -> UIImage? {
let size = sizeForFit(size) * scale
UIGraphicsBeginImageContextWithOptions(size, true, scale)
draw(in: CGRect(origin: CGPoint.zero, size: size))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
private func sizeForFit(_ size: CGSize) -> CGSize {
let wr = size.width / self.size.width
......@@ -107,8 +142,8 @@ extension UIImage {
}
}
extension CGSize {
static func * (lhs: CGSize, rhs: CGFloat) -> CGSize {
return CGSize(width: lhs.width * rhs, height: lhs.height * rhs)
extension UIImage {
private func error(code: Int, msg: String) -> Error {
return NSError(domain: "com.MRFramework.UIImage+file", code: code, userInfo: [NSLocalizedDescriptionKey: msg])
}
}
//
// UIScreen+util.swift
// MRFramework
//
// Created by Sarkizz on 2019/8/23.
// Copyright © 2019 sarkizz. All rights reserved.
//
import Foundation
extension UIScreen {
public class var statusBarHeight: CGFloat {
return UIApplication.shared.statusBarFrame.size.height
}
@available(iOS 11.0, *)
public class var safeArea: UIEdgeInsets? {
return UIApplication.shared.keyWindow?.rootViewController?.additionalSafeAreaInsets
}
public var width: CGFloat {
return bounds.size.width
}
public var height: CGFloat {
return bounds.size.height
}
}
extension UIScreen {
@available(iOS 11.0, *)
public class func safeArea(for viewController: UIViewController) -> UIEdgeInsets {
return viewController.additionalSafeAreaInsets
}
}
extension CGRect {
public static func +(lhs: CGRect, rhs: CGFloat) -> CGRect {
return lhs.offsetBy(dx: rhs, dy: rhs)
}
public static func +(lhs: CGRect, rhs: CGPoint) -> CGRect {
return lhs.offsetBy(dx: rhs.x, dy: rhs.y)
}
public static func *(lhs: CGRect, rhs: CGFloat) -> CGRect {
return lhs.insetBy(dx: rhs, dy: rhs)
}
public static func *(lhs: CGRect, rhs: CGPoint) -> CGRect {
return lhs.insetBy(dx: rhs.x, dy: rhs.y)
}
}
extension CGSize {
public static func +(lhs: CGSize, rhs: CGFloat) -> CGSize {
return CGSize(width: lhs.width * rhs, height: lhs.height * rhs)
}
public static func +(lhs: CGSize, rhs: CGSize) -> CGSize {
return CGSize(width: lhs.width + rhs.width, height: lhs.height + rhs.height)
}
public static func *(lhs: CGSize, rhs: CGFloat) -> CGSize {
return CGSize(width: lhs.width * rhs, height: lhs.height * rhs)
}
public static func *(lhs: CGSize, rhs: CGSize) -> CGSize {
return CGSize(width: lhs.width * rhs.width, height: lhs.height * rhs.height)
}
}
//
// UITableView+reusable.swift
// MRFramework
//
// Created by Sarkizz on 2019/8/26.
// Copyright © 2019 sarkizz. All rights reserved.
//
import Foundation
extension UITableView: NamespaceWrappable {}
extension TypeWrapperProtocol where WrappedType == UITableView {
/// 注册复用的Cell
public func registerCell<T>(_ cellType: T.Type) where T: UITableViewCell {
wrappedValue.register(cellType, forCellReuseIdentifier: "\(cellType)")
}
/// 自动注册并获取Cell
public func dequeueCell<T>(_ cellType: T.Type) -> T where T: UITableViewCell {
let identifier = "\(cellType)"
if let cell = wrappedValue.dequeueReusableCell(withIdentifier: identifier) as? T {
return cell
} else {
registerCell(cellType)
return wrappedValue.dequeueReusableCell(withIdentifier: identifier) as? T ?? T()
}
}
/// 使用本方法前先注册Cell,否则崩溃
public func dequeueReusableCell<T>(_ cellType: T.Type, for indexPath: IndexPath) -> T where T: UITableViewCell {
return wrappedValue.dequeueReusableCell(withIdentifier: "\(cellType)", for: indexPath) as? T ?? T()
}
}
extension TypeWrapperProtocol where WrappedType == UITableView {
/// 注册复用 HeaderFooterView
public func regisgerHeaderFooterView<T>(_ viewType: T.Type) where T: UITableViewHeaderFooterView {
wrappedValue.register(viewType, forHeaderFooterViewReuseIdentifier: "\(viewType)")
}
/// 自动注册并取 HeaderFooterView
public func dequeueReusableHeaderFooterView<T>(_ viewType: T.Type) -> T where T: UITableViewHeaderFooterView {
let identifier = "\(viewType)"
if let view = wrappedValue.dequeueReusableHeaderFooterView(withIdentifier: identifier) {
return view as? T ?? T.init()
} else {
regisgerHeaderFooterView(viewType)
return wrappedValue.dequeueReusableHeaderFooterView(withIdentifier: identifier) as? T ?? T.init()
}
}
}
//
// UIView+util.swift
// MRFramework
//
// Created by Sarkizz on 2019/8/23.
// Copyright © 2019 sarkizz. All rights reserved.
//
import Foundation
extension UIView {
public var viewController: UIViewController? {
var view: UIView? = self
repeat {
if let vc = view?.next as? UIViewController {
return vc
}
view = view?.superview
} while view != nil
return nil
}
public var x: CGFloat {
get {
return frame.origin.x
}
set(nv) {
frame.origin.x = nv
}
}
public var y: CGFloat {
get {
return frame.origin.y
}
set(nv) {
frame.origin.y = nv
}
}
public var width: CGFloat {
get {
return frame.size.width
}
set(nv) {
frame.size.width = nv
}
}
public var height: CGFloat {
get {
return frame.size.height
}
set(nv) {
frame.size.height = nv
}
}
}
extension UIView {
public func setGradientBackground(colors: [CAGradientLayer.RenderColor], direction: CAGradientLayer.RenderDirection = .vertical(1)) {
let layer = CAGradientLayer.layer(with: colors, direction: direction)
layer.frame = frame
self.layer.insertSublayer(layer, at: 0)
}
}
extension UIView {
private enum LineTag: Int {
case left = 28191
case right = 28201
case top = 29191
case bottom = 29201
}
public enum LineStyle {
case full
case startMargin(CGFloat)
case endMargin(CGFloat)
case custom(startMargin: CGFloat, endMargin: CGFloat)
public var marginStart: CGFloat {
switch self {
case .full, .endMargin(_):
return 0
case .startMargin(let m), .custom(startMargin: let m, endMargin: _):
return m
}
}
public var marginEnd: CGFloat {
switch self {
case .full, .startMargin(_):
return 0
case .endMargin(let m), .custom(startMargin: _, endMargin: let m):
return m
}
}
}
public enum LinePosition {
case top(offset: CGFloat, style: LineStyle)
case bottom(offset: CGFloat, style: LineStyle)
case left(offset: CGFloat, style: LineStyle)
case right(offset: CGFloat, style: LineStyle)
public var tag: Int {
switch self {
case .top(offset: _, style: _):
return LineTag.top.rawValue
case .bottom(offset: _, style: _):
return LineTag.bottom.rawValue
case .left(offset: _, style: _):
return LineTag.left.rawValue
case .right(offset: _, style: _):
return LineTag.right.rawValue
}
}
}
public class BorderLine: UIView {}
public var topLine: BorderLine? {
return viewWithTag(LineTag.top.rawValue) as? BorderLine
}
public var bottomLine: BorderLine? {
return viewWithTag(LineTag.bottom.rawValue) as? BorderLine
}
public var leftLine: BorderLine? {
return viewWithTag(LineTag.left.rawValue) as? BorderLine
}
public var rightLine: BorderLine? {
return viewWithTag(LineTag.right.rawValue) as? BorderLine
}
public func addLine(position: LinePosition = .bottom(offset: 0, style: .full), width: CGFloat = 1) {
let _ = line(position: position, width: width)
}
public func line(position: LinePosition, width: CGFloat = 1) -> BorderLine {
if let line = viewWithTag(position.tag) as? BorderLine {
return line
}
let line = BorderLine()
line.tag = position.tag
addSubview(line)
switch position {
case .top(offset: let offset, style: let style):
line.snp.makeConstraints { (make) in
make.top.equalToSuperview().offset(offset)
make.left.equalToSuperview().offset(style.marginStart)
make.right.equalToSuperview().offset(style.marginEnd)
make.height.equalTo(width)
}
break
case .bottom(offset: let offset, style: let style):
line.snp.makeConstraints { (make) in
make.bottom.equalToSuperview().offset(offset)
make.left.equalToSuperview().offset(style.marginStart)
make.right.equalToSuperview().offset(style.marginEnd)
make.height.equalTo(width)
}
break
case .left(offset: let offset, style: let style):
line.snp.makeConstraints { (make) in
make.left.equalToSuperview().offset(offset)
make.top.equalToSuperview().offset(style.marginStart)
make.bottom.equalToSuperview().offset(style.marginEnd)
make.width.equalTo(width)
}
break
case .right(offset: let offset, style: let style):
line.snp.makeConstraints { (make) in
make.right.equalToSuperview().offset(offset)
make.top.equalToSuperview().offset(style.marginStart)
make.bottom.equalToSuperview().offset(style.marginEnd)
make.width.equalTo(width)
}
break
}
return line
}
}
//
// BLEManager.swift
// VMatrix
//
// Created by Sarkizz on 2019/8/13.
// Copyright © 2019 maxrocky. All rights reserved.
//
import Foundation
import CoreBluetooth
public typealias BLEDidConnectBlock = (_ manager: BLEManager, _ p: CBPeripheral, _ error: Error?) -> Void
public typealias BLEDidDisconnectBlock = (_ manager: BLEManager, _ p: CBPeripheral, _ error: Error?) -> Void
public enum BLEManagerState: Int {
case unknown
case resetting
case unsupported
case unauthorized
case poweredOff
case poweredOn
var description: String {
switch self {
case .unknown:
return "unknown"
case .resetting:
return "resetting"
case .unsupported:
return "unsupported"
case .unauthorized:
return "unauthorized"
case .poweredOff:
return "poweredOff"
case .poweredOn:
return "poweredOn"
}
}
}
public enum BLEStopType {
case normal
case timeout
case outside
}
public final class BLEManager: NSObject {
public static let shared = BLEManager()
private let bleQueue = DispatchQueue(label: "com.vmatrix.ble")
private var manager: CBCentralManager!
public var state: BLEManagerState {
get {
return BLEManagerState(rawValue: BLEManager.shared.manager.state.rawValue)!
}
}
public var connectedPeripherals: [CBPeripheral] {
get {
return _connectedPeripherals
}
}
public var stateChange: ((_ manager: BLEManager, _ state: BLEManagerState) -> Void)?
public var didScanPeripheral: ((_ manager: BLEManager, _ p: CBPeripheral, _ advertisementData: [String: Any], _ rssi: Int) -> Void)?
public var didStop: ((_ manager: BLEManager, _ type: BLEStopType) -> Void)?
private var shouldScanWhenStatePowerOn = false
private var filterUUIDs: [String]?
private var timeout: TimeInterval?
private var countdown: Countdown?
//连接前需要保存实例,防止在回调的时候设备被释放(防止外部没有保存而导致连接没有任何回调)
private var preConnectPeripheral: CBPeripheral?
private var didConnect: BLEDidConnectBlock?
private var didDisconnect: BLEDidDisconnectBlock?
private var _connectedPeripherals: [CBPeripheral] = [CBPeripheral]()
override init() {
super.init()
manager = CBCentralManager(delegate: self, queue: self.bleQueue, options: [CBCentralManagerOptionShowPowerAlertKey: true])
}
}
extension BLEManager {
public func scan(_ filterUUIDs: [String]? = nil, timeout: TimeInterval? = nil) {
if manager.isScanning {
return
}
self.timeout = timeout
if manager.state == .poweredOn {
startScan(filterUUIDs?.toUUIDs())
} else {
self.filterUUIDs = filterUUIDs
shouldScanWhenStatePowerOn = true
}
}
public func stop() {
if manager.isScanning {
stop(.outside)
}
}
public func connect(_ p: CBPeripheral, complate: BLEDidConnectBlock?) {
didConnect = complate
preConnectPeripheral = p
manager.connect(p, options: nil)
}
public func disconnect(_ p: CBPeripheral, complate: BLEDidDisconnectBlock?) {
didDisconnect = complate
manager.cancelPeripheralConnection(p)
}
}
extension BLEManager {
private func startScan(_ uuids: [CBUUID]?) {
if manager.isScanning {
stop(.normal)
}
manager.scanForPeripherals(withServices: uuids, options: [CBCentralManagerScanOptionAllowDuplicatesKey: true])
if let timeout = timeout {
weak var `self` = self
countdown = timeout.repeat({ _ in
if let isScanning = self?.manager.isScanning, isScanning {
self?.stop(.timeout)
}
})
countdown?.resume()
}
}
private func stop(_ type: BLEStopType) {
manager.stopScan()
didStop?(self, type)
}
}
extension BLEManager: CBCentralManagerDelegate {
public func centralManagerDidUpdateState(_ central: CBCentralManager) {
stateChange?(self, BLEManagerState(rawValue: central.state.rawValue)!)
switch central.state {
case .poweredOn:
if shouldScanWhenStatePowerOn {
startScan(filterUUIDs?.toUUIDs())
shouldScanWhenStatePowerOn = false
filterUUIDs = nil
}
break
default:
break
}
}
public func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
didScanPeripheral?(self, peripheral, advertisementData, RSSI.intValue)
}
public func centralManager(_ central: CBCentralManager, didConnect peripheral: CBPeripheral) {
peripheral.delegate = self
_connectedPeripherals.append(peripheral)
if let pre = preConnectPeripheral, pre == peripheral {
preConnectPeripheral = nil
}
didConnect?(self, peripheral, nil)
}
public func centralManager(_ central: CBCentralManager, didFailToConnect peripheral: CBPeripheral, error: Error?) {
didConnect?(self, peripheral, error)
}
public func centralManager(_ central: CBCentralManager, didDisconnectPeripheral peripheral: CBPeripheral, error: Error?) {
let _ = _connectedPeripherals.drop(while: { $0 == peripheral })
didDisconnect?(self, peripheral, error)
}
}
extension BLEManager: CBPeripheralDelegate {
public func peripheral(_ peripheral: CBPeripheral, didReadRSSI RSSI: NSNumber, error: Error?) {
peripheral.getRSSI?(peripheral, RSSI.intValue, error)
}
public func peripheral(_ peripheral: CBPeripheral, didDiscoverServices error: Error?) {
peripheral.didFindServices?(peripheral, peripheral.services, error)
}
public func peripheral(_ peripheral: CBPeripheral, didDiscoverCharacteristicsFor service: CBService, error: Error?) {
peripheral.didFindChars?(peripheral, service.characteristics, error)
}
public func peripheral(_ peripheral: CBPeripheral, didUpdateValueFor characteristic: CBCharacteristic, error: Error?) {
if let block = peripheral.didReadCharsValue {
block(peripheral, characteristic, error)
peripheral.clearReadCharsValueBlock()
}
if let block = peripheral.charsValueObservers[characteristic.observerKey] {
block(peripheral, characteristic, error)
}
}
public func peripheral(_ peripheral: CBPeripheral, didWriteValueFor characteristic: CBCharacteristic, error: Error?) {
if let block = peripheral.writeValueBlocks[characteristic.observerKey] {
block(peripheral, characteristic, error)
}
}
}
extension Array where Element == String {
public func toUUIDs() -> [CBUUID] {
return map({ CBUUID(string: $0) })
}
}
//
// BLEPeripheralExtention.swift
// VMatrix
//
// Created by Sarkizz on 2019/8/14.
// Copyright © 2019 maxrocky. All rights reserved.
//
import Foundation
import CoreBluetooth
public typealias BLERSSIValueBlock = (_ p: CBPeripheral, _ rssi: Int, _ error: Error?) -> Void
public typealias BLEFindServicesComplate = (_ p: CBPeripheral, _ services: [CBService]?, _ error: Error?) -> Void
public typealias BLEFindCharsComplate = (_ p: CBPeripheral, _ chars: [CBCharacteristic]?, _ error: Error?) -> Void
public typealias BLECharsValueBlock = (_ p: CBPeripheral, _ chars: CBCharacteristic?, _ error: Error?) -> Void
public typealias BLEWriteValueComplate = (_ p: CBPeripheral, _ chars: CBCharacteristic?, _ error: Error?) -> Void
public enum BLEErrorCode: Int {
case parserKeyError = -99
case notfound = -1
}
extension CBPeripheral {
private static var _charsValueObservers = [String: BLECharsValueBlock]()
private static var _writeValueBlocks = [String: BLEWriteValueComplate]()
public var charsValueObservers: [String: BLECharsValueBlock] {
return CBPeripheral._charsValueObservers
}
public var writeValueBlocks: [String: BLEWriteValueComplate] {
return CBPeripheral._writeValueBlocks
}
//RSSI查询回调
public var getRSSI: BLERSSIValueBlock? {
return objc_getAssociatedObject(self, "BLE_SETRSSI_BLOCK") as? BLERSSIValueBlock
}
private func setGetRSSI(_ block: BLERSSIValueBlock?) {
objc_setAssociatedObject(self, "BLE_SETRSSI_BLOCK", block, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
// 查询服务回调
public var didFindServices: BLEFindServicesComplate? {
return objc_getAssociatedObject(self, "BLE_SET_DIDFINDSERVICES_BLOCK") as? BLEFindServicesComplate
}
private func setDidFindServices(_ block: @escaping BLEFindServicesComplate) {
objc_setAssociatedObject(self, "BLE_SET_DIDFINDSERVICES_BLOCK", block, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
// 查询特征回调
public var didFindChars: BLEFindCharsComplate? {
return objc_getAssociatedObject(self, "BLE_SET_DIDFINDCHARS_BLOCK") as? BLEFindCharsComplate
}
private func setDidFindChars(_ block: @escaping BLEFindCharsComplate) {
objc_setAssociatedObject(self, "BLE_SET_DIDFINDCHARS_BLOCK", block, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
// 查询特征值回调
public var didReadCharsValue: BLECharsValueBlock? {
return objc_getAssociatedObject(self, "BLE_SET_DIDREADCHARSVALUE_BLOCK") as? BLECharsValueBlock
}
private func setDidReadCharsValue(_ block: BLECharsValueBlock?) {
objc_setAssociatedObject(self, "BLE_SET_DIDREADCHARSVALUE_BLOCK", block, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
public func clearReadCharsValueBlock() {
setDidReadCharsValue(nil)
}
}
// MARK: 读写操作
extension CBPeripheral {
public func readRSSI(_ complate: @escaping BLERSSIValueBlock) {
if delegate != nil {
setGetRSSI(complate)
readRSSI()
}
}
public func findServices(_ filterUUIDs: [CBUUID]?, complate: @escaping BLEFindServicesComplate) {
if delegate != nil {
setDidFindServices(complate)
discoverServices(filterUUIDs)
}
}
public func findChars(_ filterUUIDs: [CBUUID]?, for service: CBService, complate: @escaping BLEFindCharsComplate) {
if delegate != nil {
setDidFindChars(complate)
discoverCharacteristics(filterUUIDs, for: service)
}
}
public func readValue(for chars: CBCharacteristic, complate: @escaping BLECharsValueBlock) {
if delegate != nil {
setDidReadCharsValue(complate)
readValue(for: chars)
}
}
public func observerValue(_ chars: CBCharacteristic, change: @escaping BLECharsValueBlock) -> Bool {
if delegate != nil && chars.observerable {
CBPeripheral._charsValueObservers[chars.observerKey] = change
setNotifyValue(true, for: chars)
return true
}
return false
}
public func removeValueObserver(_ chars: CBCharacteristic) {
CBPeripheral._charsValueObservers.removeValue(forKey: chars.observerKey)
setNotifyValue(false, for: chars)
}
public func removeAllValueObservers() {
if let services = services {
services.forEach({ s in
if let chars = s.characteristics {
chars.filter({ $0.isNotifying == true }).forEach({ c in
CBPeripheral._charsValueObservers.removeValue(forKey: c.observerKey)
self.setNotifyValue(false, for: c)
})
}
})
}
}
public func removeWriteValueBlock(_ chars: CBCharacteristic) {
removeWriteValueBlock(chars.observerKey)
}
public func writeValue(_ value: Data, for chars: CBCharacteristic, complate: BLEWriteValueComplate? = nil) {
if complate != nil {
CBPeripheral._writeValueBlocks[chars.observerKey] = complate
writeValue(value, for: chars, type: .withResponse)
} else {
writeValue(value, for: chars, type: .withoutResponse)
}
}
}
// MARK: 为了避免外部回调地狱,增加Key读写操作。Key的格式为serviceUUID/characteristicsUUID,例如:F9F7/FFF9
extension CBPeripheral {
public func removeWriteValueBlock(_ key: String) {
CBPeripheral._writeValueBlocks.removeValue(forKey: key)
}
public func findChars(for key: String, complate: @escaping BLEFindCharsComplate) {
if let rs = parserKey(key) {
findServices([CBUUID(string: rs.suuid)]) { (p, services, error) in
if error != nil {
complate(p, nil, error)
} else if let s = services?.first {
self.findChars([CBUUID(string: rs.cuuid)], for: s, complate: { (p, chars, error) in
if error != nil {
complate(p, nil, error)
} else if let list = chars, list.count > 0 {
complate(p, chars, nil)
} else {
complate(p, nil, self.error(code: .notfound, desc: "未找到对应特征"))
}
})
} else {
complate(p, nil, self.error(code: .notfound, desc: "未找到对应服务"))
}
}
} else {
complate(self, nil, error(code: .parserKeyError, desc: "传入的key格式不正确"))
}
}
public func getCharsValue(for key: String, complate: @escaping BLECharsValueBlock) {
findChars(for: key) { (p, chars, error) in
if error != nil {
complate(self, nil, error)
} else if let c = chars?.first {
self.readValue(for: c, complate: complate)
} else {
complate(self, nil, self.error(code: .notfound, desc: "未找到对应特征"))
}
}
}
public func observerValue(for key: String, change: @escaping BLECharsValueBlock) {
findChars(for: key) { (p, chars, error) in
if error != nil {
change(self, nil, error)
} else if let c = chars?.first {
let _ = self.observerValue(c, change: change)
} else {
change(p, nil, self.error(code: .notfound, desc: "未找到对应特征"))
}
}
}
public func writeValue(_ value: Data, for key: String, complate: BLEWriteValueComplate?) {
findChars(for: key) { (p, chars, error) in
if error != nil {
complate?(self, nil, error)
} else if let c = chars?.first {
self.writeValue(value, for: c, complate: complate)
} else {
complate?(p, nil, self.error(code: .notfound, desc: "未找到对应特征"))
}
}
}
}
// MARK: Private
extension CBPeripheral {
private func parserKey(_ key: String) -> (suuid: String, cuuid: String)? {
let temp = key.split(separator: "/").map({ String($0 ) })
if temp.count == 2 {
return (temp[0], temp[1])
}
return nil
}
private func error(code: BLEErrorCode, desc: String) -> Error {
return NSError(domain: "com.MRFramework.BLEPeripheralExtention", code: code.rawValue, userInfo: [NSLocalizedDescriptionKey: desc])
}
}
extension CBCharacteristic {
public var observerKey: String {
return "\(service.uuid.uuidString)/\(uuid.uuidString)"
}
public var observerable: Bool {
return properties.contains(.notify) && properties.contains(.indicate)
}
}
//
// MRDevice.swift
// MRFramework
//
// Created by Sarkizz on 2019/8/23.
// Copyright © 2019 sarkizz. All rights reserved.
//
import Foundation
import KeychainAccess
public final class MRDevice {
private static let defaultUniqueUUIDKey = "com.keychain.default.uniqueUUID"
public class var modelVersion: String {
return UIDevice.current.modelVersion
}
public class var appShortVersion: String? {
return Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
}
public class var appBuildVersion: String? {
return Bundle.main.infoDictionary?["CFBundleVersion"] as? String
}
public class var appName: String? {
return Bundle.main.infoDictionary?["CFBundleDisplayName"] as? String
}
public class var bundleID: String? {
return Bundle.main.infoDictionary?["CFBundleIdentifier"] as? String
}
public class var defaultKeychainServerName: String {
return "com.keychain.\(appName ?? "").\(bundleID ?? "")"
}
public class var uniqueUUID: String {
if let uuid = string(for: defaultUniqueUUIDKey) {
return uuid
}
let uuid = UUID().uuidString
setString(uuid, for: defaultUniqueUUIDKey)
return uuid
}
}
extension MRDevice {
public class func setString(_ string: String, for key: String, keychainServer: String = defaultKeychainServerName, group: String? = nil) {
let keychain = group == nil ? Keychain(service: keychainServer) :
Keychain(service: keychainServer, accessGroup: group!)
keychain[key] = string
}
public class func string(for key: String, keychainServer: String = defaultKeychainServerName, group: String? = nil) -> String? {
let keychain = group == nil ? Keychain(service: keychainServer) :
Keychain(service: keychainServer, accessGroup: group!)
return keychain[key]
}
}
extension MRDevice {
public class var isiPod: Bool {
return UIDevice.current.modelVersion.contains("iPod")
}
public class var isiPhone: Bool {
return UIDevice.current.modelVersion.contains("iPhone")
}
public class var isiPad: Bool {
return UIDevice.current.modelVersion.contains("iPad")
}
public class var isAppleTV: Bool {
return UIDevice.current.modelVersion.contains("AppleTV")
}
public class var isSimulator: Bool {
let mv = UIDevice.current.modelVersion
return mv.contains("i386") || mv.contains("x86_64")
}
}
extension UIDevice {
var modelVersion: String {
var systemInfo = utsname()
uname(&systemInfo)
let machineMirror = Mirror(reflecting: systemInfo.machine)
let identifier = machineMirror.children.reduce("") { identifier, element in
guard let value = element.value as? Int8, value != 0 else { return identifier }
return identifier + String(UnicodeScalar(UInt8(value)))
}
return identifier
}
}
......@@ -290,6 +290,23 @@ extension MRFiles {
return (nil, error(.readError, msg: e.localizedDescription))
}
}
public class func syncHash(of fileURL: URL) -> String? {
let rs = syncContents(of: fileURL)
if let data = rs.0 {
return data.md5
}
return nil
}
public class func asyncHash(of fileURL: URL, finish: @escaping (_ md5: String?) -> Void) {
asyncReadOperator {
let rs = syncHash(of: fileURL)
DispatchQueue.main.async {
finish(rs)
}
}
}
}
extension MRFiles {
......@@ -387,6 +404,22 @@ extension URL {
public func asyncMoveItems(to url: URL, filter: MRFilesFilterBlock? = nil, finish: MRFilesFinishBlock?) {
MRFiles.asyncMoveItems(fromDic: self, toDic: url, filter: filter, finish: finish)
}
public func syncDelete() -> Error? {
return MRFiles.syncDelete(self)
}
public func asyncDelete(_ finish: MRFilesFinishBlock?) {
MRFiles.asyncDelete(self, finish: finish)
}
public var fileContent: Data? {
return MRFiles.syncContents(of: self).0
}
public func fileContent(_ finish: @escaping (_ data: Data?, _ error: Error?) -> Void) {
MRFiles.asyncContents(of: self, finish: finish)
}
}
extension Data {
......
//
// NamespaceWrappable.swift
// MRFramework
//
// Created by Sarkizz on 2019/8/26.
// Copyright © 2019 sarkizz. All rights reserved.
//
import Foundation
/// 被包裹类型应遵循的协议
public protocol TypeWrapperProtocol {
associatedtype WrappedType
var wrappedValue: WrappedType { get }
init(value: WrappedType)
}
/// 包裹体
public struct NamespaceWrapper<T>: TypeWrapperProtocol {
public let wrappedValue: T
public init(value: T) {
self.wrappedValue = value
}
}
/// 命名空间协议
public protocol NamespaceWrappable {
associatedtype WrapperType
var mr: WrapperType { get }
static var mr: WrapperType.Type { get }
}
/// 命名空间协议默认实现
public extension NamespaceWrappable {
var mr: NamespaceWrapper<Self> {
return NamespaceWrapper(value: self)
}
static var mr: NamespaceWrapper<Self>.Type {
return NamespaceWrapper.self
}
}
......@@ -18,5 +18,7 @@
<string>1.0</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要使用相册</string>
</dict>
</plist>
//
// SAPhotoPicker.swift
// WebBridgeDemo
//
// Created by Sarkizz on 2019/8/1.
// Copyright © 2019 Maxrocky. All rights reserved.
//
import Foundation
import UIKit
public enum SAPhotoPickerType {
case all
case camera
case photo
case savedPhotosAlbum
}
public typealias SAPhotoPickerComplateBlock = (_ image: UIImage?, _ imageURL: URL?, _ error: Error?) -> Void
extension SAPhotoPicker {
//config keys
public struct ConfigKey: Hashable, Equatable, RawRepresentable {
public var rawValue: String
public init(rawValue: String) {
self.rawValue = rawValue
}
}
}
extension SAPhotoPicker.ConfigKey {
public static let cameraTitle = SAPhotoPicker.ConfigKey(rawValue: "SAPhotoPicker.cameraTitle")
public static let libraryTitle = SAPhotoPicker.ConfigKey(rawValue: "SAPhotoPicker.libraryTitle")
public static let albumTitle = SAPhotoPicker.ConfigKey(rawValue: "SAPhotoPicker.albumTitle")
}
public class SAPhotoPicker: NSObject {
public static let shared = SAPhotoPicker()
var config: [SAPhotoPicker.ConfigKey: Any] = [
.cameraTitle: "拍照",
.libraryTitle: "相册",
.albumTitle: "相册"
]
private var complateBlock: SAPhotoPickerComplateBlock?
public func show(_ type: SAPhotoPickerType = .all, from viewController: UIViewController?,
complate: SAPhotoPickerComplateBlock?) {
complateBlock = complate
switch type {
case .all:
let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
alert.addAction(UIAlertAction(title: "拍照", style: .default, handler: { _ in
self.showImagePicker(.camera)
}))
alert.addAction(UIAlertAction(title: "相册", style: .default, handler: { _ in
self.showImagePicker(.photoLibrary)
}))
alert.addAction(UIAlertAction(title: "取消", style: .cancel, handler: { _ in
complate?(nil, nil, nil)
}))
UIApplication.shared.keyWindow?.rootViewController?.present(alert, animated: true, completion: nil)
break
case .photo:
self.showImagePicker(.photoLibrary)
break
case .camera:
self.showImagePicker(.camera)
break
case .savedPhotosAlbum:
self.showImagePicker(.savedPhotosAlbum)
break
}
}
}
extension SAPhotoPicker {
private func showImagePicker(_ type: UIImagePickerController.SourceType) {
let vc = UIImagePickerController()
vc.sourceType = type
vc.delegate = self
vc.title = navTitle(with: type)
UIApplication.shared.keyWindow?.rootViewController?.present(vc, animated: true, completion: nil)
}
private func navTitle(with sourceType: UIImagePickerController.SourceType) -> String? {
switch sourceType {
case .camera:
return config[.cameraTitle] as? String
case .photoLibrary:
return config[.libraryTitle] as? String
case .savedPhotosAlbum:
return config[.albumTitle] as? String
default:
return nil
}
}
}
extension SAPhotoPicker: UINavigationControllerDelegate, UIImagePickerControllerDelegate {
public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
if let image = info[.originalImage] as? UIImage {
complateBlock?(image, info[.referenceURL] as? URL, nil)
} else {
complateBlock?(nil, nil, error(code: -1, msg: "获取图片失败"))
}
picker.dismiss(animated: true)
}
public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
complateBlock?(nil, nil, nil)
picker.dismiss(animated: true)
}
}
extension SAPhotoPicker {
private func error(code: Int, msg: String) -> Error {
return NSError(domain: "com.MRFramework.SAPhotoPicker", code: code, userInfo: [NSLocalizedDescriptionKey: msg])
}
}
//
// ToastView.swift
// MRFramework
//
// Created by Sarkizz on 2019/8/22.
// Copyright © 2019 sarkizz. All rights reserved.
//
import Foundation
import SnapKit
public class ToastView: UIView {
/// 中心点位置枚举
public enum Position {
/// 屏幕中心
case center
/// 上方
case up
/// 下方
case down
/// 自定
case custom(CGFloat)
}
// MARK: - 类属性
/// 内容背景高度
static let height: CGFloat = 190 / 2
// MARK: - 类方法
/// 显示
@discardableResult
public static func show(_ message: String, inView view: UIView? = nil, position: Position = .center) -> ToastView? {
let toast = ToastView()
switch position {
case .center:
toast.contentCenterY = UIScreen.main.bounds.height * 0.5
case .up:
toast.contentCenterY = 237.5
case .down:
toast.contentCenterY = 429.5
case .custom(let offset):
toast.contentCenterY = offset
}
guard let inView = view ?? UIApplication.shared.keyWindow else {
return nil
}
toast.show(withMessage: message, inView: inView)
return toast
}
// MARK: - 公开属性
/// 中心坐标的Y轴偏移值,默认值0:位于屏幕中心无偏移
var contentCenterY: CGFloat = UIScreen.main.bounds.height * 0.5 {
didSet {
setNeedsUpdateConstraints()
}
}
/// 内容背景
let backgroundView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.black.withAlphaComponent(0.7)
view.layer.cornerRadius = 5
view.layer.masksToBounds = true
return view
}()
/// 文字
let messageLabel: UILabel = {
let label = UILabel()
label.backgroundColor = UIColor.clear
label.font = UIFont.systemFont(ofSize: 16)
label.textColor = UIColor.white
label.textAlignment = .center
label.numberOfLines = 0
label.adjustsFontSizeToFitWidth = true
return label
}()
// MARK: - 私有属性
/// 所有当前显示中的Toast,最后加入的排在最前
private static var showingToasts = [ToastView]()
/// 动画时长
private let animationDuration = 0.25
/// 消失后回调
private var dismissCallback: (() -> Void)?
// MARK: - 生命周期
override private init(frame: CGRect) {
super.init(frame: frame)
addSubview(backgroundView)
backgroundView.addSubview(messageLabel)
backgroundView.snp.makeConstraints { (make) in
make.height.equalTo(ToastView.height)
make.width.equalTo(452 / 2)
make.centerX.equalToSuperview()
make.centerY.equalTo(contentCenterY)
}
messageLabel.snp.makeConstraints { (make) in
make.left.equalToSuperview().offset(10)
make.right.equalToSuperview().offset(-10)
make.top.equalToSuperview().offset(10)
make.bottom.equalToSuperview().offset(-10)
}
isUserInteractionEnabled = true
let tap = UITapGestureRecognizer(target: self, action: #selector(spaceDidTap))
addGestureRecognizer(tap)
let pan = UIPanGestureRecognizer(target: self, action: #selector(spaceDidTap))
addGestureRecognizer(pan)
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override public func updateConstraints() {
super.updateConstraints()
backgroundView.snp.updateConstraints { (make) in
make.centerY.equalTo(contentCenterY)
}
}
// MARK: - 公开方法
/// 显示
public func show(withMessage message: String, inView view: UIView) {
messageLabel.text = message
// 如果与最近一个message相同,则不显示
if isEqualToLastMessage() {
return
}
// 若需要移动已存在toast,则等待其移动完成后再显示本toast
// 否则有前面的toast待移动
if moveShowingToasts() {
// 等待其移动完毕再显示本toast
DispatchQueue.main.asyncAfter(deadline: .now() + animationDuration, execute: {
self._show(inView: view)
})
} else {
_show(inView: view)
}
}
/// 消失
public func dismiss(animated: Bool) {
if animated {
UIView.animate(withDuration: animationDuration, animations: {
self.backgroundView.alpha = 0
}, completion: { _ in
self.removeSelf()
})
} else {
self.removeSelf()
}
}
/// 消失后,回调
public func addCallbackWhenDismissCompleted(_ callback: @escaping () -> Void) {
dismissCallback = callback
}
// MARK: - 内部方法
/// 显示
@objc private func _show(inView view: UIView) {
guard let message = self.messageLabel.text else { return }
view.addSubview(self)
ToastView.showingToasts.insert(self, at: 0)
self.snp.makeConstraints { (make) in
make.edges.equalToSuperview()
}
self.backgroundView.alpha = 0
UIView.animate(withDuration: animationDuration, animations: {
self.backgroundView.alpha = 1
}) { _ in
// 根据message长度计算消失延时时间,单位毫秒,最大6秒
let delay: Int = min(300 + message.count * 150, 6 * 1000)
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(delay), execute: {
self.dismiss(animated: true)
})
}
}
/// 点空白
@objc private func spaceDidTap() {
dismiss(animated: true)
}
/// 把当前显示中的Toast向屏幕上方移动,若不需要移动则返回false,若有移动则返回true
private func moveShowingToasts() -> Bool {
if ToastView.showingToasts.count <= 0 {
return false
}
for (index, toast) in ToastView.showingToasts.enumerated() {
// 所遍历的toast应调整到的位置
let position = contentCenterY - (CGFloat(index + 1) * (10 + ToastView.height))
// 如果此toast位置合格,则无需移动
if toast.contentCenterY < (position - ToastView.height / 2.0) {
continue
}
if toast.contentCenterY < (position - ToastView.height / 2.0) {
continue
}
toast.backgroundView.snp.updateConstraints({ (make) in
make.centerY.equalTo(position)
})
UIView.animate(withDuration: animationDuration, animations: {
toast.layoutIfNeeded()
})
}
return true
}
/// 从视图栈移除
private func removeSelf() {
self.snp.removeConstraints()
self.removeFromSuperview()
if let index = ToastView.showingToasts.firstIndex(of: self) {
ToastView.showingToasts.remove(at: index)
}
if dismissCallback != nil {
dismissCallback!()
dismissCallback = nil
}
}
/// 判断是否与最近一个message相同
private func isEqualToLastMessage() -> Bool {
if ToastView.showingToasts.count <= 0 {
return false
}
guard let element = ToastView.showingToasts.first else {
return false
}
return element.messageLabel.text == self.messageLabel.text
}
}
// MARK: - 扩展String
extension String {
/// toast自身
@discardableResult
public func toast(inView view: UIView? = nil, position: ToastView.Position = .center) -> ToastView? {
return ToastView.show(self, inView: view, position: position)
}
}
//
// MRWebBridge.swift
// WebBridgeDemo
//
// Created by Sarkizz on 2019/6/18.
// Copyright © 2019 Maxrocky. All rights reserved.
//
import WebKit
public enum MRRemoveJSHandlerType {
case all
case name(_ name: String)
}
@available(iOS 11.0, *)
public enum MRRemoveRuleListType {
case all
case one(_ ruleList: WKContentRuleList)
}
public protocol MRWebBridge: class {}
extension MRWebBridge where Self: WKWebView {
public static var `default`: WKWebView {
let wk = WKWebView(frame: CGRect.zero, configuration: defaultConfig)
return wk
}
public static func webView(_ config: (_ webConfig: WKWebViewConfiguration) -> Void) -> Self {
let webConfig = WKWebViewConfiguration()
config(webConfig)
let wk = Self(frame: CGRect.zero, configuration: webConfig)
return wk
}
}
extension MRWebBridge where Self: WKWebView {
public func add(_ jsHandler: @escaping MRWebMessageHandlerBlock, for name: String) {
let handler = MRWebMessageHandler(jsHandler)
configuration.userContentController.add(handler, name: name)
messageHandlers.add(name)
}
public func add(handler: @escaping MRWebMessageBlock, for name: String) {
let handler = MRWebMessage(handler)
configuration.userContentController.add(handler, name: name)
messageHandlers.add(name)
}
public func removeJSHandler(with type: MRRemoveJSHandlerType = .all) {
switch type {
case .all:
messageHandlers.forEach({
if let name = $0 as? String {
configuration.userContentController.removeScriptMessageHandler(forName: name)
}
})
break
case .name(let n):
configuration.userContentController.removeScriptMessageHandler(forName: n)
break
}
}
@available(iOS 11.0, *)
public func addRuleList(_ ruleList: WKContentRuleList) {
configuration.userContentController.add(ruleList)
}
@available(iOS 11.0, *)
public func removeRuleList(with type: MRRemoveRuleListType = .all) {
switch type {
case .all:
configuration.userContentController.removeAllContentRuleLists()
break
case .one(let ruleList):
configuration.userContentController.remove(ruleList)
break
}
}
}
extension MRWebBridge {
private static var defaultConfig: WKWebViewConfiguration {
let config = WKWebViewConfiguration()
return config
}
private var messageHandlers: NSMutableArray {
guard let list = objc_getAssociatedObject(self, "mr_message_handlers") as? NSMutableArray else {
let list = NSMutableArray()
objc_setAssociatedObject(self, "mr_message_handlers", list, .OBJC_ASSOCIATION_RETAIN)
return list
}
return list
}
}
extension WKWebView: MRWebBridge {}
extension WKWebViewConfiguration {
public func addStartScript(_ script: String, forMainFrameOnly: Bool = false) {
let script = WKUserScript(source: script,
injectionTime: .atDocumentStart,
forMainFrameOnly: forMainFrameOnly)
add(script)
}
public func addEndScript(_ script: String, forMainFrameOnly: Bool = false) {
let script = WKUserScript(source: script,
injectionTime: .atDocumentEnd,
forMainFrameOnly: forMainFrameOnly)
add(script)
}
public func add(_ jsScript: WKUserScript) {
userContentController.addUserScript(jsScript)
}
public func removeAllUserScripts() {
userContentController.removeAllUserScripts()
}
/// 使用JS注入cookie。在webView初始化之前设置,这个最好配合NSMutableURLRequest同时设置Cookie
///
/// - Parameter cookie: Cookie内容
public func setCookie(_ cookie: [String: String]) {
let prefix = "document.cookie = "
let s = cookie.cookie({ "\(prefix)'\($0)'" })
addStartScript(s)
}
}
//
// MRWebMessageHandler.swift
// WebBridgeDemo
//
// Created by Sarkizz on 2019/6/21.
// Copyright © 2019 Maxrocky. All rights reserved.
//
import Foundation
import WebKit
public typealias MRWebMessageHandlerBlock = (_ userContentController: WKUserContentController, _ message: WKScriptMessage) -> Void
public typealias MRWebMessageBlock = (_ body: Any) -> Void
public class MRWebMessageHandler: NSObject, WKScriptMessageHandler {
private var handler: MRWebMessageHandlerBlock
public init(_ handler: @escaping MRWebMessageHandlerBlock) {
self.handler = handler
}
public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
handler(userContentController, message)
}
}
public class MRWebMessage: NSObject, WKScriptMessageHandler {
private var handler: MRWebMessageBlock
public init(_ handler: @escaping MRWebMessageBlock) {
self.handler = handler
}
public func userContentController(_ userContentController: WKUserContentController, didReceive message: WKScriptMessage) {
handler(message.body)
}
}
//
// DeviceInfoTest.swift
// MRFrameworkTests
//
// Created by Sarkizz on 2019/8/23.
// Copyright © 2019 sarkizz. All rights reserved.
//
import XCTest
import MRFramework
class DeviceInfoTest: XCTestCase {
override func setUp() {
// Put setup code here. This method is called before the invocation of each test method in the class.
}
override func tearDown() {
// Put teardown code here. This method is called after the invocation of each test method in the class.
}
}
extension DeviceInfoTest {
func testDeviceModelVersion() {
let model = UIDevice.current.modelVersion
XCTAssert(model == "x86_64", "这都能为空?")
}
}
......@@ -18,5 +18,7 @@
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要使用相册</string>
</dict>
</plist>
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
target 'MRFramework' do
# Comment the next line if you're not using Swift and don't want to use dynamic frameworks
use_frameworks!
pod 'SnapKit', '4.2.0', :inhibit_warnings => true
pod 'KeychainAccess', '3.2.0'
# Pods for MRFramework
target 'MRFrameworkTests' do
inherit! :search_paths
# Pods for testing
end
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment