美文网首页iOS开发iOS干货程序员
Swift 项目总结 02 - 常用分类方法

Swift 项目总结 02 - 常用分类方法

作者: 执着丶执念 | 来源:发表于2018-03-25 15:54 被阅读111次

    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哈哈~

    相关文章

      网友评论

      本文标题:Swift 项目总结 02 - 常用分类方法

      本文链接:https://www.haomeiwen.com/subject/koehcftx.html