So implementieren Sie die T9-Suche in iOS
Veröffentlicht: 2022-03-11Vor ein paar Jahren habe ich mit meinem iOS/Android-Team an einer App namens „BOG mBank - Mobile Banking“ gearbeitet. Es gibt eine grundlegende Funktion in der App, mit der Sie die Mobile-Banking-Funktionalität verwenden können, um Ihr eigenes Handy-Postpaid-Guthaben oder das Handy-Guthaben eines beliebigen Kontakts aufzuladen.
Bei der Entwicklung dieses Moduls haben wir festgestellt, dass es in der Android-Version der App viel einfacher war, einen bestimmten Kontakt zu finden als in der iOS-Version. Warum? Der Hauptgrund dafür ist die T9-Suche, die auf Apple-Geräten fehlt.
Lassen Sie uns erklären, worum es bei T9 geht und warum es wahrscheinlich kein Teil von iOS wurde und wie iOS-Entwickler es bei Bedarf implementieren können.
Was ist T9?
T9 ist eine Texterkennungstechnologie für Mobiltelefone, insbesondere solche, die einen physischen 3x4-Ziffernblock enthalten.
T9 wurde ursprünglich von Tegic Communications entwickelt, und der Name steht für Text auf 9 Tasten .
Sie können sich vorstellen, warum T9 es wahrscheinlich nie zu iOS geschafft hat. Während der Smartphone-Revolution wurde die T9-Eingabe obsolet, da moderne Smartphones dank ihrer Touchscreen-Displays auf vollständige Tastaturen angewiesen waren. Da Apple nie Telefone mit physischen Tastaturen hatte und während der Blütezeit von T9 nicht im Telefongeschäft war, ist es verständlich, dass diese Technologie von iOS weggelassen wurde.
T9 wird immer noch auf bestimmten preiswerten Telefonen ohne Touchscreen (sogenannte Feature-Phones) verwendet. Trotz der Tatsache, dass die meisten Android-Telefone nie über physische Tastaturen verfügten, bieten moderne Android-Geräte Unterstützung für die T9-Eingabe, die zum Wählen von Kontakten verwendet werden kann, indem der Name des Kontakts, den man anrufen möchte, buchstabiert wird.
Ein Beispiel für T9 Predictive Input in Aktion
Auf einem Telefon mit Ziffernblock gibt der Algorithmus jedes Mal, wenn eine Taste (1-9) gedrückt wird (in einem Textfeld), eine Vermutung zurück, welche Buchstaben für die bis zu diesem Punkt gedrückten Tasten am wahrscheinlichsten sind.
Um beispielsweise das Wort „the“ einzugeben, würde der Benutzer 8, dann 4, dann 3 drücken, und das Display würde „t“, dann „th“ und dann „the“ anzeigen. Wenn das weniger gebräuchliche Wort „fore“ beabsichtigt ist (3673), kann der Vorhersagealgorithmus „Ford“ auswählen. Durch Drücken der „Weiter“-Taste (normalerweise die „*“-Taste) wird möglicherweise „Dosis“ und schließlich „Fore“ angezeigt. Wenn "fore" ausgewählt ist, dann ist es wahrscheinlicher, dass fore das erste angezeigte Wort ist, wenn der Benutzer das nächste Mal die Sequenz 3673 drückt. Ist hingegen das Wort „Felix“ gemeint, erscheint bei der Eingabe von 33549 „E“, dann „De“, „Del“, „Deli“ und „Felix“.
Dies ist ein Beispiel für einen Buchstaben, der sich während der Eingabe von Wörtern ändert.
Programmatische Verwendung von T9 in iOS
Lassen Sie uns also in diese Funktion eintauchen und ein einfaches Beispiel für die T9-Eingabe für iOS schreiben. Zunächst müssen wir ein neues Projekt erstellen.
Die für unser Projekt erforderlichen Voraussetzungen sind grundlegend: Xcode und Xcode-Build-Tools, die auf Ihrem Mac installiert sind.
Um ein neues Projekt zu erstellen, öffnen Sie Ihre Xcode-Anwendung auf Ihrem Mac und wählen Sie „Neues Xcode-Projekt erstellen“, benennen Sie Ihr Projekt und wählen Sie den Typ der zu erstellenden Anwendung aus. Wählen Sie einfach „Single View App“ und drücken Sie auf „Weiter“.
Auf dem nächsten Bildschirm werden, wie Sie sehen können, einige Informationen angezeigt, die Sie angeben müssen.
- Produktname: Ich habe es T9Search genannt
- Mannschaft . Wenn Sie diese Anwendung auf einem echten Gerät ausführen möchten, müssen Sie über ein Entwicklerkonto verfügen. In meinem Fall werde ich dafür mein eigenes Konto verwenden.
Hinweis: Wenn Sie kein Entwicklerkonto haben, können Sie dieses auch im Simulator ausführen.
- Name der Organisation: Ich habe sie Toptal genannt
- Organisationskennung: Ich habe sie „com.toptal“ genannt
- Sprache: Wählen Sie Swift
- Deaktivieren Sie „Kerndaten verwenden“, „Einheitentests einbeziehen“ und „UI-Tests einbeziehen“.
Drücken Sie die Schaltfläche Weiter, und wir können beginnen.
Einfache Architektur
Wie Sie bereits wissen, haben Sie beim Erstellen einer neuen App bereits die Klasse MainViewController
und Main.Storyboard
. Zu Testzwecken können wir diesen Controller natürlich verwenden.
Bevor wir mit dem Entwerfen beginnen, erstellen wir zunächst alle erforderlichen Klassen und Dateien, um sicherzustellen, dass alles eingerichtet und ausgeführt wird, um zum UI-Teil des Jobs zu wechseln.
Erstellen Sie einfach irgendwo in Ihrem Projekt eine neue Datei mit dem Namen „ PhoneContactsStore.swift “. In meinem Fall sieht es so aus.
Unsere erste Aufgabe besteht darin, eine Karte mit allen Variationen von numerischen Tastatureingaben zu erstellen.
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" ]
Das ist es. Wir haben die komplette Map mit allen Varianten umgesetzt. Lassen Sie uns nun damit fortfahren, unsere erste Klasse mit dem Namen " PhoneContact " zu erstellen.
Ihre Datei sollte wie folgt aussehen:
Zuerst müssen wir in dieser Klasse sicherstellen, dass wir einen Regex-Filter von AZ + 0-9 haben.
private let regex = try! NSRegularExpression(pattern: "[^ az()0-9+]", options: .caseInsensitive)
Grundsätzlich hat der Benutzer Standardeigenschaften, die angezeigt werden müssen:
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) } }
Stellen Sie sicher, dass Sie hash
und isEqual
überschrieben haben, um Ihre benutzerdefinierte Logik für die Listenfilterung anzugeben.
Außerdem brauchen wir die Methode replace, um zu vermeiden, dass irgendetwas außer Zahlen in der Zeichenfolge enthalten ist.
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: "") }
Jetzt brauchen wir eine weitere Methode namens calculateT9
, um Kontakte zu finden, die sich auf fullname
oder phonenumber
.

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)) } }
Nach der Implementierung des PhoneContact
Objekts müssen wir unsere Kontakte irgendwo im Speicher speichern. Zu diesem Zweck werde ich eine neue Klasse namens PhoneContactStore
.
Wir werden zwei lokale Eigenschaften haben:
fileprivate let contactsStore = CNContactStore()
Und:
fileprivate lazy var dataSource = Set<PhoneContact>()
Ich verwende Set
, um sicherzustellen, dass beim Ausfüllen dieser Datenquelle keine Duplizierung erfolgt.
final class PhoneContactStore { fileprivate let contactsStore = CNContactStore() fileprivate lazy var dataSource = Set<PhoneContact>() static let instance : PhoneContactStore = { let instance = PhoneContactStore() return instance }() }
Wie Sie sehen können, ist dies eine Singleton-Klasse, was bedeutet, dass wir sie im Speicher behalten, bis die App ausgeführt wird. Weitere Informationen zu Singletons oder Entwurfsmustern finden Sie hier.
Wir stehen jetzt kurz vor dem Abschluss der T9-Suche.
Alles zusammenfügen
Bevor Sie auf die Kontaktliste von Apple zugreifen, müssen Sie zuerst um Erlaubnis fragen.
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)) } }
Nachdem wir den Zugriff auf Kontakte autorisiert haben, können wir die Methode schreiben, um die Liste aus dem System abzurufen.
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 versions try 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.stringValue let strArr = phoneNum.components(separatedBy: CharacterSet.decimalDigits.inverted) phoneNum = NSArray(array: strArr).componentsJoined(by: "") local.phoneNumber = phoneNum local.calculateT9() self.dataSource.insert(local) } } }) } catch {} } }
Die Kontaktliste haben wir bereits in den Speicher geladen, sodass wir nun eine einfache Methode schreiben können:
-
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 }
Das ist es. Wir sind fertig.
Jetzt können wir die T9-Suche in UIViewController
.
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 = true override func viewDidLoad() { super.viewDidLoad() self.tableView.register( UINib( nibName: "ContactListCell", bundle: nil ), forCellReuseIdentifier: "ContactListCell" ) self.searchBar.keyboardType = .numberPad PhoneContactStore.requestForAccess { (ok, err) in } } func filter(searchString: String, t9: Bool = true) { } func reloadListSection(section: Int, animation: UITableViewRowAnimation = .none) { } }
Implementierung der Filtermethode:
func filter(searchString: String, t9: Bool = true) { self.searchString = searchString self.searchInT9 = t9 if 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) }
Implementierung der Reload-List-Methode:
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) }
Und hier ist der letzte Teil unseres kurzen Tutorials, UITableView
Implementierung:
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) } }
Einpacken
Damit ist unser T9-Suchtutorial abgeschlossen, und hoffentlich fanden Sie es unkompliziert und einfach in iOS zu implementieren.
Aber warum sollten Sie? Und warum hat Apple T9-Unterstützung nicht von Anfang an in iOS integriert? Wie wir in der Einleitung betont haben, ist T9 kaum ein Killer-Feature auf den heutigen Telefonen – es ist eher ein nachträglicher Einfall, ein Rückblick auf die Tage der „dummen“ Telefone mit mechanischen Nummernblöcken.
Es gibt jedoch immer noch einige triftige Gründe, warum Sie die T9-Suche in bestimmten Szenarien implementieren sollten, entweder aus Gründen der Konsistenz oder um die Zugänglichkeit und Benutzererfahrung zu verbessern. Auf einer fröhlicheren Anmerkung, wenn Sie der nostalgische Typ sind, könnte das Herumspielen mit T9-Eingaben schöne Erinnerungen an Ihre Schulzeit wecken.
Schließlich finden Sie den vollständigen Code für die T9-Implementierung in iOS in meinem GitHub-Repo.