This is the first day of my participation in Gwen Challenge
When the App is available to friends around the world, when entering a phone number, we have to consider how to format the phone number according to the local language, how to display the ICONS of various countries and other issues. You can use PhoneNumberKit when dealing with phone numbers.
Here is my source in the process of reading the goods.
No newline space “\u{00A0}”
We often need to filter whitespace characters, and one other special character needs to be considered: “\u{00A0}” non-newline Spaces.
var spaceCharacterSet: CharacterSet = {
let characterSet = NSMutableCharacterSet(charactersIn: "\u{00a0}")
characterSet.formUnion(with: CharacterSet.whitespacesAndNewlines)
return characterSet as CharacterSet} ()Copy the code
Non-newline Spaces have two uses:
- Disallow merging consecutive Spaces. In HTML, for example, successive whitespace characters are combined into one.
- Disable line wrapping. Editors typically place a wrap at a space character. However, there are some types of text that do not fit at the end of successive lines and at the beginning of the next, such as “100 km”.
Details can be found on Wikipedia.
NSRange and Range convert to each other
PhoneNumberKit: NSRange and Range:
extension String {
func nsRange(from range: Range<String.Index>) -> NSRange {
let utf16view = self.utf16
let from = range.lowerBound.samePosition(in: utf16view) ?? self.startIndex
let to = range.upperBound.samePosition(in: utf16view) ?? self.endIndex
return NSRange(location: utf16view.distance(from: utf16view.startIndex, to: from),
length: utf16view.distance(from: from, to: to))
}
func range(from nsRange: NSRange) -> Range<String.Index>? {
guard
let from16 = utf16.index(utf16.startIndex, offsetBy: nsRange.location, limitedBy: utf16.endIndex),
let to16 = utf16.index(from16, offsetBy: nsRange.length, limitedBy: utf16.endIndex),
let from = String.Index(from16, within: self),
let to = String.Index(to16, within: self)
else { return nil }
return from..<to
}
}
Copy the code
Platform judgment
Because platform differences can cause bugs, or are treated differently, you need to judge platforms where necessary. In the PhoneNumberKit framework, canImport is used to determine platform health.
// Check whether the ObjectiveC operating environment exists
#if canImport(ObjectiveC)
/ /...
#else
/ /...
#endif
// Check whether there is UIKit
#if canImport(UIKit)
// ...
#endif
Copy the code
More information about the platform can be found in the Apple documentation.
Platform condition | Valid arguments |
---|---|
os() | macOS, iOS, watchOS, tvOS, Linux, Windows |
arch() | i386, x86_64, arm, arm64 |
swift() | >= or < followed by a version number |
compiler() | >= or < followed by a version number |
canImport() | A module name |
targetEnvironment() | simulator, macCatalyst |
There are two platform judgments made to avoid bugs in PhoneNumberKit:
// Use case 1:
extension NSRegularExpression {
#if canImport(ObjectiveC)
func enumerateMatches(in string: String.options: NSRegularExpression.MatchingOptions =[].range: Range<String.Index>? = nil.using block: (NSTextCheckingResult? .NSRegularExpression.MatchingFlags.UnsafeMutablePointer<ObjCBool- > >)Swift.Void) {
let range = range ?? string.startIndex..<string.endIndex
let nsRange = string.nsRange(from: range)
self.enumerateMatches(in: string, options: options, range: nsRange, using: block)
}
#else
// FIX: block needs to be @escaping
func enumerateMatches(in string: String.options: NSRegularExpression.MatchingOptions =[].range: Range<String.Index>? = nil.using block: @escaping (NSTextCheckingResult? .NSRegularExpression.MatchingFlags.UnsafeMutablePointer<ObjCBool- > >)Swift.Void) {
let range = range ?? string.startIndex..<string.endIndex
let nsRange = string.nsRange(from: range)
self.enumerateMatches(in: string, options: options, range: nsRange, using: block)
}
#endif
}
// Use case 2:
#if canImport(ObjectiveC)
let prefixPattern = String(format: "^ (? : % @)", possibleNationalPrefix)
#else
// FIX: String format with %@ doesn't work without ObjectiveC (e.g. Linux)
let prefixPattern = "^ (? :\(possibleNationalPrefix))"
#endif
Copy the code
Any way to test for the presence of the Objective-C runtime?
NSRegularExpression cache
When our App uses regular NSRegularExpression regularly, it needs to consider caching the regees to improve performance.
PhoneNumberKit uses RegexManager to cache created NSregularexpressions and use custom queues for saving and reading.
The key codes are as follows:
final class RegexManager {
// MARK: Regular expression pool
var regularExpresionPool = [String: NSRegularExpression] ()// concurrent Concurrent queue
private let regularExpressionPoolQueue = DispatchQueue(label: "com.phonenumberkit.regexpool",
attributes: .concurrent)
// MARK: Regular expression
func regexWithPattern(_ pattern: String) throws -> NSRegularExpression {
var cached: NSRegularExpression?
// Put all the operations on the array in its own parallel queue,
// Read using.sync
// set to.async(flags:.barrier)
self.regularExpressionPoolQueue.sync {
cached = self.regularExpresionPool[pattern]
}
if let cached = cached {
return cached
}
do {
// caseInsensitive ignores case
let regex = try NSRegularExpression(pattern: pattern,
options: .caseInsensitive)
regularExpressionPoolQueue.async(flags: .barrier) {
self.regularExpresionPool[pattern] = regex
}
return regex
} catch {
throw PhoneNumberError.generalError
}
}
}
Copy the code
NSLocale
Gets a localized string for the country or region code.
let name = (Locale.current as NSLocale).localizedString(forCountryCode: countryCode)
Copy the code
For example, calling this method in the American English (en_US) locale returns the string “United Kingdom” when countryCode is “GB.”
LocalizedString (forCountryCode:
Numeral to string
Public var description: String {get} public var description: String {get}
The code is as follows:
var number: Int? = nil
debugPrint(number?.description ?? "empty")
number = 1
debugPrint(number?.description ?? "empty")
/ / output
"empty"
"1"
Copy the code
National code transfer emoji
When selecting a country, the corresponding icon is usually added in front of the country to increase the recognition and facilitate the selection of users.
Using images to show ICONS of individual countries increases the size of the App package. The author of PhoneNumberKit uses emogi’s method to find out the corresponding emoji by country code, so as to avoid the enlargement of App installation caused by pictures and improve the loading speed.
The corresponding emoji can be found through the county code as follows:
static func getFlag(countryCode: String) -> String? {
var flag = ""
let flagBase = UnicodeScalar("🇦").value - UnicodeScalar("A").value
countryCode.uppercased().unicodeScalars.forEach {
if let scaler = UnicodeScalar(flagBase + $0.value) {
flag.append(String(describing: scaler))
}
}
guard flag.count = = 1 else {
return nil
}
return flag
}
Copy the code
Also check out the following article: StackOverflow discussion Wikipedia
String comparison
Compares two strings case insensitive
When comparing two strings without case sensitivity, convert them to all uppercase or lowercase characters before comparing. This is how the authors filter the list of countries through search:
public func updateSearchResults(for searchController: UISearchController) {
let searchText = searchController.searchBar.text ?? ""
filteredCountries = allCountries.filter { country in
country.name.lowercased().contains(searchText.lowercased()) ||
country.code.lowercased().contains(searchText.lowercased()) ||
country.prefix.lowercased().contains(searchText.lowercased())
}
tableView.reloadData()
}
Copy the code
Alternatively, you can use the caseInsensitiveCompare(_:) (Apple documentation) method of strings, This method is the result of calling compare(_:options:) with nscaseSensitivesearch as the only option. The authors sort the list of countries in this way:
lazy var allCountries = phoneNumberKit
.allCountries()
.compactMap({ Country(for: $0, with: self.phoneNumberKit) })
.sorted(by: { $0.name.caseInsensitiveCompare(The $1.name) = = .orderedAscending })
Copy the code
In addition: when operating strings need to show the user needs to call localizedCaseInsensitiveCompare (_) method.
Very strange why the author will adopt two ways, I feel that all use case sensitive software method can be.
Compares strings in a way that ignores width or diacritic differences
When processing text, especially Latin text, and comparing strings in ways that ignore differences in case (uppercase or lowercase), width (full or half corner), and diacritics (accent and half corner), You can create a suitable string for comparison by using Folding (Options: Locale 🙂 (Apple documentation) to remove the specified character distinction from the string.
The author used the folding method when displaying countries in groups (the code has been changed) :
let lhs = lName.folding(options: .diacriticInsensitive, locale: nil)
let rhs = rName.folding(options: .diacriticInsensitive, locale: nil)
if lhs?.first = = rhs.first {
// Start with the same character
} else {
// Inconsistent initial letters
}
Copy the code
Obtain UIFont based on user Settings
The author uses a preferredFont(forTextStyle:) to set the Cell font.
// Set font size and thickness according to user Settings
@available(iOS 7.0.*)
open class func preferredFont(forTextStyle style: UIFontTextStyle) - >UIFont
Copy the code
Set the font code as follows:
cell.textLabel?.font = .preferredFont(forTextStyle: .callout)
cell.detailTextLabel?.font = .preferredFont(forTextStyle: .body)
Copy the code
NSCharacterSet
How do I get the non-set of a character set (a character set that does not have the specified character set)? There are Inverted methods in NSCharacterSet (Apple documentation).
Use cases:
let nonNumericSet: NSCharacterSet = {
var mutableSet = NSMutableCharacterSet.decimalDigit().inverted
mutableSet.remove(charactersIn: PhoneNumberConstants.plusChars)
mutableSet.remove(charactersIn: PhoneNumberConstants.pausesAndWaitsChars)
mutableSet.remove(charactersIn: PhoneNumberConstants.operatorChars)
return mutableSet as NSCharacterSet} ()Copy the code
Collection, string allSatisfy
AllSatisfy (_:) (Apple documentation) can be used to determine whether all elements of a set, string, or a set meet certain criteria.
extension String {
var isBlank: Bool {
return allSatisfy({ $0.isWhitespace })
}
}
Copy the code
Every time I read a great framework, I feel enlightened, thanks to the great open source authors.