[SC]()

iOS. Apple. Indies. Plus Things.

A Best-in-Class iOS App

// Written by Jordan Morgan // Feb 25th, 2019 // Updated Nov 23rd, 2022 // Read it in about 17 minutes // RE: The Indie Dev Diaries

This post is brought to you by Emerge Tools, the best way to build on mobile.

let concatenatedThoughts = """

A Best-in-Class App: The Book Series is here! Five, complete books packaged together in .epub and .pdf plus a sample project and Discord access - go grab it here!

"""

  • Get the public Github Issue version of this list here.
  • This post has been updated to reflect iOS 16.

For as long as I’ve been a part of this industry, I’ve watched incredible, well-deserving apps take home an Apple Design Award. And that’s my endgame.

So, years ago, I set out to create a list that succinctly answers only one question:

What things can I quantify that help make an app great?

I believe I’ve created such a list that helps answer that question. Yours might look different, but this one is mine. It attempts to take all of the emotion and (mostly) opinions out of it. I want to capture what Apple says is great, not what other people may define it as. Though those opinions can assuredly be of value, they don’t give out A.D.A.s - only Apple does.

Here’s a quick brief on my methodology behind how I created it:

  • First, and most importantly, I read iOS’ Human Interface Guidelines annually from top to bottom after the GM releases of the upcoming version of iOS. This list applies to primarily the latest version of iOS and iPadOS.
  • Where there’s smoke, there’s fire. If Apple has said on record that “This is great” about an app, I include whatever that thing or interaction was. These things aren’t clandestine trade secrets, but rather they are things that Apple or an Apple design evangelist publicly gave their seal of approval to.
  • For many technologies, the tips apply to both UIKit and SwiftUI. For example, if a string is a piece of code and VoiceOver should read out its punctuation, you would use speechAlwaysIncludesPunctuation(_:) for SwiftUI and the accessibilitySpeechPunctuation key for UIKit. Many times, one or the other is listed.
  • Lastly, remember this is largely a “yes/no” list. As such, it’s only part of the equation of what makes a truly great app. Innovation, technology and user experience all have to come together. It’s about enriching people’s day to day interactions with our apps, and knowing who uses them extremely well.

That’s it.

let concatenatedThoughts = """

As always, if you've got an item or two that belongs on this list, by all means create a pull request to get it added in by visiting the link at the bottom of the post.

"""

The five sections it covers are:

  1. Accessibility: Designing for everyone is the right thing to do, and the best apps do it and they do it exceptionally well.
  2. Platform Technology: Apple loves it when apps utilize their new APIs to great effect, you should too. It’s not about shoehorning features, it’s about looking at your product and seeing how to utilize iOS around it.
  3. User Experience: Don’t make people think. Your app should have a core function that acts as a thesis to a paper - and your UX is the body that supports it.
  4. Design: Explaining design is hard, but you know a good one when you see it. This section lists some things those apps which are thoughtfully created do.
  5. App Store Presence: This is by far the most nascent category I’ve been tracking, so its list is short. It includes best practices for the App Store.

Accessibility is First Class

  • If you have text that represents a link, don’t rely solely on its color to distinguish it from the rest of the text. Consider adding an underline to it, or something else in addition to color.
  • Offer dictation in all text input scenarios. For example, if you created a custom keyboard, also include a microphone key to enable dictation.
  • If a button or control opens another view or generally navigates the user elsewhere, consider appending an ellipsis to its title or text. For example, Mail adds an ellipsis to the Move button.
  • For people with motor disabilities or who otherwise might not rely on touch, offer Siri Shortcuts for your app’s highest impact actions so they can perform them using only their voice.
  • If you’ve got graphs or custom charts, you represent them to the Accessibility Engine through Audio Graph APIs.
  • If you’ve got color strings in your interface that are critical to the experience, you represent those to Voice Over using AXNameFromColor().
  • Text that has specific meaning, like code samples or a messaging string for example, are marked with the appropriate UIAccessibilityTextualContext in UIKit or AccessibilityTextContentType in SwiftUI.
  • Voice Over is fully supported and any custom rotor controls are implemented by including the relevant headings. Using Screen Curtain yields an experience that’s not only usable, but up to par with the regular app using only Voice Over.
  • Voice Over gestures are overridden where necessary:
    • Escape: A two-finger Z-shaped gesture that dismisses a modal dialog, or goes back one level in a navigation hierarchy.
      • func accessibilityPerformEscape() -> Bool
    • Magic Tap: A two-finger double-tap that performs the most-intended action.
      • func accessibilityPerformMagicTap() -> Bool
    • Three-Finger Scroll: A three-finger swipe that scrolls content vertically or horizontally.
      • func accessibilityScroll(_ direction: UIAccessibilityScrollDirection) -> Bool
    • Increment: A one-finger swipe up that increments a value in an element.
      • func accessibilityIncrement()
    • Decrement: A one-finger swipe down that decrements a value in an element.
      • func accessibilityDecrement()
  • Voice Control is also supported, and appropriate accessibilityUserInputLabels are set if needed.
  • Your app respects the following settings:
    • Including Bold Text.
    • High Contrast Cursors.
    • Reduce Transparency.
    • Dark Mode.
    • Reducing Motion.
  • When presenting new controllers, you set the Voice Over Cursor to an appropriate element if the top-most left element doesn’t make sense: UIAccessibilityPostNotification(.screenChangedNotification, myHeading);
  • Context considering, you use accessibilityIgnoresInvertColors for images and video.
  • If an image is purely decorative and doesn’t convey meaning, you hide it from the accessibility engine.
  • If punctuation should be spoken in your accessibility text, you use an attributed string to set accessibilitySpeechPunctuation to true.
  • You group content logically using accessibility containers for efficient navigation.
  • Adaptive and supports all devices and multitasking scenarios elegantly.
  • You take care to realize that not all text is read correctly by VoiceOver, and you give it contextual hints when you need to. For example, if some text represents source code, you leverage the accessibilitySpeechPunctuation key in UIKit or the speechAlwaysIncludesPunctuation(_:) modifier in SwiftUI.
  • Fully supports Dynamic Type.
    • If you do limit text sizes in some cases, you ensure they still adapt to Dynamic Type by leveraging minimumContentSizeCategory and maximumContentSizeCategory.
  • Readable text uses readableContentGuide.
  • Color blind support and a 7:1 color contrast ratio. For text specifically, you follow these rules:
    • Font up to 17 points: 4.5:1.
    • 18 points or more: 3:1
    • Any text at bold weights: 3:1
  • Smart Invert Color Support and the app responds well to color inversion.
  • All bar buttons have their landscapeImagePhone and largeContentSizeImage properties set.
  • Any custom views that would benefit from the Large Content Viewer provide their own via UILargeContentViewerInteraction.
  • Controls are grouped in a way that makes navigation trivial with shouldGroupAccessibilityChildren if they aren’t already.
  • If you have long text that truncates via clipped bounds within views like collection or table views, you set an accessibilityLabel on them with truncated text so VoiceOver doesn’t read out the whole text content that’s clipped in the interface which makes navigation cumbersome.
  • You use system vended items for common bar buttons, quick actions, tab and navigation bar items (i.e. such as save, done, etc.).
  • All glyphs have their accessibility images set (i.e. adjustsImageSizeForAccessibilityContentSizeCategory).
  • Includes closed captions and audio descriptions, all images and icons have alternative text set.
  • Full rotor control support is provided via UIAccessibilityCustomAction.
  • Leading and trailing margins are used for constraints to support left to right languages.
  • If you’ve got UI that doesn’t inherit from UIView or UIControl, you leverage UIAccessibilityElement to make it accessible if needed.
  • If you include your own videos, you add necessary subtitle and audio tracks.
  • The User Interface appears flawlessly when tested using Double Length Pseudo-languages.
  • Using NSShowNonLocalizedStrings yields no results.
  • If you support drag and drop, UIAccessibilityLocationDescriptors are all set. You’ll also leverage accessibilityDragSourceDescriptors and accessibilityDropPointDescriptors.
  • For modally presented views, there is a clear button available to dismiss it instead of relying solely on a swipe gesture.
  • In iMessage Sticker extensions, accessibility labels are provided.
  • Magic taps are supported for the app’s most common functionality.
  • It uses CFBundleSpokenName if the app’s name could potentially be mispronounced by the system (i..e CoolApp23 would be “CoolApp Twenty Three”).
  • You respect videos automatically playing or not based off of isVideoAutoplayEnabled settings.
  • If you have graphs or visually rich imagery, you expose them to VoiceOver using the Audio Graph APIs.
  • If you have additional content to expose to VoiceOver about interface elements, but they could interrupt navigation - then you expose it via AXCustomContent.
  • You only request permissions from iOS until you truly need them. From the HIG:

    When your request is clearly related to the current context, you help people understand your app’s intentions.

    • You also use straightforward and easy to understand prompts for permission dialogs.
  • At the end of the day, you’re considering and building for all of these accessibility technologies (some of these deal with Mac Catalyst):
    • Alternate pointer actions
    • Slow keys
    • Larger text
    • Header pointer
    • Speak screen
    • Guided access
    • Typing feedback
    • Full keyboard access
    • Assistive Touch
    • Reduce Motion
    • Voice Control
    • Reachability
    • Live Listen
    • RTT/TTY
    • Accessibility Keyboard
    • Zoom
    • Magnifier
    • VoiceOver
    • Sticky Keys
    • Switch Control
    • Speak Selection
    • Display Accommodations
    • Audio Descriptions
    • Mouse Keys
    • Hover Text
    • Large Text
    • Braille
    • Dwell
  • From the above, the main things to address broadly are Vision, Hearing, Physical and Motor and Literacy and Learning.
  • Lastly, running the entire app through Accessibility Inspector produced no warnings and turning on Screen Curtain to navigate the app works flawlessly.

iOS Technology is Tightly Integrated

  • Contextual menus are integrated for long presses, showing previews where appropriate.
    • Find a good balance on what to include. Having too many options can be overwhelming.
    • Use glyphs to reinforce each action’s meaning.
  • Edit menus are used in the right places, and don’t conflict with a context menu.
  • You opt for pull down style menus over action sheets, and use them to reduce modality throughout your app to keep the focus squarely on the content.
    • Action sheets are still used to confirm a destructive action. It provides enough friction to ensure the user really wants to perform the deletion.
    • You also don’t include a cancel menu item. Canceling is an implicit action the system provides by tapping outside of the menu bounds.
    • You use menus for disambiguation, navigation, selection or showing more options.
    • You offer a menu to present more “power user” functionality when long pressing on bar buttons if necessary, in addition to providing the standard action for tapping them.
  • It fully supports dark mode.
  • Multiple spaces and scene support for iPadOS.
    • You leverage the idea of a primary window versus an auxiliary window. One provides access to your full feature set, while the other helps users complete a focused, singular task and is usually closed afterwards.
    • To handle notification routing to the right scene, you set a target content identifier on either UNNotificationContent, UIApplicationShortcutItem or NSUserActivity.
    • You support prominent scene placement via UIWindowScene.ActivationAction.
    • You use the correct scene request option - prominent, standard and automatic.
    • In collection views, you leverage the system provided two finger pinch out to open scenes.
  • For iPadOS, you support desktop class toolbars:
    • This means you group common actions together, and provide support for customizing them.
    • You put the most important items in the trailing section so that they aren’t collpased in smaller window sizes.
    • For advanced use cases, you supply a document menu in the toolbar for editing names and performing common actions that affect the document as a whole.
  • Full multitasking support.
  • Home screen quick action support.
  • If you support PencilKit, include your own undo and redo buttons in a compact environment.
    • Additionally, ensure the system-wide double tap gesture doesn’t modify any content if it’s been customized by your app.
    • Using UIPencilInteraction, handle double tap actions and respect preferredTapAction if it’s set.
    • In compact size classes, you don’t obscure content from the docked tool picker by leveraging toolPickerFramesObscuredDidChange(_ toolPicker: PKToolPicker).
    • You deliver your drawing to the screen shot service using UIScreenshotServiceDelegate.
    • If you use your own drawing engine, then on iPad you respect the UIPencilInteraction.prefersPencilOnlyDrawing setting and ignore finger input if it’s set to true.
  • If you offer a sign in, Sign in with Apple is included.
  • Spotlight search and indexing support.
    • If present, you support search continuation within your app via the CoreSpotlightContinuation info.plist key.
  • You have custom UIPointerInteraction support if your app needs it, and your interface supports cursor support correctly.
    • You don’t force any interaction paradigm over another (i.e. touch is as viable as pointer or keyboard, and vice-versa).
    • You use the correct content effects consistently (highlight, lift and hover).
    • You support band interactions via UIBandSelectionInteraction if you don’t get them out of the box.
  • Effective energy management (i.e. supports low power mode and reacts to it).
  • Keyboard shortcuts have been added. The app could be used almost, or completely, with solely the use of a keyboard.
    • Modifier keys along with mouse or pointer clicks are supported.
    • Along the same lines, you fully support the focus system to allow for quick keyboard navigation.
    • You leverage UIMenuBuilder for global keyboard shortcuts, as others will be put under the “Uncategorized” menu.
  • It supports handoff on Mac (if applicable).
  • Meaningful extensions are included with the app, whether it’s via a share extension, action extension, file providers, sticker pack, custom keyboard, etc.
    • Those extensions are carefully thought out. For example, a widget isn’t a mini app but strives to provide glanceable, actionable information.
    • Widgets:
      • Lock Screen Widgets
        • You offer all widget accessory sizes - .accessoryInline, .accessoryCircular, and .accessoryRectangular.
        • Stick to .title, .headline, .body and .caption text styles to match the rest of the system’s fonts in these widgets.
        • Your widgets adapt to three different rendering modes (full color, accent and vibrant) by using things such as .widgetAccentable() to group view components for accent rendering or the AccessoryWidgetBackground() view for vibrant rendering.
      • Small sizes only contain a max of up to four pieces on information.
      • To surface the widget throughout the system, you utilize the INRelevantShortcutRoleInformation shortcut role.
      • You leverage the correct reloadPolicy to be a good citizen of the device’s resources.
      • If private data is shown, you consider using the .privacy redaction reason.
      • You don’t force supporting all WidgetFamily sizes, and avoid expanding smaller size designs up to larger ones. Only support the families you’ve designed for by designating the sizes using supportedFamilies.
      • You ensure users are taken to the place any button deep links to, they shouldn’t have to navigate on their own.
      • Strive to keep font at 11 points or higher for clarity.
      • You avoid rasterizing text, as it would mean VoiceOver couldn’t read your widget.
  • For table and collection view cells that make sense to focus, selectionFollowsFocus is set.
  • Callback urls are supplied and documented so other apps may integrate with it (x-callback-url)/.
  • UIMenuController support if necessary via overriding UIResponderStandardEditActions.
  • You supply custom icons to enhance a user’s personal connection to your app if they want it.
  • If you display links, consider using LPLinkView.
  • Deep Siri Intent support:
    • App Shortcuts
      • You support App Shortcuts for your intents so users can easily discover what Siri Shortcuts your app offers, e.g. “Hey Siri, what can I do here?” and automatic discovery in Siri, Spotlight and the Shortcuts app.
      • You include a Siri Tip view and use the ShortcutsLink to let users know about your App Shortcuts.
      • You list your highest impact shortcuts first, along with their most common phrase in your source code since this is the same order the system will suggest them.
      • If your app serves different content based on context, you support Focus Filters via SetFocusFilterIntent.
    • You donate shortcut actions that the user has already taken.
    • In the case of Widgets, you opt to share INInteraction so that you can participate in Smart Rotate and Widget Suggestions. If you can’t use that, then INRelevantShortcut is used to still support Widget Suggestions.
    • You suggest shortcuts for the user’s “Shortcuts from your Apps” section in the Shortcuts app by suggesting them to INVoiceShortcutCenter.
    • Integrate with Wind Down if it makes sense.
    • If you support more than a few shortcuts, include a UI to view them all.
    • Also include intent phrases to help coach users.
    • Alternate app names are included when appropriate.
    • Watchface support.
    • Localized via NSString.deferredLocalizedIntentsString.
  • If it makes sense, document sharing is supported via the file provider.
  • Drag and drop has first class support:
    • A fully fleshed out NSItemProvider exists for custom objects.
      • You use a preview thumbnail where it makes sense via previewImageHandler.
      • It also works well on iPhone, in addition to iPad.
    • Editable controls for data entry should also accept its contents via a drop.
    • This is used for reordering, should the app support it.
    • “If you can drag it, it can make a new window.”
    • If data could be copied, moved, inserted or duplicated it should also do so via drag and drop.
    • You leverage custom drag and drop via targeted previews.
    • Bar button items and regular buttons that help drive drag and drop interactions support spring loading.
  • If it makes sense, data can be shared via AirDrop.
  • Natural language processing support if necessary.
  • All tab bar images are vector .pdf images or have each corresponding size included to ensure they adapt correctly and are vended to accessibility modals properly.
  • Any displayed Live Photo will animate when force touched and utilize PHLivePhotoImageView for playback.
    • Let the user know that playback is available by using PHLivePhotoViewPlaybackStyle.hint.
    • If you need the playback gesture to be utilized on a different view, you install it by using the live photo view’s playbackGestureRecognizer property.
  • Unless there is a compelling reason not to, show the Live Text button in any modal image view presentations using ImageAnalysisInteraction.
  • For system specific features such as Live Photos or ARKit experiences, you use system badge to indicate they are available (i.e. a live photo or ARKit badge).
  • If it makes sense, it supports printing via UIPrintInteractionController.
    • You also have UIApplicationSupportsPrintCommand in your info.plist to get a system provided printing menu item.
  • There are no calls to UIGraphicsBeginImageContextWithOptions, and UIGraphicsImageRenderer is used instead.
  • Universal Links are supported, especially if your app’s content is available online.
  • If you need to secure data, you opt to user Touch or Face ID.
  • You vend useful interactions for the current context via UIActivity and UIActivityViewController.
  • If it’s relevant for your app, you supply an App Clip.
    • Prefer App Clip codes to launch your clip (though QR and NFC codes will work too).
    • Your clip performs a quick, to-the-point action that shows users why they’d want to continue on with the full app.
    • If your full app is installed then ensure you bring over user metadata, such as if they had logged into your service from an app clip, instead of requiring it again.
    • If you need to send a push notification within an App Clip’s 8 hour window, ensure NSAppClipRequestEphemeralUserNotification is set within your info.plist file.
    • Opt for imagery in the App Clip’s card, as text won’t be localizable.
  • When you use system capabilities, such as ARKit, you lean on platform defined conventions to help users get started. For example, to get an ARKit experience started you’d typically use ARCoachingOverlayView instead of rolling your own solution.
  • If you have interactive or shareable experiences, you offer SharePlay via GroupActivities.

The User Experience is Top of Mind

  • Supports native “undo” and “redo” actions, typically from shaking or from the system defined gestures.
  • When supporting multiple selections, you also provide a context menu interaction to operate on all of them at once.
    • Further, on iPadOS, you support bringing up a context menu in blank space to create new items for the current context using UIMenuEditInteraction.
  • You thoughtfully consider the size of the context menus according to their tasks by using .preferredElementSize.
    • Further, if an action from a menu is meant to be operated on several times (such as a text editing control for making text bold) and it makes sense to persist it even after it was tapped, you leverage .keepsMenuPresented for its corresponding UIMenuElement.
  • For text controls, you support the standard editing interactions while appending, or prepending, your own based on the context of the selected text.
  • When restoring state, your text views and text fields leverage interactionState to properly restore their scroll position, text and first responder status.
  • The content type of all text views and text fields is included, and the correct keyboard type is used for the current context. The keyboard’s language identifier is integrated correctly.
    • This also ensures text captured from the camera searches quickly for the right kinds of text, such as a phone number or email.
    • Further, you support Find & Replace interactions on text views by setting isFindInteractionEnabled to true.
  • You support two finger drag to edit gestures in table and collection views.
  • If you’re using a custom input view in place of keyboard, you provide the audible tap noise using UIDevice.playInputClick().
  • If you’ve got customized text inputs, you support UIScribbleInteraction.
  • If you’ve customized the back button image for navigation controller, you’ve also set a backIndicatorTransitionMaskImage.
  • If you have searching capabilities, it supports field tokens.
  • Handles the keyboard being undocked on the iPad, if views are constrained to it via an inputAccessoryView.
  • If data is quantifiable while data transfer is occurring, a progress indicator is used over an activity indicator.
  • It’s localized for all territories it’s released in.
    • Right-to-left locales work phenomenally:
    • Text properly aligns. For example, a paragraph (defined as three or more sentences) still left align, but headings and other text properly right align.
    • If you have progress indicators, rating systems or any elements that associate text with a numerical meaning, then those digits and text properly order right-to-left as well.
    • Controls that require lateral movement for input (i.e. a UISlider) also work right-to-left.
    • If necessary, balance out Arabic and Hebrew text with a higher font size if it’s next to Latin text that’s capitalized. Hebrew and Arabic text don’t have uppercase letters, so the visual and semantic meaning may not flow naturally next to Latin text.
    • Flip any glyphs that help promote navigational meaning, forward or backward directions or reading directions.
  • Text tends to not truncate and it never clips but rather it’s always readable.
  • All tappable interface elements are at least 44 by 44 points.
  • The entire app binary is under 30 megabytes. (No source here, this is based off a multitude of data points.)
  • Delete actions always are followed by a confirmation prompt.
  • If your app stores rich information files like a Keynote presentation, it uses the Quick Look API to preview it.
    • You also support scene previews using QLPreviewScene ActivationConfiguration if the previewed content is a file.
  • Robust NSUserActivity support for:
    • State restoration
    • Handoff
    • Targeting correct scenes for multiple windows
    • Drag and drop for creating new windows
    • Core spotlight
    • Donating to Siri for INIntent
    • Quick Note
  • It uses the correct audio settings and responds to audio interruption gracefully, if audio can be played at all within it.
  • Custom edit options are supported when text or an image is selected, if appropriate.
  • The user is provided ample time to form an opinion about your app before you request a rating, and you opt for the built-in SKStoreReviewController to ask for it.
  • The launch screen is branding free and closely resembles the first screen of the app.
  • When displaying video using AVplayerViewController or your own player, you display content at its original aspect ratio and don’t include extra padding around the frame which would cause the video the appear smaller in full-screen and fit-to-screen modes.
  • Before opening a link that could lead to another app in a web view or SFSafariViewController, try calling UIApplication’s openURL: with the UIApplicationOpenURLOptionUniversalLinksOnly option first.
  • Table views deselect selected rows in viewDidAppear when popping back to them.
  • If you’re loading in data, let the user know the progress of the operation either using determinate or indeterminate views.
  • Notifications..
    • Supply a value for hiddenPreviewsBodyPlaceholder and a detail view.
    • You also consider using a short, memorable sound if it makes sense for the notification using UNNotificationSound.
    • You filter notifications according to the Focus Filters using setBadgeCount on UnUserNotificationCenter.
    • You supply a detail view to expand on the context of the notification. If a user can accomplish the task within the notification without having to open the app, that’s a good thing.
    • You also let users edit their notification preferences within your own app.
    • You use localizedUserNotificationStringForKey:arguments: to defer loading localized strings until your notification is presented.
    • If you have in-app notification settings, you provide deep linking to support to that view from within the Settings app via providesAppNotificationSettings.
  • UITextInputAssistantItem items are used to support common tasks on iPad that are at home within the shortcuts bar.
  • When performing CRUD operations on a table or collection view, you opt to animate these changes using a diffable datasource instead of reloadData.
  • Testing for leaks and freed memory is part of your workflow, as consuming an unnecessary amount of memory and power hampers everyone.
  • If you have long running tasks, you keep the UI free and up to date by using background tasks.
  • When using system materials, you accompany the content contained within them with a matching vibrancy effect (i.e. you don’t mix and match different semantic effects).
  • You tend to use the system’s semantic colors instead of hard coding your own. If you do have custom colors, they adapt to both light and dark mode, increased contrast and transparency reduction scenarios.
    • You also use semantic colors in a consistent way, and don’t redefine their meaning (i.e. use Link for a label color).
  • You defer from providing custom gestures towards the edge of the screen. If it’s appropriate (such as for viewing media), you override preferredScreenEdgesDeferringSystemGestures as needed.
  • If you have user interface elements constrained by the keyboard’s frame, you handle situations where it may be undocked, floating or split.
  • If you use a tab bar, aim for 3-5 items. If you need the “More” tab, you’re likely going in the wrong direction.
  • Multiple app icons are offered in addition to providing a strong and recognizable primary one to help people feel a strong sense of connection to your app.
  • Modality is used sparingly, and clearly brings them back to where they were when dismissed.
  • Navigation is clear and foolproof. You use either flat, hierarchical or content/UX driven navigation.
  • You use a sidebar to flatten your information hierarchy if it makes sense. For example, if you app has several folders, playlists or similar collections.
  • Your design also considers text and your app’s voice or messaging as part of the design and experience, and it stays consistent.
    • You don’t mix playful error messages juxtaposed with serious ones.
    • iOS technology, or any other technical term, is made for anyone to understand (i.e. you’d use ‘Scan the tag’ instead of ‘Activate NFC reading Session’ or similar.).
  • If you offer settings options within the Settings app, you fast track access to it using openSettingsURLString along with open(_:options completionHandler:)
  • When offering local authentication, Face ID kicks off authentication immediately. This is different than Touch ID, where the user manually decides when to begin authentication and has time to consider canceling. To accommodate for this affordance, consider a different UI that let’s the user manually begin authentication via button or something similar.
  • Performance is unwavering. This means things like:
    • You carefully monitor mission critical resources that you could draw from such as CPU usage, networking or location requests.
    • You stay below 20% CPU utilization, and if you go over - it’s only temporary.
    • Your app is never unresponsive for more than 250 milliseconds, and which point any frame hitches become irritating and noticeable to end users.
      • You leverage XCTest to address scroll hitches using scrollDecelarationMetric with mxSignpostAnimationIntervalBegin.
      • For situations that can diminish smooth scrolling, such as large media that hasn’t been carefully decoded, you use APIs such as CoreGraphics or image thumbnailing.
    • Core Location is used only when needed, and you prompt for the correct usage duration.
    • You audit memory usage in the Organizer, and identify if the graph is growing in the wrong direction between releases.
    • You minimize disk writes and batch them where they are heavy.
      • You avoid anything more than 1GB within a 224 hour period.
      • If they are heavy, you use MetricKit with bookend writes to find out where they are occurring.
      • You visit the Insights panel in the Organizer to keep tabs on disk writes between releases.
      • Your app’s launch time is incredibly fast and is below two to three seconds.
        • You avoid time outs and memory limits on launches, two of the leading causes of slow ones.
        • If you have a slow launch, they are fixed quickly using the Launch Time and Terminations templates within Instruments.
        • To avoid any regressions, launch times are part of your testing suite via XCTApplicationsLaunchMetric.
  • Finally, running the Analyze function in Xcode yields no errors, warnings or suggestions.

The Design Drips with Polish

  • Correct system margins are used throughout the app, and no hard coded ones are used (i.e. layoutMarginsGuide, safeAreaLayoutGuide, etc.)
  • Haptic feedback is used throughout the system to complement user interactions, and they are not overdone.
    • The right haptics are used at the right time. For example, you use .selection for only selection changes instead of something like .rigid.
    • For example, they often accompany visual and auditory feedback.
  • You utilize SF Symbols for common iOS glyphs on apps running iOS 13 and later. You show the dedicated ones for system APIs (i.e. the glyphs exclusively for iCloud usage).
    • If you use custom ones or custom glyphs, they adapt well to all environments. All of them also have outlined paths that are closed so all of the variant types work correctly.
    • Monochrome, hierarchical, palette and multicolor styles are used correctly.
    • The same goes for variants. For example, you use filled variants in places like a tab bar, and outlined ones for navigation bars.
  • Controller transitions feel natural and fluid. Great examples are Calendar and Photos.
  • You opt for vector assets to combat the differing resolutions and avoid any blurry assets.
  • Your content is always the focus, and you constantly challenge if that’s true throughout the development cycles.
  • Particular and specific user experience guidelines are followed:
    • No segment controls are used in toolbars, and they have five or less options.
    • There are no toolbars and tab bars in the same screen.
    • Destructive actions are the top choice in action sheets.
    • Alerts, if used, ideally have to two choices and titles have no punctuation. They avoid using Yes and No as choices.
      • If you need more than two choices, you opt for an action sheet.
    • Any picker’s height is equal to about 5 list values.
    • If a progress indicator is in a bar, it should have the unfolded portion of the track clear. Otherwise, it is colored to denote the amount of work left to do.
    • Network activity indicators are only shown if the network requests lasts a few seconds or more.
    • Popovers on iPhones are avoided.
    • You discard work only when a user actively taps a “Cancel” button. For example, if someone taps outside of a popover - their work is saved.
    • Switches are exclusively used within a table (or a list style collection view) row.
    • Pages controls are always centered at the bottom of the screen.
  • You aspire to ship on all of Apple’s platforms (iOS iPhone + iPad, watchOS, tvOS and macOS).
  • Animations are tasteful and reinforce, or introduce, actions. They aren’t confusing and likely mimic real physical laws.
  • In general, you reduce modality where possible to keep the focus on the content.
  • You supply all high-resolution assets for images (@1/2/3x).
  • You use the right asset format for a given scenario:
    • PNG for icons and bitmap/raster artwork.
    • JPEG for photos.
    • PDF for glyphs and vector artwork.
  • Controller specific configurations are thoughtfully implemented:
    • Status bars are only hidden in exchange for equal value.
    • Pointer locking only occurs when it boosts the experience.
    • The home indicator is only hidden during rich media experiences.
  • Full width buttons respect UIKit margins and don’t extend edge to edge of the screen.
  • Colors are used to denote value and purpose and don’t provide mixed signals. For example, enabled and disabled controls aren’t colored the same or a red triangle conveys a problem but only if red isn’t used everywhere else in your app.
  • If you create rich media apps or apps primarily used for reading and watching, you provide a sensible value for UIWhitePointAdaptivityStyle to adjust along with True Tone.
  • You handle color management well, and ensure any P3 colors display fine on sRGB devices.
  • Your app has a voice, and you use it consistently. For example, if your app strikes a formal tone, you wouldn’t display an error message with text that’s overtly out of place or humorous.
  • You opt to lean on system fonts to better support all sizes, improve legibility and consistency. If you have a custom font, it provides value and has a direct purpose for being used over system fonts. They also work with all weights and dynamic type sizes.
  • You only give your primary buttons a prominent style in SwiftUI, such as .buttonStyle(.borderedProminent). In UIKit, that would be driven from the button configuration. Too many would be overwhelming and diminish what the primary action is.
  • Inclusive design is implemented:
    • You strive to make sure everyone can access and understand your app.
    • You employ a high degree of empathy to understand what people want or how they may react. This includes welcoming language, being approachable and other similar affordances.
    • You avoid generalizations, make your app easy to navigate, support accessibility and keep people at the heart of what you make.
  • Lastly, your app is “jank” free. You know what this means for you.

App Store Page and Logistical Assets are Thoughtfully Crafted

  • An App Store preview video is used.
  • Its keywords and category were carefully researched.
  • A link to a privacy policy is provided.
  • Each supported territory is localized.
  • The app icon follows the golden grid:
    • It likely should include your brand’s primary color as well.
    • It embraces simplicity and conveys your app’s main idea.
    • You supply all the sizes needed for spotlight, settings, etc. Otherwise, iOS will shrink your App Store icon which won’t be ideal.

Wrapping Up

I love checklists. And this is my quality checklist that’s constantly evolving. One of the main reasons I created Spend Stack was to see if I could get it in a state where it meets or exceeds almost every single bullet point.

Certainly, quality takes time. If you nailed everything on this list, it’s because you’ve been working towards them for years. If you aren’t close to meeting the items listed here, no stress (it also means you probably shipped!). Take it piece by piece and work your way down.

And, as a wise honey-loving bear once said, “I get to where I’m going by walking away from where I’ve been.” If your app might lack some polish, better now more than ever to spend some time giving it some.

Until next time ✌️.

···

Spot an issue, anything to add?

Reach Out.