13

I am using Eureka to build a form in iOS using Swift. I have created a multivalued section, e.g.:

form +++ MultivaluedSection(multivaluedOptions: [.Insert, .Delete], header: "My Header", footer: "My footer") { section in
    section.tag = "mySectionTag"
    section.addButtonProvider = { _ in
        return ButtonRow() { row in
                row.title = "Add row"
        }
    }

    section.multivaluedRowToInsertAt = { index in
        return TimeInlineRow() { row in
                row.title = "My row title"
        }
    }
    // initialize form with any times that have already been set previously, times: [Date]
    for time in times {
    section <<< TimeInlineRow(tag) { row in
        row.value = time
        row.title = "My row title"
    }
}

I would like to limit the number of rows you can insert into my multi valued section. Was thinking about doing it by hiding the ButtonRow using some kind of Condition but I'm not sure how to connect it. Alternatively could just present an alert if you tap the button row when the count of values() in the section is too high, but then how do you block the actual inserting?

Was also considering that I could do something inside multivaluedRowToInsertAt based on index but still not sure what.

Looked through the issues and was surprised not to find anything on this already, so I can only assume I'm missing something obvious.

Another thought I had was to set a Condition on the ButtonRow in the addButtonProvider that returns true if the row with a certain max row tag (that I create) is not nil in the form (i.e. no such row exists), and then in the multivaluedRowToInsertAt it would check if the index is greater than the max allowable index and if so it applies the max tag when creating that row. But it seems the green + insert button is automatically applied to the last row of the section regardless of the type. Then I tried changing the multivaluedOptions to just .Delete when the max rows are reached but I'm having trouble figuring out how to get it to go back to allowing inserting after a row is deleted.

Also tried putting a condition on the ButtonRow's disabled property based on a similar method as above (with a max row) but it also runs into duplicate row tag issues and the green add button still responds to taps, and the showInsertIconInAddButton property has no effect.

Even if I get this method working, it seems unnecessarily convoluted and I would have expected there to be a much simpler solution since it seems like this would be the kind of functionality a lot of people would need.

shim
  • 9,289
  • 12
  • 69
  • 108
  • You can try using the Section delegate functions rowsHaveBeenAdded and rowsHaveBeenRemoved to show/hide the ButtonRow. – koropok Dec 28 '17 at 03:11
  • Yes, thank you. This is something I have been trying out earlier today, although I was hitting some unexpected crashes. Will update post if I get anywhere with it. – shim Dec 28 '17 at 05:34
  • alright, good luck! – koropok Dec 28 '17 at 09:04
  • Unexpected crashes due to forgetting to call super implementation :D – shim Dec 28 '17 at 21:15
  • Setting `.hidden = true` on the button row (retrieved via `form.rowBy(tag:)`) doesn't have any effect. – shim Dec 28 '17 at 23:05
  • well, what i did was to declare the button row as a stored property of the view controller. the .isHidden will work in the delegate function. – koropok Dec 29 '17 at 01:11
  • `isHidden` is read only Also, why would it make a difference if you got it using `rowBy` vs using a stored property? – shim Dec 29 '17 at 05:12
  • Found this `evaluateHidden` method that seems to need to be called… but after adding that after setting `hidden` my UI just freezes up — Xcode doesn't even crash, the app is just frozen… – shim Dec 29 '17 at 05:34
  • Ah, endless loop, that's why. Presumably because it keeps calling the `rowsHaveBeenAdded` / `rowsHaveBeenRemoved` when the row gets hidden… – shim Dec 29 '17 at 05:48
  • oops i'm using row.cell.isHidden. i did not try rowBy as my project requires the buttonRow to be a stored property. sorry for not clarifying xD – koropok Dec 29 '17 at 07:05
  • nope, the delegate functions were not called when i use row.cell.isHidden – koropok Dec 29 '17 at 07:22
  • Did you use `evaluateHidden()` ? Also I tried it with a stored property, didn't make a difference. Also are you using `rowsHaveBeenAdded` and `rowsHaveBeenRemoved` on the `FormViewController` subclass? I noticed also `Section` has the same methods… Then there's also the delegate methods, so other classes can use these methods, but doesn't seem necessary to set a `FormViewController` subclass to be its own delegate. – shim Dec 29 '17 at 14:49

2 Answers2

7

As laid out in Mahbub's answer and hinted at in the original question, one can check the index in the multivaluedRowToInsertAt block and update the multivaluedOptions and hide the button row accordingly.

Properties in FormViewController:

private var myButtonRow: ButtonRow! // Can also just refer to it by tag
let kMaxCount = 5

Inside a setup function in FormViewController: (not shown, setting up the section / button row / add provider etc)

section.multivaluedRowToInsertAt = { index in
    if index >= self.kMaxCount - 1 {
        section.multivaluedOptions = [.Delete]                    

        self.myButtonRow.hidden = true

        DispatchQueue.main.async() { // I'm not sure why this is necessary
            self.myButtonRow.evaluateHidden()
        }
    }

    return TimeRow() { row in // any row type you want — although inline rows probably mess this up
        row.title = title
        row.value = Date()
    }
}

The changes to the button row inside multivaluedRowToInsertAt didn't seem to take hold until the 6th row was added, regardless of when the hidden method is called and what the max count is set to, and the last row that inserts goes in the second last place. So then I tried the code as written above, with a dispatch call delaying evaluateHidden() and it seems to work. I'm not sure why, presumably some conflicting race condition. Note, when the insert method is called it is on the main thread, so it's not about changing UI on a background thread.

Then for when rows are deleted there is a function called rowsHaveBeenRemoved you can override in a FormViewController subclass that is called whenever a row (in any section) is removed:

override func rowsHaveBeenRemoved(_ rows: [BaseRow], at indexes: [IndexPath]) {
    super.rowsHaveBeenRemoved(rows, at: indexes)
    if let index = indexes.first?.section, let section = form.allSections[index] as? MultivaluedSection {
        if section.count < kMaxCount {
            section.multivaluedOptions = [.Insert, .Delete]
            myButtonRow.hidden = false // or could 
            myButtonRow.evaluateHidden()
        }
    }
}
shim
  • 9,289
  • 12
  • 69
  • 108
  • 1
    Note: not a fan of having the logic split up like this, plus this solution is really much more complicated than one would expect for functionality that ought to be built-in – shim Jan 02 '18 at 23:46
  • Also, if you're looking to set a minimum number of rows as well (e.g. no less than 1 valued row in the section) you can use a similar approach and set the multivalue options to `[.Insert]` only in the `rowsHaveBeenRemoved` based on the `section.count` (keep in mind that count includes the button row if it's visible). – shim Jan 03 '18 at 00:09
  • Note about "DispatchQueue.main.async() { // I'm not sure why this is necessary self.myButtonRow.evaluateHidden() }" --> If you don't use dispatch async, the active field after inserting new field is at wrong place. – Rowan Gontier Jan 02 '19 at 02:07
3

This is how can you limit the number of rows in a multivalued section:

section.multivaluedRowToInsertAt = { index in

    if index > 2 {
        let multiValuedSection = self?.form.sectionBy(tag: "MultivaluedSectionTag") as! MultivaluedSection
        multiValuedSection.multivaluedOptions = [.Reorder, .Delete]

        self?.form.rowBy(tag: "AddButtonProviderTag")?.hidden = true
        self?.form.rowBy(tag: "AddButtonProviderTag")?.evaluateHidden()
    }

    // Do other stuff
}
shim
  • 9,289
  • 12
  • 69
  • 108
Mahbub Morshed
  • 915
  • 9
  • 20
  • Yeah this is essentially what I've tried, but then what do you do when a row is deleted? Also the last row insertion gets out of order when you do this because of the hiding of the add button row. – shim Jan 02 '18 at 20:14
  • And running into a weird seeming bug where it never hides the button row until the 6th row is added… are you seeing that? – shim Jan 02 '18 at 21:32
  • 1
    This is an incomplete answer, but I awarded the bounty because it is the only other one and helped me formulate the accepted one. – shim Jan 03 '18 at 18:53