PS:Xcode 版本是 Version 9.2 (9C40b),編譯 Swift 版本是:3.2
NSObject+ClassName
功能:獲取某個物件或者某個類的類名字串(比如 xib 載入)
extension NSObject {
/// 返回類名字串
static var className: String {
return String(describing: self)
}
/// 返回類名字串
var className: String {
return String(describing: type(of: self))
}
}
複製程式碼
String+BoundingRect
功能:計算字串在 label 上的寬高(比如 cell 自適應高度)
extension String {
/// 給定最大寬計算高度,傳入字型、行距、對齊方式(便捷呼叫)
func heightForLabel(width: CGFloat, font: UIFont, lineSpacing: CGFloat = 5, alignment: NSTextAlignment = .left) -> CGFloat {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = lineSpacing
paragraphStyle.alignment = alignment
let attributes: [String : Any] = [
NSFontAttributeName: font,
NSParagraphStyleAttributeName: paragraphStyle
]
let textSize = textSizeForLabel(width: width, height: CGFloat(Float.greatestFiniteMagnitude), attributes: attributes)
return textSize.height
}
/// 給定最大寬計算高度,傳入屬性字典(便捷呼叫)
func heightForLabel(width: CGFloat, attributes: [String: Any]) -> CGFloat {
let textSize = textSizeForLabel(width: width, height: CGFloat(Float.greatestFiniteMagnitude), attributes: attributes)
return textSize.height
}
/// 給定最大高計算寬度,傳入字型(便捷呼叫)
func widthForLabel(height: CGFloat, font: UIFont) -> CGFloat {
let labelTextAttributes = [NSFontAttributeName: font]
let textSize = textSizeForLabel(width: CGFloat(Float.greatestFiniteMagnitude), height: height, attributes: labelTextAttributes)
return textSize.width
}
/// 給定最大高計算寬度,傳入屬性字典(便捷呼叫)
func widthForLabel(height: CGFloat, attributes: [String: Any]) -> CGFloat {
let textSize = textSizeForLabel(width: CGFloat(Float.greatestFiniteMagnitude), height: height, attributes: attributes)
return textSize.width
}
/// 給定最大寬高計算寬度和高度,傳入字型、行距、對齊方式(便捷呼叫)
func textSizeForLabel(width: CGFloat, height: CGFloat, font: UIFont, lineSpacing: CGFloat = 5, alignment: NSTextAlignment = .left) -> CGSize {
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.lineSpacing = lineSpacing
paragraphStyle.alignment = alignment
let attributes: [String : Any] = [
NSFontAttributeName: font,
NSParagraphStyleAttributeName: paragraphStyle
]
let textSize = textSizeForLabel(width: width, height: height, attributes: attributes)
return textSize
}
/// 給定最大寬高計算寬度和高度,傳入屬性字典(便捷呼叫)
func textSizeForLabel(size: CGSize, attributes: [String: Any]) -> CGSize {
let textSize = textSizeForLabel(width: size.width, height: size.height, attributes: attributes)
return textSize
}
/// 給定最大寬高計算寬度和高度,傳入屬性字典(核心)
func textSizeForLabel(width: CGFloat, height: CGFloat, attributes: [String: Any]) -> CGSize {
let defaultOptions: NSStringDrawingOptions = [.usesLineFragmentOrigin, .usesFontLeading]
let maxSize = CGSize(width: width, height: height)
let rect = self.boundingRect(with: maxSize, options: defaultOptions, attributes: attributes, context: nil)
let textWidth: CGFloat = CGFloat(Int(rect.width) + 1)
let textHeight: CGFloat = CGFloat(Int(rect.height) + 1)
return CGSize(width: textWidth, height: textHeight)
}
}
extension NSAttributedString {
/// 根據最大寬計算高度(便捷呼叫)
func heightForLabel(width: CGFloat) -> CGFloat {
let textSize = textSizeForLabel(width: width, height: CGFloat(Float.greatestFiniteMagnitude))
return textSize.height
}
/// 根據最大高計算寬度(便捷呼叫)
func widthForLabel(height: CGFloat) -> CGFloat {
let textSize = textSizeForLabel(width: CGFloat(Float.greatestFiniteMagnitude), height: height)
return textSize.width
}
/// 計算寬度和高度(核心)
func textSizeForLabel(width: CGFloat, height: CGFloat) -> CGSize {
let defaultOptions: NSStringDrawingOptions = [.usesLineFragmentOrigin, .usesFontLeading]
let maxSize = CGSize(width: width, height: height)
let rect = self.boundingRect(with: maxSize, options: defaultOptions, context: nil)
let textWidth: CGFloat = CGFloat(Int(rect.width) + 1)
let textHeight: CGFloat = CGFloat(Int(rect.height) + 1)
return CGSize(width: textWidth, height: textHeight)
}
}
複製程式碼
String+RegularExpression
功能:主要是簡化和統一外部使用正規表示式
extension String {
/// 通過正規表示式匹配替換
func replacingStringOfRegularExpression(pattern: String, template: String) -> String {
var content = self
do {
let range = NSRange(location: 0, length: content.count)
let expression = try NSRegularExpression(pattern: pattern, options: .caseInsensitive)
content = expression.stringByReplacingMatches(in: content, options: .reportCompletion, range: range, withTemplate: template)
} catch {
print("regular expression error")
}
return content
}
/// 通過正規表示式匹配返回結果
func matches(pattern: String) -> [NSTextCheckingResult] {
do {
let range = NSRange(location: 0, length: count)
let expression = try NSRegularExpression(pattern: pattern, options: .caseInsensitive)
let matchResults = expression.matches(in: self, options: .reportCompletion, range: range)
return matchResults
} catch {
print("regular expression error")
}
return []
}
/// 通過正規表示式返回第一個匹配結果
func firstMatch(pattern: String) -> NSTextCheckingResult? {
do {
let range = NSRange(location: 0, length: count)
let expression = try NSRegularExpression(pattern: pattern, options: .caseInsensitive)
let match = expression.firstMatch(in: self, options: .reportCompletion, range: range)
return match
} catch {
print("regular expression error")
}
return nil
}
}
複製程式碼
String+Substr
功能:字串擷取快捷方法,你懂的(⊙o⊙)…
extension String {
/// 尋找在 startString 和 endString 之間的字串
func substring(between startString: String, and endString: String?, options: String.CompareOptions = .caseInsensitive) -> String? {
let range = self.range(of: startString, options: options)
if let startIndex = range?.upperBound {
let string = self.substring(from: startIndex)
if let endString = endString {
let range = string.range(of: endString, options: options)
if let startIndex = range?.lowerBound {
return string.substring(to: startIndex)
}
}
return string
}
return nil
}
/// 尋找 prefix 字串,並返回從 prefix 到尾部的字串
func substring(prefix: String, options: String.CompareOptions = .caseInsensitive, isContain: Bool = true) -> String? {
let range = self.range(of: prefix, options: options)
if let startIndex = range?.upperBound {
var resultString = self.substring(from: startIndex)
if isContain {
resultString = "\(prefix)\(resultString)"
}
return resultString
}
return nil
}
/// 尋找 suffix 字串,並返回從頭部到 suffix 位置的字串
func substring(suffix: String, options: String.CompareOptions = .caseInsensitive, isContain: Bool = false) -> String? {
let range = self.range(of: suffix, options: options)
if let startIndex = range?.lowerBound {
var resultString = self.substring(to: startIndex)
if isContain {
resultString = "\(resultString)\(suffix)"
}
return resultString
}
return nil
}
/// 從 N 位置到尾位置的字串
func substring(from: IndexDistance) -> String? {
let index = self.index(self.startIndex, offsetBy: from)
return self.substring(from: index)
}
/// 從頭位置到 N 位置的字串
func substring(to: IndexDistance) -> String? {
let index = self.index(self.startIndex, offsetBy: to)
return self.substring(to: index)
}
/// 以 lower 為起點,偏移 range 得到的字串
func substring(_ lower: IndexDistance, _ range: IndexDistance) -> String? {
let lowerIndex = self.index(self.startIndex, offsetBy: lower)
let upperIndex = self.index(lowerIndex, offsetBy: range)
let range = Range(uncheckedBounds: (lowerIndex, upperIndex))
return self.substring(with: range)
}
}
複製程式碼
UIFont+Convenience
功能:除了簡化自定義字型的引入外,這裡可以統一管理字型大小,比如對字型大小進行統一螢幕適配
enum FontWeight: String {
case light = "Light"
case regular = "Regular"
case medium = "Medium"
case semibold = "Semibold"
case bold = "Bold"
case heavy = "Heavy"
}
enum FontType: String {
case PingFangSC = "PingFangSC"
case SFProText = "SFProText"
}
extension UIFont {
static func heavyFont(ofSize fontSize: CGFloat, type: FontType = .PingFangSC) -> UIFont {
return customFont(type, weight: .heavy, fontSize: fontSize)
}
static func regularFont(ofSize fontSize: CGFloat, type: FontType = .PingFangSC) -> UIFont {
return customFont(type, weight: .regular, fontSize: fontSize)
}
static func boldFont(ofSize fontSize: CGFloat, type: FontType = .PingFangSC) -> UIFont {
return customFont(type, weight: .bold, fontSize: fontSize)
}
static func lightFont(ofSize fontSize: CGFloat, type: FontType = .PingFangSC) -> UIFont {
return customFont(type, weight: .light, fontSize: fontSize)
}
static func mediumFont(ofSize fontSize: CGFloat, type: FontType = .PingFangSC) -> UIFont {
return customFont(type, weight: .medium, fontSize: fontSize)
}
static func semiboldFont(ofSize fontSize: CGFloat, type: FontType = .PingFangSC) -> UIFont {
return customFont(type, weight: .semibold, fontSize: fontSize)
}
/// 自定義字型
static func customFont(_ type: FontType, weight: FontWeight, fontSize: CGFloat) -> UIFont {
let realFontSize = fontSize
if let customFont = UIFont(name: "\(type.rawValue)-\(weight.rawValue)", size: realFontSize) {
return customFont
}
if #available(iOS 8.2, *) {
var systemWeight: CGFloat = UIFontWeightRegular
switch weight {
case .light:
systemWeight = UIFontWeightLight
case .regular:
systemWeight = UIFontWeightRegular
case .medium:
systemWeight = UIFontWeightMedium
case .semibold:
systemWeight = UIFontWeightSemibold
case .bold:
systemWeight = UIFontWeightBold
case .heavy:
systemWeight = UIFontWeightHeavy
}
return UIFont.systemFont(ofSize: realFontSize, weight: systemWeight)
} else {
return UIFont.systemFont(ofSize: realFontSize)
}
}
}
複製程式碼
UIView+Convenience
功能:一些常用的檢視操作,比如 xib 載入、截圖、找響應控制器
extension UIView {
/// 從 xib 中載入檢視
func loadViewFromNib(index: Int = 0) -> UIView? {
let classInstance = type(of: self)
let nibName = classInstance.className
let nib = UINib(nibName: nibName, bundle: nil)
if let views = nib.instantiate(withOwner: self, options: nil) as? [UIView] {
return views.safeIndex(index)
}
return nil
}
/// 尋找當前檢視所在的控制器
var responderController: UIViewController? {
var nextReponder: UIResponder? = self.next
while nextReponder != nil {
if let viewController = nextReponder as? UIViewController {
return viewController
}
nextReponder = nextReponder?.next
}
return nil
}
/// 生成檢視的截圖
func displayViewToImage() -> UIImage? {
UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0)
if let context = UIGraphicsGetCurrentContext() {
self.layer.render(in: context)
}
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
}
複製程式碼
CollectionView+Convenience
功能:主要是為了簡化 CollectionView 註冊和從緩衝池取控制元件的程式碼
extension UICollectionView {
/// 批量註冊 Cell
func registerForCells<T: UICollectionReusableView>(_ cellClasses: [T.Type], isNib: Bool = true) {
cellClasses.forEach { cellClass in
registerForCell(cellClass, isNib: isNib)
}
}
/// 註冊 Cell
func registerForCell<T: UICollectionReusableView>(_ cellClass: T.Type, identifier: String? = nil, isNib: Bool = true) {
let nibName = cellClass.className
let cellIdentifier = identifier ?? nibName
if isNib {
self.register(UINib(nibName: nibName, bundle: nil), forCellWithReuseIdentifier: cellIdentifier)
} else {
self.register(cellClass, forCellWithReuseIdentifier: cellIdentifier)
}
}
/// 註冊頂部檢視
func registerForHeader<T: UICollectionReusableView>(_ cellClass: T.Type, identifier: String? = nil, isNib: Bool = true) {
let nibName = cellClass.className
let headerIdentifier = identifier ?? nibName
if isNib {
self.register(UINib(nibName: nibName, bundle: nil), forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: headerIdentifier)
} else {
self.register(cellClass, forSupplementaryViewOfKind: UICollectionElementKindSectionHeader, withReuseIdentifier: headerIdentifier)
}
}
/// 註冊底部檢視
func registerForFooter<T: UICollectionReusableView>(_ cellClass: T.Type, identifier: String? = nil, isNib: Bool = true) {
let nibName = cellClass.className
let footerIdentifier = identifier ?? nibName
if isNib {
self.register(UINib(nibName: nibName, bundle: nil), forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier: footerIdentifier)
} else {
self.register(cellClass, forSupplementaryViewOfKind: UICollectionElementKindSectionFooter, withReuseIdentifier: footerIdentifier)
}
}
/// 從快取池取出 Cell
func dequeueCell<T: UICollectionReusableView>(_ cellClass: T.Type, reuseIdentifier: String? = nil, indexPath: IndexPath) -> T {
let identifier: String = reuseIdentifier ?? cellClass.className
if let cell = dequeueReusableCell(withReuseIdentifier: identifier, for: indexPath) as? T {
return cell
} else {
return T()
}
}
/// 從快取池取出頂部或者底部實體
func dequeueSupplementaryView<T: UICollectionReusableView>(_ viewClass: T.Type, kind: String, indexPath: IndexPath) -> T {
let identifier = viewClass.className
if let cell = dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: identifier, for: indexPath) as? T {
return cell
} else {
return T()
}
}
/// 滑動到第一個 Cell 位置,通過增加判斷,防止奔潰
func scrollToFirstCell(animated: Bool = true) {
guard self.numberOfSections > 0 else { return }
guard let count = self.dataSource?.collectionView(self, numberOfItemsInSection: 0) else { return }
if count > 0 {
if let flowLayout = self.collectionViewLayout as? UICollectionViewFlowLayout {
if flowLayout.scrollDirection == .horizontal {
scrollToItem(at: IndexPath(row: 0, section: 0), at: .left, animated: animated)
} else {
scrollToItem(at: IndexPath(row: 0, section: 0), at: .top, animated: animated)
}
}
}
}
}
複製程式碼
Array+Convenience
功能:陣列便捷操作,其中 safeIndex 是為了防止陣列越界,雙邊遍歷的需求是為了優化顯示圖片列表載入,從使用者當前看到的圖片開始向兩邊載入
extension Array {
/// 獲取陣列中的元素,增加了陣列越界的判斷
func safeIndex(_ i: Int) -> Array.Iterator.Element? {
guard !isEmpty && self.count > abs(i) else {
return nil
}
for item in self.enumerated() {
if item.offset == I {
return item.element
}
}
return nil
}
/// 從前面取 N 個陣列元素
func limit(_ limitCount: Int) -> [Array.Iterator.Element] {
let maxCount = self.count
var resultCount: Int = limitCount
if maxCount < limitCount {
resultCount = maxCount
}
if resultCount <= 0 {
return []
}
return self[0..<resultCount].map { $0 }
}
/// 填充陣列數量到 N
func full(_ fullCount: Int) -> [Array.Iterator.Element] {
var items = self
while items.count > 0 && items.count < fullCount {
items = (items + items).limit(fullCount)
}
return items.limit(fullCount)
}
/// 雙邊遍歷,從中間向兩邊進行遍歷
func bilateralEnumerated(_ beginIndex: Int, handler: (Int, Array.Iterator.Element) -> Void) {
let arrayCount: Int = self.count
var leftIndex: Int = Swift.max(0, Swift.min(beginIndex, arrayCount - 1))
var rightIndex: Int = leftIndex + 1
var currentIndex: Int = leftIndex
var isLeftEnable: Bool = leftIndex >= 0 && leftIndex < arrayCount
var isRightEnable: Bool = rightIndex >= 0 && rightIndex < arrayCount
var isLeft: Bool = isLeftEnable ? true : isRightEnable
while isLeftEnable || isRightEnable {
currentIndex = isLeft ? leftIndex : rightIndex
if let element = self.safeIndex(currentIndex) {
handler(currentIndex, element)
}
if isLeft {
leftIndex -= 1
} else {
rightIndex += 1
}
isLeftEnable = leftIndex >= 0 && leftIndex < arrayCount
isRightEnable = rightIndex >= 0 && rightIndex < arrayCount
if isLeftEnable && !isRightEnable {
isLeft = true
} else if !isLeftEnable && isRightEnable {
isLeft = false
} else if isLeftEnable && isRightEnable {
isLeft = !isLeft
}
}
}
}
複製程式碼
NSDictionary+Convenience
功能:簡化從字典中取值的程式碼,並支援多鍵查詢
extension NSDictionary {
// MARK: - 以下都是從字典裡取值的快捷方法,支援多鍵查詢和預設返回
func bool(_ keys: String..., defaultValue: Bool = false) -> Bool {
return valueForKeys(keys, type: Bool.self) ?? defaultValue
}
func double(_ keys: String..., defaultValue: Double = 0.0) -> Double {
return valueForKeys(keys, type: Double.self) ?? defaultValue
}
func int(_ keys: String..., defaultValue: Int = 0) -> Int {
return valueForKeys(keys, type: Int.self) ?? defaultValue
}
func string(_ keys: String..., defaultValue: String? = nil) -> String? {
return valueForKeys(keys, type: String.self) ?? defaultValue
}
func dictionary(_ keys: String..., defaultValue: NSDictionary? = nil) -> NSDictionary? {
return valueForKeys(keys, type: NSDictionary.self) ?? defaultValue
}
func array<T>(_ keys: String..., type: T.Type, defaultValue: [T] = []) -> [T] {
return valueForKeys(keys, type: Array<T>.self) ?? defaultValue
}
// MARK: - 以下是從字典裡取值的核心方法,支援多鍵查詢
fileprivate func valueForKeys<T>(_ keys: [String], type: T.Type) -> T? {
for key in keys {
if let result = self[key] as? T {
return result
}
}
return nil
}
}
複製程式碼
Demo 原始碼在這:02-CommonExtensionDemo
這些分類方法都是我在專案中比較常用的,能簡化很多程式碼,希望你們喜歡!O(∩_∩)O哈哈~