Hot questions for Using Eureka in forms

Question:

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.


Answer:

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()
        }
    }
}

Question:

As Eureka is used for creating a new record, which includes different datatypes, one being MediaPicker, I'm wondering how to present its viewController.

Which row would do the job? PushRow or ButtonRow

Here is my class where I try to create this.

import UIKit
import MediaPlayer

import Eureka

public final class MusicRow<T: Equatable> : SelectorRow<T, PushSelectorCell<T>, SelectorViewController<T>>, RowType {

    public required init(tag: String?) {
        super.init(tag: tag)
        presentationMode = .Show(controllerProvider: ControllerProvider.Callback {
            return AddMusicViewController(){ _ in }
            }, completionCallback: { vc in
                vc.navigationController?.popViewControllerAnimated(true)
        })
    }
}

public class AddMusicViewController: MPMediaPickerController, MPMediaPickerControllerDelegate {

    var musicPicker: MPMediaPickerController!

    public var row: RowOf<MPMediaItemCollection>!

    public var completionCallback : ((UIViewController) -> ())?

    public override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        // Setup sections/rows for tableView
        addMusic()
    }

    // Initialize musicPicker and customize it
    func addMusic() {
        musicPicker = MPMediaPickerController.self(mediaTypes:.Music)
        musicPicker.delegate = self
        musicPicker.allowsPickingMultipleItems = true
        view.addSubview(musicPicker.view)
    }

    // After selection, store the data into an array
    public func mediaPicker(mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection) {
        musicTemp = nil
        musicTemp = mediaItemCollection
        if musicTemp == nil {
            noMusic = true
        } else {
            noMusic = false
        }
        completionCallback?(self)
    }

    // Cancel mediaPickerController
    public func mediaPickerDidCancel(mediaPicker: MPMediaPickerController){
        // Dismiss the picker if the user canceled
        noMusic = true
        completionCallback?(self)
    }
}

Below is the error that I face.


Answer:

Although, this was a generic question, my requirement was to use a MPMediaPickerController.

Below is my setup.

import UIKit
import MediaPlayer

import Eureka

// MusicRow
public final class MusicRow : SelectorRow<MPMediaItemCollection, PushSelectorCell<MPMediaItemCollection>, AddMusicViewController>, RowType {
    public required init(tag: String?) {
        super.init(tag: tag)
        presentationMode = .Show(controllerProvider: ControllerProvider.Callback { return AddMusicViewController(){ _ in } }, completionCallback: { vc in vc.navigationController?.popViewControllerAnimated(true) })
displayValueFor = {
        guard var musicTitle = $0 else { return "" }
        musicTitle = musicTemp!
        let representativeItem = musicTitle.representativeItem
        print("representativeItem = \(representativeItem)")
        let representativeItemTitle = representativeItem?.title
        return  "\(representativeItemTitle)"
        }
    }
}

// MusicViewController
public class AddMusicViewController : UIViewController, TypedRowControllerType, MPMediaPickerControllerDelegate {

    public var row: RowOf<MPMediaItemCollection>!
    public var completionCallback : ((UIViewController) -> ())?

    lazy var musicPicker : MPMediaPickerController = { [unowned self] in
        let mediaPicker = MPMediaPickerController.self(mediaTypes:.Music)
        return mediaPicker
    }()

    required public init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }

    public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
        super.init(nibName: nil, bundle: nil)
    }

    convenience public init(_ callback: (UIViewController) -> ()){
        self.init(nibName: nil, bundle: nil)
        completionCallback = callback
    }

    public override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(musicPicker.view)

        musicPicker.delegate = self
        musicPicker.allowsPickingMultipleItems = true

        self.navigationItem.leftBarButtonItem = UIBarButtonItem(title: "", style: .Plain, target: nil, action: nil)
    }

    // After selection, store the data into a temporary variable
    public func mediaPicker(mediaPicker: MPMediaPickerController, didPickMediaItems mediaItemCollection: MPMediaItemCollection) {
        musicTemp = nil
        musicTemp = mediaItemCollection
        if musicTemp == nil {
            noMusic = true
        } else {
            noMusic = false
            row.value? = musicTemp!
        }
        completionCallback?(self)
    }

    // Cancel mediaPickerController
    public func mediaPickerDidCancel(mediaPicker: MPMediaPickerController){
        // Dismiss the picker if the user canceled
        noMusic = true
        completionCallback?(self)
    }
}

I believe, it isn't hard to tweak it to anyone's personal requirements!

Question:

I use Eureka library and i encountered a problem. I need a multiline label row but don't know how to do it. I can see only one-line with truncation label rows.

 class MainViewController: FormViewController {
    override func viewDidLoad() {
      super.viewDidLoad()

      form +++=

        Section()

        <<< LabelRow { row in
            row.title = "Hello World 1. Hello World 2. Hello World 3"
        }
 }

Answer:

With row.cell you get the whole cell and you can customize it.

The label row should like something like this:

<<< LabelRow { row in
    row.title = "Hello World 1. Hello World 2. Hello World 3"
    row.cell.textLabel?.numberOfLines = 0
}

Question:

I am building a Eureka form and would like to put a loop within the form to build a list of steppers based on an array.

The code I am trying to use is:

let itemNames = ["one","two","three"]

// Eureka From Set-up
form
    +++ Section("Select item values")

    for itemName in itemNames{
        <<< StepperRow() {
            $0.tag = itemName
            $0.title = itemName
            $0.value = 0
        }
    }

However, when I do this I get an error on the StepperRow line that says:

Unary operator cannot be separated from its operand

So it looks as though Swift no longer thinks it is within the form and is looking at the < symbol as less than, rather than the row declaration.

Any thoughts on how to get around this?


Answer:

The <<< is a binary operator, which expects two operands (lhs <<< rhs), whereas in your example above, you only supply it one (<<< operand).

It's not possible to "pipe" each pass of a for loop such as if each pass was a rhs to be used with a lhs operand outside of the scope of the loop (with lhs for first pass being the result of form +++ Section(...)). You could, however, make use of reduce to achieve such functionality. Now, I haven't tested this with Eureka forms (however on dummy structures and operators), but it should look something like the following: (based on the +++ and <<< operator functions declared in Eureka/Source/Core/Operators.swift)

form 
    +++ itemNames.reduce(Section("Select item values")) { (section, itemName) in
        section 
            <<< StepperRow() {
                $0.tag = itemName
                $0.title = itemName
                $0.value = 0
            }
    }

Question:

How do I remove the toolbar with " < > Done" on top of the keyboard when a Eureka form row is selected? I'd just like to have the regular keyboard. It isn't clear from the documentation how to do this. Also, what is this toolbar generally referred to as in the iOS documentation? Thank you.


Answer:

You must override the following method on your viewController

override func inputAccessoryViewForRow(row: BaseRow) -> UIView?

and simply return nil there something like this

override func inputAccessoryViewForRow(row: BaseRow) -> UIView? {
    return nil
}
Swift 3.0

thanks to @ T.Coutlakis

override func inputAccessoryView(for row: BaseRow) -> UIView? {
    return nil
}

this is the result

Hope this helps you

Question:

I am looking for a way to change the textField text alignment. I have tried it with

cell.textField.textAlignment = .Left

but that doesn't change the alignment. It will always stay right aligned. I was wondering why this was the default, as Apple themselves use left aligned text inputs in most, if not all places.


Answer:

For Eureka is a little different. You have to use cell update callback. It wasn't well documented about changing alignment. But it did mention here that to change color or font you will have to use the .cellUpdate. Thus, i believe this work will work for you:

let row = TextRow() {
    row.title = "Field:"
}.cellUpdate { cell, row in
    cell.textField.textAlignment = .left
}

Question:

I am creating an IOS app using Swift 3 and implementing Eureka Forms. As part of a form I have a Button Row that is being used as a Delete button. I'm therefore looking to change the text colour to white.

I have tried the following, however cell text colour throws an error. Any thoughts on how I implement this correctly?

+++ Section("Delete Item")
  <<< ButtonRow() {
  $0.title = "Delete"
  }.cellSetup() {cell, row in
    cell.backgroundColor = UIColor.red
    cell.textLabel?.textColor = UIColor.whiteColor()
  }.onCellSelection {  cell, row in self.deleteItem() }

Answer:

you are close, you must use tintColor instead of textColor, use this code

 <<< ButtonRow() {
      $0.title = "Delete"
      }.cellSetup() {cell, row in
          cell.backgroundColor = UIColor.red
          cell.tintColor = UIColor.white
      }

I hope this helps you

Question:

I am creating a series of ImageCheckRows as a SelectableSection, and I would like to set some default values. Essentially each row returns a true/false value, so somewhere there should be a simply method of setting true or false on each row. I tried the row.value but this requires a 'String?'

currentSection! <<< ImageCheckRow<String> { row in
   row.tag = "\(tagname)_\(optionKey)"
   row.title = optionValue as? String
   row.selectableValue = optionKey as? String

   if let dvkey = optionKey as? String {
      print("dvkey = \(dvkey)")
      if let _ = defaultValues?.value(forKey: dvkey) {
         print("we found dvkey in the defaultValues dict - try row.select")
         row.value = true
      }
   }

}.cellSetup { cell, _ in
   cell.trueImage = UIImage(named: imageChecked)!
   cell.falseImage = UIImage(named: imageUnchecked)!
}

I also tried using the function:

row.select()

But this didn't work either. Then I tried moving it to the cellSetup, but this didn't work either.

Can anyone help me here?


Answer:

Worked it out myself...

row.value = optionKeyValue as? String

This is a radio button style control, and as I create the row with the default radio button I set the value to the key, however it must be a string representation, even if it is numeric. Who would have guessed!

My first thought was that the radio button control is checked/unchecked, hence it should really be a true/false value.

My second thought was that the radio button control default value would be set against the group, which is a more logical place to set or change the value.

Wrong on both counts.

Question:

)

I have been using Eureka for a while,It's amazing!!!

Recently I work on with MultivaluedSection,I write a simple project for test : It simple add/delete person from tableView.

here are code , first for model : Person

struct Person:Equatable,CustomStringConvertible{
    var description: String{
        return "\(name) \(id)"
    }

    static func ==(lhs: Person, rhs: Person) -> Bool {
        return lhs.id == rhs.id
    }

    var id:String
    var name:String

    init(name:String){
        self.id = UUID().uuidString
        self.name = name
    }
}

next code for VC :

class ViewController: FormViewController {

    var people:[Person] = []

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        //hide delete button at row left
        tableView.isEditing = false
    }

    override func viewDidLoad() {
        super.viewDidLoad()

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

        peopleSection.tag = "people"
        peopleSection.multivaluedRowToInsertAt = {idx in
            let newRow = LabelRow(){row in
                let person = Person(name: "h\(idx)")
                row.value = person.description
                self.people.append(person)

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

        peopleSection.addButtonProvider = {section in
            let addBtn = ButtonRow("add"){row in
                row.title = "new person"
            }
            return addBtn
        }

        form +++ peopleSection
    }
}

Run app , like the following picture:

There are 2 questions :

1:You can see when I add 3 person then delete them in order , everything is fine! But when I add person again some wrong is happened : it seem like the section's header was pulled very long. why???

2:When I added some people to tableView,there're title are not left-aligned,Why is that :

Thanks a lot!


Answer:

Please update the code,

peopleSection.multivaluedRowToInsertAt = {idx in
    return LabelRow() {
        let person = Person(name: "h\(idx)")
        $0.title = person.description
        self.people.append(person)
    }
}

It will give you following output, and delete will also work properly.

Question:

I am using xmartlabs/Eureka to build an app with a dynamic form.

In order to fill the form I have to use setValues(values: [String: Any?]).

But I have the form values in an NSDictionary variable and I cannot cast it to [String:Any?].

Is there a way to convert an NSDictionary to [String:Any?] ?


Answer:

Just an example:

if let content = data["data"] as? [String:AnyObject] {
    print(content)
}

The data is a JSON object here. Use it accordingly.

Question:

I've just started using Eureka Form Builder for Swift 3 but I'm wondering if there is a way to show validation errors. I've added my form with one row in it below where I'm trying to set the error message to an optional detailTextLabel, however it's not showing. My question is, does Eureka have a default error message UILabel built into the rows or do I have to make a custom row that is able to show the message?

form +++ Section("Contactgegevens")
        <<< NameRow(){ row in
            row.title = "Achternaam"
            row.placeholder = "Achternaam"
            row.value = user?.surname
            row.add(rule: RuleRequired(msg: "Dit veld is verplicht."))
            row.validationOptions = .validatesOnChangeAfterBlurred
            }.onChange({ (row) in
                if !row.isValid {

                    var errors = ""

                    for error in row.validationErrors {
                        let errorString = error.msg + "\n"
                        errors = errors + errorString
                    }
                    print(errors)
                    row.cell.detailTextLabel?.text = errors
                    row.cell.detailTextLabel?.isHidden = false

                }
            })

The documentation on Eureka seems to be really incomplete, but the framework itself does seem really robust so I'd like to learn how to use it.


Answer:

In the Eureka example project they use onRowValidationChanged and add/remove a LabelRow with a message that can be customised.

Question:

Asking this question here because this has not been covered in docs yet and they monitor and answer this tag. I am using Eureka to build a Multivalued Form. This is my code:

    +++ MultivaluedSection(multivaluedOptions: [.Reorder, .Insert, .Delete],
        header: "Options",
        footer: "footer") {
                            $0.addButtonProvider = { section in
                                return ButtonRow(){
                                    $0.title = "Add New Option"
                                }
                            }
                            $0.multivaluedRowToInsertAt = { index in
                                print(self.form.values())
                                return NameRow() {
                                    $0.placeholder = "Your option"
                                }
                            }
                            $0 <<< NameRow() {
                                $0.placeholder = "Your option"
                            }
    }

Now I want to extract all the values in NameRows at the end. There can be any number of rows(based on user input). This is what I tried:

self.form.values() but it results in [ : ].

How do I get all the values?


Answer:

For anyone with similar problem:

The form.values() was being empty because there were no tags given to the Rows. To get values from form, give tags to rows and you will get a dictionary of values with keys as these tags. For this case

+++ MultivaluedSection(multivaluedOptions: [.Reorder, .Insert, .Delete],
                           header: "header",
                           footer: "footer") {
                            $0.addButtonProvider = { section in
                                return ButtonRow(){
                                    $0.title = "Button Title"
                                }
                            }
                            $0.multivaluedRowToInsertAt = { index in
                                return NameRow("tag_\(index+1)") {
                                    $0.placeholder = "Your option"
                                }
                            }
                            $0 <<< NameRow("tag_1") {
                                $0.placeholder = "Your option"
                            }
    }

Now the values will be returned as ["tag_1" : "value 1", "tag 2 : "value 2" ....] for any number of rows inserted by user. P.S.: Used the index in tag because duplicate tags aren't allowed and index value is different for different rows.

Question:

I am using the Eureka Swift form library found here.

I have a textfield and whenever you start typing in it, I would like the other sections of the form to be hidden. I started out by just trying to hide 1 section but nothing happens when I start typing in the field. My code is as follows:

  form  +++ Section("Device Search")

        <<< IntRow()
            {
                $0.title = "Asset Tag"
                $0.placeholder = "Enter Asset Tag #"
            }

            .onChange { row in

              self.form.sectionBy(tag: "iOS Version")?.hidden = true
        }

        +++ Section("iOS Version")


        for version in countArray
        {

            form.last! <<< CheckRow()
                {
                    $0.title = version
                    $0.tag = $0.title


            }
        }

Also, is there a way to use an IntRow but remove the formatter for just the row?


Answer:

About your second question is there a way to use an IntRow but remove the formatter for just the row?

Add this line $0.formatter = nil just below this one $0.placeholder = "Enter Asset Tag #"

Your first question, how hide a section in EurekaForm, first of all your section initialisation is not what you think it is, I will explain myself, you think that you are initalizating your section with tag, but in section definition none of init methods use tag as parameter so to get the correct section you need change your section initialization for this one

        +++ Section("iOS Version"){ //"iOS Version" is actually the header text"
            $0.tag = "test"  //this is the tag
        }

after that you need to modify this

.onChange { row in

              self.form.sectionBy(tag: "iOS Version")?.hidden = true
        }

by this one

 .onChange { row in
                if let section = self.form.sectionBy(tag: "test")
                {
                    section.hidden = true 
                    section.evaluateHidden()   //you are missing calling this method
                }
        }

After that your header named "iOS Version" is hidden after you write any number on your IntRow

Full Code
form  +++ Section("Device Search")

            <<< IntRow()
                {
                    $0.title = "Asset Tag"
                    $0.placeholder = "Enter Asset Tag #"
                    $0.formatter = nil
                }

                .onChange { row in
                    if let section = self.form.sectionBy(tag: "test")
                    {
                        section.hidden = true
                        section.evaluateHidden()
                    }
            }

            +++ Section("iOS Version"){
                $0.tag = "test"
        }

        for version in countArray
        {
            form.last! <<< CheckRow()
                {
                    $0.title = version
                    $0.tag = $0.title
            }
        }

Question:

Can I configure a TextAreaRow of an Eureka form to fill the available vertical space? I will have just a few rows above and toolbar at the bottom.


Answer:

You will have to play with textAreaHeight property values by setting up it initial height depending on device and available bottom space. Check out this pull request https://github.com/xmartlabs/Eureka/pull/416/files to see how it works.