diff --git a/Former-Demo/Former-Demo.xcodeproj/project.pbxproj b/Former-Demo/Former-Demo.xcodeproj/project.pbxproj index 719a10c..18c4e61 100644 --- a/Former-Demo/Former-Demo.xcodeproj/project.pbxproj +++ b/Former-Demo/Former-Demo.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 181DCBF025D26E6D002C780F /* DeleteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 181DCBEF25D26E6D002C780F /* DeleteViewController.swift */; }; 2A53250E1BE48C3F00E4A3F2 /* EditProfileViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A53250D1BE48C3F00E4A3F2 /* EditProfileViewController.swift */; }; 2A5325101BE4935700E4A3F2 /* ProfileImageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A53250F1BE4935700E4A3F2 /* ProfileImageCell.swift */; }; 2A5325151BE4A55D00E4A3F2 /* ProfileFieldCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2A5325141BE4A55D00E4A3F2 /* ProfileFieldCell.swift */; }; @@ -71,6 +72,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 181DCBEF25D26E6D002C780F /* DeleteViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeleteViewController.swift; sourceTree = ""; }; 2A53250D1BE48C3F00E4A3F2 /* EditProfileViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EditProfileViewController.swift; sourceTree = ""; }; 2A53250F1BE4935700E4A3F2 /* ProfileImageCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileImageCell.swift; sourceTree = ""; }; 2A5325141BE4A55D00E4A3F2 /* ProfileFieldCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProfileFieldCell.swift; sourceTree = ""; }; @@ -198,6 +200,7 @@ 2AA777A41BECED6200900B01 /* AddEventViewController.swift */, 2AA777B21BEE80A800900B01 /* LoginViewController.swift */, 2AA777AA1BED4BF000900B01 /* CustomCellViewController.swift */, + 181DCBEF25D26E6D002C780F /* DeleteViewController.swift */, ); path = Controllers; sourceTree = ""; @@ -331,6 +334,7 @@ 2AC381531BA447D3009C7EFF /* ExampleViewController.swift in Sources */, 2AC3814E1BA447D3009C7EFF /* AppDelegate.swift in Sources */, 2AA777B31BEE80A800900B01 /* LoginViewController.swift in Sources */, + 181DCBF025D26E6D002C780F /* DeleteViewController.swift in Sources */, 2AA777A51BECED6200900B01 /* AddEventViewController.swift in Sources */, 2AA777B51BEE86B000900B01 /* Login.swift in Sources */, 2A53250E1BE48C3F00E4A3F2 /* EditProfileViewController.swift in Sources */, diff --git a/Former-Demo/Former-Demo/Controllers/DeleteViewController.swift b/Former-Demo/Former-Demo/Controllers/DeleteViewController.swift new file mode 100644 index 0000000..8e69097 --- /dev/null +++ b/Former-Demo/Former-Demo/Controllers/DeleteViewController.swift @@ -0,0 +1,51 @@ +// +// DeleteViewController.swift +// Former-Demo +// +// Created by ZhangShuai on 2021/2/9. +// Copyright © 2021 Ryo Aoyama. All rights reserved. +// This is a simple removable cell, available IOS 11 later + +import UIKit +import Former + +final class DeleteViewController: FormViewController { + + override func viewDidLoad() { + super.viewDidLoad() + configure() + } + + private func configure() { + title = "Delete Cell" + tableView.contentInset.top = 10 + tableView.contentInset.bottom = 30 + tableView.contentOffset.y = -10 + + // Create RowFomers + let titleRow = LabelRowFormer.init().configure(handler: { + $0.text = "General Cell" + }) + + let removableCell = LabelRowFormer.init().configure(handler: { + $0.text = "removable Cell" + $0.isCanDeleted = true + $0.deletedTitle = "remove cell" + }) + .deletingCompleted { (row, index) in + print("cell has benn removed at \(index) ") + } + + // Create Headers + let createHeader: (() -> ViewFormer) = { + return CustomViewFormer() + .configure { + $0.viewHeight = 20 + } + } + + // Create SectionFormers + let titleSection = SectionFormer(rowFormer: titleRow, removableCell).set(headerViewFormer: createHeader()) + former.append(sectionFormer: titleSection) + } +} diff --git a/Former-Demo/Former-Demo/Controllers/TopViewController.swift b/Former-Demo/Former-Demo/Controllers/TopViewController.swift index e149aa5..281fdd3 100644 --- a/Former-Demo/Former-Demo/Controllers/TopViewController.swift +++ b/Former-Demo/Former-Demo/Controllers/TopViewController.swift @@ -63,6 +63,10 @@ final class TopViewContoller: FormViewController { let defaultRow = createMenu("All Defaults") { [weak self] in self?.navigationController?.pushViewController(DefaultsViewController(), animated: true) } + let deleteRow = createMenu("Delete Cell") { [weak self] in + self?.navigationController?.pushViewController(DeleteViewController(), animated: true) + } + // Create Headers and Footers @@ -84,7 +88,7 @@ final class TopViewContoller: FormViewController { // Create SectionFormers - let realExampleSection = SectionFormer(rowFormer: editProfileRow, addEventRow, loginRow) + let realExampleSection = SectionFormer(rowFormer: editProfileRow, addEventRow, loginRow, deleteRow) .set(headerViewFormer: createHeader("Real Examples")) let useCaseSection = SectionFormer(rowFormer: exampleRow, customCellRow) .set(headerViewFormer: createHeader("Use Case")) diff --git a/Former-Demo/Former-Demo/Resources/LoginViewController.storyboard b/Former-Demo/Former-Demo/Resources/LoginViewController.storyboard index 285606a..8e98453 100644 --- a/Former-Demo/Former-Demo/Resources/LoginViewController.storyboard +++ b/Former-Demo/Former-Demo/Resources/LoginViewController.storyboard @@ -1,8 +1,10 @@ - - + + + - + + @@ -14,24 +16,22 @@ - + - + - - - + + - - + @@ -41,8 +41,7 @@ - - + diff --git a/Former/Commons/Former.swift b/Former/Commons/Former.swift index 0029e9f..010695f 100644 --- a/Former/Commons/Former.swift +++ b/Former/Commons/Former.swift @@ -18,17 +18,17 @@ public final class Former: NSObject { // MARK: Public /** - InstantiateType is type of instantiate of Cell or HeaderFooterView. - If the cell or HeaderFooterView to instantiate from the nib of mainBudnle , use the case 'Nib(nibName: String)'. - Using the '.NibBundle(nibName: String, bundle: NSBundle)' If also using the custom bundle. - Or if without xib, use '.Class'. - **/ + InstantiateType is type of instantiate of Cell or HeaderFooterView. + If the cell or HeaderFooterView to instantiate from the nib of mainBudnle , use the case 'Nib(nibName: String)'. + Using the '.NibBundle(nibName: String, bundle: NSBundle)' If also using the custom bundle. + Or if without xib, use '.Class'. + **/ public enum InstantiateType { case Class case Nib(nibName: String) - case NibTag(nibName: String, tag:Int) + case NibTag(nibName: String, tag:Int) case NibBundle(nibName: String, bundle: Bundle) - case NibBundleTag(nibName: String, bundle: Bundle, tag:Int) + case NibBundleTag(nibName: String, bundle: Bundle, tag:Int) } /// All SectionFormers. Default is empty. @@ -45,7 +45,7 @@ public final class Former: NSObject { } /// Number of all rows. - public var numberOfRows: Int { + public var numberOfRows: Int { return rowFormers.count } @@ -88,11 +88,11 @@ public final class Former: NSObject { public subscript(range: Range) -> [SectionFormer] { return Array(sectionFormers[range]) } - + public subscript(range: ClosedRange) -> [SectionFormer] { return Array(sectionFormers[range]) } - + /// To find RowFormer from indexPath. public func rowFormer(indexPath: IndexPath) -> RowFormer { return self[indexPath.section][indexPath.row] @@ -132,7 +132,7 @@ public final class Former: NSObject { willDisplayCell = handler return self } - + /// Call just before header is display. @discardableResult public func willDisplayHeader(_ handler: @escaping ((/*section:*/Int) -> Void)) -> Self { @@ -268,7 +268,7 @@ public final class Former: NSObject { } return self } - + /// To end editing of tableView. @discardableResult public func endEditing() -> Self { @@ -325,12 +325,12 @@ public final class Former: NSObject { tableView?.reloadSections(sections, with: rowAnimation) return self } - + /// Reload sections from instance of SectionFormer. @discardableResult public func reload(sectionFormer: SectionFormer, rowAnimation: UITableView.RowAnimation = .none) -> Self { guard let section = sectionFormers.firstIndex(where: { $0 === sectionFormer }) else { return self } - return reload(sections: IndexSet(integer: section), rowAnimation: rowAnimation) + return reload(sections: IndexSet(integer: section), rowAnimation: rowAnimation) } /// Reload rows from indesPaths. @@ -740,10 +740,10 @@ public final class Former: NSObject { fileprivate func removeCurrentInlineRow() -> IndexPath? { var indexPath: IndexPath? = nil if let oldInlineRowFormer = (inlineRowFormer as? InlineForm)?.inlineRowFormer, - let removedIndexPath = removeRowFormers([oldInlineRowFormer]).first { - indexPath = removedIndexPath - (inlineRowFormer as? InlineForm)?.editingDidEnd() - inlineRowFormer = nil + let removedIndexPath = removeRowFormers([oldInlineRowFormer]).first { + indexPath = removedIndexPath + (inlineRowFormer as? InlineForm)?.editingDidEnd() + inlineRowFormer = nil } return indexPath } @@ -781,7 +781,7 @@ public final class Former: NSObject { removeIndexPaths.append(IndexPath(row: row, section: section)) sectionFormer.remove(rowFormers: [rowFormer]) if let oldInlineRowFormer = (rowFormer as? InlineForm)?.inlineRowFormer, - let _ = removeRowFormers([oldInlineRowFormer]).first { + let _ = removeRowFormers([oldInlineRowFormer]).first { removeIndexPaths.append(IndexPath(row: row + 1, section: section)) (inlineRowFormer as? InlineForm)?.editingDidEnd() inlineRowFormer = nil @@ -851,7 +851,7 @@ public final class Former: NSObject { if case let (tableView?, inset?) = (tableView, oldBottomContentInset) { let duration = (keyboardInfo[UIResponder.keyboardAnimationDurationUserInfoKey]! as AnyObject).doubleValue! let curve = (keyboardInfo[UIResponder.keyboardAnimationCurveUserInfoKey]! as AnyObject).integerValue! - + UIView.beginAnimations(nil, context: nil) UIView.setAnimationDuration(duration) UIView.setAnimationCurve(UIView.AnimationCurve(rawValue: curve)!) @@ -924,27 +924,27 @@ extension Former: UITableViewDelegate, UITableViewDataSource { public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let rowFormer = self.rowFormer(indexPath: indexPath) guard rowFormer.enabled else { return } - + rowFormer.cellSelected(indexPath: indexPath) onCellSelected?(indexPath) // InlineRow if let oldInlineRowFormer = (inlineRowFormer as? InlineForm)?.inlineRowFormer { if let currentInlineRowFormer = (rowFormer as? InlineForm)?.inlineRowFormer, - rowFormer !== inlineRowFormer { - self.tableView?.beginUpdates() - if let removedIndexPath = removeRowFormers([oldInlineRowFormer]).first { - let insertIndexPath = + rowFormer !== inlineRowFormer { + self.tableView?.beginUpdates() + if let removedIndexPath = removeRowFormers([oldInlineRowFormer]).first { + let insertIndexPath = (removedIndexPath.section == indexPath.section && removedIndexPath.row < indexPath.row) - ? indexPath : IndexPath(row: indexPath.row + 1, section: indexPath.section) - insert(rowFormers: [currentInlineRowFormer], toIndexPath: insertIndexPath) - self.tableView?.deleteRows(at: [removedIndexPath], with: .middle) - self.tableView?.insertRows(at: [insertIndexPath], with: .middle) - } - self.tableView?.endUpdates() - (inlineRowFormer as? InlineForm)?.editingDidEnd() - (rowFormer as? InlineForm)?.editingDidBegin() - inlineRowFormer = rowFormer + ? indexPath : IndexPath(row: indexPath.row + 1, section: indexPath.section) + insert(rowFormers: [currentInlineRowFormer], toIndexPath: insertIndexPath) + self.tableView?.deleteRows(at: [removedIndexPath], with: .middle) + self.tableView?.insertRows(at: [insertIndexPath], with: .middle) + } + self.tableView?.endUpdates() + (inlineRowFormer as? InlineForm)?.editingDidEnd() + (rowFormer as? InlineForm)?.editingDidBegin() + inlineRowFormer = rowFormer } else { removeCurrentInlineRowUpdate() } @@ -970,16 +970,39 @@ extension Former: UITableViewDelegate, UITableViewDataSource { } public func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool { - return false + if #available(iOSApplicationExtension 11.0, *) { + return self.rowFormer(indexPath: indexPath).isCanDeleted + } + else{ + return false + } } public func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool { return false } + public func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle { + return .delete + } - // for Cell + @available(iOSApplicationExtension 11.0, *) + public func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + let rowFormer = self.rowFormer(indexPath: indexPath) + let deleteAction = UIContextualAction.init(style: .destructive, title: rowFormer.deletedTitle) { (action, view, completion) in + let rowFormer = self.rowFormer(indexPath: indexPath) + if let deletingCompleted = rowFormer.deletingCompleted{ + deletingCompleted(rowFormer, indexPath) + self.removeUpdate(rowFormer: rowFormer) + } + completion(true) + } + let configuration = UISwipeActionsConfiguration(actions: [deleteAction]) + configuration.performsFirstActionWithFullSwipe = false + return configuration + } + // for Cell public func numberOfSections(in tableView: UITableView) -> Int { return numberOfSections } @@ -987,7 +1010,7 @@ extension Former: UITableViewDelegate, UITableViewDataSource { public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return self[section].numberOfRows } - + public func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat { let rowFormer = self.rowFormer(indexPath: indexPath) if let dynamicRowHeight = rowFormer.dynamicRowHeight { @@ -1012,15 +1035,15 @@ extension Former: UITableViewDelegate, UITableViewDataSource { } // for HeaderFooterView - + // Not implemented for iOS8 estimatedHeight bug -// public func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat { -// let headerViewFormer = self[section].headerViewFormer -// if let dynamicViewHeight = headerViewFormer?.dynamicViewHeight { -// headerViewFormer?.viewHeight = dynamicViewHeight(tableView, section) -// } -// return headerViewFormer?.viewHeight ?? 0 -// } + // public func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat { + // let headerViewFormer = self[section].headerViewFormer + // if let dynamicViewHeight = headerViewFormer?.dynamicViewHeight { + // headerViewFormer?.viewHeight = dynamicViewHeight(tableView, section) + // } + // return headerViewFormer?.viewHeight ?? 0 + // } public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat { let headerViewFormer = self[section].headerViewFormer @@ -1031,13 +1054,13 @@ extension Former: UITableViewDelegate, UITableViewDataSource { } // Not implemented for iOS8 estimatedHeight bug -// public func tableView(_ tableView: UITableView, estimatedHeightForFooterInSection section: Int) -> CGFloat { -// let footerViewFormer = self[section].footerViewFormer -// if let dynamicViewHeight = footerViewFormer?.dynamicViewHeight { -// footerViewFormer?.viewHeight = dynamicViewHeight(tableView, section) -// } -// return footerViewFormer?.viewHeight ?? 0 -// } + // public func tableView(_ tableView: UITableView, estimatedHeightForFooterInSection section: Int) -> CGFloat { + // let footerViewFormer = self[section].footerViewFormer + // if let dynamicViewHeight = footerViewFormer?.dynamicViewHeight { + // footerViewFormer?.viewHeight = dynamicViewHeight(tableView, section) + // } + // return footerViewFormer?.viewHeight ?? 0 + // } public func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat { let footerViewFormer = self[section].footerViewFormer diff --git a/Former/Commons/RowFormer.swift b/Former/Commons/RowFormer.swift index 9cc2f18..9f64ee8 100644 --- a/Former/Commons/RowFormer.swift +++ b/Former/Commons/RowFormer.swift @@ -21,6 +21,8 @@ open class RowFormer { public final let cellType: UITableViewCell.Type public final var rowHeight: CGFloat = 44 public final var isEditing = false + public final var isCanDeleted = false + public final var deletedTitle = "Delete" public final var enabled = true { didSet { update() } } open var canBecomeEditing: Bool { return false @@ -48,6 +50,12 @@ open class RowFormer { return self } + @discardableResult + public final func deletingCompleted(_ handler: @escaping ((RowFormer, IndexPath) -> Void)) -> Self { + deletingCompleted = handler + return self + } + open func initialized() {} open func update() { @@ -79,6 +87,7 @@ open class RowFormer { internal final var cellSetup: ((UITableViewCell) -> Void)? internal final var onSelected: ((RowFormer) -> Void)? internal final var onUpdate: ((RowFormer) -> Void)? + internal final var deletingCompleted: ((RowFormer, IndexPath) -> Void)? internal final var dynamicRowHeight: ((UITableView, IndexPath) -> CGFloat)? internal final var cellInstance: UITableViewCell { @@ -90,17 +99,17 @@ open class RowFormer { case .Nib(nibName: let nibName): cell = Bundle.main.loadNibNamed(nibName, owner: nil, options: nil)!.first as? UITableViewCell assert(cell != nil, "[Former] Failed to load cell from nib (\(nibName)).") - case .NibTag(nibName: let nibName, tag: let tag): - let nibChildren = Bundle.main.loadNibNamed(nibName, owner: nil, options: nil)! - cell = nibChildren.first {($0 as? UIView)?.tag == tag} as? UITableViewCell - assert(cell != nil, "[Former] Failed to load cell from nib (nibName: \(nibName)), tag: (\(tag)).") + case .NibTag(nibName: let nibName, tag: let tag): + let nibChildren = Bundle.main.loadNibNamed(nibName, owner: nil, options: nil)! + cell = nibChildren.first {($0 as? UIView)?.tag == tag} as? UITableViewCell + assert(cell != nil, "[Former] Failed to load cell from nib (nibName: \(nibName)), tag: (\(tag)).") case .NibBundle(nibName: let nibName, bundle: let bundle): cell = bundle.loadNibNamed(nibName, owner: nil, options: nil)!.first as? UITableViewCell assert(cell != nil, "[Former] Failed to load cell from nib (nibName: \(nibName), bundle: \(bundle)).") - case .NibBundleTag(nibName: let nibName, bundle: let bundle, tag: let tag): - let nibChildren = bundle.loadNibNamed(nibName, owner: nil, options: nil)! - cell = nibChildren.first {($0 as? UIView)?.tag == tag} as? UITableViewCell - assert(cell != nil, "[Former] Failed to load cell from nib (nibName: \(nibName), bundle: \(bundle), tag: \(tag)).") + case .NibBundleTag(nibName: let nibName, bundle: let bundle, tag: let tag): + let nibChildren = bundle.loadNibNamed(nibName, owner: nil, options: nil)! + cell = nibChildren.first {($0 as? UIView)?.tag == tag} as? UITableViewCell + assert(cell != nil, "[Former] Failed to load cell from nib (nibName: \(nibName), bundle: \(bundle), tag: \(tag)).") } _cellInstance = cell cellInstanceInitialized(cell!)