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.
.../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.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 2016-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
.Visual components should have interaction tests built with Earl Grey.
//TODO and explain how
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.
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.Conforming to NSCoding is necessary for Interface Builder support in views and could be used to serialize non-view classes. Tip: write a unit test for this.
initWithCoder:
.encodeWithCoder:
.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
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.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_NAME
NS_SWIFT_NOTHROW
NS_SWIFT_UNAVAILABLE
NS_REFINED_FOR_SWIFT
typedef 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.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
.