100 Days of Swift UI – Day 17
WeSplit – Part 2
Reading text from the user with TextField
Now that we have a clean slate to work on, add three
@State properties, an empty String called
checkAmount, and two Integer properties,
tipPercentage initialised to 2. We need to use strings for TextField, we have no choice here. The
tipPercentage is set to 2 for reasons that will become clearer shortly. To show that add an array of integers as a constant called
tipPercentages containing 10, 15, 20, 25, 0.
body property create a
Form, with a
Section inside and, inside that, a
TextField with a placeholder text of “Amount” and a binding text of
The nice thing of
@State is that it will reload the
body property every time something happens with the things marked with
Now add a second Section with a Text which reads
The two sections are synchronised because we have two-way binding to the
checkAmount property and because it is marked with
@State, thing that reloads the UI by calling the
body property again.
To clean this up we should change the
keyboardType() to a
.decimalPad, adding a modifier to the TextField in the form of
.keyboardType(.decimalPad). These modifiers should be written on a new line and indented by a tab inside. The user can still enter other characters via an external keyboard, but we’ll fix this later.
Creating pickers in a form
Pickers, like text fields, need a two-way binding to a property. For our picker, that property is
numberOfPeople. In the first Section create a Picker with a label of “Number of people”, a selection of
$numberOfPeople and closure that calls the ForEach struct with a range of
2 ..< 100 creating a Text with
"\($0) people" inside the parentheses. This will populate the picker’s options with a text that says n people where n is a number between 2 and 100.
The issue here is that even if we have a new row that says “Number of people” on the left and “4 people” on the right (where did this 4 come from?!) and even if this row has a disclosure indicator on the right edge (the thing that, in table view, that invoked a detail view controller), tapping on it doesn’t do anything.
The “4 people” is not a bug because the
numberOfPeople index brings us to the third row, which is 4. To solve the second and most important thing we need to wrap our view into a NavigationView. Then, outside of the Form, add a modifier
Right now, on Xcode 11.1 on macOS 10.14.6 the checkmark Paul is talking about is not appearing and a lot of errors and warnings in the console.
Adding a segmented control for tip percentages
Add a third Section between the other two with a Picker saying “Tip percentage”, with a selection of
$tipPercentage and a closure that calls ForEach between 0 up to and excluding
tipPercentages.count to create a Text that shows
"\(self.tipPercentages[$0])%". At the end of this Picker add this modifier:
This is certainly working, but, on the Simulator at least, if the keyboard was not dismissed and we rotate the device in landscape, we are not going to see anything on the screen, not even scrolling. Also, the checkmark is not showing up when going into the detail view controller.
Next to this Section add a parentheses to invoke the extra options and write
(header: Text("How much tip do you want to leave?")).
Calculating the total per person.
Our next task is to show how much each person should be paying. The problems we have to overcome are: the
numberOfPeople being off by 2, the
tipPercentage storing an index of an array instead of the real value and the
checkAmount which is a String, that is, it is not safe.
Double computed property called
totalPerPerson and make it
return 0 by now. Above that, create a
Double constant called
peopleCount which converts into Double the
numberOfPeople amount after adding 2 to it. Next, create a new
tipSelection constant that converts into a Double the extracted value from the
tipPercentages array at the
tipPercentage index. Finally, add a
orderAmount constant that tries to converts the
checkAmountproperty to a Double and gives out
0 if an incompatible piece of data is found.
Now, create three new constants: a
tipValue equal to
orderAmount divided by 100 and multiplied by
grandTotal equal to the order amount plus the tip’s value and, finally, an
amountPerPerson equal to the grand total divided by the people’s count. Then return this last constant.
Last change before running the app: in the last Section replace the passed variable with
totalPerPerson. To prevent bad rounding, add a
specifier: "%.2f" at the end, which is a C-language modifier that allows us to cut the amount of digits for floating-point numbers.
Now, if we run it, this works, but only in portrait orientation. In landscape we just see a totally blank screen. Also, a full load of errors appear on the console, such as:
- ForEach<Range<Int>, Int, Text> count (98) != its initial count (3).
ForEach(_:content:)should only be used for constant data. Instead conform data to
ForEach(_:id:content:)and provide an explicit
- 2019-10-11 11:55:24.355542+0200 WeSplit [4351:1017463]. Can’t find keyplane that supports type 8 for keyboard iPhone-PortraitTruffle-DecimalPad; using 25724PortraitTruffleiPhone-Simple-PadDefault
- 2019-10-11 11:55:57.462859+0200 WeSplit [4351:1017463] [TableView] Warning once only: UITableView was told to layout its visible cells and other contents without being in the view hierarchy (the table view or one of its superviews has not been added to a window). This may cause bugs by forcing views inside the table view to load and perform layout without accurate information (e.g. table view bounds, trait collection, layout margins, safe area insets, etc), and will also cause unnecessary performance overhead due to extra layout passes. Make a symbolic breakpoint at UITableViewAlertForLayoutOutsideViewHierarchy to catch this in the debugger and see what caused this to occur, so you can avoid this action altogether if possible, or defer it until the table view has been added to a window. Table view: <TtC7SwiftUIP33BFB370BA5F1BADDC9D83021565761A4925UpdateCoalescingTableView: 0x7fd4371bf200; baseClass = UITableView; frame = (0 0; 414 896); clipsToBounds = YES; gestureRecognizers = <NSArray: 0x60000125d9e0>; layer = <CALayer: 0x600001c8f9e0>; contentOffset: [0, -140]; contentSize: [414, 4393.5]; adjustedContentInset: [140, 0, 34, 0]; dataSource: <TtGC7SwiftUIP13$7fff2c69bad819ListCoreCoordinatorGVS20SystemListDataSourceOs5NeverGOS19SelectionManagerBoxS2: 0x7fd436ee9580>>
I am not really sure I can do anything about that so I will just surrender and move on.
That’s it for today.
If you like what I’m doing here please consider liking this article and sharing it with some of your peers. If you are feeling like being really awesome, please consider making a small donation to support my studies and my writing (please appreciate that I am not using advertisement on my articles).
If you are interested in my music engraving and my publications don’t forget visit my Facebook page and the pages where I publish my scores (Gumroad, SheetMusicPlus, ScoreExchange and on Apple Books).
You can also support me by buying Paul Hudson’s books from this Affiliate Link.
Anyways, thank you so much for reading!
Till the next one!