كيفية تنفيذ بحث T9 في iOS
نشرت: 2022-03-11قبل عامين ، كنت أعمل على تطبيق يسمى "BOG mBank - Mobile Banking" مع فريق iOS / Android. هناك ميزة أساسية في التطبيق حيث يمكنك استخدام وظيفة الخدمات المصرفية عبر الهاتف المحمول لزيادة رصيد الهاتف المحمول الخاص بك أو رصيد الهاتف المحمول الخاص بأي جهة اتصال.
أثناء تطوير هذه الوحدة ، لاحظنا أنه كان من الأسهل بكثير العثور على جهة اتصال معينة في إصدار Android من التطبيق مقارنةً بنظام iOS. لماذا ا؟ السبب الرئيسي وراء ذلك هو بحث T9 ، وهو مفقود من أجهزة Apple.
دعنا نوضح ما هو كل شيء عن T9 ، ولماذا ربما لم يصبح جزءًا من iOS ، وكيف يمكن لمطوري iOS تنفيذه إذا لزم الأمر.
ما هو T9؟
T9 هي تقنية نصية تنبؤية للهواتف المحمولة ، وتحديداً تلك التي تحتوي على لوحة مفاتيح رقمية فعلية 3 × 4.
تم تطوير T9 في الأصل بواسطة Tegic Communications ، والاسم يقف على النص على 9 مفاتيح .
يمكنك تخمين سبب عدم وصول T9 إلى نظام التشغيل iOS على الإطلاق. خلال ثورة الهواتف الذكية ، أصبح إدخال T9 قديمًا ، حيث اعتمدت الهواتف الذكية الحديثة على لوحات مفاتيح كاملة ، بفضل شاشات اللمس الخاصة بها. نظرًا لأن Apple لم يكن لديها أبدًا أي هواتف بها لوحات مفاتيح فعلية ولم تكن تعمل في مجال الهاتف خلال ذروة T9 ، فمن المفهوم أنه تم حذف هذه التقنية من iOS.
لا يزال يتم استخدام T9 في بعض الهواتف الرخيصة التي لا تحتوي على شاشة تعمل باللمس (ما يسمى بالهواتف المميزة). ومع ذلك ، على الرغم من حقيقة أن معظم هواتف Android لم تعرض أبدًا لوحات مفاتيح فعلية ، فإن أجهزة Android الحديثة تتميز بدعم إدخال T9 ، والذي يمكن استخدامه للاتصال بجهات الاتصال عن طريق تهجئة اسم جهة الاتصال التي يحاول الشخص الاتصال بها.
مثال على الإدخال التنبئي T9 قيد التنفيذ
على هاتف به لوحة مفاتيح رقمية ، في كل مرة يتم فيها الضغط على مفتاح (1-9) (عندما يكون في حقل نصي) ، تُرجع الخوارزمية تخمينًا للأحرف الأكثر ترجيحًا للمفاتيح التي تم الضغط عليها حتى تلك النقطة.
على سبيل المثال ، لإدخال الكلمة "the" ، سيضغط المستخدم على 8 ثم 4 ثم 3 ، وستعرض الشاشة "t" ثم "th" ثم "the." إذا كانت الكلمة الأقل شيوعًا "المقدمة" مقصودة (3673) ، فقد تختار الخوارزمية التنبؤية "فورد". قد يؤدي الضغط على مفتاح "التالي" (عادةً مفتاح "*") إلى إظهار كلمة "جرعة" وأخيرًا "المقدمة". إذا تم تحديد "المقدمة" ، فعندئذ في المرة التالية التي يضغط فيها المستخدم على التسلسل 3673 ، من المرجح أن تكون المقدمة هي الكلمة الأولى التي يتم عرضها. ومع ذلك ، إذا كانت كلمة "فيليكس" مقصودة ، فعند إدخال 33549 ، تعرض الشاشة "E" ثم "De" و "Del" و "Deli" و "Felix".
هذا مثال على تغيير حرف أثناء إدخال الكلمات.
الاستخدام البرمجي لـ T9 في iOS
لذا ، دعنا نتعمق في هذه الميزة ونكتب مثالاً سهلاً على إدخال T9 لنظام iOS. بادئ ذي بدء ، نحتاج إلى إنشاء مشروع جديد.
المتطلبات الأساسية اللازمة لمشروعنا أساسية: أدوات بناء Xcode و Xcode مثبتة على جهاز Mac الخاص بك.
لإنشاء مشروع جديد ، افتح تطبيق Xcode الخاص بك على جهاز Mac الخاص بك وحدد "إنشاء مشروع Xcode جديد" ، ثم قم بتسمية مشروعك ، واختر نوع التطبيق المراد إنشاؤه. ما عليك سوى تحديد "تطبيق Single View" واضغط على "التالي".
في الشاشة التالية ، كما ترى ، سيكون هناك بعض المعلومات التي تحتاج إلى تقديمها.
- اسم المنتج: سميته T9Search
- فريق . هنا ، إذا كنت تريد تشغيل هذا التطبيق على جهاز حقيقي ، فيجب أن يكون لديك حساب مطور. في حالتي ، سأستخدم حسابي الخاص لهذا الغرض.
ملاحظة: إذا لم يكن لديك حساب مطور ، فيمكنك تشغيل هذا على Simulator أيضًا.
- اسم المنظمة: سميتها Toptal
- معرّف المنظمة: سميته "com.toptal"
- اللغة: اختر Swift
- قم بإلغاء تحديد "استخدام البيانات الأساسية" و "تضمين اختبارات الوحدة" و "تضمين اختبارات واجهة المستخدم"
اضغط على زر التالي ، ونحن على استعداد للبدء.
هندسة معمارية بسيطة
كما تعلم بالفعل ، عند إنشاء تطبيق جديد ، يكون لديك بالفعل فئة MainViewController
و Main.Storyboard
. لأغراض الاختبار ، بالطبع ، يمكننا استخدام وحدة التحكم هذه.
قبل أن نبدأ في تصميم شيء ما ، دعنا أولاً ننشئ جميع الفئات والملفات الضرورية للتأكد من إعداد كل شيء وتشغيله للانتقال إلى جزء واجهة المستخدم من الوظيفة.
في مكان ما داخل مشروعك ، ببساطة قم بإنشاء ملف جديد يسمى “ 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 ، مما يعني أننا نحتفظ بها في الذاكرة حتى يتم تشغيل التطبيق. لمزيد من المعلومات حول المفردات أو أنماط التصميم ، يمكنك القراءة هنا.
نحن الآن على وشك الانتهاء من بحث 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.