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. By Nicholas Sakaimbo.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Eureka Tutorial – Start Building Easy iOS Forms
30 mins
- Getting Started
- Adding Eureka to our View Controller
- Adding a Section and a Row
- Setting the Due Date with a Date Picker
- Selecting the Repeat Frequency
- Adding a Priority Selector
- Setting a Reminder with an Alert Row
- Validation
- Adding More Pizazz with Eureka Plugins
- Creating a Eureka Plugin
- Adding a Custom Cell Subclass
- Adding a Custom Row Subclass
- Adding a Dynamic Section Footer
- The Home Stretch
- Finishing Touches
- Where To Go From Here?
Creating a Eureka Plugin
Open EditToDoItemViewModel.swift and check out the categoryOptions
array. You can see that the starter project includes possible to-do item categories of Home, Work, Personal, Play and Health. You will create a custom component to allow the user to assign one of these categories to a to-do item.
You will use a Row
subclass that provides the default functionality of a PushRow
but whose layout is more tailored to your needs. Admittedly, this example is a little contrived, but it will help you understand the essentials of crafting your own custom components.
In Xcode's File Navigator, control click the Views group and create a new file named ToDoCategoryRow.swift. Import Eureka at the top of this file:
import Eureka
Until now, you have been dealing almost exclusively with subclasses of Eureka's Row
class. Behind the scenes, the Row
class works together with the Cell class. The Cell class is the actual UITableViewCell
presented on screen. Both a Row
and Cell
must be defined for the same value
type.
Adding a Custom Cell Subclass
You'll start by creating the cell. At the top of ToDoCategoryRow.swift, insert the following:
//1
class ToDoCategoryCell: PushSelectorCell<String> {
//2
lazy var categoryLabel: UILabel = {
let lbl = UILabel()
lbl.textAlignment = .center
return lbl
}()
//3
override func setup() {
height = { 60 }
row.title = nil
super.setup()
selectionStyle = .none
//4
contentView.addSubview(categoryLabel)
categoryLabel.translatesAutoresizingMaskIntoConstraints = false
let margin: CGFloat = 10.0
categoryLabel.heightAnchor.constraint(equalTo: contentView.heightAnchor, constant: -(margin * 2)).isActive = true
categoryLabel.widthAnchor.constraint(equalTo: contentView.widthAnchor, constant: -(margin * 2)).isActive = true
categoryLabel.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
categoryLabel.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
}
//5
override func update() {
row.title = nil
accessoryType = .disclosureIndicator
editingAccessoryType = accessoryType
selectionStyle = row.isDisabled ? .none : .default
categoryLabel.text = row.value
}
}
You've created a custom PushSelectorCell
, which derives from UITableViewCell
and is managed by PushRow
. The cell will display a centered label. Here are some details on how this works:
- You'll be displaying string values in this cell, so you provide
String
as the optional type. - Instantiate the
UILabel
that will be added to the cell. -
setup()
is called when the cell is initialized. You'll use it to lay out the cell - starting with setting theheight
(provided by a closure),title
andselectionStyle
. - Add the
categoryLabel
and the constraints necessary to center it within the cell'scontentView
. - Override the cell's
update()
method, which is called every time the cell is reloaded. This is where you tell the cell how to present theRow
'svalue
. Note that you're not calling the super implementation here, because you don't want to configure thetextLabel
included with the base class.
Adding a Custom Row Subclass
Below the ToDoCategoryCell
class, add ToDoCategoryRow
:
final class ToDoCategoryRow: _PushRow<ToDoCategoryCell>, RowType { }
Because Row
subclasses are required to be final
, PushRow
cannot be subclassed directly. Instead, subclass the generic _PushRow
provided by Eureka. In the angle brackets, associate the ToDoCategoryRow
with the ToDoCategoryCell
you just created. Finally, every row must adhere to the RowType
protocol.
Now your custom row is all set up and ready to use!
Adding a Dynamic Section Footer
The custom row will be embedded in a "Category" section which will be initially hidden from the user. This section will be unhidden when the user taps a custom table view footer. Open EditToDoItemViewController.swift, and right below the declaration of the dateFormatter
constant, add the following:
let categorySectionTag: String = "add category section"
let categoryRowTag: String = "add category row"
The tag
property is used by the Form
to obtain references to a specific Eureka Row
or Section
. You'll use this constant to tag and later retrieve the section and row used to manage an item's category.
Next, add the following lines at the end of viewDidLoad()
:
//1
+++ Section("Category") {
$0.tag = categorySectionTag
//2
$0.hidden = (self.viewModel.category != nil) ? false : true
}
//3
<<< ToDoCategoryRow() { [unowned self] row in
row.tag = self.categoryRowTag
//4
row.value = self.viewModel.category
//5
row.options = self.viewModel.categoryOptions
//6
row.onChange { [unowned self] row in
self.viewModel.category = row.value
}
}
This adds a new section that includes your custom ToDoCategoryRow
, which is initially hidden. Here are some details:
- Add a section to the form, assigning the
categorySectionTag
constant. - Set the section's
hidden
property totrue
if thecategory
property on the view model is nil. The plain nil-coalescing operator cannot be used here as the hidden property requires a Boolean literal value instead. - Add an instance of
ToDoCategoryRow
to the section tagged withcategoryRowTag
. - Set the row's
value
toviewModel.category
. - Because this row inherits from
PushRow
, you must set the row'soptions
property to the options you want displayed. - As you've seen in prior examples, use the row's
onChange(_:)
callback to update the view model'scategory
property whenever the row'svalue
changes.
Near the top of EditToDoItemViewController
, right below the categorySectionTag
definition, add the following:
lazy var footerTapped: EditToDoTableFooter.TappedClosure = { [weak self] footer in //1
//2
guard let form = self?.form,
let tag = self?.categorySectionTag,
let section = form.sectionBy(tag: tag) else {
return
}
//3
footer.removeFromSuperview()
//4
section.hidden = false
section.evaluateHidden()
//5
if let rowTag = self?.categoryRowTag,
let row = form.rowBy(tag: rowTag) as? ToDoCategoryRow {
//6
let category = self?.viewModel.categoryOptions[0]
self?.viewModel.category = category
row.value = category
row.cell.update()
}
}
EditToDoTableFooter
is a view class included in the starter that contains a button with the title Add Category. It also includes TappedClosure
, a typealias for an action to execute when tapped. The code you added defines a closure of this type that takes a footer
, removes it from the view and displays the category section.
Here is a more detailed look:
- To avoid retain cycles, pass
[weak self]
to the closure. - Safely unwrap references to the view controller and its
form
andcategorySectionTag
properties. You obtain a reference to theSection
instance you defined with thecategorySectionTag
. - When the footer is tapped, remove it from the view since the user shouldn't be allowed to tap it again.
- Unhide the section by setting
hidden
tofalse
then callingevaluateHidden()
.evaluateHidden()
updates the form based on thehidden
flag. - Safely unwrap the reference to the
ToDoCategoryRow
we added to the form. - Ensure the view model's
category
property and the cell's rowvalue
property are defaulted to the first item in the array of options. Call the cell'supdate()
method so its label is refreshed to show the row's value.