让代码更整洁的24个Swift扩展

原文: Medium: 24 Swift Extensions for Cleaner Code

更高效地构建你的移动应用程序。

在我看来,Swift 和 Objective-C 最好的功能之一就是扩展(extension)。它们使你能够不必通过继承或者覆写,就可以在任何类中添加新的方法,并且可以在整个项目中使用。

作为一名移动开发者,我同时从事 iOS 和 Android 的开发工作,我经常看到 Android 的功能和方法比 Swift 中的更简短、清晰、易懂。利用扩展,其中的一些方法可以移植到 Swift 中。通过这些新的(扩展)方法,就可以让 Swift 拥有简短、干净、易维护的代码。

我会偏向于使用 Swift 编程,但是这些扩展也可以移植到 Objective-C 中,或者直接和 Objective-C 一起使用,不需要转换。

String.trim() 和 Swift.trimmed

在 99% 的情况中,当我在 Swift 中裁剪 String 类型的字符串时,我想去掉空格和其他类似的符号(例如,换行和制表符)。

这个简单的扩展就能实现:

swift 复制代码
import Foundation

extension String {
    var trimmed: String {
        self.trimmingCharacters(in: .whitespacesAndNewlines)
    }
    
    mutating func trim() {
        self = self.trimmed
    }
}

用法:

swift 复制代码
var str1 = "  a b c d e   \n"
var str2 = str1.trimmed
str1.trim() // a b c d e

Int.toDouble() 和 Double.toInt()

如果你的工作涉及到 optionals 可选值,这些方法可能会很有用。如果你有非可选的 Int,你可以用 Double(a) 转换它,其中 a 是一个整数变量。但是如果 a 是可选值,你就没法这样做。

让我们为 IntDouble 添加扩展。

swift 复制代码
import Foundation

extension Int {
    func toDouble() -> Double {
        Double(self)
    }
}

extension Double {
    func toInt() -> Int {
        Int(self)
    }
}

用法:

swift 复制代码
let a = 15.78
let b = a.toInt()

String.toDate(...) 和 Date.toString(...)

String 中获取 Date 日期和格式化 Date 日期以显示它或发送到 API 是常见的任务。标准的转换方式需要三行代码。让我们看看如何使其更短。

swift 复制代码
import Foundation

extension String {
    func toDate(format: String) -> Date? {
        let df = DateFormatter()
        df.dateFormat = format
        return df.date(from: self)
    }
}

extension Date {
    func toString(format: String) -> String {
        let df = DateFormatter()
        df.dateFormat = format
        return df.string(from: self)
    }
}

用法:

swift 复制代码
let strDate = "2020-08-10 15:00:00"
let date = strDate.toDate(format: "yyyy-MM-dd HH:mm:ss")
let strDate2 = date?.toString(format: "yyyy-MM-dd HH:mm:ss")

Int.centsToDollars()

一些支付API(例如 Stripe)喜欢使用货币单位(美分)进行支付处理。它可以避免 FloatDouble 的不精确性。同时,使用这些类型来显示数值会更舒服。

这个扩展可以实现这种转换:

swift 复制代码
import Foundation

extension Int {
    // 美分转换为美元
    func centsToDollars() -> Double {
        Double(self) / 100
    }
}

用法:

swift 复制代码
let cents = 12350
let dollars = cents.centsToDollars()

String.asCoordinates()

一个地点在地球上的坐标至少需要两个数字--纬度和经度。另一个是海拔高度,但这只有在三维空间中才有意义,这在软件开发中并不常见。

从 API 中我们可以得到两个独立的字段,或者一个字段的逗号分隔的值。这个扩展允许将这些字符串转换为 CLLocationCoordinate2D

swift 复制代码
import Foundation
import CoreLocation

extension String {
    var asCoordinates: CLLocationCoordinate2D? {
        let components = self.components(separatedBy: ",")
        if components.count != 2 { return nil }
        let strLat = components[0].trimmed
        let strLng = components[1].trimmed
        if let dLat = Double(strLat),
            let dLng = Double(strLng) {
            return CLLocationCoordinate2D(latitude: dLat, longitude: dLng)
        }
        return nil
    }
}

用法:

swift 复制代码
let strCoordinates = "41.6168, 41.6367"
let coordinates = strCoordinates.asCoordinates

String.asURL()

iOS 和 macOS 使用 URL 类型来处理链接。它更灵活,它允许获取组件,它可以处理不同类型的 URLs。同时,我们通常会输入它或从 API 字符串 String 中获取它。

将一种类型转换为另一种类型很容易,但这个扩展允许我们处理可选类型或链式转换。

swift 复制代码
import Foundation

extension String {
    var asURL: URL? {
        URL(string: self)
    }
}

用法:

swift 复制代码
let strUrl = "https://medium.com"
let url = strUrl.asURL

UIDevice.vibrate()

iPhone 振动可以成为一种很酷的效果,用于设备的按钮点击和其他反馈。对于 iPhone 振动有一种特殊的声音,由 AudioToolbox 框架处理。

将 AudioToolbox 加入到所有带有振动的 UIViewControllers 中是很烦人的,而且从逻辑上讲,振动更多的是一种设备功能(它不是来自扬声器而是来自设备本身),而不是播放声音。这个扩展可以将其简化为一行代码。

swift 复制代码
import UIKit
import AudioToolbox

extension UIDevice {
    static func vibrate() {
        AudioServicesPlaySystemSound(1519)
    }
}

用法:

swift 复制代码
UIDevice.vibrate()

String.width(...) 和 String.height(...)

iOS 可以使用提供的约束条件自动计算 UILabel 的大小,但有时自己设置大小很重要。

这个扩展允许我们使用提供的 UIFont 来计算字符串的宽度和高度。

swift 复制代码
import UIKit

extension String {
    func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
        let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
        let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil)

        return ceil(boundingBox.height)
    }

    func width(withConstrainedHeight height: CGFloat, font: UIFont) -> CGFloat {
        let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
        let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil)

        return ceil(boundingBox.width)
    }
}

extension NSAttributedString {
    func height(withConstrainedWidth width: CGFloat) -> CGFloat {
        let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
        let boundingBox = boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, context: nil)

        return ceil(boundingBox.height)
    }

    func width(withConstrainedHeight height: CGFloat) -> CGFloat {
        let constraintRect = CGSize(width: .greatestFiniteMagnitude, height: height)
        let boundingBox = boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, context: nil)

        return ceil(boundingBox.width)
    }
}

用法:

swift 复制代码
let text = "Hello, world!"
let textHeight = text.height(withConstrainedWidth: 100, font: UIFont.systemFont(ofSize: 16))

String.containsOnlyDigits

当你需要限制用户输入或验证来自 API 的数据时,下面的扩展是非常有用的。它检查字符串是否只包含数字。

swift 复制代码
import Foundation

extension String {
    var containsOnlyDigits: Bool {
        let notDigits = NSCharacterSet.decimalDigits.inverted
        return rangeOfCharacter(from: notDigits, options: String.CompareOptions.literal, range: nil) == nil
    }
}

用法:

swift 复制代码
let digitsOnlyYes = "1234567890".containsOnlyDigits
let digitsOnlyNo = "12345+789".containsOnlyDigits

String.isAlphanumeric

和之前的扩展一样,这个扩展检查 String 的内容,如果字符串不是空的并且只包含字母数字字符,则返回 true。这个扩展的倒置版本可以用来确认密码是否含有非字母数字字符。

swift 复制代码
import Foundation

extension String {
    var isAlphanumeric: Bool {
        !isEmpty && range(of: "[^a-zA-Z0-9]", options: .regularExpression) == nil
    }
}

用法:

swift 复制代码
let alphanumericYes = "asd3kJh43saf".isAlphanumeric
let alphanumericNo = "Kkncs+_s3mM.".isAlphanumeric

字符串下标

Swift 5 有一种可怕的下标字符串的方式。例如,如果你想获得从5到10的字符,计算索引和偏移量是很麻烦的。这个扩展允许使用简单的 Ints 类型来实现这个目的。

swift 复制代码
import Foundation

extension String {
    subscript (i: Int) -> Character {
        return self[index(startIndex, offsetBy: i)]
    }
    
    subscript (bounds: CountableRange<Int>) -> Substring {
        let start = index(startIndex, offsetBy: bounds.lowerBound)
        let end = index(startIndex, offsetBy: bounds.upperBound)
        if end < start { return "" }
        return self[start..<end]
    }
    
    subscript (bounds: CountableClosedRange<Int>) -> Substring {
        let start = index(startIndex, offsetBy: bounds.lowerBound)
        let end = index(startIndex, offsetBy: bounds.upperBound)
        if end < start { return "" }
        return self[start...end]
    }
    
    subscript (bounds: CountablePartialRangeFrom<Int>) -> Substring {
        let start = index(startIndex, offsetBy: bounds.lowerBound)
        let end = index(endIndex, offsetBy: -1)
        if end < start { return "" }
        return self[start...end]
    }
    
    subscript (bounds: PartialRangeThrough<Int>) -> Substring {
        let end = index(startIndex, offsetBy: bounds.upperBound)
        if end < startIndex { return "" }
        return self[startIndex...end]
    }
    
    subscript (bounds: PartialRangeUpTo<Int>) -> Substring {
        let end = index(startIndex, offsetBy: bounds.upperBound)
        if end < startIndex { return "" }
        return self[startIndex..<end]
    }
}

用法:

swift 复制代码
let subscript1 = "Hello, world!"[7...]
let subscript2 = "Hello, world!"[7...11]

UIImage.squared

当你要求用户拍摄自己的照片或选择一张现有的照片作为个人资料照片时,他们几乎不会提供一张正方形的照片。同时,大多数用户界面都使用正方形或圆形。

这个扩展可以对提供的 UIImage 进行裁剪,使其成为一个完美的正方形。

swift 复制代码
import UIKit

extension UIImage {
    var squared: UIImage? {
        let originalWidth  = size.width
        let originalHeight = size.height
        var x: CGFloat = 0.0
        var y: CGFloat = 0.0
        var edge: CGFloat = 0.0
        
        if (originalWidth > originalHeight) {
            // landscape
            edge = originalHeight
            x = (originalWidth - edge) / 2.0
            y = 0.0
            
        } else if (originalHeight > originalWidth) {
            // portrait
            edge = originalWidth
            x = 0.0
            y = (originalHeight - originalWidth) / 2.0
        } else {
            // square
            edge = originalWidth
        }
        
        let cropSquare = CGRect(x: x, y: y, width: edge, height: edge)
        guard let imageRef = cgImage?.cropping(to: cropSquare) else { return nil }
        
        return UIImage(cgImage: imageRef, scale: scale, orientation: imageOrientation)
    }
}

这个扩展也可以作为一个方法来实现。因为正方形图像不是原始图像的属性,而是它的处理版本。如果你认为方法是一个更好的解决方案,只需将 var squared.UIImage? 替换为 func squared() -> UIImage?

用法:

swift 复制代码
let img = UIImage() // Must be a real UIImage
let imgSquared = img.squared // img.squared() for method

UIImage.resized(...)

在上传图片到你的服务器之前,你必须确保图片的尺寸足够小。 iPhone 和 iPad 有非常强大的摄像头,系统图库中的图片有可能非常大。

为了确保上传的 UIImage 图片不超过给定的尺寸大小,例如 512 像素或 1024 像素,你可以使用这个扩展:

swift 复制代码
import UIKit

extension UIImage {
    func resized(maxSize: CGFloat) -> UIImage? {
        let scale: CGFloat
        if size.width > size.height {
            scale = maxSize / size.width
        }
        else {
            scale = maxSize / size.height
        }
        
        let newWidth = size.width * scale
        let newHeight = size.height * scale
        UIGraphicsBeginImageContext(CGSize(width: newWidth, height: newHeight))
        draw(in: CGRect(x: 0, y: 0, width: newWidth, height: newHeight))
        let newImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        return newImage
    }
}

用法:

swift 复制代码
let img2 = UIImage() // Must be a real UIImage
let img2Thumb = img2.resized(maxSize: 512)

上面两个扩展可以使用链式语法链接起来:

swift 复制代码
let img = UIImage() // Must be a real UIImage
let imgPrepared = img.squared?.resized(maxSize: 512)

Int.toString()

Java 中最有用的功能之一是 toString() 方法。它是一个绝对是所有类和类型的方法。Swift 允许使用字符串插值来实现类似的功能。"\(someVar)"。但有一个区别------返回的变量是可选值类型。Swift 会在输出中加入 optional 这个词。Java 会直接崩溃,但 Kotlin 会漂亮地处理可选值类型:someVar?.toString() 会返回一个可选的字符串,如果 someVar 是空的(nil),则为 null(nil),否则为包含 String 类型的 var 变量。

不幸的是,Swift 不允许扩展 Any,所以我们至少可以在 Int 类型上添加 toString() 方法。

swift 复制代码
import Foundation

extension Int {
    func toString() -> String {
        "\(self)"
    }
}

用法:

swift 复制代码
let i1 = 15
let i1AsString = i1.toString()

Double.toString()

如同前面的例子,将 Double 类型转换为 String 类型也非常有用。但这种情况下,我们将限制让它输出两个小数位。我不能说这个扩展适用于所有情况,但对于大多数场景,它都能很好地工作。

swift 复制代码
import Foundation

extension Double {
    func toString() -> String {
        String(format: "%.02f", self)
    }
}

用法:

swift 复制代码
let d1 = 15.67
let d1AsString = d1.toString()

Double.toPrice()

生成带有价格的 String 字符串只是格式化 Double 的另一种方式。这种算法并不通用,它取决于区域设置。但你可以把它作为一个总体思路,并根据你的应用进行调整。

swift 复制代码
import Foundation

extension Double {
    func toPrice(currency: String) -> String {
        let nf = NumberFormatter()
        nf.decimalSeparator = ","
        nf.groupingSeparator = "."
        nf.groupingSize = 3
        nf.usesGroupingSeparator = true
        nf.minimumFractionDigits = 2
        nf.maximumFractionDigits = 2
        return (nf.string(from: NSNumber(value: self)) ?? "?") + currency
    }
}

用法:

swift 复制代码
let dPrice = 16.50
let strPrice = dPrice.toPrice(currency: "€")

String.asDict

JSON 是一种交换或存储结构化数据的流行格式。大多数 API 都喜欢使用 JSON。JSON 是一种 JavaScript 结构。Swift 有完全相同的数据类型---字典(dictionary)。

将一种类型转换为另一种类型是非常简单的技巧:

swift 复制代码
import Foundation

extension String {
    var asDict: [String: Any]? {
        guard let data = self.data(using: .utf8) else { return nil }
        return try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any]
    }
}

用法:

swift 复制代码
let json = "{\"hello\": \"world\"}"
let dictFromJson = json.asDict

String.asArray

这个扩展与之前的一个扩展类似,但它将 JSON 数组转换为 Swift 数组。

swift 复制代码
import Foundation

extension String {
    var asArray: [Any]? {
        guard let data = self.data(using: .utf8) else { return nil }
        return try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [Any]
    }
}

用法:

swift 复制代码
let json2 = "[1, 2, 3]"
let arrFromJson2 = json2.asArray

String.asAttributedString

有时我们需要对文本进行一些简单的独立于平台的样式设计。一个比较常见的方法是使用简单的 HTML 来实现这一目的。

UILabel 可以显示带有粗体(<strong>)部分的文本、带下划线的文本、更大和更小的片段等。您只需要将 HTML 转换为 NSAttributedString,并将其分配给 UILabel.attributedText 即可。

这个扩展将帮助您完成第一个任务。

swift 复制代码
import Foundation

extension String {
    var asAttributedString: NSAttributedString? {
        guard let data = self.data(using: .utf8) else { return nil }
        return try? NSAttributedString(data: data, options: [.documentType: NSAttributedString.DocumentType.html], documentAttributes: nil)
    }
}

用法:

swift 复制代码
let htmlString = "<p>Hello, <strong>world!</string></p>"
let attrString = htmlString.asAttributedString

Bundle.appVersion

本系列的最后一个扩展可以从 Info.plist 文件中获取应用程序版本号。它可以用于:

  • 发送应用程序版本到 API。
  • 检查可用的更新。
  • 在设备屏幕上显示应用程序版本。
  • 在支持邮件中包含应用程序版本。

下面的扩展允许你在一行代码中获取应用程序版本(如果没有版本,则为零)。

swift 复制代码
import Foundation

extension Bundle {
    var appVersion: String? {
        self.infoDictionary?["CFBundleShortVersionString"] as? String
    }
    
    static var mainAppVersion: String? {
        Bundle.main.appVersion
    }
}

用法:

swift 复制代码
let appVersion = Bundle.mainAppVersion

总结

我希望这些扩展能帮助你使你的代码更简洁。欢迎修改它们以满足你的要求,并将它们纳入你的项目中。

如果你对更多有用的 Swift String 扩展感兴趣,你可以阅读我的其他文章:

10 个实用的 Swift 字符串扩展

相关推荐
桂月二二4 小时前
探索前端开发中的 Web Vitals —— 提升用户体验的关键技术
前端·ux
胖虎14 小时前
实现 iOS 自定义高斯模糊文字效果的 UILabel(文末有Demo)
ios·高斯模糊文字·模糊文字
hunter2062065 小时前
ubuntu向一个pc主机通过web发送数据,pc端通过工具直接查看收到的数据
linux·前端·ubuntu
qzhqbb5 小时前
web服务器 网站部署的架构
服务器·前端·架构
刻刻帝的海角5 小时前
CSS 颜色
前端·css
浪浪山小白兔6 小时前
HTML5 新表单属性详解
前端·html·html5
lee5767 小时前
npm run dev 时直接打开Chrome浏览器
前端·chrome·npm
2401_897579657 小时前
AI赋能Flutter开发:ScriptEcho助你高效构建跨端应用
前端·人工智能·flutter
limit for me7 小时前
react上增加错误边界 当存在错误时 不会显示白屏
前端·react.js·前端框架
浏览器爱好者7 小时前
如何构建一个简单的React应用?
前端·react.js·前端框架