iOS'ta T9 Arama Nasıl Uygulanır?
Yayınlanan: 2022-03-11Birkaç yıl önce iOS/Android ekibimle “BOG mBank - Mobil Bankacılık” adlı bir uygulama üzerinde çalışıyordum. Uygulamada, kendi cep telefonu faturalı bakiyenizi veya herhangi bir kişinin cep telefonu bakiyesini doldurmak için mobil bankacılık işlevini kullanabileceğiniz temel bir özellik vardır.
Bu modülü geliştirirken, uygulamanın Android sürümünde belirli bir kişiyi bulmanın iOS sürümünden çok daha kolay olduğunu fark ettik. Niye ya? Bunun en önemli nedeni ise Apple cihazlarında eksik olan T9 araması.
T9'un neyle ilgili olduğunu ve muhtemelen neden iOS'un bir parçası haline gelmediğini ve gerekirse iOS geliştiricilerinin bunu nasıl uygulayabileceğini açıklayalım.
T9 nedir?
T9 , özellikle fiziksel bir 3x4 sayısal tuş takımı içeren cep telefonları için akıllı bir metin teknolojisidir.
T9, orijinal olarak Tegic Communications tarafından geliştirilmiştir ve adı , 9 tuşlu Metin anlamına gelir.
T9'un neden iOS'a hiç gelmediğini tahmin edebilirsiniz. Akıllı telefon devrimi sırasında, modern akıllı telefon telefonları dokunmatik ekranları sayesinde tam klavyelere dayandığından, T9 girişi modası geçmiş oldu. Apple'ın hiçbir zaman fiziksel klavyeli telefonları olmadığından ve T9'un en parlak döneminde telefon işinde bulunmadığından, bu teknolojinin iOS'tan çıkarılması anlaşılabilir.
T9, dokunmatik ekranı olmayan bazı ucuz telefonlarda hala kullanılmaktadır (özellikli telefonlar olarak adlandırılır). Bununla birlikte, çoğu Android telefonun hiçbir zaman fiziksel klavyeye sahip olmamasına rağmen, modern Android cihazlar, kişinin aramaya çalıştığı kişinin adını heceleyerek kişileri aramak için kullanılabilen T9 girişini destekler.
Eylemde T9 Tahmini Girdi Örneği
Sayısal tuş takımı olan bir telefonda, bir tuşa (1-9) her basıldığında (metin alanındayken), algoritma o noktaya kadar basılan tuşlar için hangi harflerin en olası olduğuna dair bir tahmin döndürür.
Örneğin, "the" kelimesini girmek için kullanıcı 8'e, ardından 4'e ve ardından 3'e basar ve ekranda "t", ardından "th" ve ardından "the" görüntülenir. Daha az yaygın olan "ön" kelimesi amaçlanıyorsa (3673), tahmin algoritması "Ford"u seçebilir. "Sonraki" tuşuna (tipik olarak "*" tuşu) basılması "doz" ve son olarak "ön" kelimesini getirebilir. "Ön" seçilirse, kullanıcı 3673 dizisine bir sonraki basışında, görüntülenen ilk sözcüğün fore olması daha olasıdır. Ancak “Felix” kelimesi amaçlanıyorsa, 33549 girilirken ekranda “E”, ardından “De”, “Del”, “Deli” ve “Felix” gösterilir.
Bu, kelime girerken harfin değişmesine bir örnektir.
iOS'ta T9'un Programlı Kullanımı
Öyleyse, bu özelliğe dalalım ve iOS için kolay bir T9 girişi örneği yazalım. Öncelikle yeni bir proje oluşturmamız gerekiyor.
Projemiz için gereken ön koşullar temeldir: Mac'inizde yüklü Xcode ve Xcode oluşturma araçları.
Yeni bir proje oluşturmak için Mac'inizde Xcode uygulamanızı açın ve "Yeni bir Xcode projesi oluştur"u seçin, ardından projenize bir ad verin ve oluşturulacak uygulama türünü seçin. Basitçe “Single View Uygulaması”nı seçin ve İleri'ye basın.
Bir sonraki ekranda, göreceğiniz gibi, sağlamanız gereken bazı bilgiler olacak.
- Ürün Adı: Adını T9Search koydum
- takım . Burada, bu uygulamayı gerçek bir cihazda çalıştırmak istiyorsanız, bir geliştirici hesabına sahip olmanız gerekecek. Benim durumumda, bunun için kendi hesabımı kullanacağım.
Not: Geliştirici hesabınız yoksa bunu Simulator'da da çalıştırabilirsiniz.
- Kuruluş Adı: Adını Toptal koydum
- Kuruluş Tanımlayıcı: Adını “com.toptal” olarak verdim
- Dil: Swift'i seçin
- "Çekirdek Verileri Kullan", "Birim Testlerini Dahil Et" ve "Kullanıcı Arayüzü Testlerini Dahil Et" seçeneğinin işaretini kaldırın.
İleri düğmesine basın ve başlamaya hazırız.
Basit Mimari
Bildiğiniz gibi, yeni bir uygulama oluşturduğunuzda, zaten MainViewController
sınıfınız ve Main.Storyboard
. Test amacıyla elbette bu denetleyiciyi kullanabiliriz.
Bir şey tasarlamaya başlamadan önce, işin UI kısmına geçmek için her şeyin kurulduğundan ve çalıştığından emin olmak için gerekli tüm sınıfları ve dosyaları oluşturalım.
Projenizin içinde bir yerde “ PhoneContactsStore.swift ” adlı yeni bir dosya oluşturun. Benim durumumda şöyle görünüyor.
İlk işimiz, sayısal klavye girişlerinin tüm çeşitlerini içeren bir harita oluşturmaktır.
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" ]
Bu kadar. Tüm varyasyonlarla eksiksiz haritayı uyguladık. Şimdi “ PhoneContact ” adlı ilk sınıfımızı oluşturmaya devam edelim.
Dosyanız şöyle görünmelidir:
Öncelikle bu sınıfta AZ + 0-9'dan bir Regex Filtresine sahip olduğumuzdan emin olmamız gerekiyor.
private let regex = try! NSRegularExpression(pattern: "[^ az()0-9+]", options: .caseInsensitive)
Temel olarak, kullanıcının görüntülenmesi gereken varsayılan özellikleri vardır:
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) } }
Liste filtreleme için özel mantığınızı belirtmek için geçersiz kılınmış hash
ve isEqual
sahip olduğunuzdan emin olun.
Ayrıca, dizede sayılar dışında bir şey olmaması için replace yöntemine sahip olmamız gerekir.
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: "") }
Şimdi tam fullname
veya phonenumber
ile ilgili kişileri bulmak için calculateT9
adlı bir yönteme daha ihtiyacımız var.

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
nesnesini uyguladıktan sonra, kişilerimizi hafızada bir yerde saklamamız gerekiyor. Bu amaçla PhoneContactStore
adında yeni bir sınıf oluşturacağım.
İki yerel özelliğimiz olacak:
fileprivate let contactsStore = CNContactStore()
Ve:
fileprivate lazy var dataSource = Set<PhoneContact>()
Bu veri kaynağını doldururken tekrarlama olmadığından emin olmak için Set
kullanıyorum.
final class PhoneContactStore { fileprivate let contactsStore = CNContactStore() fileprivate lazy var dataSource = Set<PhoneContact>() static let instance : PhoneContactStore = { let instance = PhoneContactStore() return instance }() }
Gördüğünüz gibi, bu bir Singleton sınıfıdır, yani uygulama çalışana kadar onu bellekte tutarız. Singletons veya tasarım desenleri hakkında daha fazla bilgi için burayı okuyabilirsiniz.
Artık T9 aramasının sonuçlandırılmasına çok yakınız.
Hepsini bir araya koy
Apple'daki kişiler listesine erişmeden önce izin istemeniz gerekir.
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)) } }
Kişilere erişim yetkisi verdikten sonra sistemden liste alma yöntemini yazabiliriz.
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 {} } }
Kişi listesini zaten belleğe yükledik, yani artık basit bir yöntem yazabiliriz:
-
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 }
Bu kadar. İşimiz bitti.
Artık UIViewController
içinde T9 aramasını kullanabiliriz.
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) { } }
Filtre yöntemi uygulaması:
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) }
Yeniden Yükleme Listesi yöntemi uygulaması:
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) }
Ve işte kısa öğreticimiz UITableView
uygulamasının son kısmı:
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) } }
Toplama
Bu, T9 arama eğitimimizi tamamlıyor ve umarım bunu iOS'ta uygulamayı basit ve kolay bulmuşsunuzdur.
Ama neden yapmalısın? Ve Apple neden başlangıçta iOS'ta T9 desteğini dahil etmedi? Girişte belirttiğimiz gibi, T9 günümüz telefonlarında pek de öldürücü bir özellik değil - daha çok sonradan düşünülmüş, mekanik sayısal tuş takımlarına sahip “aptal” telefon günlerine bir geri dönüş.
Ancak, tutarlılık veya erişilebilirlik ve kullanıcı deneyimini iyileştirmek için belirli senaryolarda T9 aramasını uygulamanız için hala birkaç geçerli neden vardır. Daha neşeli bir kayda göre, nostaljik biriyseniz, T9 girişi ile oynamak okul günlerinize dair güzel anıları geri getirebilir.
Son olarak, iOS'ta T9 uygulamasının tam kodunu GitHub depomda bulabilirsiniz.