Group Group Group Group Group Group Group Group Group

raywenderlich.com Forums

Eureka Tutorial – Start Building Easy iOS Forms

This Eureka tutorial will teach you how Eureka makes it easy to build forms into your iOS app with various commonly-used user interface elements.


This is a companion discussion topic for the original entry at https://www.raywenderlich.com/605-eureka-tutorial-start-building-easy-ios-forms

Thanks for your tutorial!

I have one question:
When a value in the model is changed by a background process or a user interaction in a different UIController (not by the user in the current Form)… what is the preferred / most elegant way to get this changed value visible on the user interface with Eureka?

Best regards,
Gerd

Hi, Gerd!

If you anticipate that particular parts of the form might need to be refreshed in case their underlying data changes, make sure to have a reference to those components by assigning a value to a section or row’s tag property. This will allow you to get a reference to a specific row or section instance after the form has been created.

You could reload those components by invoking updateCell() or section.reload(), either when the form is already on-screen or every time the form is presented in viewWillAppear. You can also reload the entire form by calling tableView.reloadData().

Depending on the situation, you could consider making the view controller containing the form listen for specific event updates, either as a delegate or through KVO, and refresh its UI accordingly using the methods outlined above.

Hope that helps!

I have an App that does that. What I ended up is this:

  • in most cases I can’t use those operators any more, so the code looks completely diferent from yous (or I have to use long code with many guards as you do in footerTapped)

  • code for one cell is cluttered around: declaration, definition and update in different places.

  • if the visibility of one cell depends on the value of another, the code gets even more messy: more places where there is code for one cell.

All in all the whole promise that all code for one cell is together at one place is broken in my implementation and you suggestion. and the coding style of your excellent tutorial can’t be used.

I was hoping for a coding style where I simply can update the viewModel which is the automagically reflected in the view as I can do with mvvmFX for example.

This was a good tutorial but for someone new as me i would like to know how the data was saved and displayed on the first viewcontroller? I spent a bit of time trying to figure this out with no luck.

A couple of thoughts/suggestions:

  • If you find yourself unwrapping references to the same cell instance in a bunch of places, one way to minimize this is to declare a property for it on your view controller similar to how you would declare a storyboard outlet:
var dateRow: DateInlineRow! {
  return form.rowBy(tag: "date row") as! DateInlineRow
}

You could then use the dateRow property anywhere else in your code, including in another cell’s onChange callback, without having to conditionally unwrap everywhere (just as with a storyboard outlet).

  • Rows, sections, section headers and section footers have a hidden property of optional type Condition which can be set using a function or an NSPredicate. You pass in the tags of the other rows that this component depends on to toggle it’s visibility:
<<< LabelRow() {
  $0.hidden = Condition.function(["switchRowTag"], { form in
  return !((form.rowBy(tag: "switchRowTag") as? SwitchRow)?.value ?? false)
  })
 }

Eureka’s own Github documentation and sample project provide numerous examples of hiding/unhiding a component dynamically. This tutorial doesn’t use this approach because the hidden property applies to section headers and footers, not to table footers (at least as of this writing).

  • Typically, MVVM is associated with reactive bindings, but doesn’t have to be. This tutorial employed view models simply as a form of cleaner separation between model and view layers. While Eureka provides callbacks that respond to interface events (e.g. onChange or onPresent callbacks), it doesn’t provide any binding mechanism for your model or view model layer - you have to provide this yourself if you’d like a more reactive model/view model layer while keeping Eureka’s operators. This could be as simple as notifications or delegate callbacks, or crafting your own simple, custom bindings (in fact, RWDevCon 2016 has a great session on MVVM that has an example of this). Short of using a full-blown reactive framework like RxSwift or ReactiveCocoa, there are other, lighter frameworks like Intersetellar and Snail that might be worth a look as well.

Cheers!

Hi - Glad you enjoyed the tutorial.

The data is loaded in the AppDelegate file: a single to-do item is initialized (“Walk the dog”) and added to ToDoListViewController’s view model in application(_:didFinishLaunchingWithOptions:). :]

Yes, I’ve done hiding/unhiding and it works.

Just searching for an elegant solution, which I haven’t found yet.

Taken from your tutorial:

form
  +++ Section()	//2
  <<< TextRow() { // 3
    $0.title = "Description" //4
    $0.placeholder = "e.g. Pick up my laundry" 
    $0.value = viewModel.title //5
    $0.onChange { [unowned self] row in //6
      self.viewModel.title = row.value
    }
}

So Eureka knows how to fill the visible textRow from the model initially.

What I’d like to do is to call something like
textRow3.refetchModelValueAndTitle()

Since I have not found a way doing this, I have to write the exact same code again - filling textRow3 again from the viewModel. This time using a different programming style and at a different place of the controller. And I have to something like

func viewModelChanged() {
   //...

   textRow3.placeholder = "..."
   textRow3.value = viewModel.title
   textRow3.evaluateHidden()

    for row in self.form.allRows {
        row.reload()  // sync
    }
    DispatchQueue.main.async{
        self.tableView?.reloadData()
    }
}

It is not hat I don’t know how to do this. The app is in the App Store and works fine.

I just don’t know how to do this without implementing the same thing twice with different programming styles: initial loading using the style explained in your tutorial and again when the model changes.

I was hoping you or someone reading this has an elegant solution that prevents implementing the same task twice in one controller.

Hi. Thanks for your tutorial!
Have a question . How to update a TableView in PushRow when i add a new item ?

Hi, arsen - Thanks and you’re welcome! Just wanted to clarify - which table view are you referring to? The table view with the list of options after the PushRow is tapped, or the table view in the form that contains the PushRow? Maybe a quick example of what it is you’re trying to do would be helpful.

 <<< PushRow<String>() {
            row in
            row.title = "Заказчик"
            row.options = self.client
            row.tag = "Заказчик"
            
            self.tableView?.backgroundColor = #colorLiteral(red: 0.2392156869, green: 0.6745098233, blue: 0.9686274529, alpha: 1)
            }.cellUpdate({ (cell, row) in
                cell.textLabel?.textColor = UIColor.white
                cell.backgroundColor = #colorLiteral(red: 0.2392156869, green: 0.6745098233, blue: 0.9686274529, alpha: 1)
                cell.detailTextLabel?.textColor = #colorLiteral(red: 1, green: 1, blue: 1, alpha: 1)
                
                
            })
            .onPresent{ from, to in
                to.view.layoutSubviews()
                to.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "Добавить", style: .plain, target: self, action: #selector(self.addButtonDidPressed(_:)))
                //to.tableView?.backgroundColor = #colorLiteral(red: 0.2392156869, green: 0.6745098233, blue: 0.9686274529, alpha: 1)
                to.reloadInputViews()
                
                
            }.cellUpdate({ (cell, row) in
                row.tag = "11"
                cell.textLabel?.textColor = UIColor.white
                cell.backgroundColor = #colorLiteral(red: 0.2392156869, green: 0.6745098233, blue: 0.9686274529, alpha: 1)
                row.options = self.client
                row.reload()
                
                
            }).onChange { row in
                
                self.curentClient = row.value!
                
        }
func addButtonDidPressed(_ sender: Any) {
    let alertController = UIAlertController(title:"Новый элемент", message: "Добавить элемент", preferredStyle: .alert)

    alertController.addTextField { (textField: UITextField)  in
        textField.placeholder = "новый элемент"
    }
    let ok = UIAlertAction(title: "Добавить", style: .default) { _ in
        
        guard let textField = alertController.textFields?.first, textField.text != "" else {return}
 
            self.client.append(textField.text!)
        
        for row in self.form.allRows {
            row.reload()
        }
       
        DispatchQueue.main.async {
            self.tableView?.reloadData()
        }
        
        // отправка в базу список заказчиков (клиенты)
        let clientRef = self.refCont
        clientRef?.setValue(self.client)
        
     
        print(self.client)
        
    }
    let cancel = UIAlertAction(title: "Отмена", style: .cancel, handler: nil)
    
    alertController.addAction(ok)
    alertController.addAction(cancel)
    
    self.present(alertController, animated: true) {
                    // ...
    }
}

Hi, arsen -

Please see the code sample at this link (I confirmed this code works in a quick sample project I put together.)

It seems you want to reload the PushRow every time a value is added to the list of options via the text field. In order to achieve this, create a variable that holds a reference to the PushRow instance, and reload the row in the completion handler of the UIAlertAction after the new item is appended to itemArray.

Hope this helps and cheers!

Thank you very much, you helped me a lot))

@lunarboots I found that customizing the look and feel of the forms using Eureka is a bit of an adventure. The out of the box look and feel looks like stock iOS settings page and there doesn’t seem to be much documentation or discussions around using custom fields (in terms of look and feel) etc. The library looks awesome but just a regular form is looking quite out of place within my app and I can’t seem to wrap my head around being able to customize the look and feel and there’s no Storyboard support. I looked at 1 of their blog posts where they demonstrate a custom field using a xib file but that sounds a little too much to create individual xib files for each of the fields on the form. Any thoughts or ideas?

This is exactly my thought.

Hey guys, you are helping me a lot! It is amazing! Thank you!

So I am using the EditToDoItemViewController as a user register. But there are fields that the user dont want to fill. Even date, they dont have to fill. How can I filter and make the other “unfill” rows disappear when I save and appear when I click on Edit button? Thanks!

Hello. I’m getting an error currently that is saying that the Eureka Cococapod in the tutorial doesn’t conform to to protocol 'RangeReplaceableCollection". I believe this is because I’m on swift 4, so I tried to update the pods to the latest versions (Eureka, '~>3.0.0 and ‘imageRow’, ‘~>2.0.0’) to see if that would work, but they still aren’t building. This is happening straight out of the box from the tutorial. Is there something really obvious that I’m missing to make this work or do I have to wait for an update to swift 4

This tutorial is more than six months old so questions are no longer supported at the moment for it. We will update it as soon as possible. Thank you! :]