Chapters

Hide chapters

Real-World iOS by Tutorials

First Edition · iOS 15 · Swift 5.5 · Xcode 13

Before You Begin

Section 0: 4 chapters
Show chapters Hide chapters

11. Customizing the Look & Feel of Your App
Written by Josh Steele

In the previous chapter, you learned about implementing accessibility in your app. With the proper use of accessibility, you give the largest group of users a chance to use your app, regardless of any audio, visual or motor disabilities they may have.

Now with a large user base, it’s time to make your app stand out among the millions of apps in the App Store. There are different ways to do this. Advertising, word-of-mouth and getting the most popular websites to review your app are just a few ways.

Before you do any of that, it’s important to make your app look unique. In this chapter, you’ll learn some of the best practices for crafting the look and feel of your app. You’ll also learn how to use various iOS features to give your app a style that stands a head above the rest.

Defining look and feel

In this section of the book, you’ve learned a lot about Apple’s Human Interface Guidelines (HIG). This chapter is no different. Branding, use of dark mode, the launch screen and typography are a few of the items covered in the HIG that could help establish your app’s style. This style is usually referred to as your app’s look and feel.

The definition of look and feel can vary, but in this chapter, you’ll focus on two areas:

  • Branding: Your app name, logo and design scheme can help your app stand out from the crowd.
  • Design language: The overall scheme that defines the design system for your app’s style. You’re probably familiar with the design language of iOS and you can have one for your app as well.

You may have noticed an overlap between those two areas. Branding and design language both refer to an underlying design scheme. Your design scheme is more evident in your app logo or launch screen. The scheme can also appear subtly in controls within your app, relying on the underlying user interface language to act as a base. Here, the scheme acts as a skin on existing controls.

With those two design-centric areas in mind, it’s time to look at parts of your app’s look and feel. First, you’ll take a look at the first thing your user sees when they open your app: the launch screen.

Launch Screen

If you have a brand, it’s essential to get it to the users’ eyes as soon as possible. Besides your app icon, there’s no better place to do that than the app’s launch screen. The HIG contains important guidance when it comes to the launch screen:

  • Use the built-in storyboard: Take advantage of your project’s LaunchScreen.storyboard and avoid using a static image, if possible. The storyboard can adapt to the various screen sizes and orientations that iOS devices support.

  • Mimic a fast launch: If your storyboard looks a lot like your app’s initial home screen, it can mimic a fast launch of the app. It’s a sleight of hand trick, or maybe a sleight of eye trick, that can keep your user from detecting any slowness in your app’s startup.

  • Support Dark Mode: The launch screen should support dark mode, which you’ll learn about later in this chapter.

  • Avoid Localization: iOS renders launch screen content differently from other storyboards in your project and doesn’t change. Therefore, iOS won’t localize any text on that storyboard.

Here’s what the current PetSave launch screen looks like:

The PetSave launch screen.
The PetSave launch screen.

It’s a static image! That helps convey the app’s branding. However, according to the HIG, it’s not ideal since you should avoid static images if possible. Replace that with something that looks like the main window without the content in the middle.

Open the project in this chapter’s starter folder, then open Launchboard.storyboard. Bring up the Xcode Library by pressing Command-Shift-L and add a View Controller. Be sure to keep the existing view controller with the image for now:

Add a new view controller for the LaunchScreen.
Add a new view controller for the LaunchScreen.

From the library again, drag over a Tab Bar and Navigation Bar, and place them at the bottom and top, respectively:

Add a tab bar and a navigation bar.
Add a tab bar and a navigation bar.

Then remove the text from the navigation bar, and set the tab bar item’s icons to match what’s in the main app: a custom item with the location SF Symbol and a custom item with the magnifyingglass SF Symbol. Leaving the text out ensures your users will have a consistent experience, regardless of their native language.

Set the initial state for the bars.
Set the initial state for the bars.

Set this view controller as this storyboard’s initial view controller in the Attributes inspector:

Set the initial view controller.
Set the initial view controller.

By designating this view controller as the one to use, you can keep both in case your designer changes their mind later.

Build and run the app in the simulator. Now instead of seeing the raywenderlich.com branded launch screen, the simulator presents something that looks similar to your home screen but without content:

The new interstitial launch screen.
The new interstitial launch screen.

This new screen gives you a better transition from launch to the actual home page look and feel, but it doesn’t work well with the onboarding module that opens on first launch since the background is different.

This could be one of those good enough design decisions you may make when designing your app. You and your designer will have to make that decision.

Speaking of onboarding, using onboarding in your app is a best practice, so you’ll revisit that next.

Using onboarding

Using iOS features such as location tracking, HealthKit access and motion of the device requires the user to grant your app permission to access that data. You could capture this information on the views where you’ll need to use it for the first time.

However, putting those prompts inside an onboarding module is a better technique. While the HIG does suggest avoiding asking for setup information upfront, it says it’s permissible on the first launch. When using onboarding, keep in mind:

  • Get to your app quickly: Your user doesn’t want to sit through screen after screen of onboarding material. They’d rather use your app as soon as possible. Get the information you need, and transition into your app once you get it.
  • Avoid licensing agreements: Avoid displaying licensing agreements and other legalese users have to accept before using your app. If absolutely necessary, it can go somewhere else in your app.
  • Run onboarding once: The onboarding module should execute on the first launch of the app. Subsequent launches should go straight to the app’s home screen. If you can, store settings in the cloud. This way, if the user reinstalls the app on a device, your app can get as much information from the cloud as possible and only reprompt the user for the other information when it needs it.

The onboarding in PetSave is in pretty good shape, as you saw earlier in the book:

The 3 screens of PetSave's onboarding module.
The 3 screens of PetSave's onboarding module.

  • Simple and to the point: The onboarding doesn’t drag the user through many screens and quickly introduces them to the higher-level features of the app.
  • Shows design language: The background image and the images in the onboarding screens show playful animals drawn in the app’s style.

From a design perspective, the onboarding includes the app’s branding, which is great. However, you saw earlier that it doesn’t quite align with the launch screen. Later in this chapter, you’ll touch on one more reason you might want to send the onboarding module back to the developer for more work - dark mode.

Crafting the finer details

Your launch screen and onboarding focus more on the branding aspect of your app. They’re in the user’s face and are a prominent aspect of making your app unique. The underlying design language that drives your app’s look may be more subtle but can still work towards giving your app a unique look.

As with the launch screen and onboarding, built-in features in iOS and Xcode can help you craft this design language with little effort.

System and custom fonts

Almost all apps have text to convey information to users. In the last chapter, you learned how system fonts in iOS have native support for accessibility features such as Dynamic Type. In addition, system fonts:

  • Are easy to read: Apple designed both San Francisco (SF) and New York (NY) system fonts to be easy to read in various sizes, weights and styles.
  • Support multiple languages: When you localize your app, it becomes available to people worldwide. System fonts in iOS have built-in support for over a hundred languages.

The system fonts contain a lot of built-in flexibility. Here are some guidelines about when to take advantage of that flexibility in your app:

  • Emphasize importance: Just like in the text of this book, you can emphasize the importance of a word or phrase by making it bold or by italicizing it.
  • Use the fewest fonts possible: If you switch back and forth between different fonts in your app, it can appear disjointed and give the user a less than ideal experience.
  • Check custom fonts: If you use a custom font, make sure it’s legible and adapts to accessibility features. System fonts already support these features out of the box.

You can install a custom font into your Xcode project in a few simple steps. First, add the font file to your project:

Adding a font to the PetSave project.
Adding a font to the PetSave project.

The font here is called Sheep Sans. It’s available at DaFont.com. The author was kind enough to make it free to use.

Before proceeding, add the font to both the PetSave and PetSaveTests targets:

Add the font to the appropriate targets.
Add the font to the appropriate targets.

Then, you need to specify which fonts to include in the app bundle.

Add a new entry in Info.plist called Fonts provided by application. Under that entry, set Item 0’s value to the name of the font file, sheep_sans.ttf:

Add a font to Info.plist
Add a font to Info.plist

You can then use the new font with the Font.custom method. Open AnimalRow.swift and update the modifiers for the animal name and breed. Type to use the new font:

Text(animalName)
  .multilineTextAlignment(.center)
  .font(Font.custom("sheep_sans", size: 18, relativeTo: .title3))
  .accessibilityLabel(animalName)
Text(animalBreedAndType)
  .font(Font.custom("sheep_sans", size: 15, relativeTo: .callout))
  .accessibilityLabel(animalBreedAndType)
  .accessibilityHidden(true)

The custom method on the Font class includes a relativeTo: argument that lets the font scale adaptively to that text style instead of the default, body.

Use the preview canvas to see how the new font looks:

The Sheep Sans font in the AnimalRow preview.
The Sheep Sans font in the AnimalRow preview.

This text looks very similar to the built in font, but there is a bit of a flair in the tail of the lowercase “a”. Even subtle differences from the built in fonts may be enough to give your app the unique look you want.

A unique font can, of course, help make your design language more unique. However, a font that is too unique can impair the user. If the font is hard to read, especially on a small display like the iPhone, your user may be discouraged from using your app.

How you style your font may also have an impact. The discussion about colors in the accessibility chapter comes to mind here. Also, your design language should ensure that a font, especially a custom one, remains legible when it’s bold or italicized.

Colors are a vital component of your design language, especially when the entire display inverts itself as it does in Dark Mode.

Dark mode

Starting in iOS 13, Apple offered users the option of viewing their views in Dark Mode. Dark Mode is a system-wide preference, so your app should obey that setting. You have to ensure that your app looks good in both light and dark modes.

Chances are, if you didn’t plan for Dark Mode support from the start, you probably designed in Light Mode. That’s ok! Keep these things in mind when determining how your app will look in Dark Mode:

  • Your current color scheme: How does your current color scheme look with a darker theme? Are there now dark colors on dark colors?
  • Did you lose contrast?: With a much darker theme, be sure to check contrast ratios. Don’t forget your logos or images.
  • Check your custom colors: While some colors and other user interface elements, such as Spacer(), automatically handle the switch between Light and Dark Modes, custom colors don’t. You’ll need a Color Set to handle those.

As with many other areas of iOS, there are some nice, built-in defaults in SwiftUI that make Dark Mode convenient to use:

  • Adaptable system colors: Dark Mode is dynamic, which means that when user interface elements come to the front, their background color shifts slightly to provide a visual difference between the foreground and background. Custom colors don’t support this dynamic quality, so stick with system background colors.
  • High contrast colors: System colors also provide a sufficient amount of contrast, at least 7:1, which is another reason to use them.
  • Label color support: The primary, secondary, tertiary and quaternary label colors also, you guessed it, naturally respond to changes in Light and Dark Mode. Thanks, Apple!
  • SF Symbols support: SF Symbols support both Light and Dark Mode, which is another reason to use SF Symbols when possible.

With this knowledge, it’s time to look at PetSave and see where it could use some improvements in Dark Mode. You have a few options to see views in Dark Mode.

In your SwiftUI previews, use this modifier to enable Dark Mode:

.preferredColorScheme(.dark)

That’s pretty easy! If you’re using the simulator, you’ll need to put the entire simulator in Dark Mode. You do this through the Simulator’s Features -> Toggle Appearance menu item or the Shift-Command-A keyboard shortcut.

Here’s what the preview canvas for the PetSave home page looks like:

The PetSave home screen in Dark Mode.
The PetSave home screen in Dark Mode.

At a high level, this looks good. The text has automatically flipped from black to white, courtesy of the OS. The same is true for the background of the List and the rows within the list. Even the colors that list the age and gender look ok against the darker background.

Once you tap a row to see details, however, you’ll see a problem:

The list row in Dark Mode.
The list row in Dark Mode.

The tag field that shows the pet’s coat has a contrast issue! The now dark background sits behind the dark tag background and lighter text color.

Color sets provide a solution to this problem. You can specify a color set that uses different colors based on whether the app is in light or dark mode.

To make a color set, open the Assets Catalog in the project. Right-click the existing colors folder and choose New Color Set:

Add a new color set to the project assets.
Add a new color set to the project assets.

A new color entry appears, allowing you to specify a color for Any Appearance and Dark. Rename the color to coat-background-color. In the Any Appearance swatch, supply the current color for the background, which is systemBrownColor. Update the attributes panel to reflect this:

Use the system brown color for Light Mode.
Use the system brown color for Light Mode.

For the Dark Mode swatch, you need to choose a color that stands out well against the darker background. Choose systemYellowColor from the dropdown menu:

Use the system yellow color for Dark Mode.
Use the system yellow color for Dark Mode.

This color acts as a good contrast to the brown from Light Mode.

In AnimalDetailRow.swift, update the block that displays the coat:

if let coat = animal.coat {
  AnimalDetailCard(
    title: "Coat",
    value: coat.rawValue,
    color: Color(UIColor(named: "coat-background-color") ??
      .systemBrown)
  )
}

This update makes the color an appropriate color from the coat-background-color set, along with a fallback if that color is nil. iOS determines the correct color to load based on whether the device is in Light or Dark Mode.

Refresh the preview canvas to see the result:

The coat tag is much easier to see.
The coat tag is much easier to see.

While Dark mode is easy to take advantage of, it may require a little work on your end to make sure your design language is compatible. When you implement your changes for Dark Mode, try to maintain a uniform look and feel throughout the app to the greatest extent possible to help maintain a sense of consistency through your app.

Maintaining consistency

Consistency is an essential quality of your design language. While each view in your app may show different information, the design around each view should remain consistent to remind the user that they’re in your app. They’ll associate your unique look and feel with using your app.

The iOS’s existing design language is well known and is mostly adhered to throughout Apple’s apps. Take advantage of this design language by building upon it with your customizations.

Earlier, you learned that fonts should remain consistent. It’s the same with color. Your design language may dictate, for example, that headers should always be particular shades of green whether you’re in light or dark mode.

Iconography is an important part of your design language. You can use SF Symbols to not only provide icon consistency within your app, but with other apps on your user’s device. Therefore, the guidance for iconography is to use SF Symbols whenever possible.

As usual, SF Symbols have built-in features to help you:

  • Tinting: SF Symbols look great when tinted. If you choose an app-wide tint color, any SF Symbols you use will automatically adjust.
  • Hollow vs. filled: Many SF Symbols come in two formats: hollow and filled. Depending on your background and overall design scheme, you may want some symbols hollow and others filled.
  • Built-in Dark Mode: Apple designed SF Symbols to look great in Dark Mode.

Surprise! You’ve been using SF Symbols the whole time you’ve been reading the book! The icons in the TabView are common SF Symbols and should be familiar to most iOS users:

The icons in the TabView.
The icons in the TabView.

There’s another bit of iconography that the app uses. Longtime readers of raywenderlich.com books may recognize it:

The raywenderlich.com logo is part of PetSave's branding.
The raywenderlich.com logo is part of PetSave's branding.

That’s right! The raywenderlich.com logo is the placeholder for the table row images and provides subtle branding inside the app.

Xcode and iOS features help bring your app’s design scheme elements to life. There’s one more set of tools that will help widen your app’s reach and enhance the look and feel at the same time.

Internationalization and localization

You might not associate look and feel with internationalization and localization. Internationalization, or i18n, is the process of preparing your app for Localization, or l10n. Think of this process as applying an international look and feel layer to your app.

This process can take place in several areas of your project:

  • Localized resources: Xcode supports localized resource bundles for text and images and maintains them separately from the rest of the project. This way, you can localize them separately and bring them into your project later in the development cycle.
  • SF Symbols: SF Symbols seem to have a never-ending list of capabilities, including supporting localization.
  • Foundation APIs: The APIs in Foundation provide a great deal of functionality for dealing with different dates, prices, currencies, lengths and more. Now you don’t have to write a date converter for the millionth time!
  • Layout support: SwiftUI and UIKit have built-in support for localization features such as right-to-left text.

Once you know which locales and languages you want to support, configuring your project is straightforward. For this example, you’ll apply a Spanish localization to PetSave.

Making a localizable Strings file

Before localizing the project, add Localizable.strings to the project. Right-click the project, select New File and choose Strings File:

Adding a Strings file to the project.
Adding a Strings file to the project.

Call it Localizable.strings, this file stores key-value pairs representing the text in your app and the translation for the given language.

iOS will automatically look for localizable strings files like these when run in associated languages. To tell iOS that the app supports other languages, you need to add a locale to Xcode.

Adding a locale to Xcode

Now with the localizable Strings file in place, it’s time to localize your project!

Open the project file. Under Localizations, click the + button and add the Spanish locale:

Localizing PetSave in Xcode.
Localizing PetSave in Xcode.

A dialog will pop up asking what you want to localize. Only the LaunchScreen.storyboard is available, so click Finish:

Localizing PetSave in Xcode.
Localizing PetSave in Xcode.

You need to make sure the Localizable.strings you made is localizable. Select that file, and in the File Inspector in Xcode, click the Localize button:

Localizing PetSave in Xcode.
Localizing PetSave in Xcode.

From the dialog that appears, choose Spanish:

Localize the file for Spanish.
Localize the file for Spanish.

You want to specify entries for English as well, so enable English localization as well in the File inspector:

Add English to the localization options.
Add English to the localization options.

Adding localization entries

Add the following entries to the English file Localizable(English):

"Age" = "Age";
"Gender" = "Gender";
"Size" = "Size";
"Coat" = "Coat";
"Adult" = "Adult";
"Male" = "Male";
"Female" = "Female";
"Unknown" = "Unknown";
"Short" = "Short";
"Details" = "Details";
"Contact" = "Contact";
"Location" = "Location";
"Rank me!" = "Rank me!";
"Animals near you" = "Animals near you";
"Near you" = "Near you";
"Search" = "Search";
"Young" = "Young";

The values here match the keys because English is the project’s base language. Now, add the following translations to the Spanish file Localizable(Spanish):

"Age" = "Edad";
"Gender" = "Género";
"Size" = "Talla";
"Coat" = "Pelo";
"Adult" = "Adulta";
"Male" = "Masculina";
"Female" = "Femenino";
"Unknown" = "Ignoto";
"Short" = "Corta";
"Details" = "Detalles";
"Contact" = "Contacto";
"Location" = "Posición";
"Rank me!" = "Clasificarme!";
"Animals near you" = "Animales cerca de usted";
"Near you" = "Cerca de usted";
"Search" = "Búsqueda";
"Young" = "Menor";

Configuring your scheme’s locale

To properly test your localization, you’ll need to update your project’s scheme. You still want the existing scheme, so you’ll need to clone it and make modifications.

Open the scheme dropdown and choose Edit Scheme. In the dialog that appears, choose Duplicate Scheme:

Duplicating a scheme in Xcode.
Duplicating a scheme in Xcode.

After clicking Duplicate, rename the scheme PetSave (Debug, Spanish):

Renaming a scheme in Xcode.
Renaming a scheme in Xcode.

Under the Run entry in the side column, find the Options tab. Change the App Language to Spanish:

Changing the App Language to Spanish.
Changing the App Language to Spanish.

Testing your project

From the scheme picker, choose the PetSave (Debug, Spanish) scheme, and run the app in the simulator.

PetSave's home screen in Spanish.
PetSave's home screen in Spanish.

The animal detail view in Spanish.
The animal detail view in Spanish.

iOS doesn’t translate all of the words. Much of the data used by the app comes from the remote API, and that text is only available in English.

But, Xcode can translate other items like headers, fixed string values and the tab bar item titles ahead of time. With these translations available, you’ve added another layer of look and feel to your app. You’ve also taken another step in expanding your app’s reach to more people around the world!

Key points

  • Branding and your app’s design language are the major components of your app’s look and feel.
  • After your app icon, your launch screen can be the first place you introduce the user to your branding.
  • Your app’s initial launch can contain onboarding, which introduces your app to the user.
  • Onboarding should be brief but informative.
  • Fonts help define the text-based portion of your app’s look and feel.
  • Fonts, especially custom ones, should be accessible and easy to read.
  • Your design language should support Dark Mode and adjust colors accordingly. Color Sets are useful when adapting your app for Dark Mode.
  • Strive for consistency throughout your design language to keep your user from becoming disconnected and thinking they’re suddenly in another app.
  • Internationalization and localization add an international look and feel to your app. Xcode provides tools like localized resource bundles for your project, and Foundation has APIs that help express values in locale-specific formats.

Where to go from here?

Congratulations! You’ve finished this chapter and this section of the book. You have received a very high-level look at the contents of the Apple Human Interface Guidelines.

In this section of the book, you learned how animations, custom controls, accessibility features and look and feel customizations can all contribute to making your app unique in the vast sea of apps in the App Store.

There’s always more to learn, though. As before, check out the rest of Apple’s Human Interface Guidelines (HIG) for other topics not covered in this section. Apple has some guidance on Localization, and they also provide a deeper dive into using SF Symbols.

Using a translation API to perform dynamic translation is one way to translate text coming from a source that’s outside of your control, such as Petfinder’s API. If you’re interested in this approach, you could look into using such a translation API. There are free and paid options available.

In the next chapter, you’ll learn about maintaining users’ privacy when they use your app.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.