swift 扩展_24个用于更清洁代码的Swift扩展

翟学文
2023-12-01

swift 扩展

One of the best features of both Swift and Objective-C, in my opinion, is extensions. They enable you to add new methods to any class and the whole project will be able to call them without additional inheritance and overloading.

我认为,Swift和Objective-C的最佳功能之一就是扩展。 它们使您可以向任何类添加新方法,并且整个项目将能够调用它们而无需其他继承和重载。

As a mobile developer I work with both iOS and Android, and I very often see Android functions and methods which are shorter, cleaner and more understandable than in Swift. Using extensions, some of these methods can be ported to Swift. Plus with several new methods (extensions) we’ll get a short, clean and easily maintainable code in Swift.

作为移动开发人员,我同时使用iOS和Android,而且我经常看到Android功能和方法比Swift中的功能更短,更简洁,更易于理解。 使用扩展,可以将其中一些方法移植到Swift。 加上几种新方法(扩展名),我们将在Swift中获得一个简短,干净且易于维护的代码。

I’ll use Swift, but all these extensions can be migrated to Objective-C or used with Objective-C directly, without conversion.

我将使用Swift,但是所有这些扩展都可以迁移到Objective-C或直接与Objective-C一起使用,而无需进行转换。

String.trim()和Swift.trimmed (String.trim() and Swift.trimmed)

In 99% of the cases when I trim String in Swift, I want to remove spaces and other similar symbols (for example, new lines and tabs).

在99%的情况下,当我在Swift中修剪String时,我想删除空格和其他类似的符号(例如,新行和制表符)。

This simple extension does the trick:

这个简单的扩展可以达到目的:

import Foundation


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

Usage:

用法:

var str1 = "  a b c d e   \n"var str2 = str1.trimmed
str1.trim()

Int.toDouble()和Double.toInt() (Int.toDouble() and Double.toInt())

These methods can be useful if you work with optionals. If you have non-optional Int, you can convert it with Double(a), where a is an integer variable. But if a is optional, you can’t do it.

如果使用可选方法,那么这些方法可能会很有用。 如果具有非可选的Int ,则可以使用Double(a)对其进行转换,其中a是整数变量。 但是,如果a是可选的,则不能这样做。

Let’s add extensions to Int and Double:

让我们为IntDouble添加扩展名:

import Foundation


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


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

Usage:

用法:

let a = 15.78let b = a.toInt()

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

Getting the Date from String and formatting the Date to display it or send to API are common tasks. The standard way to convert takes three lines of code. Let’s see how to make it shorter:

String获取Date并格式化Date以将其显示或发送给API是常见的任务。 转换的标准方法需要三行代码。 让我们看看如何使其更短:

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)
    }
}

Usage:

用法:

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() (Int.centsToDollars())

Some payment APIs (for example, Stripe) prefer to use monetary units (cents) for payment processing. It allows avoidance of the imprecision of Float and Double. At the same time, it’s more comfortable to use these types to display values.

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

This extension makes this conversion:

此扩展名可以进行以下转换:

import Foundation


extension Int {
    func centsToDollars() -> Double {
        Double(self) / 100
    }
}

Usage:

用法:

let cents = 12350let dollars = cents.centsToDollars()

String.asCoordinates() (String.asCoordinates())

A coordinate of a place on Earth has at least two numbers — latitude and longitude. Another one is altitude, but that makes sense only in 3D space, which is not very common in software development.

地球上某个位置的坐标至少有两个数字-纬度和经度。 另一个是高度,但这仅在3D空间才有意义,这在软件开发中不是很常见。

From API we get either two separate fields, or one field with comma-separated values. This extension allows to make conversion of such strings into CLLocationCoordinate2D.

通过API,我们可以得到两个单独的字段,或者一个带有逗号分隔值的字段。 此扩展允许将此类字符串转换为CLLocationCoordinate2D

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
    }
}

Usage:

用法:

let strCoordinates = "41.6168, 41.6367"let coordinates = strCoordinates.asCoordinates

String.asURL() (String.asURL())

iOS and macOS use the URL type to handle links. It’s more flexible, it allows to get components, and it handles different types of URLs. At the same time, we usually enter it or get it from API String.

iOS和macOS使用URL类型来处理链接。 它更加灵活,可以获取组件,并且可以处理不同类型的URL。 同时,我们通常输入它或从API String获得它。

It’s quite easy to convert one to another, but this extension allows us to handle optionals or chain this conversion:

将一个转换为另一个很容易,但是此扩展允许我们处理可选内容或链接此转换:

import Foundation


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

Usage:

用法:

let strUrl = "https://medium.com"let url = strUrl.asURL

UIDevice.vibrate() (UIDevice.vibrate())

The iPhone vibration can be a cool effect for button taps and other feedback from devices. For iPhone vibration there is a special kind of sound, handled by the AudioToolbox framework.

iPhone震动对于按键和其他来自设备的反馈可能是很酷的效果。 对于iPhone振动,有一种特殊的声音,由AudioToolbox框架处理。

Including AudioToolbox to all UIViewControllers with vibration is annoying, and logically vibration is more of a device function (it doesn’t come from the speakers but from the device itself) than playing sounds. This extensions allows to simplify it to one line:

包括振动在内的所有UIViewController都包含AudioToolbox令人讨厌,并且从逻辑AudioToolbox ,振动更是一种设备功能(它不是来自扬声器,而是来自设备本身)而不是播放声音。 此扩展允许将其简化为一行:

import UIKit
import AudioToolbox


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

Usage:

用法:

UIDevice.vibrate()

String.width(…)和String.height(…) (String.width(…) and String.height(…))

iOS can calculate the size of UILabel automatically, using provided constraints, but sometimes it’s important to set the size yourself.

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

This extension allows us to calculate the String width and height using the provided UIFont:

此扩展允许我们使用提供的UIFont计算String宽度和高度:

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)
    }
}

Usage:

用法:

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

String.containsOnlyDigits (String.containsOnlyDigits)

The extension below is useful when you need to limit the user input or validate data from API. It checks if String has only digits:

当您需要限制用户输入或验证来自API的数据时,以下扩展名很有用。 它检查String是否只有数字:

import Foundation


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

Usage:

用法:

let digitsOnlyYes = "1234567890".containsOnlyDigitslet digitsOnlyNo = "12345+789".containsOnlyDigits

String.is字母数字 (String.isAlphanumeric)

Like the previous extension, this one checks the content of String. It returns true if the string is not empty and contains only alphanumeric characters. An inverted version of this extension can be useful to confirm that passwords have non-alphanumeric characters.

与上一个扩展类似,此扩展检查String的内容。 如果字符串不为空并且仅包含字母数字字符,则返回true。 此扩展的反向版本对于确认密码具有非字母数字字符很有用。

import Foundation


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

Usage:

用法:

let alphanumericYes = "asd3kJh43saf".isAlphanumericlet alphanumericNo = "Kkncs+_s3mM.".isAlphanumeric

字符串下标 (String Subscripts)

Swift 5 has a horrible way of subscripting Strings. Calculating indexes and offsets is annoying if you want to get, for example, characters from 5 to 10. This extension allows to use simple Ints for this purpose:

Swift 5有一种可怕的方法来给String下标。 如果要获取5到10之间的字符,则计算索引和偏移量很烦人。此扩展名为此可以使用简单的Int

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]
    }
}

Usage:

用法:

let subscript1 = "Hello, world!"[7...]let subscript2 = "Hello, world!"[7...11]

UIImage.squared (UIImage.squared)

When you ask users to take their photo or choose an existing one as a profile photo, they’ll hardly provide a square picture. At the same time, most UIs use squares or circles.

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

This extension crops the provided UIImage, making it a perfect square:

该扩展裁剪了提供的UIImage ,使其成为一个完美的正方形:

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)
    }
}

This extension can be also implemented as a method. As squared image is not a property of original image, but processed version of it. If you think method is a better solution, just replace var squared: UIImage? with func squared() -> UIImage?.

该扩展也可以实现为方法。 平方图像不是原始图像的属性,而是原始图像的处理版本。 如果您认为方法是更好的解决方案,则只需替换var squared: UIImage?func squared() -> UIImage?

Usage:

用法:

let img = UIImage() // Must be a real UIImagelet imgSquared = img.squared // img.squared() for method

UIImage.resize(...) (UIImage.resized(…))

Before uploading a picture to your server, you must make sure that it has a small enough size. iPhones and iPads have very good cameras and pictures from their gallery have potentially unlimited size.

将图片上传到服务器之前,必须确保其尺寸足够小。 iPhone和iPad具有非常好的相机,而其图库中的图片可能大小不受限制。

To make sure that UIImage is not bigger than a given size, for example, 512 pixels or 1024 pixels, use this extension:

要确保UIImage不大于给定的大小(例如512像素或1024像素),请使用以下扩展名:

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
    }
}

Usage:

用法:

let img2 = UIImage() // Must be a real UIImagelet img2Thumb = img2.resized(maxSize: 512)

The last two extensions can be chained:

可以链接最后两个扩展:

let img = UIImage() // Must be a real UIImagelet imgPrepared = img.squared?.resized(maxSize: 512)

Int.toString() (Int.toString())

One of the most useful features of Java is toString() method. It’s a method of absolutely all classes and types. Swift allows to do something similar using string interpolation: "\(someVar)". But there’s one difference — your variable is optional. Swift will add the word optional to the output. Java will just crash, but Kotlin will handle optionals beautifully: someVar?.toString() will return an optional String, which is null (nil) if someVar is null (nil) or String containing value of var otherwise.

Java最有用的功能之一是toString()方法。 这是绝对所有类和类型的方法。 Swift允许使用字符串插值来做类似的事情: "\(someVar)" 。 但是有一个区别-您的变量是可选的。 Swift将在输出中添加optional单词。 Java将刚刚崩溃,但Kotlin将精美办理自选: someVar?.toString()将返回一个可选的字符串,它是null ( nil )如果someVarnull ( nil )或String包含的值var否则。

Unfortunately, Swift doesn’t allow to extend Any, so let’s add toString() method at least to Int:

不幸的是,Swift不允许扩展Any ,所以让我们至少将toString()方法添加到Int

import Foundation


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

Usage:

用法:

let i1 = 15let i1AsString = i1.toString()

Double.toString() (Double.toString())

As in the previous example, converting Double to String can be very useful. But in this case we’ll limit the output with two fractional digits. I can’t say this extension will be useful for all cases, but for most uses it will work well:

与前面的示例一样,将Double转换为String可能非常有用。 但是在这种情况下,我们将输出限制为两个小数位数。 我不能说此扩展将在所有情况下都有用,但是对于大多数使用而言,它将很好地工作:

import Foundation


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

Usage:

用法:

let d1 = 15.67let d1AsString = d1.toString()

Double.toPrice() (Double.toPrice())

Generating Strings with prices is just another way to format Doubles. This algorithm is not universal, it depends on regional settings. But you can use it as a general idea and make adjustments for your app:

用价格生成String只是格式化Double的另一种方法。 此算法不是通用算法,它取决于区域设置。 但是您可以将其用作一般思路,并针对您的应用进行调整:

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
    }
}

Usage:

用法:

let dPrice = 16.50let strPrice = dPrice.toPrice(currency: "€")

String.asDict (String.asDict)

JSON is a popular format to exchange or store structured data. Most APIs prefer to use JSON. JSON is a JavaScript structure. Swift has exactly the same data type — dictionary.

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

Converting one to another is a fairly simple trick:

将一个转换为另一个是一个很简单的技巧:

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]
    }
}

Usage:

用法:

let json = "{\"hello\": \"world\"}"let dictFromJson = json.asDict

String.asArray (String.asArray)

This extension is similar to a previous one, but it converts the JSON array into a Swift array:

此扩展与先前的扩展类似,但是将JSON数组转换为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]
    }
}

Usage:

用法:

let json2 = "[1, 2, 3]"let arrFromJson2 = json2.asArray

String.asAttributedString (String.asAttributedString)

Sometimes we need some simple platform-independent styling for texts. A rather common way is to use simple HTML for this purpose.

有时我们需要一些简单的与平台无关的文本样式。 一种相当普遍的方法是为此目的使用简单HTML。

UILabel can show text with bold (<strong>) parts, underlined text, bigger and smaller fragments, etc. You just need to convert HTML to NSAttributedString and assign it to UILabel.attributedText.

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

This extension will help you with the first task:

此扩展程序将帮助您完成第一个任务:

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)
    }
}

Usage:

用法:

let htmlString = "<p>Hello, <strong>world!</string></p>"let attrString = htmlString.asAttributedString

Bundle.appVersion (Bundle.appVersion)

The last extension in this set allows to get the app version from Info.plist file. It can be useful for:

此集中的最后一个扩展名允许从Info.plist文件获取应用程序版本。 它可用于:

  • Sending the app version to API.

    将应用程序版本发送到API。
  • Checking available updates.

    检查可用更新。
  • Showing the app version on a device screen.

    在设备屏幕上显示应用程序版本。
  • Including the app version into a support email.

    将应用程序版本包含在支持电子邮件中。

The extension below allows you to get app version (or nil if it’s not available) in one line of code:

下面的扩展允许您通过一行代码获取应用版本(如果不可用,则为nil ):

import Foundation


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

Usage:

用法:

let appVersion = Bundle.mainAppVersion

结论 (Conclusion)

I hope these extensions helped you to make your code shorter and cleaner. Feel free to modify them to meet your requirements and include them into your projects.

我希望这些扩展可以帮助您使代码更短,更清晰。 随意修改它们以满足您的要求,并将其包含在您的项目中。

If you’re interested in more useful Swift String extensions, you can read my other article:

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

Happy coding and see you next time!

祝您编程愉快,下次再见!

翻译自: https://medium.com/better-programming/24-swift-extensions-for-cleaner-code-41e250c9c4c3

swift 扩展

 类似资料: