Over time we have curated a growing checklist of things we feel improve the experience of using a custom UIKit component. Many of these checks are performed by humans but we're now increasing the number of checks that can be performed by scripts.
Before a component is built, the API proposed must be agreed upon by the main contributors.
Every component has a README.md file describing what it is, what it does, when to use it, etc, in the root of the component's folder. To create a new README.md file see the template at writing_readmes.
Sometimes, the inline comments and README.md will not be sufficient to describe usage of the component. In these cases, create additional README.md files in child folders of the component. See: Collections for a good example.
Each component must have a short video captured from either iPhone or iPhone simulator of it in action.
.../docs/assets folder contains a video named component_name.mp4.Each component must also have a still image to use when video cannot play. This image should be 750 x 1334, which is the device resolution of an iPhone 8. If captured from a simulator, this image should only contain the pixels on screen, no bezel.
.../docs/assets folder contains a still named component_name.png.The included catalog application uses Core Graphics to draw landing page tiles for each component. These tiles are created by Google's Material Design department specifically for this purpose and then converted to Core Graphics code via PaintCode.
catalog/MDCCCatalog/MDCCatalogTiles.h, declare a function for the new component.catalog/MDCCCatalog/MDCCatalogTiles.m, add that function (empty.)catalog/MDCCatalog/MDCCatalogTileView.swift, add a new case for the new component and have it create newImage from the new function.The material.io site contains a list of all components, each with an icon. These icons are created by Google's Material Design department specifically for this purpose. Adding it to the site is done by the core team.
The material.io site‘s content is generated from the headers of MDC’s files using Jazzy and Jekyll. This requires some YAML.
<!--docs: title: "App Bars" layout: detail section: components excerpt: "The App Bar is a flexible navigation bar designed to provide a typical Material Design navigation experience." iconId: toolbar path: /catalog/app-bars/ api_doc_root: true -->
The root of each component should also contain a .jazzy.yaml file that's auto-generated during the publishing process.
Every source file must have the Apache 2.0 license stanza at the top of the file. The copyright should be assigned to “the Material Components for iOS authors“. You can do this manually or use a tool such as autogen to add a license.
Sample:
/* Copyright 2018-present the Material Components for iOS authors. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */
Unit tests in MDC are run by the developer, the continuous integration service, and the release engineer: the developer runs them regularly during development, the CI service when a pull request is submitted, and the release engineer as part of the release process prior to final merging. If a developer submits a PR with broken unit tests, the CI service will prevent merging thru GitHub.
components/ComponentName/tests/unit/ClassNameIssue + issue number + Tests. e.g. AppBarContainerIssue246Tests, FlexibleHeaderControllerIssue176Tests.MDC-iOS uses Google‘s continuous integration service Kokoro for automated tests on each PR. Kokoro builds MDC with Google’s open source build system, Bazel. For more information, see the Kokoro & Bazel document.
BUILD file to the root directory of the component.BUILD files for any components that are dependencies of the component. (If necessary.)Google strives to support as many different written languages as possible in components containing static text. The necessary translations must be written by Google's internal translators. To request translations, open an issue with all text and all requested languages.
Any UI code that isn’t centered - e.g. has directionality - will need RTL support.
Custom controls should support VoiceOver. See Apple's Accessibility Programming Guide for iOS for further information.
Any component that has text, interacts with text, or lays itself out in relation to text should implement Dynamic Type. See Apple's Building Apps with Dynamic Type video for further information.
[UIFont preferredTextForStyle:] or [UIFont mdc_perferredTextForMaterialStyle:], or via UIFontMetrics.mdc_adjustsFontForContentSizeCategory as a BOOL property.UIContentSizeCategoryDidChangeNotification.numberOfLines to 0.Any component that has visual elements that can be colorized should include expose public properties. Each element should have its own UIColor property.
Any component that has added custom color support should include a color themer. A color themer applies a set of colors, known as a color scheme, to a component in a systematic way. The user of the color themer passes a color scheme and component to the color themer and the component is automatically colorized in the correct way. Themers should operate on an instance of a component or its UIAppearance proxy.
applyColorScheme:toComponent.ColorThemer directory with the color themer to the src directory of the component.MaterialComponents.podspec to include the color themer.Comments are useful when used properly. In addition, they are necessary for the system of documentation generation used in MDC.
Every component has to support all features outlined for it in the Material Design guidelines. It can support additional features or customization.
Both documentation and code can contain URLs to assets or additional text. Make sure they are converted to production values.
Each component must have its own standalone examples in Swift and Objective-C. If you only include a single example, use Swift. If you are including multiple examples, it's preferable to use Swift for some and Objective-C for others so our users can get a feel for how the component works in both languages.
Examples will appear automatically in MDCCatalog if they are placed on disk in conformance to the “Catalog by Convention.” These examples should follow the format set forth in // TODO: Link to doc on examples and supplemental
They should focus on educating thru the catalog’s visual result and the code itself. Include comments when helpful.
Our users create their views both in code and in Interface Builder. It’s important to support both usages. Almost all components should be able to be added to a view hierarchy thru Interface Builder.
If a component supports Interface Builder usage, then we need to show our users how to do that.
Sometimes the operating system changes in ways that cause unpredictable or surprising behavior (even if your code is unchanged.)
Many components could be sensibly used in an extension. But sometimes code prevents a component from working correctly in extensions (or at all.)
[UIApplication sharedApplication].NS_EXTENSION_UNAVAILABLE_IOS can take care of most cases.We want to avoid misuse of initializers both in the calling of existing classes and the implementation of our new classes. Aside from being a best practice in Objc, it is mandatory in Swift. Don't forget that some classes have more than one designated initializer (e.g. UIView.)
NS_DESIGNATED_INITIALIZER macro to new designated initializers in all new classes (even private.) Remember, designated initializers must call an initializer of the super class. All others (the convenience initializers) must call an initializer within the class (self level, not super).NS_DESIGNATED_INITIALIZER if you want them to still to be designated. If those initializers should no longer be called, declare them NS_UNAVAILABLE.NS_UNAVAILABLE, nothing needs to be done.init if you know that it is, or refers to, the designated initializer.Classes that set ivar values or perform other commands from the initializer, should avoid duplicate code by writing a common*MDCClass*Init method to call from all initializers.
common + the name of the class prefixed with MDC + Init.intrinsicContentSize.)intrinsicContentSize on your view. This will ensure that, if your view is used in an auto-layout-based hierarchy, that the system knows the view’s preferred size. This is analogous to implementing sizeThatFits:.invalidateIntrinsicContentSize to tell the layout engine that something changed.updateConstraints, override requiresConstraintBasedLayout to return YES.intrinsicContentSize along with content-hugging / compression settings (see: UIView.)Advanced Auto Layout Toolbox Auto Layout Performance on iOS
Components with text elements should allow clients to choose a custom font. Each text element should have its own UIFont property. (titleFont, bodyFont, etc.)
If a component exposes any custom font properties, create a font themer that will allow cliets to easily theme the component with a type hierarchy or MDCFontScheme. Themers should operate on an instance of a component or its UIAppearance proxy.
applyFontScheme:toComponent.FontThemer directory with the themer to the src directory of the component.MaterialComponents.podspec to include the themer.All of our components should work as expected on iOS 11, and support new devices like the iPhone X.
We use the UIAppearance proxy with our visible components to allow setting default values of properties and state.
UISlider.thumbTintColor, UISlider.maximumTrackTintColor, UISwitch.onTintColor, UINavigationBar.barTintColor, etc.tintColor is not compatible with the UIAppearance proxy. Avoid using tintColor unless we are implementing it as UIView does.NSObject properties should be nullable.We use class properties to pass defaults to instance properties to mimic the functionality of UIAppearance.
null_resettable.nameOfPropertyItAffects + Default. For example, the class MDCTextInputControllerDefault inherits from NSObject. Its borderFillColor has a borderFillColorDefault class property.@interface.@implementation.Default is [class property name], the class property should say Default value for [regular property name] and Default is [whatever value is default].static BOOL _floatingEnabledDefault = YES;.Consider adding support for IBDesignable. If you have created a public subclass of UIView this may be as simple as adding IB_DESIGNABLE above your @interface declaration. Note: Do not include @IBInspectable as it interferes with UIAppearance support.
IB_DESIGNABLE @interface MDCBrandNewView : UIView
IB_DESIGNABLE.Nullability annotations improve Swift usage of a component‘s APIs. Learn more in Apple’s documentation.
Material Components explicitly annotate all public APIs rather than use NS_ASSUME_NONNULL_BEGIN. This is an intentional deviation from Apple’s practice of using the ASSUME macros. Further reading
Swift coding conventions differ from Objective-C. It‘s important to consider the experience of users implementing Material Components in Swift targets and add naming annotations, refinements, and other cross-language features accordingly. See Apple’s documentation.
NS_SWIFT_NAMENS_SWIFT_NOTHROWNS_SWIFT_UNAVAILABLENS_REFINED_FOR_SWIFTtypedef NS_ENUM for enums to allow truncation of enumeration value name prefixes.NS_STRING_ENUM for importing as a String backed enum.NS_EXTENSIBLE_STRING_ENUM for importing as an extensible struct in Swift.NS_NOESCAPE and non-escaping closures as @noescape.Material Components for iOS is built primarily for adoption with CocoaPods. There is MaterialComponents.podspec file in the root folder of the project. It contains information on component naming, dependencies, resources, etc. To learn more about .podspec files, go to CocoaPods.org
MaterialComponents.podspec contains a properly filled out entry for the component.Material Components should always use umbrella headers to access API of other components.
We have automated some of the above checks in a set of scripts. To run all the checks against every component, run:
scripts/check_components
To run the checks against particular components, list their directories on the command line:
scripts/check_components components/ActivityIndicator components/Buttons
Each check is a small script in the scripts/check directory. To run only particular checks, use the -c flag:
scripts/check_components -c scripts/check/readme -c scripts/check/video
Errors are printed out and summarized at the end of the checks:
Error: '/Users/ajsecord/Source/Git/mdc-fork/components/ActivityIndicator/examples' has no Swift examples. The following components failed: components/ActivityIndicator.
To create a new check, see scripts/check/README.md.