本文概述
- 什么是T9?
- T9预测性输入实例
- 在iOS中以编程方式使用T9
- 简单架构
- 放在一起
- 本文总结
【如何在iOS中实现T9搜索】在开发此模块时, 我们注意到在Android版本的应用程序中查找特定联系人比在iOS版本中查找联系人容易得多。为什么?这背后的关键原因是T9搜索, Apple设备缺少该搜索。
让我们解释一下T9的全部含义, 以及为什么它可能没有成为iOS的一部分, 以及iOS开发人员如何在必要时实现它。
什么是T9? T9是用于手机的预测文本技术, 特别是那些包含物理3× 4数字小键盘的手机。
文章图片
T9最初是由Tegic Communications开发的, 名称代表9键上的Text。
你可以猜测为什么T9可能从未进入iOS。在智能手机革命期间, T9输入变得过时, 因为现代智能手机采用触摸屏显示, 因此完全依靠全键盘。由于苹果在T9的鼎盛时期从未使用过带有物理键盘的手机, 并且也没有从事手机业务, 因此可以理解, iOS省略了该技术。
T9仍在某些不带触摸屏的廉价手机(所谓的功能手机)上使用。但是, 尽管大多数Android手机从未使用过物理键盘, 但现代Android设备仍支持T9输入, 可通过拼写要呼叫的联系人的姓名来拨打联系人。
T9预测性输入实例 在带有数字小键盘的手机上, 每当按下一个键(1-9)(在文本字段中)时, 该算法都会返回一个猜测值, 即对于该点所按下的键最有可能出现的字母。
文章图片
例如, 要输入单词” the” , 用户将按8, 然后按4, 再按3, 显示屏将显示” t” , 然后是” th” , 然后是” the” 。如果打算使用不太常见的单词” 前” (3673), 则预测算法可以选择” 福特” 。按下” 下一个” 键(通常为” *” 键)可能会显示” 剂量” , 最后显示” 前” 。如果选择了” 之前” , 则用户下次按下序列3673时, 最有可能成为显示的第一个单词。但是, 如果要使用单词” Felix” , 则在输入33549时, 显示屏将显示” E” , 然后显示” De” , ” Del” , ” Deli” 和” Felix” 。
这是输入单词时更改字母的示例。
在iOS中以编程方式使用T9 因此, 让我们深入研究此功能, 并编写一个简单的iOS T9输入示例。首先, 我们需要创建一个新项目。
我们项目所需的先决条件是基本的:Mac上安装的Xcode和Xcode构建工具。
要创建一个新项目, 请在Mac上打开你的Xcode应用程序, 然后选择” 创建新的Xcode项目” , 然后为你的项目命名, 并选择要创建的应用程序的类型。只需选择” Single View App” , 然后按下一步。
文章图片
在下一个屏幕上, 你将看到需要提供的一些信息。
- 产品名称:我命名为T9Search
- 球队。在这里, 如果要在真实设备上运行此应用程序, 则必须具有开发者帐户。就我而言, 我将使用自己的帐户。
- 组织名称:我命名为srcmini
- 组织标识符:我将其命名为” com.srcmini”
- 语言:选择Swift
- 取消选中” 使用核心数据” , “ 包括单元测试” 和” 包括UI测试”
简单架构 如你所知, 在创建新应用时, 你已经具有MainViewController类和Main.Storyboard。当然, 出于测试目的, 我们可以使用此控制器。
在开始设计之前, 首先创建所有必需的类和文件, 以确保我们已设置并运行所有内容以移至工作的UI部分。
在项目内的某个地方, 只需创建一个名为” PhoneContactsStore.swift” 的新文件, 就我而言, 它看起来像这样。
文章图片
我们的首要任务是创建包含数字键盘输入的所有变体的地图。
import Contacts
import UIKit
fileprivate let T9Map = [
" " : "0", "a" : "2", "b" : "2", "c" : "2", "d" : "3", "e" : "3", "f" : "3", "g" : "4", "h" : "4", "i" : "4", "j" : "5", "k" : "5", "l" : "5", "m" : "6", "n" : "6", "o" : "6", "p" : "7", "q" : "7", "r" : "7", "s" : "7", "t" : "8", "u" : "8", "v" : "8", "w" : "9", "x" : "9", "y" : "9", "z" : "9", "0" : "0", "1" : "1", "2" : "2", "3" : "3", "4" : "4", "5" : "5", "6" : "6", "7" : "7", "8" : "8", "9" : "9"
]
而已。我们已经实施了包含所有变体的完整地图。现在, 让我们继续创建名为” PhoneContact” 的第一类。
你的文件应如下所示:
文章图片
首先, 在此类中, 我们需要确保我们有一个A-Z + 0-9的正则表达式过滤器。
私人让正则表达式=尝试! NSRegularExpression(pattern:” [^ a-z()0-9 +]” , 选项:.caseInsensitive)
基本上, 用户具有需要显示的默认属性:
var firstName: String!
var lastName: String!
var phoneNumber: String!
var t9String: String = ""
var image: UIImage?var fullName: String! {
get {
return String(format: "%@ %@", self.firstName, self.lastName)
}
}
确保已覆盖哈希和isEqual以指定用于列表过滤的自定义逻辑。
另外, 我们需要使用replace方法来避免字符串中的数字以外的任何东西。
override var hash: Int {
get {
return self.phoneNumber.hash
}
}override func isEqual(_ object: Any?) ->
Bool {
if let obj = object as? PhoneContact {
return obj.phoneNumber == self.phoneNumber
}return false
}private func replace(str : String) ->
String {
let range = NSMakeRange(0, str.count)
return self.regex.stringByReplacingMatches(in: str, options: [], range: range, withTemplate: "")
}
现在, 我们需要另一种称为calculateT9的方法, 以查找与全名或电话号码相关的联系人。
func calculateT9() {
for c in self.replace(str: self.fullName) {
t9String.append(T9Map[String(c).localizedLowercase] ?? String(c))
}for c in self.replace(str: self.phoneNumber) {
t9String.append(T9Map[String(c).localizedLowercase] ?? String(c))
}
}
在实现PhoneContact对象之后, 我们需要将联系人存储在内存中的某个位置。为此, 我将创建一个名为PhoneContactStore的新类。
我们将有两个本地属性:
fileprivate让contactsStore = CNContactStore()
和:
fileprivate惰性var dataSource = Set < PhoneContact> ()
我正在使用Set来确保在填写此数据源期间没有重复。
final class PhoneContactStore {fileprivate let contactsStore= CNContactStore()
fileprivate lazy var dataSource = Set<
PhoneContact>
()static let instance : PhoneContactStore = {
let instance = PhoneContactStore()
return instance
}()
}
如你所见, 这是一个Singleton类, 这意味着我们将其保留在内存中, 直到应用程序运行为止。有关单例或设计模式的更多信息, 你可以在此处阅读。
现在, 我们非常接近完成T9搜索。
放在一起 在Apple上访问联系人列表之前, 你需要先获得许可。
class func hasAccess() ->
Bool {
let authorizationStatus = CNContactStore.authorizationStatus(for: CNEntityType.contacts)
return authorizationStatus == .authorized
}class func requestForAccess(_ completionHandler: @escaping (_ accessGranted: Bool, _ error : CustomError?) ->
Void) {
let authorizationStatus = CNContactStore.authorizationStatus(for: CNEntityType.contacts)
switch authorizationStatus {
case .authorized:
self.instance.loadAllContacts()
completionHandler(true, nil)
case .denied, .notDetermined:
weak var wSelf = self.instance
self.instance.contactsStore.requestAccess(for: CNEntityType.contacts, completionHandler: { (access, accessError) ->
Void in
var err: CustomError?
if let e = accessError {
err = CustomError(description: e.localizedDescription, code: 0)
} else {
wSelf?.loadAllContacts()
}
completionHandler(access, err)
})
default:
completionHandler(false, CustomError(description: "Common Error", code: 100))
}
}
授权访问联系人后, 我们可以编写该方法以从系统获取列表。
fileprivate func loadAllContacts() {
if self.dataSource.count == 0 {
let keys = [CNContactGivenNameKey, CNContactFamilyNameKey, CNContactThumbnailImageDataKey, CNContactPhoneNumbersKey]
do {let request = CNContactFetchRequest(keysToFetch: keys as [CNKeyDescriptor])
request.sortOrder = .givenName
request.unifyResults = true
if #available(iOS 10.0, *) {
request.mutableObjects = false
} else {} // Fallback on earlier versionstry self.contactsStore.enumerateContacts(with: request, usingBlock: {(contact, ok) in
DispatchQueue.main.async {
for phone in contact.phoneNumbers {
let local = PhoneContact()
local.firstName = contact.givenName
local.lastName = contact.familyName
if let data = contact.thumbnailImageData {
local.image = UIImage(data: data)
}
var phoneNum = phone.value.stringValuelet strArr = phoneNum.components(separatedBy: CharacterSet.decimalDigits.inverted)
phoneNum = NSArray(array: strArr).componentsJoined(by: "")
local.phoneNumber = phoneNum
local.calculateT9()
self.dataSource.insert(local)
}
}
})
} catch {}
}
}
我们已经将联系人列表加载到内存中, 这意味着我们现在可以编写一个简单的方法:
- findWith-t9String
- findWith-str
class func findWith(t9String: String) ->
[PhoneContact] {
return PhoneContactStore.instance.dataSource.filter({ $0.t9String.contains(t9String) })
}class func findWith(str: String) ->
[PhoneContact] {
return PhoneContactStore.instance
.dataSource.filter({$0.fullName.lowercased()
.contains(str.lowercased()) })
}class func count() ->
Int {
let request = CNContactFetchRequest(keysToFetch: [])
var count = 0;
do {
try self.instance.contactsStore.enumerateContacts(
with: request, usingBlock: {(contact, ok) in
count += 1;
})
} catch {}return count
}
而已。我们完了。
现在我们可以在UIViewController中使用T9搜索。
fileprivate let cellIdentifier = "contact_list_cell"final class ViewController: UIViewController {@IBOutlet weak var tableView: UITableView!
@IBOutlet weak var searchBar: UISearchBar!fileprivate lazy var dataSource = [PhoneContact]()
fileprivate var searchString : String?
fileprivate var searchInT9: Bool = trueoverride func viewDidLoad() {
super.viewDidLoad()
self.tableView.register(
UINib(
nibName: "ContactListCell", bundle: nil
), forCellReuseIdentifier: "ContactListCell"
)
self.searchBar.keyboardType = .numberPadPhoneContactStore.requestForAccess { (ok, err) in }
}func filter(searchString: String, t9: Bool = true) {}func reloadListSection(section: Int, animation: UITableViewRowAnimation = .none) {
}
}
过滤方法的实现:
func filter(searchString: String, t9: Bool = true) {
self.searchString = searchString
self.searchInT9 = t9if let str = self.searchString {
if t9 {
self.dataSource = PhoneContactStore.findWith(t9String: str)
} else {
self.dataSource = PhoneContactStore.findWith(str: str)
}
} else {
self.dataSource = [PhoneContact]()
}self.reloadListSection(section: 0)
}
重新加载列表方法的实现:
func reloadListSection(section: Int, animation: UITableViewRowAnimation = .none) {
if self.tableView.numberOfSections <
= section {
self.tableView.beginUpdates()
self.tableView.insertSections(IndexSet(integersIn:0..<
section + 1), with: animation)
self.tableView.endUpdates()
}
self.tableView.reloadSections(IndexSet(integer:section), with: animation)
}
这是我们简短的教程UITableView实现的最后一部分:
extension ViewController: UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate {func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) ->
UITableViewCell {
return tableView.dequeueReusableCell(withIdentifier: "ContactListCell")!
}func numberOfSections(in tableView: UITableView) ->
Int {
return 1
}func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) ->
Int {
return self.dataSource.count
}func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard let contactCell = cell as? ContactListCell else { return }
let row = self.dataSource[indexPath.row]
contactCell.configureCell(
fullName: row.fullName, t9String: row.t9String, number: row.phoneNumber, searchStr: searchString, img: row.image, t9Search: self.searchInT9
)
}func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) ->
CGFloat {
return 55
}
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
self.filter(searchString: searchText)
}}
本文总结 到此为止, 我们的T9搜索教程结束了, 希望你发现它在iOS中易于实现。
但是你为什么要呢?苹果为什么不首先在iOS中包含T9支持?正如我们在介绍中所指出的那样, T9并不是当今电话的杀手feature, 而是更多的事后思考, 回溯到带有机械数字键盘的” 笨拙” 电话时代。
但是, 出于一致性考虑或为了提高可访问性和用户体验, 仍然有一些合理的原因为什么你应该在某些情况下实施T9搜索。更令人愉悦的是, 如果你是怀旧的人, 那么使用T9输入可以带回你上学时的美好回忆。
最后, 你可以在我的GitHub存储库中找到iOS中T9实施的完整代码。
推荐阅读
- Flutter教程(如何创建你的第一个Flutter应用)
- 如何从头开始创建可滑动的UITabBar
- 在React Native中使用Redux,RxJS和Redux-Observable构建响应式应用程序
- 避免iOS和Android设计中的不良做法
- 保持加密,确保安全(使用ESNI,DoH和DoT)
- Spring的ApplicationContext学习
- SQLite----Android Studio3.6.3 当前最新版本数据库查找与导出方法
- Fiddler+雷电模拟器进行APP抓包
- 知识圈APP开发记录