Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Swift 4 swipeable #1249

Closed
wants to merge 12 commits into from
4 changes: 4 additions & 0 deletions Eureka.xcodeproj/project.pbxproj
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -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 = "<group>"; };
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 = "<group>"; };
8690E4BD1CFDE062004CDB1C /* Eureka.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = Eureka.bundle; sourceTree = "<group>"; };
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; };
Expand Down Expand Up @@ -225,6 +227,7 @@
28EEBFF71C7E241200300699 /* SelectableRowType.swift */,
2859CDBF1C7D138D0002982F /* Section.swift */,
2859CE231C7E141B0002982F /* SelectableSection.swift */,
798C5AD91EF1E35600A21F52 /* SwipeActions.swift */,
28EE0FDD1D5E889F00B91340 /* Validation.swift */,
);
name = Core;
Expand Down Expand Up @@ -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 */,
Expand Down
20 changes: 20 additions & 0 deletions Example/Example/Base.lproj/Main.storyboard
Expand Up @@ -172,6 +172,7 @@
<segue destination="1By-iT-GXN" kind="push" identifier="ValidationsControllerSegue" id="e8a-Jy-4iL"/>
<segue destination="1Eh-Nv-AVS" kind="push" identifier="CustomDesignControllerSegue" id="qm6-xE-x5d"/>
<segue destination="qIY-1j-dNf" kind="push" identifier="MultivaluedSectionsControllerSegue" id="HjC-JK-Qvc"/>
<segue destination="fc5-sQ-cEm" kind="push" identifier="SwipeActionsControllerSegue" id="AxK-me-pi6"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Eoz-Jq-Zy9" userLabel="First Responder" sceneMemberID="firstResponder"/>
Expand Down Expand Up @@ -264,6 +265,25 @@
</objects>
<point key="canvasLocation" x="463" y="1077"/>
</scene>
<!--SwipeActions Example-->
<scene sceneID="h0m-5n-GTL">
<objects>
<viewController title="SwipeActions Example" id="fc5-sQ-cEm" customClass="SwipeActionsController" customModule="Example" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="ImV-ow-JvS"/>
<viewControllerLayoutGuide type="bottom" id="3OX-ei-O2d"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="BDe-QW-0vU">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</view>
<navigationItem key="navigationItem" id="KY1-r2-ar2"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="axy-1K-pFt" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-530" y="1833"/>
</scene>
<!--Multivalued Sections Example-->
<scene sceneID="zbo-bQ-HRw">
<objects>
Expand Down
64 changes: 56 additions & 8 deletions Example/Example/ViewController.swift
Expand Up @@ -106,14 +106,10 @@ 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)
}
}

Expand Down Expand Up @@ -1747,6 +1743,58 @@ 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

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.trailingSwipe.actions = [deleteAction,moreAction]
$0.trailingSwipe.performsFirstActionWithFullSwipe = true

if #available(iOS 11,*) {
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
}
}
}
}

class EurekaLogoViewNib: UIView {

@IBOutlet weak var imageView: UIImageView!
Expand Down
42 changes: 42 additions & 0 deletions README.md
Expand Up @@ -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]
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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

<!--- In Project -->
[CustomCellsController]: Example/Example/ViewController.swift
Expand Down
11 changes: 11 additions & 0 deletions Source/Core/BaseRow.swift
Expand Up @@ -99,6 +99,17 @@ open class BaseRow: BaseRowType {

/// The section to which this row belongs.
public weak var section: Section?

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 lazy var _leadingSwipe = SwipeConfiguration(self)

@available(iOS 11,*)
public var leadingSwipe: SwipeConfiguration{
get{ return self._leadingSwipe }
set{ self._leadingSwipe = newValue }
}

public required init(tag: String? = nil) {
self.tag = tag
Expand Down
40 changes: 32 additions & 8 deletions Source/Core/Core.swift
Expand Up @@ -462,9 +462,7 @@ open class FormViewController: UIViewController, FormViewControllerProtocol, For
tableView.dataSource = self
}
tableView.estimatedRowHeight = BaseRow.estimatedRowHeight

tableView.setEditing(true, animated: false)
tableView.allowsSelectionDuringEditing = true
tableView.allowsSelectionDuringEditing = true
}

open override func viewWillAppear(_ animated: Bool) {
Expand Down Expand Up @@ -502,6 +500,10 @@ open class FormViewController: UIViewController, FormViewControllerProtocol, For

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 viewWillDisappear(_ animated: Bool) {
Expand Down Expand Up @@ -789,8 +791,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 row.trailingSwipe.actions.count > 0{
return true
} else if #available(iOS 11,*), row.leadingSwipe.actions.count > 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
Expand Down Expand Up @@ -849,12 +856,12 @@ extension FormViewController : UITableViewDelegate {
open 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 {
Expand All @@ -871,7 +878,7 @@ extension FormViewController : UITableViewDelegate {

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)
Expand All @@ -884,6 +891,9 @@ extension FormViewController : UITableViewDelegate {

open func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCellEditingStyle {
guard let section = form[indexPath.section] as? MultivaluedSection else {
if form[indexPath].trailingSwipe.actions.count > 0{
return .delete
}
return .none
}
if section.multivaluedOptions.contains(.Insert) && indexPath.row == section.count - 1 {
Expand All @@ -898,6 +908,20 @@ extension FormViewController : UITableViewDelegate {
open 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].leadingSwipe.contextualConfiguration
}

@available(iOS 11,*)
public func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? {
return form[indexPath].trailingSwipe.contextualConfiguration
}

public func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]?{
return form[indexPath].trailingSwipe.contextualActions as? [UITableViewRowAction]
}
}

extension FormViewController : UITableViewDataSource {
Expand Down
6 changes: 6 additions & 0 deletions Source/Core/Form.swift
Expand Up @@ -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?]()) {
Expand Down
2 changes: 1 addition & 1 deletion Source/Core/Section.swift
Expand Up @@ -455,7 +455,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
}
Expand Down
4 changes: 3 additions & 1 deletion Source/Core/SelectableSection.swift
Expand Up @@ -84,7 +84,9 @@ extension SelectableSectionType where Self: Section {
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
Expand Down