Cara Menerapkan Pencarian T9 di iOS

Diterbitkan: 2022-03-11

Beberapa tahun yang lalu saya sedang mengerjakan sebuah aplikasi bernama “BOG mBank - Mobile Banking” dengan tim iOS/Android saya. Ada fitur dasar dalam aplikasi di mana Anda dapat menggunakan fungsionalitas mobile banking untuk mengisi saldo pascabayar ponsel Anda sendiri atau saldo ponsel kontak mana pun.

Saat mengembangkan modul ini, kami melihat bahwa jauh lebih mudah untuk menemukan kontak tertentu di aplikasi versi Android daripada di iOS. Mengapa? Alasan utama di balik ini adalah pencarian T9, yang hilang dari perangkat Apple.

Mari kita jelaskan apa itu T9, dan mengapa T9 mungkin tidak menjadi bagian dari iOS, dan bagaimana pengembang iOS dapat menerapkannya jika perlu.

Apa itu T9?

T9 adalah teknologi teks prediktif untuk ponsel, khususnya yang memiliki keypad numerik 3x4 fisik.

Ilustrasi pencarian T9 pada keypad numerik

T9 awalnya dikembangkan oleh Tegic Communications, dan namanya adalah singkatan dari Text on 9 keys .

Anda bisa menebak mengapa T9 mungkin tidak pernah sampai ke iOS. Selama revolusi smartphone, input T9 menjadi usang, karena ponsel smartphone modern mengandalkan keyboard lengkap, berkat tampilan layar sentuh mereka. Karena Apple tidak pernah memiliki ponsel dengan keyboard fisik dan tidak berada dalam bisnis ponsel selama masa kejayaan T9, dapat dimengerti bahwa teknologi ini dihilangkan dari iOS.

T9 masih digunakan pada ponsel murah tertentu tanpa layar sentuh (disebut ponsel fitur). Namun, terlepas dari kenyataan bahwa sebagian besar ponsel Android tidak pernah menampilkan keyboard fisik, perangkat Android modern memiliki dukungan untuk input T9, yang dapat digunakan untuk menghubungi kontak dengan mengeja nama kontak yang coba dihubungi.

Contoh Input Prediktif T9 dalam Tindakan

Pada telepon dengan papan tombol numerik, setiap kali tombol (1-9) ditekan (ketika dalam bidang teks), algoritme mengembalikan tebakan untuk huruf apa yang paling mungkin untuk tombol yang ditekan ke titik itu.

Tangkapan layar Xcode

Misalnya, untuk memasukkan kata “the”, pengguna akan menekan 8 lalu 4 lalu 3, dan tampilan akan menampilkan “t”, lalu “th”, dan kemudian “the.” Jika kata "kedepan" yang kurang umum dimaksudkan (3673), algoritme prediktif dapat memilih "Ford." Menekan tombol "berikutnya" (biasanya tombol "*") dapat memunculkan "dosis", dan akhirnya "kedepan". Jika "kedepan" dipilih, maka saat berikutnya pengguna menekan urutan 3673, kata depan akan lebih cenderung menjadi kata pertama yang ditampilkan. Namun, jika kata “Felix” dimaksudkan, saat memasuki 33549, layar menunjukkan “E,” lalu “De,” “Del,” “Deli,” dan “Felix.”

Ini adalah contoh perubahan huruf saat memasukkan kata.

Penggunaan Terprogram T9 di iOS

Jadi, mari selami fitur ini dan tulis contoh mudah input T9 untuk iOS. Pertama-tama, kita perlu membuat proyek baru.

Prasyarat yang diperlukan untuk proyek kami adalah dasar: Xcode dan Xcode build tools diinstal pada Mac Anda.

Untuk membuat proyek baru, buka aplikasi Xcode Anda di Mac dan pilih “Buat proyek Xcode baru,” lalu beri nama proyek Anda, dan pilih jenis aplikasi yang akan dibuat. Cukup pilih "Aplikasi Tampilan Tunggal" dan tekan Berikutnya.

Tangkapan layar Xcode

Di layar berikutnya, seperti yang Anda lihat akan ada beberapa info yang perlu Anda berikan.

  • Nama Produk: Saya menamakannya T9Search
  • tim . Di sini, jika Anda ingin menjalankan aplikasi ini di perangkat nyata, Anda harus memiliki akun pengembang. Dalam kasus saya, saya akan menggunakan akun saya sendiri untuk ini.

Catatan: Jika Anda tidak memiliki akun pengembang, Anda juga dapat menjalankannya di Simulator.

  • Nama Organisasi: Saya menamakannya Toptal
  • Pengidentifikasi Organisasi: Saya menamakannya “com.toptal”
  • Bahasa: Pilih Swift
  • Hapus centang "Gunakan Data Inti," "Sertakan Tes Unit," dan "Sertakan Tes UI"

Tekan tombol Next, dan kita siap untuk memulai.

Arsitektur Sederhana

Seperti yang sudah Anda ketahui, saat Anda membuat aplikasi baru, Anda sudah memiliki kelas MainViewController dan Main.Storyboard . Untuk keperluan pengujian tentunya kita bisa menggunakan controller ini.

Sebelum kita mulai mendesain sesuatu, pertama-tama mari kita buat semua kelas dan file yang diperlukan untuk memastikan kita sudah menyiapkan dan menjalankan semuanya untuk pindah ke bagian UI dari pekerjaan.

Di suatu tempat di dalam proyek Anda, cukup buat file baru bernama " PhoneContactsStore.swift " Dalam kasus saya, ini terlihat seperti ini.

storboard dan arsitektur pencarian T9

Urutan bisnis pertama kami adalah membuat peta dengan semua variasi input keyboard numerik.

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

Itu dia. Kami telah menerapkan peta lengkap dengan semua variasi. Sekarang, mari kita lanjutkan untuk membuat kelas pertama kita yang disebut " PhoneContact ."

File Anda akan terlihat seperti ini:

teks alternatif gambar

Pertama, di kelas ini, kita perlu memastikan bahwa kita memiliki Filter Regex dari AZ + 0-9.

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

Pada dasarnya, pengguna memiliki properti default yang perlu ditampilkan:

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

Pastikan Anda telah mengganti hash dan isEqual untuk menentukan logika kustom Anda untuk pemfilteran daftar.

Juga, kita perlu memiliki metode ganti untuk menghindari apa pun kecuali angka dalam string.

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

Sekarang kita memerlukan satu metode lagi yang disebut calculateT9 , untuk menemukan kontak yang terkait dengan fullname atau nomor 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)) } }

Setelah mengimplementasikan objek PhoneContact , kita perlu menyimpan kontak kita di suatu tempat di memori. Untuk tujuan ini, saya akan membuat kelas baru bernama PhoneContactStore .

Kami akan memiliki dua properti lokal:

fileprivate let contactsStore = CNContactStore()

Dan:

fileprivate lazy var dataSource = Set<PhoneContact>()

Saya menggunakan Set untuk memastikan tidak ada duplikasi saat mengisi sumber data ini.

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

Seperti yang Anda lihat, ini adalah kelas Singleton, yang berarti kami menyimpannya di memori hingga aplikasi berjalan. Untuk informasi lebih lanjut tentang Singletons atau pola desain, Anda bisa membaca di sini.

Kami sekarang sangat dekat untuk menyelesaikan pencarian T9.

Menyatukan Semuanya

Sebelum Anda mengakses daftar kontak di Apple, Anda perlu meminta izin terlebih dahulu.

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

Setelah kami memiliki wewenang untuk mengakses kontak, kami dapat menulis metode untuk mendapatkan daftar dari 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 {} } }

Kami telah memuat daftar kontak ke dalam memori, yang berarti kami sekarang dapat menulis metode sederhana:

  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 }

Itu dia. Kami selesai.

Sekarang kita dapat menggunakan pencarian T9 di dalam 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) { } }

Penerapan metode filter:

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

Implementasi metode Reload List:

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

Dan inilah bagian terakhir dari tutorial singkat kami, implementasi 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) } }

Membungkus

Ini mengakhiri tutorial pencarian T9 kami, dan mudah-mudahan Anda menemukannya dengan mudah dan mudah diterapkan di iOS.

Tapi kenapa harus? Dan mengapa Apple tidak menyertakan dukungan T9 di iOS untuk memulai? Seperti yang kami tunjukkan dalam pendahuluan, T9 bukanlah fitur pembunuh pada ponsel saat ini - ini lebih merupakan renungan, kemunduran ke hari-hari ponsel "bodoh" dengan bantalan nomor mekanis.

Namun, masih ada beberapa alasan yang sah mengapa Anda harus menerapkan pencarian T9 dalam skenario tertentu, baik demi konsistensi, atau untuk meningkatkan aksesibilitas dan pengalaman pengguna. Pada nada yang lebih ceria, jika Anda tipe nostalgia, bermain-main dengan input T9 dapat membawa kembali kenangan indah masa sekolah Anda.

Terakhir, Anda dapat menemukan kode lengkap untuk implementasi T9 di iOS di repo GitHub saya.