Cum să implementați Căutarea T9 în iOS

Publicat: 2022-03-11

În urmă cu câțiva ani, lucram la o aplicație numită „BOG mBank - Mobile Banking” cu echipa mea iOS/Android. Există o caracteristică de bază în aplicație în care puteți utiliza funcționalitatea mobile banking pentru a vă încărca propriul sold postplătit al telefonului mobil sau soldul telefonului mobil al oricărei persoane de contact.

În timpul dezvoltării acestui modul, am observat că a fost mult mai ușor să găsiți un anumit contact în versiunea pentru Android a aplicației decât în ​​cea pentru iOS. De ce? Motivul cheie din spatele acestui lucru este căutarea T9, care lipsește de pe dispozitivele Apple.

Să explicăm despre ce este T9 și de ce probabil nu a devenit parte a iOS și cum îl pot implementa dezvoltatorii iOS dacă este necesar.

Ce este T9?

T9 este o tehnologie de text predictiv pentru telefoanele mobile, în special cele care conțin o tastatură numerică fizică 3x4.

Ilustrație a căutării T9 pe tastatura numerică

T9 a fost dezvoltat inițial de Tegic Communications, iar numele înseamnă Text pe 9 taste .

Puteți ghici de ce probabil T9 nu a ajuns niciodată la iOS. În timpul revoluției smartphone-urilor, intrarea T9 a devenit învechită, deoarece telefoanele moderne moderne se bazau pe tastaturi complete, datorită ecranelor lor tactile. Deoarece Apple nu a avut niciodată telefoane cu tastaturi fizice și nu a fost în afacerea cu telefoane în perioada de glorie a lui T9, este de înțeles că această tehnologie a fost omisă din iOS.

T9 este încă folosit pe anumite telefoane ieftine fără ecran tactil (așa-numitele telefoane cu caracteristici). Cu toate acestea, în ciuda faptului că majoritatea telefoanelor Android nu au prezentat niciodată tastaturi fizice, dispozitivele moderne Android oferă suport pentru intrarea T9, care poate fi folosită pentru a apela contacte prin ortografierea numelui contactului pe care încearcă să îl apeleze.

Un exemplu de intrare predictivă T9 în acțiune

Pe un telefon cu tastatură numerică, de fiecare dată când este apăsată o tastă (1-9) (când se află într-un câmp de text), algoritmul returnează o ghicire pentru ce litere sunt cel mai probabil pentru tastele apăsate până la acel punct.

Captură de ecran Xcode

De exemplu, pentru a introduce cuvântul „the”, utilizatorul ar apăsa 8, apoi 4, apoi 3, iar afișajul va afișa „t”, apoi „th” și apoi „the”. Dacă se dorește cuvântul mai puțin obișnuit „fore” (3673), algoritmul predictiv poate selecta „Ford”. Apăsarea tastei „următorul” (de obicei, tasta „*”) poate afișa „doză” și, în final, „fore”. Dacă este selectat „fore”, atunci când utilizatorul va apăsa următoarea secvență 3673, este mai probabil ca fore să fie primul cuvânt afișat. Dacă cuvântul „Felix” este destinat, totuși, atunci când introduceți 33549, afișajul arată „E”, apoi „De”, „Del”, „Deli” și „Felix”.

Acesta este un exemplu de schimbare a unei litere în timp ce introduceți cuvinte.

Utilizarea programatică a lui T9 în iOS

Deci, haideți să ne aprofundăm în această funcție și să scriem un exemplu simplu de intrare T9 pentru iOS. În primul rând, trebuie să creăm un nou proiect.

Cerințele preliminare necesare pentru proiectul nostru sunt de bază: instrumentele de compilare Xcode și Xcode instalate pe Mac.

Pentru a crea un proiect nou, deschideți aplicația dvs. Xcode pe Mac și selectați „Creați un nou proiect Xcode”, apoi denumiți proiectul și alegeți tipul de aplicație care trebuie creat. Pur și simplu selectați „Single View App” și apăsați Next.

Captură de ecran Xcode

Pe ecranul următor, după cum puteți vedea, vor fi câteva informații pe care trebuie să le furnizați.

  • Nume produs: l-am numit T9Search
  • Echipa . Aici, dacă doriți să rulați această aplicație pe un dispozitiv real, va trebui să aveți un cont de dezvoltator. În cazul meu, voi folosi propriul meu cont pentru asta.

Notă: dacă nu aveți un cont de dezvoltator, îl puteți rula și pe Simulator.

  • Numele organizației: L-am numit Toptal
  • Identificator organizație: l-am numit „com.toptal”
  • Limbă: alegeți Swift
  • Debifați „Utilizați datele de bază”, „Includeți teste unitare” și „Includeți teste UI”

Apăsați butonul Următorul și suntem gata să începem.

Arhitectură simplă

După cum știți deja, atunci când creați o nouă aplicație, aveți deja clasa MainViewController și Main.Storyboard . În scopuri de testare, desigur, putem folosi acest controler.

Înainte de a începe să proiectăm ceva, să creăm mai întâi toate clasele și fișierele necesare pentru a ne asigura că avem totul configurat și rulează pentru a trece la partea UI a jobului.

Undeva în interiorul proiectului, pur și simplu creați un fișier nou numit „ PhoneContactsStore.swift ” În cazul meu, arată așa.

T9 caută storboard și arhitectură

Prima noastră activitate este să creăm o hartă cu toate variantele de introducere a tastaturii numerice.

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

Asta e. Am implementat harta completă cu toate variantele. Acum, să continuăm să creăm prima noastră clasă numită „ Contact telefonic ”.

Fișierul dvs. ar trebui să arate astfel:

text alt imagine

În primul rând, în această clasă, trebuie să ne asigurăm că avem un filtru Regex de la AZ + 0-9.

private let regex = try! NSRegularExpression(pattern: "[^ az()0-9+]", options: .caseInsensitive)

Practic, utilizatorul are proprietăți implicite care trebuie afișate:

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

Asigurați-vă că ați suprascris hash și isEqual pentru a specifica logica personalizată pentru filtrarea listelor.

De asemenea, trebuie să avem metoda de înlocuire pentru a evita să avem nimic în afară de numere în șir.

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

Acum avem nevoie de încă o metodă numită calculateT9 , pentru a găsi contacte legate de fullname sau 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)) } }

După implementarea obiectului PhoneContact , trebuie să ne stocăm contactele undeva în memorie. În acest scop, voi crea o nouă clasă numită PhoneContactStore .

Vom avea două proprietăți locale:

fileprivate let contactsStore = CNContactStore()

Și:

fileprivate lazy var dataSource = Set<PhoneContact>()

Folosesc Set pentru a mă asigura că nu există o dublare în timpul completării acestei surse de date.

 final class PhoneContactStore { fileprivate let contactsStore = CNContactStore() fileprivate lazy var dataSource = Set<PhoneContact>() static let instance : PhoneContactStore = { let instance = PhoneContactStore() return instance }() }

După cum puteți vedea, aceasta este o clasă Singleton, ceea ce înseamnă că o păstrăm în memorie până când aplicația rulează. Pentru mai multe informații despre Singletons sau modele de design, puteți citi aici.

Acum suntem foarte aproape de finalizarea căutării T9.

Punând totul laolaltă

Înainte de a accesa lista de contacte pe Apple, mai întâi trebuie să ceri permisiunea.

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

După ce ne-am autorizat să accesăm contactele, putem scrie metoda pentru a obține lista din sistem.

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

Am încărcat deja lista de contacte în memorie, ceea ce înseamnă că acum putem scrie o metodă simplă:

  1. findWith - t9String
  2. 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 }

Asta e. Am terminat.

Acum putem folosi căutarea T9 în 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) { } }

Implementarea metodei de filtrare:

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

Implementarea metodei Lista de reîncărcare:

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

Și iată ultima parte a scurtului nostru tutorial, implementarea 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) } }

Încheierea

Aceasta încheie tutorialul nostru de căutare T9 și sperăm că l-ați găsit simplu și ușor de implementat în iOS.

Dar de ce ar trebui să? Și de ce nu a inclus Apple pentru început suportul T9 în iOS? Așa cum am subliniat în introducere, T9 nu este o caracteristică ucigașă a telefoanelor de astăzi - este mai degrabă o gândire ulterioară, o întoarcere la vremurile telefoanelor „proști” cu tampoane numerice mecanice.

Cu toate acestea, există încă câteva motive valide pentru care ar trebui să implementați căutarea T9 în anumite scenarii, fie de dragul coerenței, fie pentru a îmbunătăți accesibilitatea și experiența utilizatorului. Pe o notă mai veselă, dacă ești genul nostalgic, jocul cu intrarea T9 ar putea readuce amintiri frumoase din zilele tale de școală.

În cele din urmă, puteți găsi codul complet pentru implementarea T9 în iOS la depozitul meu GitHub.