Chapter 11. Building simple iOS views

This chapter covers

  • iOS storyboard resources
  • Laying out controls using auto layout and constraints
  • Supporting multiple screen sizes using size classes
  • Adding images to support multiple screen sizes using asset catalogs
  • Creating view controllers
  • The view controller lifecycle

In the last two chapters we built the UIs for SquareRt and Countr on Android. In this chapter and the next, we’ll build these view layers on iOS (figure 11.1). We’ll start here by looking at how to build an iOS UI and then build the UI for SquareRt. In the next chapter we’ll look at Countr, and you’ll see some slightly more advanced UI techniques, including using table views to show lists of data, menus, and navigation.

Figure 11.1. The view layer in an MVVM app is written in platform-specific code.

The iOS SDK is different from that of Android, but the basic principles for UIs are the same—a UI layout is defined in an XML file, and there’s a class that loads this layout and provides access to the layout’s UI components. Where on Android we used Activity for the views, on iOS we use UIViewController (iOS, like macOS, has MVC built in). Android uses layout XML files, and iOS uses storyboards.

11.1. Building iOS UIs

Let’s start by thinking about our SquareRt app, which has a single view. Figure 11.2 shows the UI that we want to build.

Figure 11.2. The UI for the SquareRt app

As in Android, we have to build two things to make this UI: a layout file called a storyboard, and a code-behind view controller class. We’ll look at what storyboards are and at the different components that go into a UI, including some examples of controls we can use. Then we’ll look at how iOS handles different screen resolutions using auto layout, and we’ll wrap up with images. Before we get started building the UI, though, it’s worth taking a brief moment to consider design and to look at the iOS human interface guidelines.

11.1.1. iOS human interface guidelines

Apple has put a huge amount of work into, and emphasis on, its human interface guidelines. Not only does Apple strongly encourage developers to follow them, it will even reject an app submission if it breaks some of the rules. For example, if you use standard icons for the wrong actions, your app will be rejected.

These concepts are behind the guidelines:

  • DeferenceThe UI should play second fiddle to the content. It should enhance the content and provide an easy way to interact with it, but it should never get in the way of or distract from the content. A good example is a weather app, with a full-screen image behind the weather details that can be associated with the current weather. This can enhance the content by conveying the main theme (the current weather) without distracting from the data, as shown in figure 11.3.
    Figure 11.3. A clear image can summarize content and not distract from it.

  • ClarityYour UI should be kept clear and simple. Use negative space (or whitespace) to help your app look calm and clean. By including space around the content, you can help focus the user’s attention on what’s important, rather than making them look though a lot of noise to find what they’re looking for. Where’s Waldo might be a fun book, but a confusing UI that forces the user to search for content doesn’t make for a good experience. Your content should also be limited to a small amount of data, with easy navigation to more data that the user can use when they’re ready. You should also ensure legibility by using the standard system fonts, which are designed to look good at all sizes, and chose your colors carefully to highlight and enhance content. The weather app in figure 11.3 is a good demonstration of this. It keeps the content minimal and it uses crisp, clear text with good spacing between the content (figure 11.4).
    Figure 11.4. Lots of negative space (or whitespace) can enhance clarity by helping to reduce clutter.

  • DepthBy displaying content in different layers, you can establish a hierarchy of information. The more important information can be on top, to guide the user’s focus, and the less important information can be hidden below, keeping it from being a distraction. This can be accomplished by using translucency to make a layer above very obvious (such as the folders on the iOS springboard), or by using a transition that zooms down to more granular data or up to less granular data (such as the iOS Calendar app that zooms in from year to month to day and back out). For example, our weather app could show an overview of the weather for each day of the week, and tapping on a day could zoom in to show a more detailed breakdown. This is shown in figure 11.5.

Figure 11.5. Zooming from one level to another shows a natural hierarchy.

By following Apple’s human interface guidelines, you can make your app match the experience that an iOS user expects and ensure that once you submit it to the app store, your app won’t be rejected for breaching these guidelines.

You can read more on these guidelines on Apple’s Developer website: http://mng.bz/GI7e.

11.1.2. Storyboards

Storyboards are XML files that allow you to define the layout of controls for one or more screens in a single file. You can use them to create a whole application in one layout file and visualize the flow from one screen to another to another, all the way through your app. Each screen on the storyboard is backed by a view controller (a class derived from UIViewController), and each view controller contains a single view (referred to as the superview), which can contain multiple controls. This is shown in figure 11.6.

Figure 11.6. Storyboards can contain multiple view controllers, and each view controller has a single view inside it that can contain one or more controls, as well as a backing class.

Storyboards can also define transitions between screens that can be triggered by UI elements, and the Apple term for these is segues. For example, you can wire up a button to trigger a segue to another view controller inside the layout file. We won’t be using segues here, as these are a pure iOS concept. Instead we’ll be using view-model navigation to show different screens.

The iOS SDK has some odd-looking class name prefixes

The SDKs for iOS are built using Objective-C, a nearly 40-year-old language that doesn’t have namespaces. This means that to avoid the possibility of name collisions (multiple classes with the same name), class names are prefixed with an identifier to group them, based on their functionality. The iOS SDKs mostly use two-character identifiers, such as UI for any class that’s used for the UI (a group of classes that are referred to as UIKit). The Xamarin wrappers keep these two-character prefixes to make it easier to see which SDK class is being used, but it also puts these classes into namespaces. For example, UIViewController is part of UIKit, so in the Xamarin wrapper it’s UIKit.UIViewController.

Although storyboards are XML, they’re not really designed to be human-readable, so unlike with Android, we’ll be doing everything inside a designer (figure 11.7). Also, because they’re complex XML files, there’s an ongoing debate about how to best build storyboards—you can create one storyboard that contains all your view controllers for your entire app, or you can create one storyboard per view controller. The downside to one storyboard per controller is that you can’t define and use segues in your layout files (not a problem for us, as we’ll be using view-model navigation), but the upside is that it’s much easier when you have multiple developers working on the same app. If multiple developers change the storyboard and commit to source, you’re asking for a world of pain, and merge conflicts that are hard to fix, as the XML isn’t particularly human-readable. Having one storyboard per screen reduces the chance of multiple developers changing the same storyboard.

Figure 11.7. The storyboard designer in Visual Studio showing two view controllers

Nibs and Xibs

Storyboards were added to iOS 5; before that Apple used Nibs and Xibs (layout files that define a single screen, as opposed to storyboards where you can define multiple screens). Nibs were the original layout files and contain binary data. Xibs are XML versions of Nibs, so they’re much better for source code control. You can still use Nibs and Xibs if you want, but storyboards are now preferred.

Storyboards in Xamarin apps are identical to the storyboards you’d use in a native Objective-C or Swift iOS app (just like Android layout files). If you want to reuse a storyboard from an existing iOS app, you can just copy it in and it will work.

You can use Xcode to edit storyboards

If you’re using Visual Studio for Mac, you can also edit storyboards using Xcode if you want. Visual Studio will create a dummy Xcode project with your storyboard, and any other resources it needs (such as images). Any edits you make in Xcode are automatically synchronized back to Visual Studio.

On Android, you design for multiple screen sizes and orientations by putting different layout files in different folders in the Resources folder. iOS, in contrast, doesn’t have different storyboards for different screen sizes. Instead you design one screen that will resize and adjust itself to all the different supported screen sizes. When you size and position controls on screen, you don’t do it based on pixels; instead you define a set of rules (called constraints) against each control to say how it should be drawn. This is very similar to the way relative layouts work on Android. You can, for example, say that a text box should be centered horizontally and vertically in the screen and have a button below it. All the supported devices and orientations are grouped into separate “size classes” based on their sizes. You can then configure the layout rules differently for different size classes.

Once you’ve defined your layout in the storyboard designer, you can view it as if you were using different devices to see how it would look on all possible screen sizes and orientations. This allows you to design your screens for all available iOS devices. The limited number of iOS device sizes makes it really easy to design an app that looks amazing for all your users, unlike on Android, where there’s a huge range of screen sizes.

11.1.3. Controls

Back in chapter 9, we defined a layout for SquareRt on Android, and now we’ll create a similar layout on iOS. Unlike Android, iOS doesn’t have a relative layout control that we can use to position everything. Instead we have to add all the controls to the view, and then create constraints between them to position everything. Android has a few layout controls for positioning controls, but iOS doesn’t really have these layout controls—it has a stack layout that arranges items horizontally or vertically, but that’s it. Constraints are the usual way to lay everything out.

iOS has all the standard user interaction controls, such as buttons, labels, and text boxes that you’d want to use for most apps. These controls are sometimes referred to as widgets, as they are for Android. Let’s look at a few of them, shown in figure 11.8:

  • LabelUIKit.UILabel is a static label that shows text that can’t be edited.
  • Text fieldUIKit.UITextField is a text-entry control. When the user taps this control, the keyboard pops up, allowing text to be entered. This control can also be configured to limit the values that can be entered, such as just allowing numbers.
  • ButtonUIKit.UIButton is a push button that can be tapped by the user to perform an action.
  • Image viewUIKit.UIImageView is used to show an image, and it can scale the image to fit, if necessary.
Figure 11.8. Some of the controls available to iOS developers

11.1.4. Different screen resolutions

Just like Android, iOS has support for different screen resolutions, but unlike Android, iOS has a small number of device sizes, and Apple was very smart about how it supports different screen resolutions, making things a lot simpler.

When the first iPhone came out 10 years ago, it had a 320 × 480 resolution—the screen was 320 pixels wide by 480 pixels high. Images were drawn with the correct pixel size, so to display an image that took up exactly half the screen, you’d draw an image that was 180 × 240 pixels. When controls were laid out on layout files, this was done using pixel positions and sizes, such as putting a button 100 pixels from the left and 40 from the top, and sizing it to 120 pixels wide by 40 pixels high. This was set inside the layout file or set programmatically in the view controller code.

After the iPhone, Apple released the iPad with a resolution of 768 × 1024 pixels (figure 11.9). This meant that apps needed to be rewritten to handle the new screen size (although you could just ignore iPads, and your app would be scaled up to not quite fit on the screen). To make it easier to write apps, developers could create different layout files for iPhones and iPads, or just handle the different layouts programmatically in their view controllers.

Figure 11.9. iPhones and iPads have different screen resolutions.

The screen size for the iPhone stayed the same until the iPhone 4 came along with its retina resolution. Retina resolution was simply double the resolution in each direction, so 640 × 960 pixels (figure 11.10). As an app developer, you wouldn’t want your app to be suddenly half the size—you’d want it to look the same on retina devices as it did on non-retina devices, just taking advantage of the higher resolution to make fonts look smoother and images sharper. Apple made everything just work by changing the layout positioning to use points—a virtual measurement system (similar to Android’s display-independent pixels) that treated each iPhone screen as being 320 points by 480 points (and iPads as 768 points by 1024 points). These points mapped one point to one pixel on the original iPhones, and one point to two pixels on the retina iPhones. This meant everything just worked—your apps would look the same on all devices, retina and non-retina. A button that was 100 pixels wide on the first iPhone would be 100 points wide in the retina iPhone, so 200 pixels, making it the same physical size on screen, just rendered at a higher resolution.

Figure 11.10. Retina devices have double the pixel resolution of non-retina devices.

Over time, the array of iPhones and iPads has grown to include retina iPads with double the resolution of the original iPad and larger phones, such as the iPhone 5 with its taller display and the phablet iPhone 7 Plus with its much larger screen. For these larger screens, iOS still uses the idea of points rather than pixels. Table 11.1 shows the physical and logical resolutions of the currently available IOS devices.

Table 11.1. Resolutions of current iOS devices

Device

Screen resolution

Points

iPhone Se 640 × 1136 320 × 568
iPhone6s 750 × 1334 375 × 667
iPhone 6s Plus 1080 × 1920 414 × 736
iPhone 7 750 × 1334 375 × 667
iPhone 7 Plus 1080 × 1920 414 × 736
iPad Mini 1356 × 2048 1024 × 768
iPad Air 1356 × 2048 1024 × 768
iPad Pro (9.7″) 1356 × 2048 1024 × 768
iPad Pro (12.9″) 2048 × 2732 1024 × 1366

With this increase in the number of devices, Apple chose not to define different layouts for all the devices, as this would be complicated and lead to developers not always supporting all devices (especially when new devices are released). Handling all this in code would also be too difficult. Instead, with iOS 6, Apple introduced a new way of laying out controls: auto layout using constraints.

11.1.5. Auto layout with constraints

If you’ve ever been to a classical concert, ballet, or opera, you may have noticed that the orchestra always seems to be arranged the same way. There’s a stage at the front, the conductor stands in the middle with their back to the audience, the first violins are on the conductor’s left, and the rest are laid out in a semicircle from left to right—second violins, then violas, then cellos. Woodwinds are behind the violas, percussion at the back (figure 11.11). This layout is consistent, regardless of the size or shape of the concert hall or theater. Essentially, there are rules that determine how the orchestra should be laid out so that it looks the same in all concert halls.

Figure 11.11. Orchestras are laid out relative to the conductor.

With auto layout, instead of sizing and positioning controls exactly using points, you create layout rules (called constraints) that size or position controls relative to other controls or the parent view. At runtime, the specific positions are calculated by considering all these rules, and the controls are laid out accordingly. This way, your UI looks the same, regardless of the size or shape of your device, just like an orchestra looks the same regardless of the size or shape of the concert hall they’re playing in.

Imagine you have an app that needs a button in the center of the screen. Laying this out using point-based positioning in a layout file would be impossible—your button might be centered on an iPhone 6 but be off-center on an iPhone SE or 6 Plus or iPad (figure 11.12). You could do it in code, but you’d need to write the code once for each device size and orientation (remember, some apps work with the device in landscape as well as portrait orientation), and if a new device was released, your code might not work.

Figure 11.12. Positioning using explicit values leads to bad layouts when changing screen sizes or orientations.

With auto layout, you can create a constraint that centers the button horizontally and vertically in its parent view. At runtime, the OS will do all the math for you and put the button in the correct place. Regardless of what device you run it on or the screen’s orientation, the button will be in the middle. You can also define offsets using points—for example, you could put a button 50 points below the vertical center.

To lay out a UI, you have to provide enough constraints that the layout engine can work out where to put everything. Too few constraints and the layout can’t be calculated, too many and there won’t be a single layout that satisfies them all.

Anatomy of a constraint

A constraint is a relationship between the sizes or positions of two components of the view, such as controls or the parent view. Constraints can be used to define the top, bottom, leading, or trailing edge positions, the position of the horizontal or vertical centers, and the height and width (figure 11.13).

Figure 11.13. Constraints can be used to set the width, height, left, right, top, bottom, and centers.

Leading and trailing edges

Rather than use “left” and “right,” iOS uses “leading” and “trailing” edges to support both left-to-right and right-to-left languages. If the device is set up for a left-to-right language such as English or Spanish, the leading edge is the left side and the trailing edge is the right. For right-to-left languages such as Arabic or Hebrew, the leading edge is the right and the trailing edge is the left.

Constraints are described as equations showing the relationship between two values, and iOS does a whole lot of algebra to satisfy all the equations. Let’s look at a simple example of positioning a button 20 points below a label, as shown in figure 11.14. The equation for this is shown in figure 11.15.

Figure 11.14. A button positioned 20 points below a label

Figure 11.15. The constraint equation for putting a button 20 points below a label

Coordinates are based on the top left

Like most UI systems, the top-left coordinate is 0,0, with horizontal values increasing as you move right and vertical values increasing as you move down. A label positioned at 0,100 would be above a button positioned at 0,120.

This constraint tells the iOS auto layout engine that the top of the button should be set to the value of the bottom of the label multiplied by 1, plus 20. This means that if the bottom of the label was 120 points from the top of the screen, the top of the button would be (1 × 120) + 20 = 140 points from the top. This constraint sets the top of the button based on the label, but it could also be defined the other way around, setting the bottom of the label as the top of the button minus 20. Either way will work just as well.

Each constraint describes the relationship between at least one attribute on a control or superview and either an attribute on another item, a constant value, or a combination of the two:

  • ItemsThese are controls or views in your layout.
  • AttributesThese are the height, width, top, bottom, left, right, horizontal center, or vertical center of the control or view. Views have margins that default to 8 points, so if one of the items is the superview, the attributes can use these margins if you want. For example, you can constrain to the leading margin rather than the leading edge. The parent view in a view controller sits at the top of the screen with the status bar overlapping it, so if you want to position something below the status bar so that it’s not overlapped, you can use the top layout guide attribute instead of the top. This is only available on the top-level view in a view controller. You can apply a multiplier to an attribute, so if you want a button to be twice as wide as a label, you can set the multiplier to be 2.0.
  • ConstantsYou can set a constant in points, and this is used as is, or it’s added (or subtracted if you use a negative constant) to the attribute value if the constraint has two items. With a single item in the constraint, a top attribute with a constant of 20 sets the top to 20. If there are two items, having a top attribute on the first item with a constant of 20 and a second item with a bottom attribute will put the first item 20 points below the bottom of the second.

Constraints are used to set position and size, but for some controls, such as image views, buttons, and labels, you don’t need to set the size yourself. These controls have what’s known as an intrinsic size, so rather than sizing them yourself, they’re automatically sized to their contents—for example, image views will resize themselves to fit the image inside them unless you tell them otherwise. Labels are the same—they have an intrinsic size based on the length of the text inside them.

By using a combination of attributes and constants, you can define pretty much any rule you need.

For auto layout to work successfully, every control must be fully constrained; there must be enough constraints defined to position and size everything. It’s all well and good saying that the button should be 20 points below the label, but if there are no constraints to set the position of the label (so the button can be positioned below it) or to set the position of the button (so the label can be positioned above it), the auto layout will fail, causing a crash at runtime.

The easiest way to get a working layout is to start with an anchor point—somewhere on the screen you can anchor one control to—and then to lay out all the rest of your controls based on that one control. For example, in our SquareRt UI we want the square root symbol in the center of the screen, so when we create this view and set the constraints, we’ll position this first, and then lay out all other controls based on it.

You don’t have to use constraints to lay out your controls—you can use point-based sizes and positions, but if you do, your app won’t look right on all devices unless you write a lot of code to lay out your app for all possible screen sizes and orientations. We’ll be using constraints for our app, as this is the easiest and recommended way to build UIs.

You can read more about auto layout and constraints in Apple’s Auto Layout Guide at http://mng.bz/SVKv.

11.1.6. Image resources and asset catalogs

When Apple introduced the retina iPhone 4, it mapped pixels to points to allow your apps to work without any changes, with one pixel in an image taking up four pixels on a retina screen—two pixels in each direction. This was great for making your app look and work the same, but you could also take advantage of the higher resolution screen by providing higher resolution images that would be rendered with one pixel in the image taking up one pixel on the retina screen. These images have the same names, but the higher resolution version has a suffix of @2x. For example, if you had a PNG image called Face.png to display on the screen, you would create two images—one with a pixel resolution of 180 × 240 for non-retina iPhones called Face.png, and one with a resolution of 360 × 480 for retina iPhones called [email protected] (figure 11.16).

Figure 11.16. @2x images are twice the resolution, but they’re the same size on screen.

In your code, you’d refer to the image by the name Face, and the OS would automatically load the correct version depending on the screen resolution—both images would take up the same physical size on screen. If the higher resolution image wasn’t there, the OS would just use the lower one and render it at the correct point size (figure 11.17).

Figure 11.17. If no @2x image is available, images are rendered at twice the pixel size on retina devices, with one image pixel taking up 4 pixels on screen.

With the iPhone 6 Plus, Apple needed to handle an even higher screen resolution, so for this device (as well as the newer iPhone 7 Plus) it introduced @3x images—three times the resolution of non-retina images. Again, you can just use a non-retina version or a @2x image, and the OS will scale it to fit, but by using @3x you get a better-looking image. Currently, you can’t buy a non-retina iPhone, and since iOS 9 there hasn’t been a version of iOS that runs on non-retina devices, so there’s no need to provide the non-retina version of an image in any apps you write unless you want to target older phones (remember, unlike Android, iOS users update, and 95% of users are on iOS 9 or 10). Instead, you should always provide the @2x and @3x versions.

Images can live in one of two places. First, there’s a Resources folder where you can put all your images, including different size variations. Unlike Android, the different sized images have different filenames, so they can live in the same folder. This is not the ideal place to keep images, particularly if you have lots of images.

In iOS 7, Apple introduced a better place to put images, called asset catalogs, as shown in figure 11.18. Asset catalogs contain image sets—named collections that store images of all supported resolutions. You can provide your @2x and @3x images inside an image set, and even provide separate iPhone and iPad images or images for Apple watch apps. Each named image set is treated as a named image, so if you have an image set called Face, you can use it as if it were an image in the Resources folder called Face—the right size for the device will be used. Each asset catalog can contain multiple image sets, and you can have multiple asset catalogs, allowing you to organize your images however you want.

Figure 11.18. Asset catalogs make organizing your images easier. They can include images for all resolutions of iPhones and iPads or images that work on both.

Asset catalogs can also be used for your app icon, allowing you to provide the many versions needed for different devices and for searching (iOS uses a different icon size for search results). These catalogs can also support other file types, including videos, audio files, and binary data. You can even have some resources download on demand, rather than being part of the initial app download. This is useful for large media such as large game maps or videos.

11.1.7. A quick recap

Before we create our first storyboard, let’s quickly recap what we’ve covered:

  • StoryboardsA storyboard is a way to visually define the UI for one or more screens. On a storyboard you can add one or more view controllers to represent the UI for each screen, and you can drag controls onto them from the toolbox to create their views.
  • Screen resolutionsDifferent devices have different screen resolutions, and iOS makes this easier by dealing with screen sizes using points, which are virtual pixels. Points were designed with the aim that controls or images with the same point size will look the same on all devices.
  • ConstraintsControls on the UI aren’t sized and positioned based on precise pixel values. Instead, they’re normally positioned relative to other controls on the screen, and sized based on other controls or their intrinsic size (the size of their content, such as the size of the text in a label, given its font type and size). Constraints provide rules for laying out the UI by specifying the size and position of controls relative to other controls or the parent view, such as positioning a control in the center of the screen, or making two controls the same width. You can set offsets to other controls, such as the distance between the bottom of one control and the top of the one below it, using points. When the UI is drawn, iOS will position and size everything automatically depending on the screen size and orientation. As you change orientation, the screen layout will be recalculated.
  • Anchor pointsWhen working out what constraints you need to design your UI, it’s easiest to start with an anchor point—a control that’s in a fixed position in the view (such as in the center)—that you can position other controls relative to.
  • ImagesTo handle different screen sizes and resolutions, images can be provided in different resolutions so that the images look sharper on higher resolution devices. If different sizes aren’t provided, smaller images are scaled up to fit or larger images are scaled down. Images can be defined in an asset catalog, providing a named image set for images of different resolutions.

11.2. Creating the SquareRt storyboard

Let’s start by creating a storyboard for the SquareRt app. Open the SquareRt solution and head to the SquareRt.iOS app. Expand the Views folder and delete the FirstView.cs and FirstView.Storyboard files—you won’t be using them. Then add a new storyboard called SquareRtView.storyboard (figure 11.19).

Figure 11.19. Adding a new storyboard

This should open the storyboard up in the designer, but if not, double-click it to open it. The storyboard designer works by connecting to a Mac build agent, so if you’re using Visual Studio on Windows, you’ll need to have an active and connected Mac build agent.

If nothing shows up, try building the app

Sometimes nothing will show up in the storyboard designer in Visual Studio. If this happens, try rebuilding your app—this usually fixes it.

On Visual Studio for Mac, when you open the storyboard designer, the toolbox, Properties, and Document Outline pads will open up. If you can’t see these, you can open them from the View > Pads menu. On Visual Studio on Windows, the same windows may not be visible, and if not they can be opened from the View menu.

11.2.1. Adding our first view controller

When you create a storyboard, it’s empty. The first thing you need to add to it is a view controller. View controllers are full screens—they contain a single view (an iOS UI layout control that contains other views or controls) that fills the screen, and it’s that view that you add your controls to. You must have a view controller to design anything. For example, you can’t just add a button to a storyboard.

View controllers come in different types, but they all derive from the UIViewController class, which you can use for a basic, empty screen. There are also view controllers for things like tabbed screens or lists. You just need a basic view controller for the SquareRt app because it’s a simple single-screen app, so drag a view controller from the toolbox to the designer (figure 11.20).

Figure 11.20. Adding a new view controller to a storyboard

The view controller you’ve created isn’t an instance of a UIViewController; instead, it’s just the UI that will be applied to a view controller. Later in this chapter, after we’ve designed the UI, we’ll create the view controller backing class and wire it up to the view controller on the storyboard.

The view controller is shown in the designer as a large white rectangle with a slightly shorter rectangle inside it. The outer rectangle is the view controller, and the inner one is the single view that the view controller contains. If you click the view, it will be highlighted, showing that it’s filling the whole screen. The default view is a simple view with a few properties you can configure, such as the background color. This is good enough for what we need, but you can delete that view and replace with another if you need anything specific, such as a scroll view allowing you to scroll through content that’s too large for the screen, or a web view to display full-screen web content.

Selecting a view controller

To select a view controller in a storyboard, you have to click the outer rectangle at the bottom of the designer. If you click in the middle, it will select the superview.

The Properties pad for the view, and for any control you add to it, shows three tabs: Widget, Layout, and Events. The Widget tab shows the properties of the control or view, such as its name, coloring, font size, source image, or whatever is relevant to the particular control. The Layout tab defines the layout of the control inside its parent view, and this is where you can edit any constraints you define to position the control. The Events tab is used to wire up events against the control, such as button clicks. We won’t be using the Events tab; after all, we’re using MVVM, so things like click events will be bound to commands!

We want the app to have a visible title, so set the Title property on the view controller to be SquareRt.

11.2.2. Adding an image

Let’s add a first control to the UI. We want to start with an image showing the square root symbol (√) in the center of the screen, and then position the rest of the controls around it.

Adding the image file

Before you can create the control, you need to add the image file you’ll use. We’ll add this to an asset catalog, and by default iOS apps come with an asset catalog called Assets.xcasset you can use. Asset catalogs are shown as a special folder inside your iOS project, just like NuGet packages or project references (figure 11.21).

Figure 11.21. Asset catalogs are shown as a special folder in your iOS project.

If you double-click the asset catalog, it will open in an editor that allows you to create new image sets and assign the images. On the left is a list of image sets, and on the right are the contents of the selected image set.

To add the image you want, right-click the list of image sets on the left and select New Image Set (figure 11.22). This will create a new image set called Image. You can double-click it to change the name, so do this and rename it to Line. On the right you can assign images for the different resolutions (@2x, @3x, and so on) for different devices. We want to set universal images—images that will be used for both iPhone and iPad. Click the box above 2x in the Universal section, and select the [email protected] image from the imagesSquareRtiOS folder from the book’s source code. Do the same for the 3x image.

Figure 11.22. New image sets are added from the asset catalog, and images can be added to each image set for the different resolutions.

Using vector images

If you don’t want to create multiple sizes of each image, you can create vector images as PDFs and put them into the Vector box on the image set. These images will be scaled to fit at compile time, automatically generating the relevant sizes for you. It may sound weird to use PDFs, because these are usually associated with documents (you might even be reading this book as a PDF), but they’re vector-based files just like other more popular formats, such as SVG, and they can be exported using tools like Sketch and Adobe Illustrator. You can read more about this in Xamarin’s “Application Fundamentals” guide, at http://mng.bz/Krwf.

Adding the control

Now that you have your image file, it’s time to put it on the screen. Open up the storyboard and drag an image view from the toolbox to the view controller. When you add the image view, it should be highlighted in blue to show that it’s selected, and the Properties pad will show its properties.

On the Widget tab of the Properties pad, there’s a property called Image with a drop-down list of all the images that have been added to the app. From that list, choose Line, the image you just added, and you should see it rendered inside the image view, albeit sized weirdly. By default, image views are added to storyboards at a fixed size of 240 × 128, and they’re positioned wherever you drop them.

Laying out the control

All controls must be fully constrained—they must have enough constraints defined to size and position everything. You could set the size, but that’s not necessary here because image views default to the size of the image inside them if no other size rules are defined. For this image view, you just need to set the position.

To create a constraint, you need to set the image view to layout mode and use the constraint drag handles. When you dragged the image view onto the view controller, it was highlighted and circular handles were displayed on each corner and in the middle of each edge. You can use these handles to set a fixed size if you want to, but we want to set constraints. If you click the Constraint editing mode button, it will change to show the constraint handles. You saw these handles in chapter 4; figure 11.23 shows these handles.

Figure 11.23. Constraint handles for constraining the size, distance to other controls, and center alignment

We want to position the image in the center of the screen, and to do this we’ll need two constraints: one for the horizontal center and one for the vertical.

Drag the square center drag handle over the center of the superview in the view controller, as shown in figure 11.24. You’ll see two green dashed lines appear at the horizontal and vertical centers of the superview. First drag to the left-to-right centerline, and you’ll see it turn blue. Drop the handle here to set the vertical center constraint. Then do the same using the top-to-bottom line to set the horizontal center constraint.

Figure 11.24. To create the center constraints, first drag the center constraint handle and drop it on the horizontal dashed guideline. Then drag and drop it onto the vertical one.

You can look at these constraints in two places. With the image view selected, select the Layout tab in the Properties pad and you’ll see these constraints listed. You can also look at the Document Outline pad, where you’ll see them as children of the superview, not the image view (figure 11.25).

Figure 11.25. Constraints are visible in the Layout tab of the Properties pad, or in the Document Outline pad.

Constraints are children of the superview because they’re rules that are used to lay out everything; they’re not specific to one control. For example, if you created a constraint to position a button under a label, this constraint could also be thought of as positioning the label on top of the button, so it wouldn’t make sense for this constraint to be a child of the button or the label.

When these constraints are created, the designer assumes the position of the image view on-screen is where you want it to sit relative to the center of the superview. In my case, the image view was slightly above and to the left of the center when I dragged the handles, so the constant values set on the constraints put my image 11 points to the left of center and 46 points above. We want the image to be in the center, so we need to set the constants to be 0.

To do this, you need to open the constraints in the Properties pad and edit the values. You can select the constraints in two ways: select the image view and find them in the Layout tab of the Properties pad, click on the cog at the left, and choose the Select and Edit option (figure 11.26). Or find them in the Document Outline pad and double-click them. Either approach will select the constraint in the Properties pad.

Figure 11.26. Constraints can be selected from the Layout tab of the Properties pad (or by double-clicking the Document Outline pad), and their properties can be edited in the Properties pad.

Once the constraint is selected in the Properties pad, go to the Widget tab to see its settings: items, attributes, multiplier, and constants. In my case, I had the following:

  • ImageView.CenterX = (1.0 × View.CenterX) + 0*
  • ImageView.CenterY = (1.0 × View.CenterY) + 156*

Your values will probably be different, depending on where on the screen you dropped the image view. To position the image in the dead center of the image view, change the value of the constants to 0, either by typing in a new value or by using the up/down spinners next to the value.

Once the constraints are set, you’ll notice that the image view doesn’t move to its new position. Instead, you’ll see the highlight change from blue to orange when the image view is selected. This indicates that the location and size of the control in the designer isn’t where the control will be when you run the app. You’ll also see a rectangle with a dashed orange outline. This indicates where the control will be positioned and how large it will be. If you select the image view and click the Update Frames button in the designer toolbar, the image view will move and be resized to reflect the layout at runtime—sizing itself to the size of the image in the view, and positioning itself at the center of the superview (figure 11.27).

Figure 11.27. Once the constraints are set, you can update the designer to show what the UI will look like at runtime.

Now you have your image in the center. Let’s put a text-entry control below it so the user can enter a number that you can calculate the square root from.

11.2.3. Adding a text field

The control for allowing a user to enter text is UITextField, called Text Field in the toolbox. Drag one of these onto the view controller. The default font is a bit small, and users can enter any values they want into it, so we’ll increase the font size and limit it to numbers. Both these changes can be made in the Properties pad. We’ll also add some placeholder text to guide the user as to what they should do, and we’ll align the text to the right as numbers are usually aligned. By default, text fields also have a border with rounded corners that doesn’t resize when the font size is increased, so we’ll change this to no border by setting the border style. These changes are shown in figure 11.28.

Figure 11.28. Setting the properties for the text field from the Properties pad

To change the font, click the T on the right edge of the Font setting and select a size of 40 in the popup. This will set it to use the default system font, but sized at 40 points. As on Android, these font sizes are based on a virtual size so that they look the same on all devices—a font with a size of 40 will look the same on retina and non-retina devices, or on larger devices like iPads or iPhone Pluses.

iOS supports dynamic type sizes based on user settings

As well as being able to set a fixed size font, you can set a named font size. In the font settings popup under the font drop-down, you’ll see some named text styles. These map to different font sizes depending on the user’s text preferences, set for the device in the Settings app. If you use these sizes, the fonts in your app will resize to respect the user’s settings. You can read more about this in Apple’s Human Interface Guidelines, at http://mng.bz/6M4B.

To limit the user to only being able to enter numbers, set the Keyboard Type property to Decimal Pad. To set the placeholder text, update the Placeholder property value to “Enter number”. To set the alignment, click the right-align button in the Alignment section. To remove the border, click the first button for the Border Style property—the one with the picture of a dashed-line border. The text will also default to “Text,” so clear the Text property.

Next up are the constraints. As on Android, you need to position this text field relative to the image. You need to align the right edges, inset the left edge of the text field from the left edge of the image view, and align the bottom of the text field with the bottom of the image (figure 11.29). We’ll be setting the width based on the left and right constraints, but like image views, text fields have intrinsic sizing so you don’t need to set the height—it will be set automatically from the font size.

Figure 11.29. The constraints to position the text field, relative to the image

To set the bottom constraint, select the text field, click the Constraint editing mode button, and then drag the bottom T-bar handle (it will be an upside-down T). You’ll see dashed horizontal green lines showing what you can constrain the bottom edge to, including the edges of the superview and the top, middle, and bottom of the image view (figure 11.30). Drag the handle over the dashed line at the bottom of the image view, which will turn blue, and drop it. Just like when you placed the image view, the constraint that’s created will assume that the current position of the text field is where you want it, so it will set a constant that you’ll need to change to 0.

Figure 11.30. Set the constraints by dragging the T-bar handles to the dashed lines displaying the edges of the image view.

To set the left and right constraints, do the same with the T-bar handles on the left and right sides, dragging them over the green dashed lines on the left and right sides of the image view. For the right side, set the constant to 0, and on the left set the constant to 60. Once you’re done, click the Update Frames button to position the text field.

This will provide enough constraints to position the text field relative to the image view. If you select the text field and look at the Layout tab in the Property pad, you’ll see the three constraints (shown in figure 11.31). You can also look at the layout of the image view and see five constraints—the two to position the image view, and the three that position the text field relative to the image view.

Figure 11.31. The constraints for the text field

That’s two out of three controls done. Now it’s time to position the result.

11.2.4. Adding the result label

The result is just static text, so we can use a UILabel, called Label in the toolbox. Drag one of these above the image view. We’ll make the font the same size as that in the text-entry control, and we’ll right-align the text. Set the properties in the same way as you did for the text field.

For the constraints, constrain the bottom of the label to the top of the image view and add a bit of spacing by setting the constant to 15. Then constrain the left and right edges of the label to the left and right edges of the image view (figure 11.32).

Figure 11.32. The constraints for the result label

11.2.5. Seeing the layout on different devices

By default, the storyboard designer shows how your app will look on a generic device—essentially a square device. This isn’t a real-world representation, as no iPhones or iPads are square, but it’s ideal for creating an initial layout. Once your view is laid out, you can see how it looks on different devices and orientations by selecting from the View As options in the storyboard toolbar.

These options allow you to view your app as it would appear on the currently available device sizes, as shown in figure 11.33. It doesn’t list all devices—just one of each of the possible sizes. For example, at the time of writing I can select iPhone 6 Plus but not iPhone 6s Plus or iPhone 7 Plus—these devices are all the same size and resolution, so there’s no real need to have them all available in the menu. You can also use the orientation button to toggle between portrait and landscape.

Figure 11.33. The SquareRt view on an iPhone 4s, iPhone 6, and iPad

If you play with these options, you’ll see the storyboard update for each device and orientation, and everything should look good—the constraints we set will always put the image in the middle of the screen and position everything else around it, so the UI should work on all sizes and orientations. You’ll also notice as you change these options that the button on the far left of the storyboard toolbar will change. This button sets the view to show different size classes.

11.2.6. Size classes

Sometimes you’ll want to tweak what your UI shows for devices of different sizes, or when your app is running in different orientations. For example, you might want to increase a font size on larger devices, or you may want to lay things out differently for an app running in landscape. To help with this, Apple introduced the concept of size classes.

What are size classes?

Size classes are the way the iOS SDK groups different device sizes and orientations together, based on similarities between screen size or aspect ratio. You can use these different size classes to provide support for configuring certain controls or constraints differently, depending on the size or aspect ratio. When viewing your UI in the storyboard designer, you can select the generic device and choose a size class to see how it would look on a range of devices (that’s instead of selecting a specific device in the storyboard). Table 11.2 shows the size classes for the currently available devices.

Table 11.2. The size classes for the currently available iOS devices

Device

Portrait (width/height)

Landscape (width/height)

iPad (Air, Mini, Pro) Regular/regular Regular/regular
iPhone Plus (6 Plus/7 Plus) Compact/regular Regular/compact
iPhone 7/6s/SE Compact/regular Compact/compact
Configuring controls based on size classes

Some apps look different in portrait and landscape orientations, with their UIs restructured to take advantage of the available space. For example, when viewing a photo in the iOS Photos app in portrait mode, the app takes advantage of the tall aspect to put a toolbar with some editing buttons on the bottom of the screen. In landscape orientation, there’s less height, so to show as much of the photo as possible, the toolbar buttons are moved to the top navigation bar—this bar is now wider, so it has more room for buttons. You can see this in figure 11.34.

Figure 11.34. The iOS Photos app has a different UI for landscape and portrait orientations.

You can configure controls differently for different size classes. For example, you can show or hide controls based on whether the app is in portrait or landscape orientation. You can also configure constraints based on size classes, so you could set an image view to be in the center for portrait and bottom for landscape. When you configure controls, you can choose which range of size classes to use in each direction, so you can choose any, compact, or regular for both the height and width. For example, you could have a control that’s visible for any height and compact width, which would only show on non-Plus iPhones in landscape and portrait, on iPhone Plus in portrait only, and not on iPads.

In the Widget tab of the Properties pad for controls, there’s a Views section, at the bottom of which is the configuration for size classes. By default, you’ll see “w Any h Any” with a ticked checkbox marked Installed next to it. This shows that the control is available for all size classes—in Apple’s terminology installed means a control is available for a particular size class, and Any refers to both regular and compact sizes (figure 11.35).

Figure 11.35. The installed size classes for a view

To set the size classes for a control, click the cog and choose the size class that you want the control installed for. You can choose multiple options, and you’ll see Regular represented by an R and Compact by a C (for example, “w C h R” means compact width and regular height). You need to make sure that only the ones you want are ticked, so untick “w Any h Any” (this is usually unticked when you add a new size class). You can also use the – button on the left to remove size classes.

Constraints can be configured the same way. If you select a control, head to the Layout tab in the Properties pad, find a constraint, and edit it using the Select and Edit option from the cog, you’ll see that the constraint also has size classes at the bottom of the Widget tab. This allows you to configure a control so it’s positioned differently for different orientations.

For example, SquareRt might not look so good in landscape orientation—it has a text field in the middle of the screen, so the keyboard would cover the text-entry control on the iPhone. Ideally we’d want to lay the screen out differently for small iPhones in landscape, so that everything fits. It would be better if everything were moved up so that the result label was right at the top of the screen—but only for landscape on small phones (as shown in figure 11.36). On the iPhone Plus and iPads in landscape, having everything in the vertical center is fine, as it is for all devices in portrait.

Figure 11.36. UIs sometimes need to be laid out differently to look good in different orientations.

To make this change to SquareRt, you need to configure the CenterY constraint on the image view to apply to everything except iPhones in landscape, and then create a new constraint on the result label to put it at the top just for landscape iPhones. Remember, the layout engine will look at all constraints to work out where it should put everything, so constraining the top of the result label to the top of the screen will be enough for the layout engine to determine where to put everything: the result label goes at the top, the image view goes underneath, and the number entry aligns its bottom to the image view.

Small iPhones in landscape use the compact width and compact height size classes, so you’ll need a constraint just for this one size class to fix the label to the top. Add a new top constraint by selecting the result label, clicking it again so the constraint handles are visible, dragging the top T-bar handle to the top of the superview, and setting the constant to 0. Then install the Compact/Compact size class by clicking the cog next to “w Any h Any” and selecting Compact > Compact. This will add a new entry of “w C h C”. Then untick “w Any h Any.”

This adds your new constraint, but you have a conflict—for landscape iPhones the result label should be at the top, but you still have the constraint putting the image in the center. You need to configure that center constraint to only apply when the size class is not Compact/Compact. To do this, you need to uninstall the Any/Any size class and add Regular/Any and Any/Regular—this covers any device or orientation where either the width or height is not compact. Select the CenterY constraint from the Document Outline or the Layout tab of the properties for the image view and add Regular > Any and Any > Regular from the cog menu (figure 11.37).

Figure 11.37. Configuring constraints to only be installed for specific size classes

This will position everything at the center or top depending on the device, as shown in table 11.3.

Table 11.3. The location of the controls on different iOS devices and orientations

Device

Portrait

Landscape

iPad (Air, Mini, Pro) Center Center
iPhone Plus (6 Plus/7 Plus) Center Center
iPhone 7/6s/SE Center Top

If you want to learn more about what you can do with size classes, there’s a great tutorial, “Adaptive Layout Tutorial in iOS 11: Getting Started,” available at http://mng.bz/5cah.

Once you’ve set this all up, you should be able to change the selected device, orientation, or size class and see the UI update to match. Play with the different sizes and orientations and see what happens. See how the different constraints cause the UI to update in different ways, depending on the size and orientation. Now is also a good time to play with different layouts and see if you can lay out the controls differently.

11.2.7. A quick recap

We’ve covered a lot so far, so let’s quickly recap before moving on to the code behind:

  • View controllersWhen building storyboards, you can add view controllers by dragging them from the toolbox. Each view controller has a single child view referred to as the superview, and you add controls to this view.
  • ControlsTo add controls such as labels, images, or text fields, drag them from the toolbox. You can configure their properties from the Widget tab of the Properties pad.
  • ConstraintsYou can set constraints from the designer by clicking the controls until the constraint handles appear, and then dragging them to the other item in the constraint relationship, such as another control or the superview. You can then configure the constraints more accurately in the Layout tab of the Properties pad.
  • Size classesiOS groups devices into size classes based on similar size characteristics. You can tweak controls and constraints so that they’re only applicable for certain size classes.

11.3. Building the SquareRt view

When you created the view controller in the storyboard, you weren’t creating a class. Instead you were defining a UI that can be used by an instance of a view controller. Now that you have the UI defined, it’s time to create the view controller proper and wire up the storyboard.

11.3.1. What is a view controller?

A view controller is an object derived from UIViewController that manages a set of views and controls that make up part of your application’s user interface. Usually a view controller is an entire screen, but it can also be a part of the screen. For example, tab controls are view controllers, with each tab being its own view controller. Each view controller has a single root view that fills the entire space on screen, and this is often referred to as the superview. From an MVVM perspective, a view controller is a view, and it will have a corresponding view model.

A view controller is analogous to an Android activity—it represents a full-screen task that the user is doing. When you navigate to another task using another screen, your app will load a new view controller. The SquareRt app is a single-screen app, so it only needs one view controller.

In non-MVVM apps, you’d define a startup storyboard in the info.plist file, and when your app starts up, it would load this storyboard, find the view controller that’s marked as the initial view controller, create the relevant view controller class for it, and launch it. This happens behind the scenes in your app, and you configure it simply by setting the startup storyboard. For our MvvmCross apps, we don’t need to do this. Instead, when the app starts up, the MvvmCross code will find the startup view model, then find the relevant view based on its name, and launch the view controller.

11.3.2. View lifecycle

Each view controller has a lifecycle—a set of methods that are called as the view is loaded, it appears on screens, it disappears, and it unloads. These are methods in the base UIViewController class that you can override, and they’re always called on the UI thread. Figure 11.38 shows this lifecycle.

Figure 11.38. The lifecycle of a view controller from being created and loading the view to closing the view controller and unloading the view.

The most used method in this lifecycle is ViewDidLoad, which is called after the view has been loaded from a storyboard or Nib file. At this point the UI will be fully created, and any properties that are wired up to controls (you’ll see these later in this chapter) are fully set and available for use. If you need to do any configuration of the UI in code, this is the method to do it in.

Unlike Android, view controllers aren’t recreated when you rotate the screen. There’s nothing you have to do manually—the view will readjust itself automatically based on the constraints you set. If there’s anything you need to do in code when the screen rotates, you can override the ViewWillTransitionToSize method.

11.3.3. Creating the view controller

The first thing to do when creating a view controller is create the class itself, so in the Views folder create a new view controller called SquareRtView (the view controller option will be in the iOS section of the New File dialog box). This will create two code files, SquareRtView.cs and SquareRtView.designer.cs, with the designer file nested underneath the view. These files both contain a single SquareRtView class, with partial class definitions in each file. The designer file is autogenerated, so if you make any manual changes to it, they’ll be lost. A Xib file will also be created, in this case called SquareRtView.xib, which can be deleted, because you’ll be using the storyboard you’ve already created instead a Xib file.

Because we’re using MvvCross, you’ll need to make a couple of small changes to the view controller code.

The first thing you need to do is change the base class to one that comes from MvvmCross, to provide some basic MVVM functionality.

Second, you need to tell MvvmCross that your view controller will load its UI from a storyboard—using both an attribute and by changing the constructor. Traditional iOS apps start from a storyboard that will create the view controller backing class, and you navigate to another view controller in the storyboard using a segue—it’s essentially view-first navigation. We’re using view-model–first navigation, so you need to tell the MvvmCross framework how to load the view from the view model.

Listing 11.1 shows the code changes you need to make in SquareRtView.cs.

Listing 11.1. Setting up the view controller for MvvmCross
using MvvmCross.iOS.Views;
...
[MvxFromStoryboard]                                        1
public partial class SquareRtView : MvxViewController      2
{
   public SquareRtView(IntPtr handle) : base(handle)       3
   {
   }
}

  • 1 This attribute tells MvvmCross that the UI for this view comes from a storyboard.
  • 2 Derives from a base MvvmCross view controller
  • 3 Uses a constructor that takes a native handle

The MvxFromStoryboard attribute tells MvvmCross that the view controller needs to load its view from a storyboard (as opposed to loading it from a Nib or creating it manually).

The MvxViewController base class derives from UIViewController but provides MVVM functionality, such as having a property called ViewModel that provides access to the view model for the view. The ViewModel property is of type IMvxViewModel, the base interface for MvvmCross view models. There’s also a generic version of MvxViewController, which takes a type derived from IMvxViewModel as its type argument, and if you use this the ViewModel property will be of the right type. For example, if you use MvxViewController<SquareRtViewModel>, the view-model property will be of type SquareRtViewModel.

The constructor that was provided when the file was generated can be deleted—it was for view controllers that use Xib files, and it passes the name of the Xib file to the base UIViewController so that it can create the UI. You’re using a storyboard here, so this constructor is redundant. Instead you need to add a constructor that takes an IntPtr and passes this to the base class. This IntPtr parameter is a native handle to the native view controller class and is outside the scope of this book—we just need to define this constructor to allow our app to work.

Once your view controller class is defined, you need to wire it up to the view controller on the storyboard.

11.3.4. Wiring up controls to the view controller

To wire up the SquareRtView view controller to the storyboard, open up the storyboard and select the view controller in it (remembering to click the bottom rectangle). In the Widget tab of the Properties pad, there will be a Class drop-down showing all the view controller classes available in your app. Select SquareRtView (figure 11.39). This tells the storyboard that SquareRtView is the backing class to use, but this is a one-way thing—the storyboard knows about the view controller, but the rest of the app doesn’t know about this link.

Figure 11.39. The backing class and storyboard ID needs to be set on the view controller in your storyboard.

When you navigate to the SquareRtViewModel, the MvvmCross framework needs to know which view to load. It could find the view controller by name, but then it wouldn’t know which storyboard to use to load the UI. Instead, what you need to do is set an ID on the view controller in the storyboard so that when you navigate, MvvmCross knows which view controller to use from the storyboard, and therefore which backing view-model class to load. To set this ID you need to set the value of the Storyboard ID on the Widget tab of the Properties pad to SquareRtView.

Next up, you need to wire up your controls. In the Android version of this app, you bound the controls inside the .axml file, but you can’t do this with storyboards as there’s no way to define any custom properties. Instead, you need to bind in code, and to do that you need access to the controls that you’ve added. This is easy enough to do—the Widget tab on the Properties pad for controls has a Name field, and setting this will create a property inside the view controller backing class for that control with the name you enter. Set the name for the text field to be NumberEntry and for the label to be ResultLabel.

If you open up the SquareRtView.cs file, you won’t see these properties. Instead, open the SquareRtView.designer.cs file and you’ll see them there. This is the purpose of the designer file—when you give controls names, properties for them are created in this file (figure 11.40). The following listing shows the contents of the file after setting the name on the text field.

Figure 11.40. Setting the name on the controls adds a property with that name to the designer file.

Listing 11.2. Designer files contain properties for named controls on the storyboard
[Register ("SquareRtView")]                         1
partial class SquareRtView
{
   [Outlet]                                         2
   [GeneratedCode ("iOS Designer", "1.0")]          2
   UIKit.UITextField NumberEntry { get; set; }      2

   void ReleaseDesignerOutlets ()                   3
   {                                                3
      if (NumberEntry != null)                      3
      {                                             3
         NumberEntry.Dispose ();                    3
         NumberEntry = null;                        3
      }
   }
 }

  • 1 This attribute registers this class with the iOS runtime.
  • 2 The property for the control
  • 3 Cleanup code to dispose of the control after the storyboard closes

The code in this file is autogenerated, so if you make any changes, they will be lost. The Register attribute tells the compiler to register this class with the iOS runtime—that’s way outside of the scope of this book, but doing so allows the iOS runtime to interact directly with the class, which is needed so that this class can be used by the storyboard designer.

The property that’s created for the number entry control is private and is of type UITextField. This designer file contains a part of the SquareRtView class, so you can access this property in the SquareRtView.cs file. The Outlet attribute is used to define a property that’s set from a control on the storyboard—the term iOS uses for controls in the view controller that are on the storyboard is outlets, and this property tells the Xamarin runtime to use the property as the outlet for the storyboard control. These properties are set once the storyboard is loaded.

The ReleaseDesignerOutlets method is called automatically by iOS when the view controller closes, and the generated code inside it calls Dispose on the controls and marks them as null so that the garbage collector can clean them up.

The SquareRtView.designer.cs is an autogenerated file, so for the purposes of this book we can just accept that it has some magic and use its properties. The magic, though, is all based on how Xamarin apps talk to the native SDKs. Xamarin apps have two objects for each control: an instance of the underlying native iOS control, and an instance of a .NET wrapper object. The wrapper exposes the same properties and methods as the underlying native class, and these are implemented by calling the property or method on the native object. This is a very advanced topic, so if you want more information, check out Xamarin’s iOS guide: http://mng.bz/f5q4.

11.3.5. Binding the view controller

Once your view has been loaded from the storyboard, you need to bind the controls to your view model. You can do this in ViewDidLoad, the lifecycle method that’s called after your view has been loaded from a storyboard. At the time this is called, all the properties for your controls will have been set. If you open SquareRtView, you’ll see that this method was created for you when the new view controller class was added.

To bind your view model to the view in code, you start by creating a binding set—this is a collection of bindings used by MvvmCross. In the set you add bindings for the relevant controls and then you apply the binding set, which binds the controls. The following listing shows the code you need to add to the bottom of the ViewDidLoad method.

Listing 11.3. Creating the binding set for SquareRtView
using MvvmCross.Binding.BindingContext;
using SquareRt.Core.ViewModels;
using SquareRt.Core.ValueConverters;
...
public override void ViewDidLoad()
{
   base.ViewDidLoad();

   var set = this.CreateBindingSet<SquareRtView, SquareRtViewModel>();  1
   set.Bind(ResultLabel)                                                2
      .To(vm => vm.Result)                                              2
      .WithConversion<DoubleToStringValueConverter>();                  2
   set.Bind(NumberEntry)                                                3
      .To(vm => vm.Number)                                              3
      .WithConversion<DoubleToStringValueConverter>();                  3
   set.Apply();                                                         4
}

  • 1 Creates the binding set
  • 2 Binds the result label to the result property on the view model
  • 3 Binds the number entry to the number property on the view model
  • 4 Applies the binding set

This code starts by creating a binding set between the SquareRtView and the SquareRtViewModel. This is strongly typed so that you can easily bind properties on the view model directly, rather than using string names. It then binds the ResultLabel label control to the Result property on the view model, using DoubleToStringValueConverter from the core project. The same is done for the NumberEntry control, binding it to the Number property. Finally, the binding set is applied, and this will read the values from the view model, update the view, and listen for changes to both the view-model properties and the UI controls.

11.3.6. Another quick recap

Before we run the app, let’s have another recap:

  • View controllersThe UIViewController class is the base class for all view controllers. On storyboards you define the layout for a view controller, and in code you define the actual backing class.
  • MvvmCross has a view controller base classMvvmCross provides MvxViewController as a backing class for a view controller that supports binding. You can wire this up to a storyboard by setting the MvxFromStoryboard attribute and setting the storyboard ID on the view controller on the storyboard.
  • Named controls become propertiesIf you name a control on a storyboard, a corresponding property is created in the designer file for the view controller class.
  • BindingControls can be bound to properties on the view model, using converters if required.
Everything you do with a storyboard you can also do in code

You don’t have to use storyboards if you don’t want to—you can just create a UIViewController and build the UI in code, adding a parent view and controls, and setting up constraints. Some developers prefer this as they find it easier to set up constraints either by writing them manually, or by using helper libraries such as Fluent Layout (https://github.com/FluentLayout/Cirrious.FluentLayout). The big downside to coding your UIs is that you lose the ability to visualize your layout at design time, which can be painful if you’re iterating a UI with a designer. The upside is that storyboards can add extra views to your UI, which may slow down a very complicated UI—something you can optimize by hand when creating your views in code.

11.3.7. Running the app

Everything is now done for SquareRt—your UI has been created and bound to the view model. It’s time to try it out, so select an appropriate simulator and run the app. Try it on an iPhone as well as an iPad to see how the auto layout makes it look awesome on all devices. Also try rotating the simulators using the Rotate Left and Rotate Right options in the Hardware menu (or by using the shortcut keys -← or →-) to see that it looks great in both landscape and portrait orientations (figure 11.41).

Figure 11.41. SquareRt running on an iPhone in portrait and landscape, and on an iPad

iPads don’t have a decimal keyboard

If you try the app on an iPad simulator, you’ll notice that the keyboard shows everything, not just numbers. This is a limitation of iPads. I guess Apple thought that having only a tiny keyboard wouldn’t look as good. For our purposes, it’s not a problem, but in a real-world app you’d want to do something to work around this, such as by building a custom keyboard-like control for number entry.

Scaling the simulator

If the simulator is too large to fit on screen, you can change the scale using the Scale option in the Window menu.

When you run the app, the following things will happen:

  1. iOS will start your app and start the MvvmCross framework, which finds the view model registered as the app start in the App class in the SquareRt.Core project—in our case, SquareRtViewModel.
  2. The MvvmCross framework will find the storyboard and view controller with the ID of SquareRtView, finding it in the SquareRtView.storyboard file.
  3. The storyboard will be launched, which in turn will create an instance of the SquareRtView view controller.
  4. The view controller will start its lifecycle and load the UI from the storyboard, laying out all the controls using the constraints specified for the current device and orientation.
  5. The ViewDidLoad method will be called, and the view controller will bind the controls to the view model.
  6. The binding will use DoubleToStringValueConverter in the SquareRt.Core project, creating an instance of it and using it as a value converter on the binding.
  7. If the screen is rotated, the layout engine will re-evaluate all the constraints and lay out the screen again.

Summary

In this chapter you learned

  • Apple defined how best to design iOS apps in its Human Interface Guidelines.
  • iOS defines user interfaces using storyboards.
  • UI controls are positioned using constraints that define relationships between the positions of controls, and these are resolved at runtime to absolute positions based on the screen size and orientation.
  • Different size classes can be used to configure screens differently for different device sizes and orientations.
  • Views are derived from view controllers, which are loaded from storyboards.
  • MvvmCross can bind controls to view models in code after the UI has been loaded.

You also learned how to

  • Create images that support multiple screen sizes using asset catalogs.
  • Lay out controls by defining constraints in the storyboard designer.
  • Configure constraints based on size classes.
  • Bind controls in view controllers.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.169.50