在MultivaluedSection中自定义“添加”按钮

时间:2018-08-01 20:16:42

标签: swift eureka-forms

我正在尝试更改Eureka中MultivaluedSection的“添加”按钮。当前的行为是,当您单击“添加”按钮时,它将在MultivaluedSection中创建一个新的空单元格。

我想要实现的是,当用户单击“添加”按钮时,它会显示一个PushRow,供用户选择单元格的初始值。

尝试获得此行为的两种方式中的任何一种我都没有运气。

我首先尝试创建一个自定义类,在其中可以完全更改MultivaluedSecion行为:

class ExerciseMultivaluedSection : MultivaluedSection {

    override public var addButtonProvider: ((MultivaluedSection) -> ButtonRow) = { _ in
        let button =  ButtonRow {
            $0.title = "MyCustomAddButton"
            $0.cellStyle = .value1
        }.cellUpdate { cell, _ in
            cell.textLabel?.textAlignment = .left
        }

        // Here i would link my button to a function 
        // that would trigger a PushRow, maybe through a segue ?

        return button
    }

    required public init() {
        super.init()
    }

    required public init<S>(_ elements: S) where S : Sequence, S.Element == BaseRow {
        super.init(elements)
    }

    required public init(multivaluedOptions: MultivaluedOptions, header: String, footer: String, _ initializer: (MultivaluedSection) -> Void) {
        super.init(header: header, footer: footer, {section in initializer(section as! ExerciseMultivaluedSection) })
    }
}

但是,由于以下错误,它不起作用:“无法使用存储的属性'addButtonProvider'覆盖”

然后,我尝试在运行时更改addButtonProvider,但是它什么也没做:

    let exerciseSection = MultivaluedSection(multivaluedOptions:[.Delete,.Reorder,.Insert],header:"Exercises")

    exerciseSection.tag = "exercise"

    exerciseSection.multivaluedRowToInsertAt = {idx in
        let newRow = LabelRow(){row in

            row.value = "TestValue"

            let deleteAction = SwipeAction(style: .destructive, title: "DEL"){action,row,completion in
                completion?(true)
            }
            row.trailingSwipe.actions = [deleteAction]
        }
        return newRow
    }

    exerciseSection.addButtonProvider = {section in
        let addBtn = ButtonRow("Test add"){ row in
            row.title = "Custom add button"
        }

        print("Custom add button" )

        return addBtn
    }

即使在那之后,我的添加按钮仍然显示“添加”,并且我的打印功能从未被调用。为什么会这样?

此外,这两种方法之一是好的吗?如果没有,实现该目标的“正确方法”是什么?

我在iOS 11.4上使用XCode 9.4.1

1 个答案:

答案 0 :(得分:0)

如果您愿意分叉并略微更改Eureka源代码,则只需进行很少的更改即可在MultiValuedSection中使用PushRow,自定义ButtonRow或任何其他行作为AddButtonProvider。

在以下GitHub提交中记录了一些必要的更改,该更改显示了源代码更改之前和之后: https://github.com/TheCyberMike/Eureka/commit/bfcba0dd04bf0d11cb6ba526235ae4c10c2d73fd

尤其是,使用PushRow作为添加按钮可能会比较棘手。我在以下问题的注释中向Eureka GitHub网站添加了信息和示例代码: https://github.com/xmartlabs/Eureka/issues/1812 Eureka的其他用户可能会在此处添加后续注释。

===来自#1812评论===

我在应用程序的分叉中对Eureka进行了非常简单的更改,以允许将任何Row用作Add行。它允许默认的ButtonRow照原样使用并自动处理。但是它还允许将其他任何行(例如PushRow)用作AddButtonProvider。

在我的应用中的多个位置,当最终用户按下添加按钮时,我会向他们显示可能添加的选项的弹出列表。他们选择一个,然后我将该选择添加到MultiValuedSection。

简单的更改是在我的应用程序中的此分叉的Eureka提交中: https://github.com/TheCyberMike/Eureka/commit/bfcba0dd04bf0d11cb6ba526235ae4c10c2d73fd

尽管我尚未在自己的应用程序中对其进行测试,但此更改还应支持允许为MVS使用自定义ButtonRow。但是我认为您需要在自己的代码中为自定义ButtonRow在.onCellSelection中处理表格视图的.insert。上面的提交代码和下面的示例中都需要执行此操作。

使用PushRow作为AddButtonProvider有点棘手,因为PushRow调用一个单独的视图控制器来显示列表,然后返回到原始表单的视图控制器。

  1. 因此,当从PushRow视图控制器返回viewWillAppear()和viewDidAppear()时,必须确保不要重建原始表单及其MultiValuedSection。

  2. 此外,选择本身在PushRow视图控制器中进行。 PushRow处理从PushRow视图控制器保留该选择的操作。但是,在PushRow视图控制器仍处于活动状态时,将调用.onChange。我使用DispatchQueue.main.async闭包来处理将tableview .insert调用推迟到原始表单的视图控制器处于活动状态时进行。

  3. 为防止PushRow显示在其最右边的灰色附件字段中做出的最后选择,必须取消其.value。但是,这也会触发.onChange,如果您不小心可能会导致无限循环。我只是使用一个简单的if语句来确保PushRow的值不为nil以防止该循环(是的,它也可以是一个保护语句)。

  4. 请确保使用[弱自我]防止内存泄漏。

下面是我的eContact Collect应用(https://github.com/TheCyberMike/eContactCollect-iOS)中使用代码的改编示例,其中从预定义列表中选择了语言,数据输入字段和电子邮件帐户之类的内容。同样,除非对引用的源代码进行更改,否则该代码将无效。

self.mForm = form
form +++ MultivaluedSection(multivaluedOptions: [.Insert, .Delete, .Reorder], header: NSLocalizedString("Shown languages in order to-be-shown", comment:"")) { mvSection in
    mvSection.tag = "mvs_langs"
    mvSection.showInsertIconInAddButton = false
    mvSection.addButtonProvider = { [weak self] section in
        return PushRow(){ row in
            // REMEMBER: CANNOT rebuild the form from viewWillAppear() ... the form must remain intact during the PushRow's view
            // controller life cycle for this to work
            row.title = NSLocalizedString("Select new language to add", comment:"")
            row.tag = "add_new_lang"
            row.selectorTitle = NSLocalizedString("Choose one", comment:"")
            row.options = langOptionsArray
        }.onChange { [weak self] chgRow in
            // PushRow has returned a value selected from the PushRow's view controller;
            // note our context is still within the PushRow's view controller, not the original FormViewController
           if chgRow.value != nil { // this must be present to prevent an infinite loop
               guard let tableView = chgRow.cell.formViewController()?.tableView, let indexPath = chgRow.indexPath else { return }
               DispatchQueue.main.async {
                   // must dispatch this so the PushRow's SelectorViewController is dismissed first and the UI is back at the main FormViewController
                   // this triggers multivaluedRowToInsertAt() below
                   chgRow.cell.formViewController()?.tableView(tableView, commit: .insert, forRowAt: indexPath)
               }
           }
       }
    } // end of addButtonProvider
    mvSection.multivaluedRowToInsertAt = { [weak self] index in
       // a verified-new langRegion code was chosen by the end-user; get it from the PushRow
        let fromPushRow = self!.mForm!.rowBy(tag: "add_new_lang") as! PushRow
        let langRegionCode:String = fromPushRow.value!

        // create a new ButtonRow based upon the new entry
        let newRow = self!.makeLangButtonRow(forLang: langRegionCode)
        fromPushRow.value = nil     // clear out the PushRow's value so this newly chosen item does not remain "selected"
        fromPushRow.reload()        // note this will re-trigger .onChange in the PushRow so must ignore that re-trigger else infinite loop
        return newRow               // self.rowsHaveBeenAdded() will get invoked after this point
    }
}