วิธีการใช้ T9 Search ใน iOS
เผยแพร่แล้ว: 2022-03-11เมื่อสองสามปีก่อน ฉันกำลังทำงานกับแอพชื่อ “BOG mBank - Mobile Banking” กับทีม iOS/Android ของฉัน มีคุณลักษณะพื้นฐานในแอปที่คุณสามารถใช้ฟังก์ชันธนาคารบนมือถือเพื่อเติมเงินในโทรศัพท์มือถือของคุณเองหรือยอดคงเหลือในโทรศัพท์มือถือของผู้ติดต่อ
ขณะพัฒนาโมดูลนี้ เราสังเกตเห็นว่าการค้นหาผู้ติดต่อเฉพาะในแอปเวอร์ชัน Android ทำได้ง่ายกว่าใน iOS ทำไม? เหตุผลหลักที่อยู่เบื้องหลังนี้คือการค้นหา T9 ซึ่งไม่มีอยู่ในอุปกรณ์ Apple
มาอธิบายว่า T9 เกี่ยวกับอะไร และเหตุใดจึงอาจไม่ได้เป็นส่วนหนึ่งของ iOS และวิธีที่นักพัฒนา iOS สามารถนำไปใช้ได้หากจำเป็น
T9 คืออะไร?
T9 เป็นเทคโนโลยีระบบช่วยสะกดคำสำหรับโทรศัพท์มือถือ โดยเฉพาะเทคโนโลยีที่มีแป้นพิมพ์ตัวเลข 3x4 จริง
T9 ได้รับการพัฒนาโดย Tegic Communications และชื่อย่อมาจาก Text on 9 keys
คุณสามารถเดาได้ว่าทำไม T9 ถึงไม่เคยทำให้ iOS มาก่อน ระหว่างการปฏิวัติของสมาร์ทโฟน อินพุต T9 นั้นล้าสมัย เนื่องจากโทรศัพท์สมาร์ทโฟนสมัยใหม่ต้องอาศัยคีย์บอร์ดเต็มรูปแบบ ด้วยความเอื้อเฟื้อของจอแสดงผลแบบสัมผัส เนื่องจาก Apple ไม่เคยมีโทรศัพท์ที่มีแป้นพิมพ์จริงและไม่ได้อยู่ในธุรกิจโทรศัพท์ในช่วงรุ่งเรืองของ T9 จึงเข้าใจได้ว่าเทคโนโลยีนี้ถูกละเว้นจาก iOS
T9 ยังคงใช้กับโทรศัพท์ราคาถูกบางรุ่นที่ไม่มีหน้าจอสัมผัส (เรียกว่าฟีเจอร์โฟน) อย่างไรก็ตาม แม้ว่าโทรศัพท์ Android ส่วนใหญ่จะไม่เคยมีแป้นพิมพ์จริง แต่อุปกรณ์ Android สมัยใหม่ยังรองรับอินพุต T9 ซึ่งสามารถใช้เพื่อโทรออกไปยังรายชื่อติดต่อได้ด้วยการสะกดชื่อผู้ติดต่อที่พยายามโทรหา
ตัวอย่างของ T9 Predictive Input ในการดำเนินการ
บนโทรศัพท์ที่มีแป้นตัวเลข ทุกครั้งที่กดปุ่ม (1-9) (เมื่ออยู่ในช่องข้อความ) อัลกอริธึมจะส่งกลับการเดาว่าตัวอักษรใดมีแนวโน้มมากที่สุดสำหรับปุ่มที่กดไปยังจุดนั้น
ตัวอย่างเช่น ในการป้อนคำว่า "the" ผู้ใช้จะกด 8 ตามด้วย 4 ตามด้วย 3 และหน้าจอจะแสดง "t" จากนั้น "th" ตามด้วย "the" หากใช้คำว่า "fore" ที่ไม่ค่อยพบบ่อย (3673) อัลกอริทึมการคาดเดาอาจเลือก "Ford" การกดปุ่ม "ถัดไป" (โดยทั่วไปคือปุ่ม "*") อาจทำให้ "ปริมาณ" และสุดท้าย "อยู่ข้างหน้า" หากเลือก "fore" ในครั้งต่อไปที่ผู้ใช้กดลำดับ 3673 fore จะมีโอกาสแสดงคำแรกมากขึ้น อย่างไรก็ตาม หากใช้คำว่า "เฟลิกซ์" เมื่อป้อน 33549 หน้าจอจะแสดง "E" ตามด้วย "De" "Del" "Deli" และ "Felix"
นี่คือตัวอย่างการเปลี่ยนตัวอักษรขณะป้อนคำ
การใช้โปรแกรม T9 ใน iOS
มาดูคุณสมบัตินี้และเขียนตัวอย่างง่ายๆ ของอินพุต T9 สำหรับ iOS ก่อนอื่นเราต้องสร้างโครงการใหม่
ข้อกำหนดเบื้องต้นที่จำเป็นสำหรับโครงการของเราเป็นพื้นฐาน: เครื่องมือสร้าง Xcode และ Xcode ที่ติดตั้งบน Mac ของคุณ
ในการสร้างโปรเจ็กต์ใหม่ ให้เปิดแอปพลิเคชั่น Xcode บน Mac ของคุณแล้วเลือก “สร้างโปรเจ็กต์ Xcode ใหม่” จากนั้นตั้งชื่อโปรเจ็กต์ของคุณ แล้วเลือกประเภทของแอปพลิเคชันที่จะสร้าง เพียงเลือก “แอปมุมมองเดียว” แล้วกดถัดไป
ในหน้าจอถัดไป อย่างที่คุณเห็นจะมีข้อมูลบางอย่างที่คุณต้องระบุ
- ชื่อสินค้า: ฉันตั้งชื่อมันว่า T9Search
- ทีมงาน . ที่นี่ หากคุณต้องการเรียกใช้แอปพลิเคชันนี้บนอุปกรณ์จริง คุณจะต้องมีบัญชีนักพัฒนาซอฟต์แวร์ ในกรณีของฉัน ฉันจะใช้บัญชีของตัวเองเพื่อสิ่งนี้
หมายเหตุ: หากคุณไม่มีบัญชีนักพัฒนา คุณสามารถเรียกใช้บน Simulator ได้เช่นกัน
- ชื่อองค์กร: ฉันตั้งชื่อมันว่า Toptal
- ตัวระบุองค์กร: ฉันตั้งชื่อมันว่า "com.toptal"
- ภาษา: เลือก Swift
- ยกเลิกการเลือก "ใช้ข้อมูลหลัก" "รวมการทดสอบหน่วย" และ "รวมการทดสอบ UI"
กดปุ่มถัดไปและเราพร้อมที่จะเริ่มต้น
สถาปัตยกรรมที่เรียบง่าย
อย่างที่คุณรู้อยู่แล้ว เมื่อคุณสร้างแอปใหม่ คุณมีคลาส MainViewController และ Main.Storyboard แล้ว สำหรับวัตถุประสงค์ในการทดสอบ เราสามารถใช้คอนโทรลเลอร์นี้ได้
ก่อนที่เราจะเริ่มออกแบบบางอย่าง เรามาสร้างคลาสและไฟล์ที่จำเป็นทั้งหมดก่อนเพื่อให้แน่ใจว่าเรามีทุกอย่างที่ตั้งค่าและกำลังทำงานเพื่อย้ายไปยังส่วน UI ของงาน
ที่ไหนสักแห่งในโครงการของคุณ เพียงแค่สร้างไฟล์ใหม่ชื่อ “ PhoneContactsStore.swift ” ในกรณีของฉัน หน้าตาจะประมาณนี้
ลำดับแรกในการทำธุรกิจของเราคือการสร้างแผนที่ที่มีการป้อนข้อมูลด้วยแป้นพิมพ์ตัวเลขทุกรูปแบบ
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" ]แค่นั้นแหละ. เราได้ใช้แผนที่ที่สมบูรณ์พร้อมรูปแบบต่างๆ ทั้งหมด ตอนนี้ มาดำเนินการสร้างคลาสแรกของเราที่ชื่อว่า “ PhoneContact ”
ไฟล์ของคุณควรมีลักษณะดังนี้:
อันดับแรก ในคลาสนี้ เราต้องตรวจสอบให้แน่ใจว่าเรามีตัวกรอง Regex จาก AZ + 0-9
private let regex = try! NSRegularExpression(pattern: "[^ az()0-9+]", options: .caseInsensitive)
โดยทั่วไป ผู้ใช้มีคุณสมบัติเริ่มต้นที่ต้องแสดง:
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) } } ตรวจสอบให้แน่ใจว่าคุณได้แทนที่ hash และ isEqual เพื่อระบุตรรกะที่กำหนดเองของคุณสำหรับการกรองรายการ
นอกจากนี้ เราจำเป็นต้องมีวิธีการแทนที่เพื่อหลีกเลี่ยงไม่ให้มีสิ่งใดนอกจากตัวเลขในสตริง
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: "") } ตอนนี้ เราต้องการวิธีอื่นที่เรียกว่า calculateT9 เพื่อค้นหาผู้ติดต่อที่เกี่ยวข้องกับชื่อ fullname หรือ 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)) } } หลังจากใช้วัตถุ PhoneContact เราจำเป็นต้องเก็บผู้ติดต่อของเราไว้ที่ใดที่หนึ่งในหน่วยความจำ เพื่อจุดประสงค์นี้ ฉันจะสร้างคลาสใหม่ชื่อ PhoneContactStore

เราจะมีคุณสมบัติท้องถิ่นสองแห่ง:
fileprivate let contactsStore = CNContactStore()
และ:
fileprivate lazy var dataSource = Set<PhoneContact>()
ฉันใช้ Set เพื่อให้แน่ใจว่าไม่มีการทำซ้ำระหว่างการกรอกแหล่งข้อมูลนี้
final class PhoneContactStore { fileprivate let contactsStore = CNContactStore() fileprivate lazy var dataSource = Set<PhoneContact>() static let instance : PhoneContactStore = { let instance = PhoneContactStore() return instance }() }อย่างที่คุณเห็น นี่คือคลาส Singleton ซึ่งหมายความว่าเราเก็บไว้ในหน่วยความจำจนกว่าแอปจะทำงาน สำหรับข้อมูลเพิ่มเติมเกี่ยวกับ Singletons หรือรูปแบบการออกแบบ คุณสามารถอ่านได้ที่นี่
ตอนนี้เราใกล้จะเสร็จสิ้นการค้นหา T9 แล้ว
วางมันทั้งหมดเข้าด้วยกัน
ก่อนที่คุณจะเข้าถึงรายชื่อผู้ติดต่อบน Apple คุณต้องขออนุญาตก่อน
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)) } }หลังจากที่เราได้รับอนุญาตให้เข้าถึงผู้ติดต่อแล้ว เราก็สามารถเขียนวิธีการรับรายการจากระบบได้
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 {} } }เราได้โหลดรายชื่อผู้ติดต่อลงในหน่วยความจำแล้ว ซึ่งหมายความว่าตอนนี้เราสามารถเขียนวิธีการง่าย ๆ ได้:
-
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 }แค่นั้นแหละ. เราเสร็จแล้ว
ตอนนี้เราสามารถใช้การค้นหา T9 ภายใน 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) { } }การใช้วิธีการกรอง:
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) }การใช้งานวิธีการโหลดรายการซ้ำ:
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) } และนี่คือส่วนสุดท้ายของบทช่วยสอนสั้นๆ ของเรา การใช้งาน 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) } }ห่อ
นี่เป็นการสรุปบทแนะนำการค้นหา T9 ของเรา และหวังว่าคุณจะพบว่ามันตรงไปตรงมาและใช้งานง่ายใน iOS
แต่ทำไมคุณควร? และเหตุใด Apple จึงไม่รองรับ T9 ใน iOS ตั้งแต่เริ่มต้น ดังที่เราได้กล่าวไว้ในบทนำ T9 นั้นแทบจะไม่ได้มีคุณสมบัตินักฆ่าในโทรศัพท์ทุกวันนี้ มันเป็นสิ่งที่คิดในภายหลังมากกว่า เป็นการย้อนเวลากลับไปสู่ยุคของโทรศัพท์ "ใบ้" ที่มีแป้นตัวเลขแบบกลไก
อย่างไรก็ตาม ยังมีสาเหตุที่ถูกต้องบางประการที่คุณควรใช้การค้นหา T9 ในบางสถานการณ์ ไม่ว่าจะเพื่อความสอดคล้องกัน หรือเพื่อปรับปรุงการช่วยสำหรับการเข้าถึงและประสบการณ์ของผู้ใช้ ในแง่ที่ร่าเริงกว่านี้ หากคุณเป็นคนชอบหวนคิดถึง การเล่นกับอินพุต T9 อาจนำความทรงจำดีๆ กลับไปสู่สมัยเรียนของคุณ
สุดท้ายนี้ คุณสามารถค้นหาโค้ดทั้งหมดสำหรับการใช้งาน T9 ใน iOS ได้ที่ GitHub repo ของฉัน
