.t9String.contains(t9String) }) } class func findWith(str: String) -> [PhoneContact] { return PhoneContactStore.instance .dataSource.filter({

T9-haun toteuttaminen iOS: ssä

Pari vuotta sitten työskentelin sovelluksella nimeltä “ BOG mBank - mobiilipankki ”IOS / Android-tiimini kanssa. Sovelluksessa on perusominaisuus, jossa voit käyttää mobiilipankkitoimintoa oman matkapuhelimesi jälkikäteen maksettavan saldon tai minkä tahansa yhteyshenkilön matkapuhelintasaldon lisäämiseen.

Tätä moduulia kehitettäessä huomasimme, että tietyn kontaktin löytäminen sovelluksen Android-versiosta oli paljon helpompaa kuin iOS-versiosta. Miksi? Tärkein syy tähän on T9-haku, joka puuttuu Apple-laitteista.

Selitetään, mistä T9 on kyse, ja miksi siitä ei todennäköisesti tullut osa iOS: ää ja miten iOS-kehittäjät voi toteuttaa sen tarvittaessa.



Mikä on T9?

T9 on ennakoiva tekstitekniikka matkapuhelimille, erityisesti niille, jotka sisältävät fyysisen 3x4-numeronäppäimistön.

Kuva T9-hausta numeronäppäimistöllä

T9 on alun perin kehittänyt Tegic Communications , ja nimi tarkoittaa Teksti 9 näppäimellä .

Voit arvata, miksi T9 ei todennäköisesti koskaan päässyt iOS: ään. Älypuhelimien vallankumouksen aikana T9-tulo vanhentui, kun nykyaikaiset älypuhelimet tukeutuivat täydellisiin näppäimistöihin kosketusnäytönsä ansiosta. Koska Applella ei koskaan ollut fyysisillä näppäimistöillä varustettuja puhelimia eikä se ollut puhelinliiketoiminnassa T9: n kukoistuksen aikana, on ymmärrettävää, että tämä tekniikka jätettiin pois iOS: sta.

T9: ää käytetään edelleen tietyissä edullisissa puhelimissa, joissa ei ole kosketusnäyttöä (ns. Ominaisuuspuhelimia). Huolimatta siitä, että useimmissa Android-puhelimissa ei koskaan ollut fyysisiä näppäimistöjä, moderneissa Android-laitteissa on tuki T9-tulolle, jota voidaan käyttää yhteystietojen soittamiseen kirjoittamalla sen kontaktin nimi, johon yrität soittaa.

Esimerkki T9: n ennakoivasta syötteestä toiminnassa

Puhelimessa, jossa on numeronäppäimistö, joka kerta kun näppäintä (1-9) painetaan (kun se on tekstikentässä), algoritmi palauttaa arvailun siitä, mitkä kirjaimet todennäköisimmin painetaan kyseiseen pisteeseen.

Xcode-kuvakaappaus

Esimerkiksi kirjoittamalla sana 'the', käyttäjä painaa 8 ja sitten 4 ja 3, ja näytössä näkyy 't', sitten 'th' ja sitten 'the'. Jos tarkoitetaan harvinaisempaa sanaa 'edestä' (3673), ennustava algoritmi voi valita 'Ford'. 'Seuraava' -näppäimen (yleensä '*' -näppäimen) painaminen saattaa nostaa 'annoksen' ja lopulta 'etukäteen'. Jos ”edestä” on valittu, seuraavalla kerralla, kun käyttäjä painaa sekvenssiä 3673, etu on todennäköisimmin ensimmäinen näytettävä sana. Jos sana 'Felix' on tarkoitettu, kirjoitettaessa 33549 näyttöön tulee kuitenkin 'E', sitten 'De', 'Del', 'Deli' ja 'Felix'.

Tämä on esimerkki kirjaimen vaihtumisesta kirjoitettaessa sanoja.

T9: n ohjelmallinen käyttö iOS: ssä

Joten, sukelletaan tähän ominaisuuteen ja kirjoitetaan helppo esimerkki T9-syötteestä iOS: lle. Ensinnäkin meidän on luotava uusi projekti.

Projektimme edellyttämät edellytykset ovat perustiedot: Maciin asennetut Xcode- ja Xcode-rakennustyökalut.

Luo uusi projekti avaamalla Xcode-sovellus Mac-tietokoneellasi ja valitse 'Luo uusi Xcode-projekti', nimeä sitten projekti ja valitse luotavan sovelluksen tyyppi. Valitse yksinkertaisesti “Single View App” ja paina Seuraava.

Xcode-kuvakaappaus

Seuraavassa näytössä, kuten näet, on joitain tietoja, jotka sinun on annettava.

Huomautus: Jos sinulla ei ole kehittäjätiliä, voit suorittaa tämän myös Simulatorissa.

Paina Seuraava-painiketta ja olemme valmiita aloittamaan.

Yksinkertainen arkkitehtuuri

Kuten jo tiedät, kun luot uuden sovelluksen, sinulla on jo MainViewController luokka ja Main.Storyboard. Testaustarkoituksiin voimme tietysti käyttää tätä ohjainta.

Ennen kuin aloitamme suunnitella jotain, luodaan ensin kaikki tarvittavat luokat ja tiedostot varmistaaksemme, että meillä on kaikki valmiit ja käynnissä siirtyäksesi käyttöliittymän työhön.

Luo jossain projektin sisällä yksinkertaisesti uusi tiedosto nimeltä PhoneContactsStore.swift ”Minun tapauksessani se näyttää tältä.

T9-hakutaulukko ja arkkitehtuuri

Ensimmäinen toimintatavamme on luoda kartta, jossa on kaikki numeronäppäimistön syötteiden muunnelmat.

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

Se siitä. Olemme toteuttaneet täydellisen kartan kaikilla muunnelmilla. Luodaan nyt ensimmäinen luokka nimeltä ' Ota yhteyttä '

Tiedostosi tulisi näyttää tältä:

kuvan vaihtoehtoinen teksti

Ensinnäkin tässä luokassa meidän on varmistettava, että meillä on Regex-suodatin A-Z + 0-9.

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

Periaatteessa käyttäjällä on oletusominaisuudet, jotka on näytettävä:

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

Varmista, että olet ohittanut hash ja isEqual Määritä mukautettu logiikka luettelosuodatusta varten.

Lisäksi meillä on oltava korvausmenetelmä, jotta merkkijonossa ei ole mitään muuta kuin numeroita.

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

Tarvitsemme vielä yhden menetelmän nimeltä calculateT9, jotta löydämme fullname: iin liittyvät yhteystiedot tai 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)) } }

Kun PhoneContact esine, meidän on tallennettava yhteystietomme jonnekin muistiin. Tätä tarkoitusta varten aion luoda uuden luokan nimeltä PhoneContactStore.

Meillä on kaksi paikallista kiinteistöä:

fileprivate let contactsStore = CNContactStore()

Ja:

fileprivate lazy var dataSource = Set()

Käytän Set varmistaaksesi, että tätä tietolähdettä täytettäessä ei ole päällekkäisyyksiä.

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

Kuten näette, tämä on Singleton-luokka, mikä tarkoittaa, että pidämme sitä muistissa, kunnes sovellus on käynnissä. Voit lukea lisätietoja Singletoneista tai suunnittelumalleista tässä .

Olemme nyt lähellä T9-haun viimeistelyä.

Yhdistämällä kaikki yhdessä

Ennen kuin avaat kontaktiluettelon Applessa, sinun on ensin kysyttävä lupaa.

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

Kun olemme valtuuttaneet käyttämään yhteystietoja, voimme kirjoittaa menetelmän saada luettelo järjestelmästä.

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

Olemme jo ladanneet yhteystietoluettelon muistiin, mikä tarkoittaa, että voimme nyt kirjoittaa yksinkertaisen menetelmän:

  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 }

Se siitä. Olemme valmiita.

Nyt voimme käyttää T9-hakua sisällä 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) { } }

Suodatinmenetelmän toteutus:

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

Päivitä luettelo -menetelmän toteutus:

func reloadListSection(section: Int, animation: UITableViewRowAnimation = .none) { if self.tableView.numberOfSections <= section { self.tableView.beginUpdates() self.tableView.insertSections(IndexSet(integersIn:0..

Ja tässä on lyhyt opetusohjelmamme viimeinen osa, UITableView toteutus:

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

Käärimistä

Tämä päättää T9-hakuoppaamme, ja toivottavasti löysit sen suoraviivaiseksi ja helposti toteutettavaksi iOS: ssä.

Mutta miksi sinun pitäisi? Ja miksi Apple ei sisällyttänyt T9-tukea aluksi iOS: ään? Kuten huomautimme johdannossa, T9 ei tuskin ole tappajaominaisuus nykypäivän puhelimissa - se on pikemminkin jälkikäsittelyä, takaisku 'tyhmien' puhelimien päiviin, joissa on mekaaniset numeronäppäimet.

On kuitenkin vielä muutama pätevä syy, miksi T9-haku tulisi ottaa käyttöön tietyissä tilanteissa joko johdonmukaisuuden vuoksi tai esteettömyyden ja käyttökokemuksen parantamiseksi. Iloisemman huomautuksen vuoksi, jos olet nostalginen, T9-syötteellä pelaaminen voi tuoda mukavia muistoja koulupäivistäsi.

Lopuksi löydät täydellisen koodin T9: n käyttöönotosta iOS: ssä osoitteesta GitHub-repo .

Perustietojen ymmärtäminen

Mikä on ennakoiva teksti?

Ennakoiva teksti on syöttötekniikka, jossa yksi näppäin tai painike edustaa monia kirjaimia, kuten vanhemmissa matkapuhelimissa käytetyissä numeronäppäimistöissä. Sitä käytetään myös esteettömyyden parantamiseen tietyissä tilanteissa.

Miksi T9 kutsutaan niin?

T9 tarkoittaa tekstiä yhdeksässä näppäimessä, koska se perustuu 9-numeroiseen numeronäppäimistöön tekstinsyötössä.

Kuinka T9: tä käytetään näppäimistöllä?

Tässä on nopea esimerkki. 'HELLO' -kohdassa sinun tarvitsee vain painaa 4-3-5-5-6. Nämä ovat numerot, jotka sisältävät kirjaimet, joissa on 'HELLO'.

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

Se siitä. Olemme valmiita.

Nyt voimme käyttää T9-hakua sisällä 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) { } }

Suodatinmenetelmän toteutus:

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

Päivitä luettelo -menetelmän toteutus:

func reloadListSection(section: Int, animation: UITableViewRowAnimation = .none) { if self.tableView.numberOfSections <= section { self.tableView.beginUpdates() self.tableView.insertSections(IndexSet(integersIn:0..

Ja tässä on lyhyt opetusohjelmamme viimeinen osa, UITableView toteutus:

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

Käärimistä

Tämä päättää T9-hakuoppaamme, ja toivottavasti löysit sen suoraviivaiseksi ja helposti toteutettavaksi iOS: ssä.

Mutta miksi sinun pitäisi? Ja miksi Apple ei sisällyttänyt T9-tukea aluksi iOS: ään? Kuten huomautimme johdannossa, T9 ei tuskin ole tappajaominaisuus nykypäivän puhelimissa - se on pikemminkin jälkikäsittelyä, takaisku 'tyhmien' puhelimien päiviin, joissa on mekaaniset numeronäppäimet.

On kuitenkin vielä muutama pätevä syy, miksi T9-haku tulisi ottaa käyttöön tietyissä tilanteissa joko johdonmukaisuuden vuoksi tai esteettömyyden ja käyttökokemuksen parantamiseksi. Iloisemman huomautuksen vuoksi, jos olet nostalginen, T9-syötteellä pelaaminen voi tuoda mukavia muistoja koulupäivistäsi.

Lopuksi löydät täydellisen koodin T9: n käyttöönotosta iOS: ssä osoitteesta GitHub-repo .

Perustietojen ymmärtäminen

Mikä on ennakoiva teksti?

Ennakoiva teksti on syöttötekniikka, jossa yksi näppäin tai painike edustaa monia kirjaimia, kuten vanhemmissa matkapuhelimissa käytetyissä numeronäppäimistöissä. Sitä käytetään myös esteettömyyden parantamiseen tietyissä tilanteissa.

Miksi T9 kutsutaan niin?

T9 tarkoittaa tekstiä yhdeksässä näppäimessä, koska se perustuu 9-numeroiseen numeronäppäimistöön tekstinsyötössä.

Kuinka T9: tä käytetään näppäimistöllä?

Tässä on nopea esimerkki. 'HELLO' -kohdassa sinun tarvitsee vain painaa 4-3-5-5-6. Nämä ovat numerot, jotka sisältävät kirjaimet, joissa on 'HELLO'.