Learning Swift – Day 203

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, numberOfPeople and 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.

Inside the 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 $checkAmount.

The nice thing of @State is that it will reload the body property every time something happens with the things marked with @State.

Now add a second Section with a Text which reads "$\(checkAmount)".

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 .navigationBarTitle("WeSplit").

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: .pickerStyle(SegmentedPickerStyle()).

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.

Create a 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 tipSelection, a 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 Identifiable or use ForEach(_:id:content:) and provide an explicit id!
  • 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!

Published by Michele Galvagno

Professional Musical Scores Designer and Engraver Graduated Classical Musician (cello) and Teacher Tech Enthusiast and Apprentice iOS / macOS Developer Grafico di Partiture Musicali Professionista Musicista classico diplomato (violoncello) ed insegnante Appassionato di tecnologia ed apprendista Sviluppatore iOS / macOS

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: