From a0b7c1b7a29f0018190ccf262a22edd0b448c33f Mon Sep 17 00:00:00 2001 From: Mauricio Cousillas Date: Wed, 7 Jun 2017 14:23:13 -0300 Subject: [PATCH 1/8] Swift 4 migration --- Eureka.xcodeproj/project.pbxproj | 4 ++-- Source/Core/Core.swift | 8 ++++---- Source/Core/Form.swift | 2 +- Source/Core/Section.swift | 6 +++--- Source/Core/SelectableSection.swift | 4 +++- Source/Rows/Common/DateFieldRow.swift | 2 +- Source/Rows/Common/FieldRow.swift | 8 ++++---- Source/Rows/DatePickerRow.swift | 2 +- Source/Rows/SegmentedRow.swift | 6 +++--- Source/Rows/SliderRow.swift | 8 ++++---- Source/Rows/StepperRow.swift | 2 +- Source/Rows/SwitchRow.swift | 2 +- Tests/SelectableSectionTests.swift | 11 ++++++----- 13 files changed, 34 insertions(+), 31 deletions(-) diff --git a/Eureka.xcodeproj/project.pbxproj b/Eureka.xcodeproj/project.pbxproj index 6bae2e97d..b2b1feecc 100644 --- a/Eureka.xcodeproj/project.pbxproj +++ b/Eureka.xcodeproj/project.pbxproj @@ -700,7 +700,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -723,7 +723,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/Source/Core/Core.swift b/Source/Core/Core.swift index 618b305b6..8b638d1e2 100644 --- a/Source/Core/Core.swift +++ b/Source/Core/Core.swift @@ -958,7 +958,7 @@ extension FormViewController { /** Called when the keyboard will appear. Adjusts insets of the tableView and scrolls it if necessary. */ - open func keyboardWillShow(_ notification: Notification) { + @objc open func keyboardWillShow(_ notification: Notification) { guard let table = tableView, let cell = table.findFirstResponder()?.formCell() else { return } let keyBoardInfo = notification.userInfo! let endFrame = keyBoardInfo[UIKeyboardFrameEndUserInfoKey] as! NSValue @@ -986,7 +986,7 @@ extension FormViewController { /** Called when the keyboard will disappear. Adjusts insets of the tableView. */ - open func keyboardWillHide(_ notification: Notification) { + @objc open func keyboardWillHide(_ notification: Notification) { guard let table = tableView, let oldBottom = oldBottomInset else { return } let keyBoardInfo = notification.userInfo! var tableInsets = table.contentInset @@ -1009,11 +1009,11 @@ extension FormViewController { // MARK: Navigation Methods - func navigationDone(_ sender: UIBarButtonItem) { + @objc func navigationDone(_ sender: UIBarButtonItem) { tableView?.endEditing(true) } - func navigationAction(_ sender: UIBarButtonItem) { + @objc func navigationAction(_ sender: UIBarButtonItem) { navigateTo(direction: sender == navigationAccessoryView.previousButton ? .up : .down) } diff --git a/Source/Core/Form.swift b/Source/Core/Form.swift index 47f39a690..569ba8eca 100644 --- a/Source/Core/Form.swift +++ b/Source/Core/Form.swift @@ -263,7 +263,7 @@ extension Form { // MARK: Private Helpers class KVOWrapper: NSObject { - dynamic private var _sections = NSMutableArray() + @objc dynamic private var _sections = NSMutableArray() var sections: NSMutableArray { return mutableArrayValue(forKey: "_sections") } var _allSections = [Section]() private weak var form: Form? diff --git a/Source/Core/Section.swift b/Source/Core/Section.swift index 028390d48..86c4c291e 100644 --- a/Source/Core/Section.swift +++ b/Source/Core/Section.swift @@ -53,7 +53,7 @@ extension Section { internal class KVOWrapper: NSObject { - dynamic private var _rows = NSMutableArray() + @objc dynamic private var _rows = NSMutableArray() var rows: NSMutableArray { return mutableArrayValue(forKey: "_rows") } @@ -232,8 +232,8 @@ extension Section : MutableCollection, BidirectionalCollection { } } - public subscript (range: Range) -> [BaseRow] { - get { return kvoWrapper.rows.objects(at: IndexSet(integersIn: range)) as! [BaseRow] } + public subscript (range: Range) -> ArraySlice { + get { return ArraySlice(kvoWrapper.rows.objects(at: IndexSet(integersIn: range)) as! [BaseRow]) } set { replaceSubrange(range, with: newValue) } diff --git a/Source/Core/SelectableSection.swift b/Source/Core/SelectableSection.swift index 29f4ddd46..81a3c1832 100644 --- a/Source/Core/SelectableSection.swift +++ b/Source/Core/SelectableSection.swift @@ -86,7 +86,9 @@ extension SelectableSectionType where Self: Section, Self.Iterator == IndexingIt for row in rows { if let row = row as? SelectableRow { row.onCellSelection { [weak self] cell, row in - guard let s = self, !row.isDisabled else { return } + guard let s = self, !row.isDisabled else { + return + } switch s.selectionType { case .multipleSelection: row.value = row.value == nil ? row.selectableValue : nil diff --git a/Source/Rows/Common/DateFieldRow.swift b/Source/Rows/Common/DateFieldRow.swift index 8cb564c6d..e2ee1a1df 100644 --- a/Source/Rows/Common/DateFieldRow.swift +++ b/Source/Rows/Common/DateFieldRow.swift @@ -82,7 +82,7 @@ open class DateCell: Cell, CellType { return datePicker } - func datePickerValueChanged(_ sender: UIDatePicker) { + @objc func datePickerValueChanged(_ sender: UIDatePicker) { row.value = sender.date detailTextLabel?.text = row.displayValueFor?(row.value) } diff --git a/Source/Rows/Common/FieldRow.swift b/Source/Rows/Common/FieldRow.swift index a74303ece..e12eeed3b 100644 --- a/Source/Rows/Common/FieldRow.swift +++ b/Source/Rows/Common/FieldRow.swift @@ -137,8 +137,8 @@ open class _FieldCell : Cell, UITextFieldDelegate, TextFieldCell where T: titleLabel = self.textLabel titleLabel?.translatesAutoresizingMaskIntoConstraints = false - titleLabel?.setContentHuggingPriority(500, for: .horizontal) - titleLabel?.setContentCompressionResistancePriority(1000, for: .horizontal) + titleLabel?.setContentHuggingPriority(UILayoutPriority(rawValue: 500), for: .horizontal) + titleLabel?.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 1000), for: .horizontal) contentView.addSubview(titleLabel!) contentView.addSubview(textField) @@ -216,7 +216,7 @@ open class _FieldCell : Cell, UITextFieldDelegate, TextFieldCell where T: textField.font = .preferredFont(forTextStyle: .body) if let placeholder = (row as? FieldRowConformance)?.placeholder { if let color = (row as? FieldRowConformance)?.placeholderColor { - textField.attributedPlaceholder = NSAttributedString(string: placeholder, attributes: [NSForegroundColorAttributeName: color]) + textField.attributedPlaceholder = NSAttributedString(string: placeholder, attributes: [NSAttributedStringKey.foregroundColor: color]) } else { textField.placeholder = (row as? FieldRowConformance)?.placeholder } @@ -301,7 +301,7 @@ open class _FieldCell : Cell, UITextFieldDelegate, TextFieldCell where T: super.updateConstraints() } - open func textFieldDidChange(_ textField: UITextField) { + @objc open func textFieldDidChange(_ textField: UITextField) { guard let textValue = textField.text else { row.value = nil diff --git a/Source/Rows/DatePickerRow.swift b/Source/Rows/DatePickerRow.swift index eb16e3d13..eddac191f 100644 --- a/Source/Rows/DatePickerRow.swift +++ b/Source/Rows/DatePickerRow.swift @@ -71,7 +71,7 @@ open class DatePickerCell: Cell, CellType { } } - func datePickerValueChanged(_ sender: UIDatePicker) { + @objc func datePickerValueChanged(_ sender: UIDatePicker) { row?.value = sender.date } diff --git a/Source/Rows/SegmentedRow.swift b/Source/Rows/SegmentedRow.swift index 0bf7de047..0d80f00cd 100644 --- a/Source/Rows/SegmentedRow.swift +++ b/Source/Rows/SegmentedRow.swift @@ -40,12 +40,12 @@ open class SegmentedCell : Cell, CellType { let segmentedControl = UISegmentedControl() segmentedControl.translatesAutoresizingMaskIntoConstraints = false - segmentedControl.setContentHuggingPriority(250, for: .horizontal) + segmentedControl.setContentHuggingPriority(UILayoutPriority(rawValue: 250), for: .horizontal) self.segmentedControl = segmentedControl self.titleLabel = self.textLabel self.titleLabel?.translatesAutoresizingMaskIntoConstraints = false - self.titleLabel?.setContentHuggingPriority(500, for: .horizontal) + self.titleLabel?.setContentHuggingPriority(UILayoutPriority(rawValue: 500), for: .horizontal) NotificationCenter.default.addObserver(forName: Notification.Name.UIApplicationWillResignActive, object: nil, queue: nil) { [weak self] _ in guard let me = self else { return } @@ -112,7 +112,7 @@ open class SegmentedCell : Cell, CellType { segmentedControl.isEnabled = !row.isDisabled } - func valueChanged() { + @objc func valueChanged() { row.value = (row as! SegmentedRow).options[segmentedControl.selectedSegmentIndex] } diff --git a/Source/Rows/SliderRow.swift b/Source/Rows/SliderRow.swift index f4032c2f0..0fdf1e026 100644 --- a/Source/Rows/SliderRow.swift +++ b/Source/Rows/SliderRow.swift @@ -64,17 +64,17 @@ open class SliderCell: Cell, CellType { // title let title = textLabel textLabel?.translatesAutoresizingMaskIntoConstraints = false - textLabel?.setContentHuggingPriority(500, for: .horizontal) + textLabel?.setContentHuggingPriority(UILayoutPriority(rawValue: 500), for: .horizontal) self.titleLabel = title let value = detailTextLabel value?.translatesAutoresizingMaskIntoConstraints = false - value?.setContentHuggingPriority(500, for: .horizontal) + value?.setContentHuggingPriority(UILayoutPriority(rawValue: 500), for: .horizontal) self.valueLabel = value let slider = UISlider() slider.translatesAutoresizingMaskIntoConstraints = false - slider.setContentHuggingPriority(500, for: .horizontal) + slider.setContentHuggingPriority(UILayoutPriority(rawValue: 500), for: .horizontal) self.slider = slider if shouldShowTitle { @@ -116,7 +116,7 @@ open class SliderCell: Cell, CellType { } - func valueChanged() { + @objc func valueChanged() { let roundedValue: Float let steps = Float(sliderRow.steps) if steps > 0 { diff --git a/Source/Rows/StepperRow.swift b/Source/Rows/StepperRow.swift index c40dd61d1..0021f030d 100644 --- a/Source/Rows/StepperRow.swift +++ b/Source/Rows/StepperRow.swift @@ -63,7 +63,7 @@ open class StepperCell: Cell, CellType { detailTextLabel?.text = nil } - func valueChanged() { + @objc func valueChanged() { row.value = stepper.value row.updateCell() } diff --git a/Source/Rows/SwitchRow.swift b/Source/Rows/SwitchRow.swift index f37f115c4..e54f9039c 100644 --- a/Source/Rows/SwitchRow.swift +++ b/Source/Rows/SwitchRow.swift @@ -58,7 +58,7 @@ open class SwitchCell: Cell, CellType { switchControl.isEnabled = !row.isDisabled } - func valueChanged() { + @objc func valueChanged() { row.value = switchControl?.isOn ?? false } } diff --git a/Tests/SelectableSectionTests.swift b/Tests/SelectableSectionTests.swift index 0008eceda..e15c634fe 100644 --- a/Tests/SelectableSectionTests.swift +++ b/Tests/SelectableSectionTests.swift @@ -81,21 +81,22 @@ class SelectableSectionTests: XCTestCase { let value1 = (formVC.form[0] as! SelectableSection>).selectedRow()?.baseValue let value2 = (formVC.form[1] as! SelectableSection>).selectedRows().map({$0.baseValue}) - XCTAssertEqual(value1 as? String, "Antarctica") + XCTAssertEqual(value1 as! String, "Antarctica") XCTAssertTrue(value2.count == 2) - XCTAssertEqual((value2[0] as? String), "Atlantic") - XCTAssertEqual((value2[1] as? String), "Pacific") + XCTAssertEqual((value2[0] as! String), "Atlantic") + XCTAssertEqual((value2[1] as! String), "Pacific") //Now deselect One of the multiple selection section and change the value of the first section formVC.tableView(formVC.tableView!, didSelectRowAt: IndexPath(row: 6, section: 0)) formVC.tableView(formVC.tableView!, didSelectRowAt: IndexPath(row: 1, section: 1)) + let value3 = (formVC.form[0] as! SelectableSection>).selectedRow()?.baseValue let value4 = (formVC.form[1] as! SelectableSection>).selectedRows().map({$0.baseValue}) - XCTAssertEqual(value3 as? String, "South America") + XCTAssertEqual(value3 as! String, "South America") XCTAssertTrue(value4.count == 1) - XCTAssertEqual((value4[0] as? String), "Pacific") + XCTAssertEqual((value4[0] as! String), "Pacific") } func testDeselectionDisabled() { From 806ba3454d8cf468e97fcbcd04e450fe0459a850 Mon Sep 17 00:00:00 2001 From: Marco Betschart Date: Thu, 15 Jun 2017 19:16:22 +0200 Subject: [PATCH 2/8] SwipeAction Support for iOS11 And a gracefull fallback to the old iOS7 behaviour which allows swipe actions only on the right. --- Eureka.xcodeproj/project.pbxproj | 4 + Example/Example/Base.lproj/Main.storyboard | 29 +++-- Example/Example/ViewController.swift | 72 +++++++++-- Source/Core/BaseRow.swift | 11 ++ Source/Core/Core.swift | 56 ++++++--- Source/Core/Form.swift | 6 + Source/Core/Section.swift | 2 +- Source/Core/SwipeActions.swift | 134 +++++++++++++++++++++ 8 files changed, 281 insertions(+), 33 deletions(-) create mode 100644 Source/Core/SwipeActions.swift diff --git a/Eureka.xcodeproj/project.pbxproj b/Eureka.xcodeproj/project.pbxproj index b2b1feecc..0fa06a87b 100644 --- a/Eureka.xcodeproj/project.pbxproj +++ b/Eureka.xcodeproj/project.pbxproj @@ -83,6 +83,7 @@ 49ADC7F91C8A83240073952B /* StepperRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49ADC7F81C8A83240073952B /* StepperRow.swift */; }; 51729DF61B9A4F5E004A00EB /* Eureka.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 51729DEB1B9A4F5E004A00EB /* Eureka.framework */; }; 51729E671B9A5FA5004A00EB /* Eureka.h in Headers */ = {isa = PBXBuildFile; fileRef = 51729E661B9A5FA5004A00EB /* Eureka.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 798C5ADA1EF1E35600A21F52 /* SwipeActions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 798C5AD91EF1E35600A21F52 /* SwipeActions.swift */; }; 8690E4BE1CFDE062004CDB1C /* Eureka.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 8690E4BD1CFDE062004CDB1C /* Eureka.bundle */; }; B257FE2E1EC0F66900043911 /* RowsInsertionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B257FE2D1EC0F66900043911 /* RowsInsertionTests.swift */; }; B2A401161EC0BA140042EDF0 /* SectionsInsertionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B2A401151EC0BA140042EDF0 /* SectionsInsertionTests.swift */; }; @@ -179,6 +180,7 @@ 51729DF51B9A4F5E004A00EB /* EurekaTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = EurekaTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 51729DFC1B9A4F5E004A00EB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = ../Tests/Info.plist; sourceTree = ""; }; 51729E661B9A5FA5004A00EB /* Eureka.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = Eureka.h; path = Source/Eureka.h; sourceTree = SOURCE_ROOT; }; + 798C5AD91EF1E35600A21F52 /* SwipeActions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeActions.swift; sourceTree = ""; }; 8690E4BD1CFDE062004CDB1C /* Eureka.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Eureka.bundle; sourceTree = ""; }; B257FE2D1EC0F66900043911 /* RowsInsertionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = RowsInsertionTests.swift; path = Tests/RowsInsertionTests.swift; sourceTree = SOURCE_ROOT; }; B2A401151EC0BA140042EDF0 /* SectionsInsertionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SectionsInsertionTests.swift; path = Tests/SectionsInsertionTests.swift; sourceTree = SOURCE_ROOT; }; @@ -225,6 +227,7 @@ 28EEBFF71C7E241200300699 /* SelectableRowType.swift */, 2859CDBF1C7D138D0002982F /* Section.swift */, 2859CE231C7E141B0002982F /* SelectableSection.swift */, + 798C5AD91EF1E35600A21F52 /* SwipeActions.swift */, 28EE0FDD1D5E889F00B91340 /* Validation.swift */, ); name = Core; @@ -530,6 +533,7 @@ 49ADC7F91C8A83240073952B /* StepperRow.swift in Sources */, 28EE46AC1C7F712300EFF4A2 /* DateInlineRow.swift in Sources */, 28EEC01C1C7E3A7400300699 /* ButtonRowWithPresent.swift in Sources */, + 798C5ADA1EF1E35600A21F52 /* SwipeActions.swift in Sources */, 28EEBFF41C7E240000300699 /* RowProtocols.swift in Sources */, 28EEBFFE1C7E281F00300699 /* CheckRow.swift in Sources */, 2859CDC41C7D19C50002982F /* HeaderFooterView.swift in Sources */, diff --git a/Example/Example/Base.lproj/Main.storyboard b/Example/Example/Base.lproj/Main.storyboard index 52d44a11d..0a7edc409 100644 --- a/Example/Example/Base.lproj/Main.storyboard +++ b/Example/Example/Base.lproj/Main.storyboard @@ -1,11 +1,11 @@ - + - + @@ -172,6 +172,7 @@ + @@ -264,6 +265,25 @@ + + + + + + + + + + + + + + + + + + + @@ -652,9 +672,4 @@ - - - - - diff --git a/Example/Example/ViewController.swift b/Example/Example/ViewController.swift index 088671bc5..80f434552 100644 --- a/Example/Example/ViewController.swift +++ b/Example/Example/ViewController.swift @@ -106,15 +106,11 @@ class HomeViewController : FormViewController { row.title = row.tag row.presentationMode = .segueName(segueName: "MultivaluedSectionsControllerSegue", onDismiss: nil) } - - - +++ Section() - <<< ButtonRow() { (row: ButtonRow) -> Void in - row.title = "About" - } - .onCellSelection { [weak self] (cell, row) in - self?.showAlert() - } + + <<< ButtonRow("Swipe Actions") { (row: ButtonRow) -> Void in + row.title = row.tag + row.presentationMode = .segueName(segueName: "SwipeActionsControllerSegue", onDismiss: nil) + } } @@ -1681,6 +1677,64 @@ class MultivaluedOnlyDeleteController: FormViewController { } } +class SwipeActionsController: FormViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + form +++ Section(footer: "Eureka sets table.isEditing = false for SwipeActions.\n\nMultivaluedSections need table.isEditing = true, therefore both can't be used on the same view.") + <<< LabelRow("Actions Right: iOS >= 7"){ + $0.title = $0.tag + + $0.trailingSwipeConfiguration = SwipeConfiguration(){ + let moreAction = SwipeAction(style: .normal, title: "More", handler: { (action, path, completionHandler) in + print("More") + completionHandler?(true) + }) + + let deleteAction = SwipeAction(style: .destructive, title: "Delete", handler: { (action, path, completionHandler) in + print("Delete") + completionHandler?(true) + }) + + $0.actions = [deleteAction,moreAction] + } + } + + <<< LabelRow("Actions Left & Right: iOS >= 11"){ + $0.title = $0.tag + + $0.trailingSwipeConfiguration = SwipeConfiguration(){ + let moreAction = SwipeAction(style: .normal, title: "More", handler: { (action, path, completionHandler) in + print("More") + completionHandler?(true) + }) + + let deleteAction = SwipeAction(style: .destructive, title: "Delete", handler: { (action, path, completionHandler) in + print("Delete") + completionHandler?(true) + }) + + $0.performsFirstActionWithFullSwipe = true + $0.actions = [deleteAction,moreAction] + } + + if #available(iOS 11,*){ + $0.leadingSwipeConfiguration = SwipeConfiguration(){ + let infoAction = SwipeAction(style: .normal, title: "Info", handler: { (action, path, completionHandler) in + print("Info") + completionHandler?(true) + }) + infoAction.backgroundColor = .blue + + $0.performsFirstActionWithFullSwipe = true + $0.actions = [infoAction] + } + } + } + } +} + class EurekaLogoViewNib: UIView { @IBOutlet weak var imageView: UIImageView! diff --git a/Source/Core/BaseRow.swift b/Source/Core/BaseRow.swift index 78ed570b6..7cc6f6f04 100644 --- a/Source/Core/BaseRow.swift +++ b/Source/Core/BaseRow.swift @@ -97,6 +97,17 @@ open class BaseRow: BaseRowType { /// The section to which this row belongs. public weak var section: Section? + + public var trailingSwipeConfiguration: SwipeConfiguration? + + //needs the accessor because if marked directly this throws "Stored properties cannot be marked potentially unavailable with '@available'" + private var _leadingSwipeConfiguration: SwipeConfiguration? + + @available(iOS 11,*) + public var leadingSwipeConfiguration: SwipeConfiguration?{ + get{ return self._leadingSwipeConfiguration } + set{ self._leadingSwipeConfiguration = newValue } + } public required init(tag: String? = nil) { self.tag = tag diff --git a/Source/Core/Core.swift b/Source/Core/Core.swift index 8b638d1e2..36b03fb9d 100644 --- a/Source/Core/Core.swift +++ b/Source/Core/Core.swift @@ -474,9 +474,7 @@ open class FormViewController: UIViewController, FormViewControllerProtocol { tableView.dataSource = self } tableView.estimatedRowHeight = BaseRow.estimatedRowHeight - - tableView.setEditing(true, animated: false) - tableView.allowsSelectionDuringEditing = true + tableView.allowsSelectionDuringEditing = true } open override func viewWillAppear(_ animated: Bool) { @@ -514,6 +512,10 @@ open class FormViewController: UIViewController, FormViewControllerProtocol { NotificationCenter.default.addObserver(self, selector: #selector(FormViewController.keyboardWillShow(_:)), name: Notification.Name.UIKeyboardWillShow, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(FormViewController.keyboardWillHide(_:)), name: Notification.Name.UIKeyboardWillHide, object: nil) + + if form.containsMultivaluedSection{ + tableView.setEditing(true, animated: false) + } } open override func viewDidDisappear(_ animated: Bool) { @@ -757,8 +759,13 @@ extension FormViewController : UITableViewDelegate { } open func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { - guard let section = form[indexPath.section] as? MultivaluedSection else { return false } - let row = form[indexPath] + let row = form[indexPath] + if let actions = row.trailingSwipeConfiguration?.actions.count, actions > 0{ + return true + } else if #available(iOS 11,*), let actions = row.leadingSwipeConfiguration?.actions.count, actions > 0{ + return true + } + guard let section = form[indexPath.section] as? MultivaluedSection else { return false } guard !row.isDisabled else { return false } guard !(indexPath.row == section.count - 1 && section.multivaluedOptions.contains(.Insert) && section.showInsertIconInAddButton) else { return true @@ -768,7 +775,7 @@ extension FormViewController : UITableViewDelegate { } return true } - + public func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { if editingStyle == .delete { let row = form[indexPath] @@ -800,7 +807,7 @@ extension FormViewController : UITableViewDelegate { } } } - + public func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { guard let section = form[indexPath.section] as? MultivaluedSection, section.multivaluedOptions.contains(.Reorder) && section.count > 1 else { return false @@ -813,16 +820,16 @@ extension FormViewController : UITableViewDelegate { } return true } - + public func tableView(_ tableView: UITableView, targetIndexPathForMoveFromRowAt sourceIndexPath: IndexPath, toProposedIndexPath proposedDestinationIndexPath: IndexPath) -> IndexPath { guard let section = form[sourceIndexPath.section] as? MultivaluedSection else { return sourceIndexPath } guard sourceIndexPath.section == proposedDestinationIndexPath.section else { return sourceIndexPath } - + let destRow = form[proposedDestinationIndexPath] if destRow is BaseInlineRowType && destRow._inlineRow != nil { return IndexPath(row: proposedDestinationIndexPath.row + (sourceIndexPath.row < proposedDestinationIndexPath.row ? 1 : -1), section:sourceIndexPath.section) } - + if proposedDestinationIndexPath.row > 0 { let previousRow = form[IndexPath(row: proposedDestinationIndexPath.row - 1, section: proposedDestinationIndexPath.section)] if previousRow is BaseInlineRowType && previousRow._inlineRow != nil { @@ -834,12 +841,12 @@ extension FormViewController : UITableViewDelegate { } return proposedDestinationIndexPath } - + public func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) { - + guard var section = form[sourceIndexPath.section] as? MultivaluedSection else { return } if sourceIndexPath.row < section.count && destinationIndexPath.row < section.count && sourceIndexPath.row != destinationIndexPath.row { - + let sourceRow = form[sourceIndexPath] animateTableView = false section.remove(at: sourceIndexPath.row) @@ -849,9 +856,12 @@ extension FormViewController : UITableViewDelegate { let _ = inputAccessoryView(for: sourceRow) } } - + public func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle { guard let section = form[indexPath.section] as? MultivaluedSection else { + if let actions = form[indexPath].trailingSwipeConfiguration?.actions.count, actions > 0{ + return .delete + } return .none } if section.multivaluedOptions.contains(.Insert) && indexPath.row == section.count - 1 { @@ -862,10 +872,24 @@ extension FormViewController : UITableViewDelegate { } return .none } - + public func tableView(_ tableView: UITableView, shouldIndentWhileEditingRowAt indexPath: IndexPath) -> Bool { return self.tableView(tableView, editingStyleForRowAt: indexPath) != .none } + + @available(iOS 11,*) + public func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + return form[indexPath].leadingSwipeConfiguration?.platformValue as? UISwipeActionsConfiguration + } + + @available(iOS 11,*) + public func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + return form[indexPath].trailingSwipeConfiguration?.platformValue as? UISwipeActionsConfiguration + } + + public func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]?{ + return form[indexPath].trailingSwipeConfiguration?.platformActions as? [UITableViewRowAction] + } } extension FormViewController : UITableViewDataSource { @@ -899,7 +923,7 @@ extension FormViewController: FormDelegate { // MARK: FormDelegate open func sectionsHaveBeenAdded(_ sections: [Section], at indexes: IndexSet) { - guard animateTableView else { return } + guard animateTableView else { return } tableView?.beginUpdates() tableView?.insertSections(indexes, with: insertAnimation(forSections: sections)) tableView?.endUpdates() diff --git a/Source/Core/Form.swift b/Source/Core/Form.swift index 569ba8eca..de39452d4 100644 --- a/Source/Core/Form.swift +++ b/Source/Core/Form.swift @@ -360,6 +360,12 @@ extension Form { } kvoWrapper.sections.insert(section, at: formIndex == NSNotFound ? 0 : formIndex + 1 ) } + + var containsMultivaluedSection: Bool{ + return kvoWrapper.sections.contains { (section) -> Bool in + return section is MultivaluedSection + } + } } extension Form { diff --git a/Source/Core/Section.swift b/Source/Core/Section.swift index 86c4c291e..438a08078 100644 --- a/Source/Core/Section.swift +++ b/Source/Core/Section.swift @@ -454,7 +454,7 @@ open class MultivaluedSection: Section { let addRow = addButtonProvider(self) addRow.onCellSelection { cell, row in guard let tableView = cell.formViewController()?.tableView, let indexPath = row.indexPath else { return } - cell.formViewController()?.tableView(tableView, commit: .insert, forRowAt: indexPath) + cell.formViewController()?.tableView(tableView, commit: .insert, forRowAt: indexPath) } self <<< addRow } diff --git a/Source/Core/SwipeActions.swift b/Source/Core/SwipeActions.swift new file mode 100644 index 000000000..ec8ab41c0 --- /dev/null +++ b/Source/Core/SwipeActions.swift @@ -0,0 +1,134 @@ +// +// Swipe.swift +// Eureka +// +// Created by Marco Betschart on 14.06.17. +// Copyright © 2017 Xmartlabs. All rights reserved. +// + +import Foundation + +public typealias SwipeActionHandler = (Any, Any, ((Bool) -> Void)?) -> Void + + +public class SwipeAction{ + + public let platformValue: Any + + public var backgroundColor: UIColor?{ + didSet{ + if #available(iOS 11, *), let platformValue = self.platformValue as? UIContextualAction{ + platformValue.backgroundColor = self.backgroundColor + } + } + } + + public var image: UIImage?{ + didSet{ + if #available(iOS 11, *), let platformValue = self.platformValue as? UIContextualAction{ + platformValue.image = self.image + } + } + } + + public var title: String?{ + didSet{ + if #available(iOS 11, *), let platformValue = self.platformValue as? UIContextualAction{ + platformValue.title = self.title + } + } + } + + public let handler: SwipeActionHandler + public let style: Style + + public init(style: Style, title: String?, handler: @escaping SwipeActionHandler){ + self.style = style + self.handler = handler + self.title = title + + if #available(iOS 11, *){ + self.platformValue = UIContextualAction(style: style.platformValue as! UIContextualAction.Style, title: title){ action, view, completion -> Void in + handler(action,view,completion) + } + + } else { + self.platformValue = UITableViewRowAction(style: style.platformValue as! UITableViewRowActionStyle,title: title){ (action, indexPath) -> Void in + handler(action,indexPath,nil) + } + } + } + + public enum Style: Int{ + case normal = 0 + case destructive = 1 + + var platformValue: Any{ + if #available(iOS 11, *){ + switch self{ + case .normal: + return UIContextualAction.Style.normal + case .destructive: + return UIContextualAction.Style.destructive + } + + } else { + switch self{ + case .normal: + return UITableViewRowActionStyle.normal + case .destructive: + return UITableViewRowActionStyle.destructive + } + } + } + + public init(platformValue: Any){ + if #available(iOS 11, *), let platformValue = platformValue as? UIContextualAction.Style{ + switch platformValue{ + case .normal: + self = .normal + case .destructive: + self = .destructive + } + + } else if let platformValue = platformValue as? UITableViewRowActionStyle{ + switch platformValue{ + case .normal: + self = .normal + case .destructive: + self = .destructive + case .default: + fatalError("Unmapped UITableViewRowActionStyle for SwipeActionStyle: \(platformValue)") + } + + } else { + fatalError("Invalid platformValue for SwipeActionStyle: \(platformValue)") + } + } + } +} + + +public class SwipeConfiguration{ + + public init(configure: (SwipeConfiguration) -> Void){ + configure(self) + } + + public var performsFirstActionWithFullSwipe: Bool = false + public var actions: [SwipeAction] = [] + + public var platformValue: Any?{ + guard #available(iOS 11, *) else{ + return nil + } + let platformValue = UISwipeActionsConfiguration(actions: self.platformActions as! [UIContextualAction]) + platformValue.performsFirstActionWithFullSwipe = self.performsFirstActionWithFullSwipe + + return platformValue + } + + public var platformActions: [Any]{ + return self.actions.map{ $0.platformValue } + } +} From 76617fc6a4fac6ed085476c2c192267832cd44e5 Mon Sep 17 00:00:00 2001 From: Marco Betschart Date: Tue, 17 Oct 2017 20:51:19 +0200 Subject: [PATCH 3/8] Readded containsMultivaluedSection If there are any multivalued sections within the form, Eureka automatically enables editingMode to allow swipe actions to be handled correctly. You have to disable editing manually if you do not want this behaviour in forms containing multivalued sections. --- Source/Core/Form.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/Core/Form.swift b/Source/Core/Form.swift index d78869540..97559938e 100644 --- a/Source/Core/Form.swift +++ b/Source/Core/Form.swift @@ -352,6 +352,12 @@ extension Form { } kvoWrapper.sections.insert(section, at: formIndex == NSNotFound ? 0 : formIndex + 1 ) } + + var containsMultivaluedSection: Bool{ + return kvoWrapper.sections.contains { (section) -> Bool in + return section is MultivaluedSection + } + } func getValues(for rows: [BaseRow]) -> [String: Any?] { return rows.reduce([String: Any?]()) { From be4c01d20b1bcfcaf990baae092d75ff2bfe07b2 Mon Sep 17 00:00:00 2001 From: Marco Betschart Date: Sat, 2 Dec 2017 15:53:29 +0100 Subject: [PATCH 4/8] Made var section open In order to allow it to be overwritten by a row which subclasses it. This is needed for the new custom row `SplitRow`. `SplitRow` enables to put two Eureka rows side by side into one UITableViewCell. --- Source/Core/BaseRow.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/Core/BaseRow.swift b/Source/Core/BaseRow.swift index 747a805df..777e15cdc 100644 --- a/Source/Core/BaseRow.swift +++ b/Source/Core/BaseRow.swift @@ -98,7 +98,7 @@ open class BaseRow: BaseRowType { public var isHidden: Bool { return hiddenCache } /// The section to which this row belongs. - public weak var section: Section? + open weak var section: Section? public required init(tag: String? = nil) { self.tag = tag From 017d4a37cf8a3da168b0ed4c17d1f33f9b6fea5b Mon Sep 17 00:00:00 2001 From: Marco Betschart Date: Sat, 9 Dec 2017 13:42:39 +0100 Subject: [PATCH 5/8] Fixed issues based upon feedback from xmartlabs --- Example/Example/ViewController.swift | 114 +++++++++++++-------------- Source/Core/BaseRow.swift | 2 +- Source/Core/Core.swift | 6 +- Source/Core/SwipeActions.swift | 104 ++++++++++++++++-------- 4 files changed, 130 insertions(+), 96 deletions(-) diff --git a/Example/Example/ViewController.swift b/Example/Example/ViewController.swift index 6eb07368b..02b906e9a 100644 --- a/Example/Example/ViewController.swift +++ b/Example/Example/ViewController.swift @@ -107,10 +107,10 @@ class HomeViewController : FormViewController { row.presentationMode = .segueName(segueName: "MultivaluedSectionsControllerSegue", onDismiss: nil) } - <<< ButtonRow("Swipe Actions") { (row: ButtonRow) -> Void in - row.title = row.tag - row.presentationMode = .segueName(segueName: "SwipeActionsControllerSegue", onDismiss: nil) - } + <<< ButtonRow("Swipe Actions") { (row: ButtonRow) -> Void in + row.title = row.tag + row.presentationMode = .segueName(segueName: "SwipeActionsControllerSegue", onDismiss: nil) + } } @@ -1745,60 +1745,60 @@ class MultivaluedOnlyDeleteController: FormViewController { class SwipeActionsController: FormViewController { - override func viewDidLoad() { - super.viewDidLoad() + override func viewDidLoad() { + super.viewDidLoad() - form +++ Section(footer: "Eureka sets table.isEditing = false for SwipeActions.\n\nMultivaluedSections need table.isEditing = true, therefore both can't be used on the same view.") - <<< LabelRow("Actions Right: iOS >= 7"){ - $0.title = $0.tag - - $0.trailingSwipeConfiguration = SwipeConfiguration(){ - let moreAction = SwipeAction(style: .normal, title: "More", handler: { (action, path, completionHandler) in - print("More") - completionHandler?(true) - }) - - let deleteAction = SwipeAction(style: .destructive, title: "Delete", handler: { (action, path, completionHandler) in - print("Delete") - completionHandler?(true) - }) - - $0.actions = [deleteAction,moreAction] - } - } - - <<< LabelRow("Actions Left & Right: iOS >= 11"){ - $0.title = $0.tag - - $0.trailingSwipeConfiguration = SwipeConfiguration(){ - let moreAction = SwipeAction(style: .normal, title: "More", handler: { (action, path, completionHandler) in - print("More") - completionHandler?(true) - }) - - let deleteAction = SwipeAction(style: .destructive, title: "Delete", handler: { (action, path, completionHandler) in - print("Delete") - completionHandler?(true) - }) - - $0.performsFirstActionWithFullSwipe = true - $0.actions = [deleteAction,moreAction] - } - - if #available(iOS 11,*){ - $0.leadingSwipeConfiguration = SwipeConfiguration(){ - let infoAction = SwipeAction(style: .normal, title: "Info", handler: { (action, path, completionHandler) in - print("Info") - completionHandler?(true) - }) - infoAction.backgroundColor = .blue - - $0.performsFirstActionWithFullSwipe = true - $0.actions = [infoAction] - } - } - } - } + form +++ Section(footer: "Eureka sets table.isEditing = false for SwipeActions.\n\nMultivaluedSections need table.isEditing = true, therefore both can't be used on the same view.") + <<< LabelRow("Actions Right: iOS >= 7") { + $0.title = $0.tag + + $0.trailingSwipeConfiguration = SwipeConfiguration() { + let moreAction = SwipeAction(style: .normal, title: "More", handler: { (action, source, completionHandler) in + print("More") + completionHandler?(true) + }) + + let deleteAction = SwipeAction(style: .destructive, title: "Delete", handler: { (action, source, completionHandler) in + print("Delete") + completionHandler?(true) + }) + + $0.actions = [deleteAction,moreAction] + } + } + + <<< LabelRow("Actions Left & Right: iOS >= 11") { + $0.title = $0.tag + + $0.trailingSwipeConfiguration = SwipeConfiguration() { + let moreAction = SwipeAction(style: .normal, title: "More", handler: { (action, source, completionHandler) in + print("More") + completionHandler?(true) + }) + + let deleteAction = SwipeAction(style: .destructive, title: "Delete", handler: { (action, source, completionHandler) in + print("Delete") + completionHandler?(true) + }) + + $0.performsFirstActionWithFullSwipe = true + $0.actions = [deleteAction,moreAction] + } + + if #available(iOS 11,*) { + $0.leadingSwipeConfiguration = SwipeConfiguration() { + let infoAction = SwipeAction(style: .normal, title: "Info", handler: { (action, source, completionHandler) in + print("Info") + completionHandler?(true) + }) + infoAction.backgroundColor = .blue + + $0.performsFirstActionWithFullSwipe = true + $0.actions = [infoAction] + } + } + } + } } class EurekaLogoViewNib: UIView { diff --git a/Source/Core/BaseRow.swift b/Source/Core/BaseRow.swift index 1c034848e..3c1fb6cff 100644 --- a/Source/Core/BaseRow.swift +++ b/Source/Core/BaseRow.swift @@ -98,7 +98,7 @@ open class BaseRow: BaseRowType { public var isHidden: Bool { return hiddenCache } /// The section to which this row belongs. - open weak var section: Section? + public weak var section: Section? public var trailingSwipeConfiguration: SwipeConfiguration? diff --git a/Source/Core/Core.swift b/Source/Core/Core.swift index 0fa73277f..b907f494f 100644 --- a/Source/Core/Core.swift +++ b/Source/Core/Core.swift @@ -911,16 +911,16 @@ extension FormViewController : UITableViewDelegate { @available(iOS 11,*) public func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - return form[indexPath].leadingSwipeConfiguration?.platformValue as? UISwipeActionsConfiguration + return form[indexPath].leadingSwipeConfiguration?.contextualConfiguration as? UISwipeActionsConfiguration } @available(iOS 11,*) public func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - return form[indexPath].trailingSwipeConfiguration?.platformValue as? UISwipeActionsConfiguration + return form[indexPath].trailingSwipeConfiguration?.contextualConfiguration as? UISwipeActionsConfiguration } public func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]?{ - return form[indexPath].trailingSwipeConfiguration?.platformActions as? [UITableViewRowAction] + return form[indexPath].trailingSwipeConfiguration?.contextualActions as? [UITableViewRowAction] } } diff --git a/Source/Core/SwipeActions.swift b/Source/Core/SwipeActions.swift index ec8ab41c0..b0f62a598 100644 --- a/Source/Core/SwipeActions.swift +++ b/Source/Core/SwipeActions.swift @@ -8,33 +8,51 @@ import Foundation -public typealias SwipeActionHandler = (Any, Any, ((Bool) -> Void)?) -> Void - +public typealias SwipeActionHandler = (ContextualAction, ContextualActionSource, ((Bool) -> Void)?) -> Void public class SwipeAction{ + public let contextualAction: ContextualAction - public let platformValue: Any - + @available(iOS 11, *) public var backgroundColor: UIColor?{ - didSet{ - if #available(iOS 11, *), let platformValue = self.platformValue as? UIContextualAction{ - platformValue.backgroundColor = self.backgroundColor - } + get{ + guard let contextualAction = self.contextualAction as? UIContextualAction else{ return nil } + return contextualAction.backgroundColor + } + set{ + guard let contextualAction = self.contextualAction as? UIContextualAction else{ return } + contextualAction.backgroundColor = newValue } } + @available(iOS 11, *) public var image: UIImage?{ - didSet{ - if #available(iOS 11, *), let platformValue = self.platformValue as? UIContextualAction{ - platformValue.image = self.image - } + get{ + guard let contextualAction = self.contextualAction as? UIContextualAction else{ return nil } + return contextualAction.image + } + set{ + guard let contextualAction = self.contextualAction as? UIContextualAction else{ return } + return contextualAction.image = newValue } } public var title: String?{ - didSet{ - if #available(iOS 11, *), let platformValue = self.platformValue as? UIContextualAction{ - platformValue.title = self.title + get{ + if #available(iOS 11, *), let contextualAction = self.contextualAction as? UIContextualAction{ + return contextualAction.title + + } else if let contextualAction = self.contextualAction as? UITableViewRowAction{ + return contextualAction.title + } + return nil + } + set{ + if #available(iOS 11, *), let contextualAction = self.contextualAction as? UIContextualAction{ + contextualAction.title = newValue + + } else if let contextualAction = self.contextualAction as? UITableViewRowAction{ + contextualAction.title = newValue } } } @@ -45,15 +63,14 @@ public class SwipeAction{ public init(style: Style, title: String?, handler: @escaping SwipeActionHandler){ self.style = style self.handler = handler - self.title = title if #available(iOS 11, *){ - self.platformValue = UIContextualAction(style: style.platformValue as! UIContextualAction.Style, title: title){ action, view, completion -> Void in + self.contextualAction = UIContextualAction(style: style.contextualStyle as! UIContextualAction.Style, title: title){ action, view, completion -> Void in handler(action,view,completion) } } else { - self.platformValue = UITableViewRowAction(style: style.platformValue as! UITableViewRowActionStyle,title: title){ (action, indexPath) -> Void in + self.contextualAction = UITableViewRowAction(style: style.contextualStyle as! UITableViewRowActionStyle,title: title){ (action, indexPath) -> Void in handler(action,indexPath,nil) } } @@ -63,7 +80,7 @@ public class SwipeAction{ case normal = 0 case destructive = 1 - var platformValue: Any{ + var contextualStyle: ContextualStyle{ if #available(iOS 11, *){ switch self{ case .normal: @@ -82,27 +99,27 @@ public class SwipeAction{ } } - public init(platformValue: Any){ - if #available(iOS 11, *), let platformValue = platformValue as? UIContextualAction.Style{ - switch platformValue{ + public init(contextualStyle: ContextualStyle){ + if #available(iOS 11, *), let contextualStyle = contextualStyle as? UIContextualAction.Style{ + switch contextualStyle{ case .normal: self = .normal case .destructive: self = .destructive } - } else if let platformValue = platformValue as? UITableViewRowActionStyle{ - switch platformValue{ + } else if let contextualStyle = contextualStyle as? UITableViewRowActionStyle{ + switch contextualStyle{ case .normal: self = .normal case .destructive: self = .destructive case .default: - fatalError("Unmapped UITableViewRowActionStyle for SwipeActionStyle: \(platformValue)") + fatalError("Unmapped UITableViewRowActionStyle for SwipeActionStyle: \(contextualStyle)") } } else { - fatalError("Invalid platformValue for SwipeActionStyle: \(platformValue)") + fatalError("Invalid platformValue for SwipeActionStyle: \(contextualStyle)") } } } @@ -118,17 +135,34 @@ public class SwipeConfiguration{ public var performsFirstActionWithFullSwipe: Bool = false public var actions: [SwipeAction] = [] - public var platformValue: Any?{ - guard #available(iOS 11, *) else{ - return nil - } - let platformValue = UISwipeActionsConfiguration(actions: self.platformActions as! [UIContextualAction]) - platformValue.performsFirstActionWithFullSwipe = self.performsFirstActionWithFullSwipe + @available(iOSApplicationExtension 11.0, *) + public var contextualConfiguration: UISwipeActionsConfiguration?{ + let contextualConfiguration = UISwipeActionsConfiguration(actions: self.contextualActions as! [UIContextualAction]) + contextualConfiguration.performsFirstActionWithFullSwipe = self.performsFirstActionWithFullSwipe - return platformValue + return contextualConfiguration } - public var platformActions: [Any]{ - return self.actions.map{ $0.platformValue } + public var contextualActions: [ContextualAction]{ + return self.actions.map{ $0.contextualAction } } } + + +public protocol ContextualAction{} +extension UITableViewRowAction: ContextualAction{} + +@available(iOSApplicationExtension 11.0, *) +extension UIContextualAction: ContextualAction{} + +public protocol ContextualStyle{} +extension UITableViewRowActionStyle: ContextualStyle{} + +@available(iOSApplicationExtension 11.0, *) +extension UIContextualAction.Style: ContextualStyle{} + +public protocol ContextualActionSource{} +extension IndexPath: ContextualActionSource{} + +@available(iOSApplicationExtension 11.0, *) +extension UIView: ContextualActionSource{} From 4ea0ef591c31d7ea25e645bb9c5cc484fd144525 Mon Sep 17 00:00:00 2001 From: Marco Betschart Date: Sat, 9 Dec 2017 13:44:32 +0100 Subject: [PATCH 6/8] Fixed spacing --- Source/Core/SwipeActions.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/Core/SwipeActions.swift b/Source/Core/SwipeActions.swift index b0f62a598..92ea4c77f 100644 --- a/Source/Core/SwipeActions.swift +++ b/Source/Core/SwipeActions.swift @@ -66,12 +66,12 @@ public class SwipeAction{ if #available(iOS 11, *){ self.contextualAction = UIContextualAction(style: style.contextualStyle as! UIContextualAction.Style, title: title){ action, view, completion -> Void in - handler(action,view,completion) + handler(action, view, completion) } } else { self.contextualAction = UITableViewRowAction(style: style.contextualStyle as! UITableViewRowActionStyle,title: title){ (action, indexPath) -> Void in - handler(action,indexPath,nil) + handler(action, indexPath, nil) } } } From 8c76499c999ce88d86a8ff4e4db43e5b412f8242 Mon Sep 17 00:00:00 2001 From: Marco Betschart Date: Wed, 13 Dec 2017 17:39:19 +0100 Subject: [PATCH 7/8] Implemented feedback of @mtnbarreto As described in https://github.com/xmartlabs/Eureka/pull/1249 --- Example/Example/ViewController.swift | 68 ++++++------- Source/Core/BaseRow.swift | 10 +- Source/Core/Core.swift | 12 +-- Source/Core/SwipeActions.swift | 139 +++++++++------------------ 4 files changed, 90 insertions(+), 139 deletions(-) diff --git a/Example/Example/ViewController.swift b/Example/Example/ViewController.swift index 02b906e9a..6af807091 100644 --- a/Example/Example/ViewController.swift +++ b/Example/Example/ViewController.swift @@ -1752,50 +1752,44 @@ class SwipeActionsController: FormViewController { <<< LabelRow("Actions Right: iOS >= 7") { $0.title = $0.tag - $0.trailingSwipeConfiguration = SwipeConfiguration() { - let moreAction = SwipeAction(style: .normal, title: "More", handler: { (action, source, completionHandler) in - print("More") - completionHandler?(true) - }) - - let deleteAction = SwipeAction(style: .destructive, title: "Delete", handler: { (action, source, completionHandler) in - print("Delete") - completionHandler?(true) - }) - - $0.actions = [deleteAction,moreAction] - } + let moreAction = SwipeAction(style: .normal, title: "More", handler: { (action, row, completionHandler) in + print("More") + completionHandler?(true) + }) + + let deleteAction = SwipeAction(style: .destructive, title: "Delete", handler: { (action, row, completionHandler) in + print("Delete") + completionHandler?(true) + }) + + $0.trailingSwipe.actions = [deleteAction,moreAction] } <<< LabelRow("Actions Left & Right: iOS >= 11") { $0.title = $0.tag + + let moreAction = SwipeAction(style: .normal, title: "More", handler: { (action, row, completionHandler) in + print("More") + completionHandler?(true) + }) + + let deleteAction = SwipeAction(style: .destructive, title: "Delete", handler: { (action, row, completionHandler) in + print("Delete") + completionHandler?(true) + }) - $0.trailingSwipeConfiguration = SwipeConfiguration() { - let moreAction = SwipeAction(style: .normal, title: "More", handler: { (action, source, completionHandler) in - print("More") - completionHandler?(true) - }) - - let deleteAction = SwipeAction(style: .destructive, title: "Delete", handler: { (action, source, completionHandler) in - print("Delete") - completionHandler?(true) - }) - - $0.performsFirstActionWithFullSwipe = true - $0.actions = [deleteAction,moreAction] - } + $0.trailingSwipe.actions = [deleteAction,moreAction] + $0.trailingSwipe.performsFirstActionWithFullSwipe = true if #available(iOS 11,*) { - $0.leadingSwipeConfiguration = SwipeConfiguration() { - let infoAction = SwipeAction(style: .normal, title: "Info", handler: { (action, source, completionHandler) in - print("Info") - completionHandler?(true) - }) - infoAction.backgroundColor = .blue - - $0.performsFirstActionWithFullSwipe = true - $0.actions = [infoAction] - } + let infoAction = SwipeAction(style: .normal, title: "Info", handler: { (action, row, completionHandler) in + print("Info") + completionHandler?(true) + }) + infoAction.backgroundColor = .blue + + $0.leadingSwipe.actions = [infoAction] + $0.leadingSwipe.performsFirstActionWithFullSwipe = true } } } diff --git a/Source/Core/BaseRow.swift b/Source/Core/BaseRow.swift index 3c1fb6cff..14f6fb508 100644 --- a/Source/Core/BaseRow.swift +++ b/Source/Core/BaseRow.swift @@ -100,15 +100,15 @@ open class BaseRow: BaseRowType { /// The section to which this row belongs. public weak var section: Section? - public var trailingSwipeConfiguration: SwipeConfiguration? + public lazy var trailingSwipe = SwipeConfiguration(self) //needs the accessor because if marked directly this throws "Stored properties cannot be marked potentially unavailable with '@available'" - private var _leadingSwipeConfiguration: SwipeConfiguration? + private lazy var _leadingSwipe = SwipeConfiguration(self) @available(iOS 11,*) - public var leadingSwipeConfiguration: SwipeConfiguration?{ - get{ return self._leadingSwipeConfiguration } - set{ self._leadingSwipeConfiguration = newValue } + public var leadingSwipe: SwipeConfiguration{ + get{ return self._leadingSwipe } + set{ self._leadingSwipe = newValue } } public required init(tag: String? = nil) { diff --git a/Source/Core/Core.swift b/Source/Core/Core.swift index b907f494f..f30f5545f 100644 --- a/Source/Core/Core.swift +++ b/Source/Core/Core.swift @@ -792,9 +792,9 @@ extension FormViewController : UITableViewDelegate { open func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { let row = form[indexPath] - if let actions = row.trailingSwipeConfiguration?.actions.count, actions > 0{ + if row.trailingSwipe.actions.count > 0{ return true - } else if #available(iOS 11,*), let actions = row.leadingSwipeConfiguration?.actions.count, actions > 0{ + } else if #available(iOS 11,*), row.leadingSwipe.actions.count > 0{ return true } guard let section = form[indexPath.section] as? MultivaluedSection else { return false } @@ -891,7 +891,7 @@ extension FormViewController : UITableViewDelegate { open func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle { guard let section = form[indexPath.section] as? MultivaluedSection else { - if let actions = form[indexPath].trailingSwipeConfiguration?.actions.count, actions > 0{ + if form[indexPath].trailingSwipe.actions.count > 0{ return .delete } return .none @@ -911,16 +911,16 @@ extension FormViewController : UITableViewDelegate { @available(iOS 11,*) public func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - return form[indexPath].leadingSwipeConfiguration?.contextualConfiguration as? UISwipeActionsConfiguration + return form[indexPath].leadingSwipe.contextualConfiguration } @available(iOS 11,*) public func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { - return form[indexPath].trailingSwipeConfiguration?.contextualConfiguration as? UISwipeActionsConfiguration + return form[indexPath].trailingSwipe.contextualConfiguration } public func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]?{ - return form[indexPath].trailingSwipeConfiguration?.contextualActions as? [UITableViewRowAction] + return form[indexPath].trailingSwipe.contextualActions as? [UITableViewRowAction] } } diff --git a/Source/Core/SwipeActions.swift b/Source/Core/SwipeActions.swift index 92ea4c77f..997ce3ad5 100644 --- a/Source/Core/SwipeActions.swift +++ b/Source/Core/SwipeActions.swift @@ -8,72 +8,44 @@ import Foundation -public typealias SwipeActionHandler = (ContextualAction, ContextualActionSource, ((Bool) -> Void)?) -> Void +public typealias SwipeActionHandler = (ContextualAction, BaseRow, ((Bool) -> Void)?) -> Void -public class SwipeAction{ - public let contextualAction: ContextualAction +public class SwipeAction: ContextualAction { + let handler: SwipeActionHandler + let style: Style - @available(iOS 11, *) - public var backgroundColor: UIColor?{ - get{ - guard let contextualAction = self.contextualAction as? UIContextualAction else{ return nil } - return contextualAction.backgroundColor - } - set{ - guard let contextualAction = self.contextualAction as? UIContextualAction else{ return } - contextualAction.backgroundColor = newValue - } - } - - @available(iOS 11, *) - public var image: UIImage?{ - get{ - guard let contextualAction = self.contextualAction as? UIContextualAction else{ return nil } - return contextualAction.image - } - set{ - guard let contextualAction = self.contextualAction as? UIContextualAction else{ return } - return contextualAction.image = newValue - } - } - - public var title: String?{ - get{ - if #available(iOS 11, *), let contextualAction = self.contextualAction as? UIContextualAction{ - return contextualAction.title - - } else if let contextualAction = self.contextualAction as? UITableViewRowAction{ - return contextualAction.title - } - return nil - } - set{ - if #available(iOS 11, *), let contextualAction = self.contextualAction as? UIContextualAction{ - contextualAction.title = newValue - - } else if let contextualAction = self.contextualAction as? UITableViewRowAction{ - contextualAction.title = newValue - } - } - } + weak var row: BaseRow! - public let handler: SwipeActionHandler - public let style: Style + public var backgroundColor: UIColor? + public var image: UIImage? + public var title: String? public init(style: Style, title: String?, handler: @escaping SwipeActionHandler){ self.style = style + self.title = title self.handler = handler + } + + var contextualAction: ContextualAction{ + var action: ContextualAction if #available(iOS 11, *){ - self.contextualAction = UIContextualAction(style: style.contextualStyle as! UIContextualAction.Style, title: title){ action, view, completion -> Void in - handler(action, view, completion) + action = UIContextualAction(style: style.contextualStyle as! UIContextualAction.Style, title: title){ [weak self] action, view, completion -> Void in + guard let strongSelf = self else{ return } + strongSelf.handler(action, strongSelf.row, completion) } - + } else { - self.contextualAction = UITableViewRowAction(style: style.contextualStyle as! UITableViewRowActionStyle,title: title){ (action, indexPath) -> Void in - handler(action, indexPath, nil) + action = UITableViewRowAction(style: style.contextualStyle as! UITableViewRowActionStyle,title: title){ [weak self] (action, indexPath) -> Void in + guard let strongSelf = self else{ return } + strongSelf.handler(action, strongSelf.row, nil) } } + + action.backgroundColor = self.backgroundColor ?? action.backgroundColor + action.image = self.image ?? action.image + + return action } public enum Style: Int{ @@ -98,42 +70,24 @@ public class SwipeAction{ } } } - - public init(contextualStyle: ContextualStyle){ - if #available(iOS 11, *), let contextualStyle = contextualStyle as? UIContextualAction.Style{ - switch contextualStyle{ - case .normal: - self = .normal - case .destructive: - self = .destructive - } - - } else if let contextualStyle = contextualStyle as? UITableViewRowActionStyle{ - switch contextualStyle{ - case .normal: - self = .normal - case .destructive: - self = .destructive - case .default: - fatalError("Unmapped UITableViewRowActionStyle for SwipeActionStyle: \(contextualStyle)") - } - - } else { - fatalError("Invalid platformValue for SwipeActionStyle: \(contextualStyle)") - } - } } } - -public class SwipeConfiguration{ +public struct SwipeConfiguration { + weak var row: BaseRow! - public init(configure: (SwipeConfiguration) -> Void){ - configure(self) + init(_ row: BaseRow){ + self.row = row } - public var performsFirstActionWithFullSwipe: Bool = false - public var actions: [SwipeAction] = [] + public var performsFirstActionWithFullSwipe = false + public var actions: [SwipeAction] = []{ + willSet{ + for action in newValue{ + action.row = self.row + } + } + } @available(iOSApplicationExtension 11.0, *) public var contextualConfiguration: UISwipeActionsConfiguration?{ @@ -148,9 +102,18 @@ public class SwipeConfiguration{ } } +public protocol ContextualAction { + var backgroundColor: UIColor?{ get set } + var image: UIImage?{ get set } + var title: String?{ get set } +} -public protocol ContextualAction{} -extension UITableViewRowAction: ContextualAction{} +extension UITableViewRowAction: ContextualAction { + public var image: UIImage? { + get { return nil } + set { return } + } +} @available(iOSApplicationExtension 11.0, *) extension UIContextualAction: ContextualAction{} @@ -160,9 +123,3 @@ extension UITableViewRowActionStyle: ContextualStyle{} @available(iOSApplicationExtension 11.0, *) extension UIContextualAction.Style: ContextualStyle{} - -public protocol ContextualActionSource{} -extension IndexPath: ContextualActionSource{} - -@available(iOSApplicationExtension 11.0, *) -extension UIView: ContextualActionSource{} From 1da1d35bec53942f017bb96e8e6b648fcbc8a018 Mon Sep 17 00:00:00 2001 From: Marco Betschart Date: Wed, 13 Dec 2017 18:02:40 +0100 Subject: [PATCH 8/8] Added feature documentation to readme --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/README.md b/README.md index bd9b6b1f4..57258b967 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,7 @@ Made with ❤️ by [XMARTLABS](http://xmartlabs.com). This is the re-creation o + [List sections] + [Multivalued sections] + [Validations] + + [Swipe Actions] * [Custom rows] + [Basic custom rows] + [Custom inline rows] @@ -581,6 +582,46 @@ Each row has the `validationErrors` property that can be used to retrieve all va As expected, the Rules must use the same types as the Row object. Be extra careful to check the row type used. You might see a compiler error ("Incorrect arugment label in call (have 'rule:' expected 'ruleSet:')" that is not pointing to the problem when mixing types. +### Swipe Actions + +Eureka 4.1.0 introduces the swipe feature. + +You are now able to define multiple `leadingSwipe` and `trailingSwipe` actions per row. As swipe actions depend on iOS system features, `leadingSwipe` is available on iOS 11.0+ only. + +Let's see how to define swipe actions. + +```swift +let row = TextRow() { + let deleteAction = SwipeAction( + style: .destructive, + title: "Delete", + handler: { (action, row, completionHandler) in + //add your code here. + //make sure you call the completionHandler once done. + completionHandler?(true) + }) + deleteAction.image = UIImage(named: "icon-trash") + + $0.trailingSwipe.actions = [deleteAction] + $0.trailingSwipe.performsFirstActionWithFullSwipe = true + + //please be aware: `leadingSwipe` is only available on iOS 11+ only + let infoAction = SwipeAction( + style: .normal, + title: "Info", + handler: { (action, row, completionHandler) in + //add your code here. + //make sure you call the completionHandler once done. + completionHandler?(true) + }) + infoAction.backgroundColor = .blue + infoAction.image = UIImage(named: "icon-info") + + $0.leadingSwipe.actions = [infoAction] + $0.leadingSwipe.performsFirstActionWithFullSwipe = true + } +``` + ## Custom rows It is very common that you need a row that is different from those included in Eureka. If this is the case you will have to create your own row but this should not be difficult. You can read [this tutorial on how to create custom rows](https://blog.xmartlabs.com/2016/09/06/Eureka-custom-row-tutorial/) to get started. You might also want to have a look at [EurekaCommunity] which includes some extra rows ready to be added to Eureka. @@ -1116,6 +1157,7 @@ It's up to you to decide if you want to use Eureka custom operators or not. [List sections]: #list-sections [Multivalued sections]: #multivalued-sections [Validations]: #validations +[Swipe Actions]: #swipe-actions [CustomCellsController]: Example/Example/ViewController.swift