
واجه الكثير مشكلة تنفيذ تعيين زر جميل لـ UITableViewCell عند التحول إلى اليسار. استخدم البعض الوظيفة القياسية "من خارج الصندوق" ، والبعض الآخر يزعجهم التنفيذ الخاص بهم ، بينما تمكن البعض الآخر باستخدام أحرف Unicode. في هذه المقالة سأخبرك بكيفية تحقيق أقصى تخصيص لـ UITableViewRowAction.
أولاً ، نظرية صغيرة:
UITableViewRowAction هي فئة تسمح للمستخدم بتنفيذ إجراءات معينة مع خلية معينة (مثل "حذف" ، "تغيير" ، إلخ) عند نقل خلية إلى اليسار. يمثل كل مثيل من هذه الفئة إجراءً واحدًا للتنفيذ ويتضمن نصًا ونمطًا ومعالج نقر. تتيح لنا iOS SDK تخصيص UITableViewRowAction على النحو التالي:
public convenience init(style: UITableViewRowActionStyle, title: String?, handler: @escaping (UITableViewRowAction, IndexPath) -> Swift.Void)
يمكنك أيضًا تعيين backgroundColor.
الأساليب الحالية.
فكر في أمثلة التنفيذ الحالية ، أو ما وجدته على الإنترنت.
الطريقة رقم 1 . استخدم الوظيفة "خارج الصندوق". هذا يكفي لاستخدام الأزرار العادية مع النص والخلفية. تبدو بسيطة ومملة للغاية:
الطريقة رقم 2 . تعيين
الخلفيةاللون باستخدام
UIColor (patternImage: UIImage) . هذا سيحقق الجمال ، لكنه سيحرم من التنوع ، ويضيف عددًا من القيود والصعوبات. نظرًا لأن عرض UITableViewRowAction يتم حسابه حسب حجم النص ، فأنت بحاجة إلى تحديد
العنوان كمجموعة من أحرف المسافات البيضاء بعرض معين. لن نتمكن من استخدام هذه الطريقة للخلايا ذات الارتفاعات الديناميكية. إذا زادت الخلية في الارتفاع ، فستبدو كما يلي:
الطريقة رقم 3 . استخدم أحرف Unicode. ليست هناك حاجة لحساب حجم النص بمسافات ، وهذه الطريقة مناسبة للخلايا ذات الارتفاع الديناميكي. تبدو جميلة:

ولكن إذا أردنا جعل النص تحت الرمز ، فإن هذه الطريقة لن تناسبنا.
الطريقة رقم 4 . استخدم CoreGraphics لتقديم مكونات واجهة المستخدم. الفكرة هي رسم صورة يدويًا بالترتيب المطلوب للعناصر (النص والصورة). في هذه الحالة ، تحتاج إلى حساب الأحجام والإحداثيات لموقع كل عنصر. باستخدام هذه الطريقة ، يمكننا عمل نص وصورة ، وترتيبهما حسب الحاجة. لكن هذه الطريقة صعبة التنفيذ. نريد شيئاً أبسط.
تطبيقات أخرى . هناك عدد قليل من الحلول الجاهزة. يستخدمون تطبيقهم الخاص للخلية مع إضافة ScrollView والمنطق الآخر. على سبيل المثال ،
SwipeCellKit أو
MGSwipeTableCell . لكن هذه الحلول لا تسمح بتحقيق سلوك iOS القياسي ؛ فهي تغطي خلية واحدة فقط ، على عكس UITableViewRowAction ، الذي تم إنشاؤه في مفوض الجدول. على سبيل المثال ، باستخدام أحد هذه الحلول ، إذا قمت بفتح إجراءات التحول الأيسر للعديد من الخلايا ، فسيتعين عليك إغلاق هذه الإجراءات يدويًا في كل منها ، وهو أمر غير مريح للغاية. للقيام بذلك ، يجب عليك إضافة منطقك الخاص للتحكم في الإجراءات المفتوحة في التمرير السريع. نريد إنشاء حل بسيط لا يتطلب تكاليف إضافية ، كما لو أننا نستخدم التطبيق القياسي لـ UITableViewRowAction.
دعنا نبدأ
دعونا نحاول إنشاء حل عالمي يسمح لك بعمل UITableViewRowAction مع أي تصميم نحتاجه.
قم بإنشاء فئة السليل الخاصة بنا من UITableViewRowAction وحدد طريقة إنشاء المثيل الرئيسية:
typealias RowActionTapHandler = (RowAction, IndexPath) -> Void class RowAction: UITableViewRowAction { static func rowAction(with actionHandler: @escaping RowActionTapHandler) -> Self { let rowAction = self.init(style: .default, title: nil) { action, indexPath in guard let action = action as? RowAction else { return } actionHandler(action, indexPath) } return rowAction } }
نظرًا لأن UITableViewRowAction لا يحتوي على مُهيئ أساسي ، فمن أجل الراحة ، يمكننا إنشاء طريقة ثابتة حيث ننشئ في الداخل UITableViewRowAction بطريقة قياسية ونرمي معالجًا للتعامل مع النقرة.
الآن دعونا نحاول تحقيق التخصيص الشامل للزر UITableViewRowAction. الفكرة هي الحصول على مظهر الزر من UIView النهائي (الذي يمكن تهيئته في xib العادي). حتى نتمكن من إنشاء زر من أي نوع ، بناءً على التفضيلات الفردية (أي خط من النص وحجم العناصر وترتيبها ، وما إلى ذلك).
لدينا شيئان يمكننا تغييرهما في UITableViewRowAction:
backgroundColor والعنوان . في الوقت الحالي ، الطريقة الوحيدة لتغيير شكل زر UITableViewRowAction هي تغيير خاصية backgroundColor. لذلك ، هدفنا هو تحويل: UIView -> UIImage -> UIColor. يمكن الحصول على اللون باستخدام الصورة كما يلي:
UIColor (patternImage: UIImage) . ويمكننا الحصول على الصورة من UIView من خلال عرضها باستخدام إطار CoreGraphics القياسي.
ولكن أولاً ، سنقرر كيفية تعيين حجم الزر: "خارج الصندوق" لا توجد هذه الوظيفة ، ويتم حساب الحجم في UITableViewRowAction حسب حجم نص حقل العنوان.
بعد إجراء التجارب (قياس العرض المحسوب والحقيقي للزر) ، وجدت حرف Unicode فارغًا يحتوي على أصغر عرض غير صفري:
U + 200Aسنستخدمه وننشئ ثابتًا:
private enum Constants { static let placeholderSymbol = "\u{200A}" }
سنكتب دالة للحصول على سلسلة من أحرف المسافات المطابقة للحجم الذي نحتاج إليه:
private static let placeholderSymbolWidth: CGFloat = { let flt_max = CGFloat.greatestFiniteMagnitude let maxSize = CGSize(width: flt_max, height: flt_max) let attributes = [NSFontAttributeName : UIFont.systemFont(ofSize: UIFont.systemFontSize)] let boundingRect = Constants.placeholderSymbol.boundingRect(with: maxSize, options: .usesLineFragmentOrigin, attributes: attributes, context: nil) return boundingRect.width }() private func emptyTitle(for size: CGSize) -> String { var usefulWidth = size.width - Constants.minimalActionWidth usefulWidth = usefulWidth < 0 ? 0 : usefulWidth let countOfSymbols = Int(floor(usefulWidth * Constants.shiftFactor / RowAction.placeholderSymbolWidth)) return String(repeating: Constants.placeholderSymbol, count: countOfSymbols) }
يحصل متغير
placeholderSymbolWidth على عرض حرف Unicode الخاص بنا ،
وتحسب الدالة
blankTitle (للحجم: CGSize) -> String عدد أحرف المسافات البيضاء المقابلة لحجم
الحجم وترجع سلسلة من هذه الأحرف. تستخدم الصيغة أعلاه ثابت
shiftFactor . هذا هو معامل انحراف العرض عن القيمة الاسمية. والحقيقة هي أنه مع زيادة عرض UITableViewRowAction ، يزداد الخطأ الصغير (أي يختلف العرض الفعلي للزر عن العرض المحسوب بالصيغة المعتادة). لتجنب هذا ، نستخدم هذا الثابت المستمد تجريبيا.
أيضًا ، يبلغ عرض UITableViewRowAction 30 حدًا أدنى (على سبيل المثال ، إذا قمت بتعيين title = ""). هذا العرض هو الحد الأدنى المطلوب للمسافة البادئة إلى يسار ويمين العنوان.
تحديث ثوابتنا:
private enum Constants { static let placeholderSymbol = "\u{200A}" static let minimalActionWidth: CGFloat = 30 static let shiftFactor: CGFloat = 1.1 }
الآن دعونا نحصل على الصورة من UIView. سنكتب الوظيفة التالية:
private func image(from view: UIView) -> UIImage? { UIGraphicsBeginImageContextWithOptions(view.bounds.size, view.isOpaque, UIScreen.main.scale) guard let context = UIGraphicsGetCurrentContext() else { assertionFailure("Something wrong with CoreGraphics image context"); return nil } context.setFillColor(UIColor.white.cgColor) context.fill(CGRect(origin: .zero, size: view.bounds.size)) view.layer.render(in: context) let img = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return img }
هذه الوظيفة تحول
وجهة نظرنا إلى صورة. في هذه الحالة ، يتوافق حجم الصورة مع حجم
العرض .
تبقى الخطوة الأخيرة ، حيث ننفذ طريقة إعداد UITableViewRowAction باستخدام UIView:
func setupForCell(with view: UIView) { guard let img = image(from: view) else { return } self.title = emptyTitle(for: img.size) self.backgroundColor = UIColor(patternImage: img) }
هنا نستخدم الوظائف المكتوبة سابقًا لتهيئة زرنا وتعيين لون
الخلفية باستخدام الصورة الناتجة.
هذا كل شئ. لقد حصلنا على حل عالمي لـ UITableViewRowAction ، والآن يمكننا تخصيصه بأي طريقة باستخدام UIView.

بدءًا من iOS 11 ، أضافت Apple فئة جديدة لإجراء التمرير السريع للخلايا في الجدول - UIContextualAction. يسمح لك بتحديد الصورة التي سيتم عرضها على الإجراء الموجود على التمرير على الفور (هذا ما فقدناه طوال هذه السنوات من استخدام UITableViewRowAction). كان من المفترض أن يجعل حياتنا أسهل ، ولكن بالإضافة إلى فرصة جديدة ، خلق مشاكل إضافية. تفرض هذه الفئة قيودًا على حجم الصورة ، ولا تسمح باستخدام النص والصورة في نفس الوقت ، ويجب أيضًا أن يكون للصورة تنسيق خاص (صورة قالب). لذلك ، انتهيت من حل دعم إجراءات السحب في iOS 11. الآن من أجل إنشاء UITableViewRowAction أو UIContextualAction ، تحتاج أولاً إلى إنشاء مصنع RowActionFactory ، تكوينه باستخدام طريقة العرض الخاصة بنا (كما فعلنا أعلاه في طريقة
setupForCell (مع طريقة عرض UIView)) وإرجاع الكيان الذي نحتاج إليه: UITableViewRowAction أو UIContextualAction (باستخدام
طرق rowAction () أو
contextualAction (لـ indexPath: IndexPath) ، على التوالي). فيما يلي مثال على استخدام RowActionFactory:
@available(iOS 11.0, *) func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { var contextualActions: [UIContextualAction] = [] for actionType in rowActionTypes { // actionType - let rowActionFactory = RowActionFactory { (indexPath) in tableView.setEditing(false, animated: true) } let rowActionView = <...> // UIView rowActionFactory.setupForCell(with: rowActionView) if let contextualAction = rowActionFactory.contextualAction(for: indexPath) { contextualActions.append(contextualAction) } } let swipeActionsConfiguration = UISwipeActionsConfiguration(actions: contextualActions) swipeActionsConfiguration.performsFirstActionWithFullSwipe = false return swipeActionsConfiguration }
يمكن العثور على الحل النهائي هنا:
TCSCustomRowActionFactory (github.com) .