diff --git a/.arcconfig b/.arcconfig index 07bbc247a35..e44627df053 100644 --- a/.arcconfig +++ b/.arcconfig @@ -9,6 +9,7 @@ "phabricator.uri" : "http://codereview.cc/", "repository.callsign" : "MDC", "arc.land.onto.default" : "develop", + "arc.feature.start.default": "origin/develop", "git.default-relative-commit" : "origin/develop", "unit.xcode": { "build": { diff --git a/CHANGELOG.md b/CHANGELOG.md index a3c53c9004e..d8178ec57d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,291 @@ +# 5.0.0 + +## API diffs + +Auto-generated by running: + + scripts/api_diff -o 55afa3aaef67799bdb8a94881f31c5c3b242e9a6 -n fe1ac2f14b7ad4179c84b01590df9c93289f2e36 + +### CollectionCells + +**New component.** + +### CollectionLayoutAttributes + +**New component.** + +### Collections + +**New component.** + +### FlexibleHeader + +- [new] [`MDCFlexibleHeaderView.statusBarHintCanOverlapHeader`](https://github.com/google/material-components-ios/blob/fe1ac2f14b7ad4179c84b01590df9c93289f2e36/components/FlexibleHeader/src/MDCFlexibleHeaderView.h#L344) + +### PageControl + +- [protocols changed] [`MDCPageControl`](https://github.com/google/material-components-ios/blob/fe1ac2f14b7ad4179c84b01590df9c93289f2e36/components/PageControl/src/MDCPageControl.h#L41). +Added *UIScrollViewDelegate*. + +## Component changes + +### AppBar + +* [Correct links for deploy on various deployment environment.](https://github.com/google/material-components-ios/commit/2b6f7f40a75410e57e13c20cab2d18dd09ae2566) (Yiran Mao) +* [Remove mention of deprecated API.](https://github.com/google/material-components-ios/commit/1b35fe48b4f411c049689b398711682513b954d1) (Louis Romero) +* [Remove obsolete jazzy.yaml files.](https://github.com/google/material-components-ios/commit/fffb75e91e8ab5b979dba7a7fec661d1a058bb11) (Yiran Mao) +* [Remove the internal MDCAppBarContainerViewController contentViewController setter.](https://github.com/google/material-components-ios/commit/8100c088133fd5799eb9f692ab76f463acaab715) (Jeff Verkoeyen) +* [Revert "Remove obsolete jazzy.yaml files."](https://github.com/google/material-components-ios/commit/ad228a154455835c3e871109f12670387b9d926d) (Jeff Verkoeyen) +* [Revert replace + with _ in icon names](https://github.com/google/material-components-ios/commit/552d66f3fb2f258446f1b41951457ee494e7bc06) (Junius Gunaratne) +* [Typical Use Example moving logic from init into viewDidLoad](https://github.com/google/material-components-ios/commit/0dc8870271222083a2a4dbbf55dd249a5b5cd4f5) (randallli) +* [Update .jazzy.yaml module property.](https://github.com/google/material-components-ios/commit/977626313e702783835f7ad1aba220b99316ba3f) (Jeff Verkoeyen) +* [Updated top-level "Documentation" to "Components".](https://github.com/google/material-components-ios/commit/ac38382e86f058593de41756fb8360dc5a156a10) (Adrian Secord) +* [[AppBar]! NSLog warning to NSAssert for incorrect parentViewController behavior.](https://github.com/google/material-components-ios/commit/8f3c3f8607ff295b8594ea1edbe8dadb244eaaaa) (randallli) +* [[AppBar]? Added NSLog to ensure that addChildViewController: is called before addSubviewsToParent](https://github.com/google/material-components-ios/commit/c7a3891fe9754962cbbb14157cd5595ca5b0abec) (randallli) +* [[Catalog & Examples] Added navigationBar example in Swift (Supplemental POC) and corrected slight mistake in Catalog by Convention logic.](https://github.com/google/material-components-ios/commit/2ab08f41337d1ef1392bec46f534fe95fb2ac79e) (Will Larche) +* [[Catalog] Example view controllers must implement init.](https://github.com/google/material-components-ios/commit/ea405734e97bf96bff2d97f46b28322a0aa2b753) (Jeff Verkoeyen) +* [[Catalog] Fixing Swift example view controller initializers.](https://github.com/google/material-components-ios/commit/8e733e163a4b308186345e03d90056e4040c693b) (Jeff Verkoeyen) +* [[Catalog] Make example titles consistent, use Component Name](https://github.com/google/material-components-ios/commit/ebd1fbd14c6d2b0c052e28a9bc3cf59a6e01e75e) (Junius Gunaratne) +* [[Catalog] Update AppBar demo design, table view should not have text](https://github.com/google/material-components-ios/commit/a147d7b359d1d99fca81aa97594d93263a3cedee) (Junius Gunaratne) +* [[Icons] Replace + with _ in icon names](https://github.com/google/material-components-ios/commit/c0bd1de8e5520102f59e9b92d8a1085b285be38e) (Junius Gunaratne) + +### ButtonBar + +* [Check UIBarButtonItem global appearance configuration when creating the buttons.](https://github.com/google/material-components-ios/commit/e3a56fe9624ab978850665ab4f06479ebe5e13bf) (Jeff Verkoeyen) +* [Correct links for deploy on various deployment environment.](https://github.com/google/material-components-ios/commit/2b6f7f40a75410e57e13c20cab2d18dd09ae2566) (Yiran Mao) +* [Remove obsolete jazzy.yaml files.](https://github.com/google/material-components-ios/commit/fffb75e91e8ab5b979dba7a7fec661d1a058bb11) (Yiran Mao) +* [Revert "Remove obsolete jazzy.yaml files."](https://github.com/google/material-components-ios/commit/ad228a154455835c3e871109f12670387b9d926d) (Jeff Verkoeyen) +* [Update .jazzy.yaml module property.](https://github.com/google/material-components-ios/commit/977626313e702783835f7ad1aba220b99316ba3f) (Jeff Verkoeyen) +* [Update tests to reflect that titleTextAttributes appearance only works on iOS 9.](https://github.com/google/material-components-ios/commit/916866a5259d3972ff359f0d57bce67bdb982ed2) (Jeff Verkoeyen) +* [Updated top-level "Documentation" to "Components".](https://github.com/google/material-components-ios/commit/ac38382e86f058593de41756fb8360dc5a156a10) (Adrian Secord) +* [[Catalog & Examples] Added navigationBar example in Swift (Supplemental POC) and corrected slight mistake in Catalog by Convention logic.](https://github.com/google/material-components-ios/commit/2ab08f41337d1ef1392bec46f534fe95fb2ac79e) (Will Larche) +* [[Catalog] Example view controllers must implement init.](https://github.com/google/material-components-ios/commit/ea405734e97bf96bff2d97f46b28322a0aa2b753) (Jeff Verkoeyen) +* [[Catalog] Fixing Swift example view controller initializers.](https://github.com/google/material-components-ios/commit/8e733e163a4b308186345e03d90056e4040c693b) (Jeff Verkoeyen) +* [[Catalog] Make example titles consistent, use Component Name](https://github.com/google/material-components-ios/commit/ebd1fbd14c6d2b0c052e28a9bc3cf59a6e01e75e) (Junius Gunaratne) + +### Buttons + +* [Correct links for deploy on various deployment environment.](https://github.com/google/material-components-ios/commit/2b6f7f40a75410e57e13c20cab2d18dd09ae2566) (Yiran Mao) +* [Remove obsolete jazzy.yaml files.](https://github.com/google/material-components-ios/commit/fffb75e91e8ab5b979dba7a7fec661d1a058bb11) (Yiran Mao) +* [Resolve deprecation warnings.](https://github.com/google/material-components-ios/commit/13bb72bed147c249d9db1e8a31e3f7d9a62ecc46) (Jeff Verkoeyen) +* [Revert "Remove obsolete jazzy.yaml files."](https://github.com/google/material-components-ios/commit/ad228a154455835c3e871109f12670387b9d926d) (Jeff Verkoeyen) +* [Update .jazzy.yaml module property.](https://github.com/google/material-components-ios/commit/977626313e702783835f7ad1aba220b99316ba3f) (Jeff Verkoeyen) +* [Updated top-level "Documentation" to "Components".](https://github.com/google/material-components-ios/commit/ac38382e86f058593de41756fb8360dc5a156a10) (Adrian Secord) +* [[Catalog & Examples] Added navigationBar example in Swift (Supplemental POC) and corrected slight mistake in Catalog by Convention logic.](https://github.com/google/material-components-ios/commit/2ab08f41337d1ef1392bec46f534fe95fb2ac79e) (Will Larche) +* [[Catalog] Adjust layout for button example in landscape mode, move layout to supplemental](https://github.com/google/material-components-ios/commit/28e1904f7884de7c18ff646b722678a044497506) (Junius Gunaratne) +* [[Catalog] Fixing Swift example view controller initializers.](https://github.com/google/material-components-ios/commit/8e733e163a4b308186345e03d90056e4040c693b) (Jeff Verkoeyen) + +### CollectionCells + +* [Correct links for deploy on various deployment environment.](https://github.com/google/material-components-ios/commit/2b6f7f40a75410e57e13c20cab2d18dd09ae2566) (Yiran Mao) +* [Rename all images with @2x/@3x.](https://github.com/google/material-components-ios/commit/2df5344ee2a5a35d2e2c6d7d33ac79ecbdc1afba) (Louis Romero) +* [Update README to indicate its present state.](https://github.com/google/material-components-ios/commit/a48e31fb2e9aa814198ad3c510b9e653a0f91e72) (Jeff Verkoeyen) +* [Updates cells to depend on MDCIcons for editing and accessory icons.](https://github.com/google/material-components-ios/commit/7598638db3ecd26e09a82988857a86806bc1dfe5) (Chris Cox) +* [[Collections] Merge Collections, CollectionCells, and CollectionLayoutAttributes components.](https://github.com/google/material-components-ios/commit/f15f6d5db6bcb0ddc6a750683daa649775f1e049) (Chris Cox) +* [[Collections] Replace EditingManager with an Editing protocol.](https://github.com/google/material-components-ios/commit/60ffaa55909fd931d976ecbd55f91b5d71394ce4) (Jeff Verkoeyen) +* [[Collections] Replace StyleManager with a Styling protocol.](https://github.com/google/material-components-ios/commit/8b925d001c8de48e226de4c83556ad5c440d9301) (Jeff Verkoeyen) +* [[Collections] Updates readmes.](https://github.com/google/material-components-ios/commit/a858523cb27c6b7f891d5cfec4722172deb71b5a) (Chris Cox) + +### CollectionLayoutAttributes + +* [Added unit tests for MDCCollectionLayoutAttributes.](https://github.com/google/material-components-ios/commit/142bd2fb7e8cb846fdb449d58873fd5f75419bd1) (Adrian Secord) +* [Correct links for deploy on various deployment environment.](https://github.com/google/material-components-ios/commit/2b6f7f40a75410e57e13c20cab2d18dd09ae2566) (Yiran Mao) +* [Removes broken image.](https://github.com/google/material-components-ios/commit/ee3daf2fe52d44c8e552f40ffdd039f6937f896b) (Chris Cox) +* [[Collections] Merge Collections, CollectionCells, and CollectionLayoutAttributes components.](https://github.com/google/material-components-ios/commit/f15f6d5db6bcb0ddc6a750683daa649775f1e049) (Chris Cox) +* [[Collections] Updates readmes.](https://github.com/google/material-components-ios/commit/a858523cb27c6b7f891d5cfec4722172deb71b5a) (Chris Cox) + +### Collections + +* [Adds swift example.](https://github.com/google/material-components-ios/commit/e88dc4ff3fffd72b29b6b98d2479b4c4334165a4) (Chris Cox) +* [Cells divider is 1 pixel.](https://github.com/google/material-components-ios/commit/3ef520462a07f8b015e96b3f5497ec11ff6caf28) (Louis Romero) +* [Correct links for deploy on various deployment environment.](https://github.com/google/material-components-ios/commit/2b6f7f40a75410e57e13c20cab2d18dd09ae2566) (Yiran Mao) +* [Merge Collections, CollectionCells, and CollectionLayoutAttributes components.](https://github.com/google/material-components-ios/commit/f15f6d5db6bcb0ddc6a750683daa649775f1e049) (Chris Cox) +* [Removes broken image.](https://github.com/google/material-components-ios/commit/ee3daf2fe52d44c8e552f40ffdd039f6937f896b) (Chris Cox) +* [Replace EditingManager with an Editing protocol.](https://github.com/google/material-components-ios/commit/60ffaa55909fd931d976ecbd55f91b5d71394ce4) (Jeff Verkoeyen) +* [Replace StyleManager with a Styling protocol.](https://github.com/google/material-components-ios/commit/8b925d001c8de48e226de4c83556ad5c440d9301) (Jeff Verkoeyen) +* [Updates readmes.](https://github.com/google/material-components-ios/commit/a858523cb27c6b7f891d5cfec4722172deb71b5a) (Chris Cox) +* [Updates to readme.](https://github.com/google/material-components-ios/commit/24972cb03d7a9ad30c635fd865ffe428788bda95) (Chris Cox) +* [Updates to readme.](https://github.com/google/material-components-ios/commit/89f5075dcdc2ddc20e22756ee408db53c8ffa967) (Chris Cox) + +### FlexibleHeader + +* [Add horizontal paging example.](https://github.com/google/material-components-ios/commit/bf2c12bd7c9b46eb23858673e6c63d3dfa20edf4) (Jeff Verkoeyen) +* [Add status bar visibility switch to configurator example.](https://github.com/google/material-components-ios/commit/34b656298fd35fe1440a5e0bfcf9e10934199d91) (Jeff Verkoeyen) +* [Add statusBarCanOverlapHeader property to MDCFlexibleHeaderView.](https://github.com/google/material-components-ios/commit/1229fc7b266eb939976a652a695f3d0b201cfea3) (Jeff Verkoeyen) +* [Configurator example is now a table view.](https://github.com/google/material-components-ios/commit/b9b819536bf19eca5298acc9fd471f92333cb555) (Jeff Verkoeyen) +* [Consolidate frame projection logic.](https://github.com/google/material-components-ios/commit/6b38608a33de7227143faa80933df7c2152c509f) (Jeff Verkoeyen) +* [Convert typical use example to use Interface Builder + auto layout.](https://github.com/google/material-components-ios/commit/a0690af49c5c92b1659950863ba8df7d21ff4428) (Jeff Verkoeyen) +* [Correct links for deploy on various deployment environment.](https://github.com/google/material-components-ios/commit/2b6f7f40a75410e57e13c20cab2d18dd09ae2566) (Yiran Mao) +* [Funnel init through initWithStyle:.](https://github.com/google/material-components-ios/commit/35206a410e0fd5bc365d69fbbc0d955006558cdd) (Jeff Verkoeyen) +* [Hide header contents in the configurator when the header is shifting.](https://github.com/google/material-components-ios/commit/9310ae9de1f5e7a05cfe195f89852eeb9ea7ae33) (Jeff Verkoeyen) +* [Implement the correct designated initializer chain in the Configurator example.](https://github.com/google/material-components-ios/commit/c1777eb1f1e9e095257f20cd30a54d7ebc13fff7) (Jeff Verkoeyen) +* [Pull the instructions view out of the typical use example.](https://github.com/google/material-components-ios/commit/589c2bf3396a9844ca91f1a8e92a96fc92eaace1) (Jeff Verkoeyen) +* [Ran arc lint --everything --apply-patches.](https://github.com/google/material-components-ios/commit/a15bfbe35bdd6e61e17739ff4199b6a3940398a0) (Jeff Verkoeyen) +* [Remove obsolete jazzy.yaml files.](https://github.com/google/material-components-ios/commit/fffb75e91e8ab5b979dba7a7fec661d1a058bb11) (Yiran Mao) +* [Remove usage of iOS 9 API in Configurator example.](https://github.com/google/material-components-ios/commit/6951e147d0a140fce0c9df61a5a074765ee0c0e8) (Jeff Verkoeyen) +* [Resolve iOS 8.4 unit test failure of issue176 tests.](https://github.com/google/material-components-ios/commit/271572706e98e93edde01a30a14daf129f639306) (Jeff Verkoeyen) +* [Revert "Remove obsolete jazzy.yaml files."](https://github.com/google/material-components-ios/commit/ad228a154455835c3e871109f12670387b9d926d) (Jeff Verkoeyen) +* [Revert "Resolve iOS 8.4 unit test failure of issue176 tests."](https://github.com/google/material-components-ios/commit/fe1ac2f14b7ad4179c84b01590df9c93289f2e36) (Jeff Verkoeyen) +* [Shift status bar with header](https://github.com/google/material-components-ios/commit/c8b22b1b344d211ecf04974bcd212a45e4673165) (keefertaylor) +* [Update .jazzy.yaml module property.](https://github.com/google/material-components-ios/commit/977626313e702783835f7ad1aba220b99316ba3f) (Jeff Verkoeyen) +* [Update README to include Swift examples](https://github.com/google/material-components-ios/commit/e46a7a032171b138b2fd62c74e1d8893fd95b166) (Ian Gordon) +* [Updated top-level "Documentation" to "Components".](https://github.com/google/material-components-ios/commit/ac38382e86f058593de41756fb8360dc5a156a10) (Adrian Secord) +* [When changing min/max height, update the opposite property to match the new bounds.](https://github.com/google/material-components-ios/commit/757c0ec76413f0bd92b92ddc13692e803c6e0bff) (Jeff Verkoeyen) +* [When injecting insets, set the contentOffset rather than change it relatively.](https://github.com/google/material-components-ios/commit/fe107a900cde37fd4f4321d8cf333a20e040669f) (Jeff Verkoeyen) +* [[Catalog & Examples] Added navigationBar example in Swift (Supplemental POC) and corrected slight mistake in Catalog by Convention logic.](https://github.com/google/material-components-ios/commit/2ab08f41337d1ef1392bec46f534fe95fb2ac79e) (Will Larche) +* [[Catalog] Example view controllers must implement init.](https://github.com/google/material-components-ios/commit/ea405734e97bf96bff2d97f46b28322a0aa2b753) (Jeff Verkoeyen) +* [[Catalog] Make example titles consistent, use Component Name](https://github.com/google/material-components-ios/commit/ebd1fbd14c6d2b0c052e28a9bc3cf59a6e01e75e) (Junius Gunaratne) +* [[FlexibleHeader]! No longer remove insets from tracking scroll views during dealloc.](https://github.com/google/material-components-ios/commit/4ddbae5d6342b3552914e1c4148e4dad11a70d88) (Jeff Verkoeyen) + +### FontDiskLoader + +* [Correct links for deploy on various deployment environment.](https://github.com/google/material-components-ios/commit/2b6f7f40a75410e57e13c20cab2d18dd09ae2566) (Yiran Mao) +* [Revert "Remove obsolete jazzy.yaml files."](https://github.com/google/material-components-ios/commit/ad228a154455835c3e871109f12670387b9d926d) (Jeff Verkoeyen) +* [Update .jazzy.yaml module property.](https://github.com/google/material-components-ios/commit/977626313e702783835f7ad1aba220b99316ba3f) (Jeff Verkoeyen) +* [Updated top-level "Documentation" to "Components".](https://github.com/google/material-components-ios/commit/ac38382e86f058593de41756fb8360dc5a156a10) (Adrian Secord) +* [[Catalog and Typography] Group Typography and Font Loader examples into Typography and Fonts](https://github.com/google/material-components-ios/commit/62f57e2b290155d5bfdfafb3f27839eb8e9c1eb5) (Junius Gunaratne) + +### HeaderStackView + +* [Correct links for deploy on various deployment environment.](https://github.com/google/material-components-ios/commit/2b6f7f40a75410e57e13c20cab2d18dd09ae2566) (Yiran Mao) +* [Ran arc lint --everything --apply-patches.](https://github.com/google/material-components-ios/commit/a15bfbe35bdd6e61e17739ff4199b6a3940398a0) (Jeff Verkoeyen) +* [Remove obsolete jazzy.yaml files.](https://github.com/google/material-components-ios/commit/fffb75e91e8ab5b979dba7a7fec661d1a058bb11) (Yiran Mao) +* [Rename mdc_theme.png to header_stack_view_theme.png.](https://github.com/google/material-components-ios/commit/03e0cdb05742eca86b0c2f2ee678d99617827cbe) (Jeff Verkoeyen) +* [Revert "Remove obsolete jazzy.yaml files."](https://github.com/google/material-components-ios/commit/ad228a154455835c3e871109f12670387b9d926d) (Jeff Verkoeyen) +* [Update .jazzy.yaml module property.](https://github.com/google/material-components-ios/commit/977626313e702783835f7ad1aba220b99316ba3f) (Jeff Verkoeyen) +* [Updated top-level "Documentation" to "Components".](https://github.com/google/material-components-ios/commit/ac38382e86f058593de41756fb8360dc5a156a10) (Adrian Secord) +* [[Catalog & Examples] Added navigationBar example in Swift (Supplemental POC) and corrected slight mistake in Catalog by Convention logic.](https://github.com/google/material-components-ios/commit/2ab08f41337d1ef1392bec46f534fe95fb2ac79e) (Will Larche) +* [[Catalog] Add autoresize masks to header stack view demo for landscape orientation](https://github.com/google/material-components-ios/commit/6206c48cb0dfb3c23ed228ba31fce6892c3094b8) (Junius Gunaratne) +* [[Catalog] Fix color change issue in header stack view demo](https://github.com/google/material-components-ios/commit/ed9c65c3d22e1318aac4b06b8442e6f8e7fa3428) (Junius Gunaratne) +* [[Catalog] Update AppBar demo design, table view should not have text](https://github.com/google/material-components-ios/commit/a147d7b359d1d99fca81aa97594d93263a3cedee) (Junius Gunaratne) +* [[Catalog] Update Header Stack View demo visuals, move layout code into supplemental](https://github.com/google/material-components-ios/commit/cdc18fb8ccda075e1f8c56fd638c3a66df6f4a93) (Junius Gunaratne) +* [added missing swift code snippet to readme.](https://github.com/google/material-components-ios/commit/06df37a74d2b9e99f620c09a2674cc3853c42b19) (randallli) + +### Ink + +* [Clarified MDCInkTouchControllerDelegate inkTouchController:shouldProcessInkTouchesAtTouchLocation: documentation.](https://github.com/google/material-components-ios/commit/6241d918df44a82218349223285d6b9881d8534b) (Adrian Secord) +* [Correct links for deploy on various deployment environment.](https://github.com/google/material-components-ios/commit/2b6f7f40a75410e57e13c20cab2d18dd09ae2566) (Yiran Mao) +* [Remove obsolete jazzy.yaml files.](https://github.com/google/material-components-ios/commit/fffb75e91e8ab5b979dba7a7fec661d1a058bb11) (Yiran Mao) +* [Revert "Remove obsolete jazzy.yaml files."](https://github.com/google/material-components-ios/commit/ad228a154455835c3e871109f12670387b9d926d) (Jeff Verkoeyen) +* [Update .jazzy.yaml module property.](https://github.com/google/material-components-ios/commit/977626313e702783835f7ad1aba220b99316ba3f) (Jeff Verkoeyen) +* [Update README to include Swift examples](https://github.com/google/material-components-ios/commit/6dfac14b518bd81dc62c3398f4416a3cf1fe57e3) (Ian Gordon) +* [Updated top-level "Documentation" to "Components".](https://github.com/google/material-components-ios/commit/ac38382e86f058593de41756fb8360dc5a156a10) (Adrian Secord) +* [[Catalog & Examples] Added navigationBar example in Swift (Supplemental POC) and corrected slight mistake in Catalog by Convention logic.](https://github.com/google/material-components-ios/commit/2ab08f41337d1ef1392bec46f534fe95fb2ac79e) (Will Larche) +* [[Catalog] Change ink demo shapes to represent pseudo button/FAB, move layout code into supplemental](https://github.com/google/material-components-ios/commit/d0683a0e0bbe73fb36641c9ea92a5e715c438d19) (Junius Gunaratne) + +### NavigationBar + +* [Correct links for deploy on various deployment environment.](https://github.com/google/material-components-ios/commit/2b6f7f40a75410e57e13c20cab2d18dd09ae2566) (Yiran Mao) +* [Explain exception for UINavigationBar/MDCNavigationBar comparison.](https://github.com/google/material-components-ios/commit/5187441e7d42f8006372dbd245e0f51ce1803c53) (Jeff Verkoeyen) +* [Ran arc lint --everything --apply-patches.](https://github.com/google/material-components-ios/commit/a15bfbe35bdd6e61e17739ff4199b6a3940398a0) (Jeff Verkoeyen) +* [Remove obsolete jazzy.yaml files.](https://github.com/google/material-components-ios/commit/fffb75e91e8ab5b979dba7a7fec661d1a058bb11) (Yiran Mao) +* [Revert "Remove obsolete jazzy.yaml files."](https://github.com/google/material-components-ios/commit/ad228a154455835c3e871109f12670387b9d926d) (Jeff Verkoeyen) +* [Update .jazzy.yaml module property.](https://github.com/google/material-components-ios/commit/977626313e702783835f7ad1aba220b99316ba3f) (Jeff Verkoeyen) +* [Updated top-level "Documentation" to "Components".](https://github.com/google/material-components-ios/commit/ac38382e86f058593de41756fb8360dc5a156a10) (Adrian Secord) +* [Use UIViewNoIntrinsicMetric to indicate the the NavigationBar has no intrinsic width.](https://github.com/google/material-components-ios/commit/1e84b7cd5173d85bffb77628341e91d08a90ced6) (Jeff Verkoeyen) +* [[Catalog & Examples] Added navigationBar example in Swift (Supplemental POC) and corrected slight mistake in Catalog by Convention logic.](https://github.com/google/material-components-ios/commit/2ab08f41337d1ef1392bec46f534fe95fb2ac79e) (Will Larche) +* [[Catalog] Make example titles consistent, use Component Name](https://github.com/google/material-components-ios/commit/ebd1fbd14c6d2b0c052e28a9bc3cf59a6e01e75e) (Junius Gunaratne) +* [[Examples] Correcting scope modifier of functions in Swift](https://github.com/google/material-components-ios/commit/6d5406baac2d0bb484fff90882c39309a68bc744) (Will Larche) + +### PageControl + +* [Added MDCPageControl initWithCoder:.](https://github.com/google/material-components-ios/commit/43cd7fea3208134fbcf324d11492a9fd182506b4) (Adrian Secord) +* [Added test for updating the currentPage when the contentOffset changes](https://github.com/google/material-components-ios/commit/ac8642f4a0b9cd90946ff6fa65440a39b934ebbf) (randallli) +* [Correct links for deploy on various deployment environment.](https://github.com/google/material-components-ios/commit/2b6f7f40a75410e57e13c20cab2d18dd09ae2566) (Yiran Mao) +* [Created swift example for page control and added it to readme](https://github.com/google/material-components-ios/commit/1272a1275b219d252dffcefc7748b0e025e50829) (randallli) +* [Fix crash when scrollView offset is set out of bounds of the numberOfPages](https://github.com/google/material-components-ios/commit/e1f584adad2950a9c46ba7dba2da01461b6ea899) (randallli) +* [Publicized conformance to UIScrollViewDelegate.](https://github.com/google/material-components-ios/commit/e0d8050ef395928ba3eee40045059b9c46c9f21a) (randallli) +* [Remove obsolete jazzy.yaml files.](https://github.com/google/material-components-ios/commit/fffb75e91e8ab5b979dba7a7fec661d1a058bb11) (Yiran Mao) +* [Revert "Remove obsolete jazzy.yaml files."](https://github.com/google/material-components-ios/commit/ad228a154455835c3e871109f12670387b9d926d) (Jeff Verkoeyen) +* [Update .jazzy.yaml module property.](https://github.com/google/material-components-ios/commit/977626313e702783835f7ad1aba220b99316ba3f) (Jeff Verkoeyen) +* [Updated top-level "Documentation" to "Components".](https://github.com/google/material-components-ios/commit/ac38382e86f058593de41756fb8360dc5a156a10) (Adrian Secord) +* [[Catalog & Examples] Added navigationBar example in Swift (Supplemental POC) and corrected slight mistake in Catalog by Convention logic.](https://github.com/google/material-components-ios/commit/2ab08f41337d1ef1392bec46f534fe95fb2ac79e) (Will Larche) + +### RobotoFontLoader + +* [Correct links for deploy on various deployment environment.](https://github.com/google/material-components-ios/commit/2b6f7f40a75410e57e13c20cab2d18dd09ae2566) (Yiran Mao) +* [Remove obsolete jazzy.yaml files.](https://github.com/google/material-components-ios/commit/fffb75e91e8ab5b979dba7a7fec661d1a058bb11) (Yiran Mao) +* [Revert "Remove obsolete jazzy.yaml files."](https://github.com/google/material-components-ios/commit/ad228a154455835c3e871109f12670387b9d926d) (Jeff Verkoeyen) +* [Update .jazzy.yaml module property.](https://github.com/google/material-components-ios/commit/977626313e702783835f7ad1aba220b99316ba3f) (Jeff Verkoeyen) +* [Updated top-level "Documentation" to "Components".](https://github.com/google/material-components-ios/commit/ac38382e86f058593de41756fb8360dc5a156a10) (Adrian Secord) +* [[Catalog & Examples] Added navigationBar example in Swift (Supplemental POC) and corrected slight mistake in Catalog by Convention logic.](https://github.com/google/material-components-ios/commit/2ab08f41337d1ef1392bec46f534fe95fb2ac79e) (Will Larche) +* [[Catalog and Typography] Group Typography and Font Loader examples into Typography and Fonts](https://github.com/google/material-components-ios/commit/62f57e2b290155d5bfdfafb3f27839eb8e9c1eb5) (Junius Gunaratne) + +### ShadowElevations + +* [Correct links for deploy on various deployment environment.](https://github.com/google/material-components-ios/commit/2b6f7f40a75410e57e13c20cab2d18dd09ae2566) (Yiran Mao) +* [Remove obsolete jazzy.yaml files.](https://github.com/google/material-components-ios/commit/fffb75e91e8ab5b979dba7a7fec661d1a058bb11) (Yiran Mao) +* [Revert "Remove obsolete jazzy.yaml files."](https://github.com/google/material-components-ios/commit/ad228a154455835c3e871109f12670387b9d926d) (Jeff Verkoeyen) +* [Update .jazzy.yaml module property.](https://github.com/google/material-components-ios/commit/977626313e702783835f7ad1aba220b99316ba3f) (Jeff Verkoeyen) +* [Update documention with Objective C examples](https://github.com/google/material-components-ios/commit/49af52411077d5587ccc4bcb4019ac5ece4a55fe) (Ian Gordon) +* [Updated top-level "Documentation" to "Components".](https://github.com/google/material-components-ios/commit/ac38382e86f058593de41756fb8360dc5a156a10) (Adrian Secord) +* [[Catalog & Examples] Added navigationBar example in Swift (Supplemental POC) and corrected slight mistake in Catalog by Convention logic.](https://github.com/google/material-components-ios/commit/2ab08f41337d1ef1392bec46f534fe95fb2ac79e) (Will Larche) +* [[Catalog and Shadow] Group shadow elevations with shadow demos](https://github.com/google/material-components-ios/commit/b8dc78d58c06c45a8616e6798839b37fd065fc05) (Junius Gunaratne) + +### ShadowLayer + +* [Correct links for deploy on various deployment environment.](https://github.com/google/material-components-ios/commit/2b6f7f40a75410e57e13c20cab2d18dd09ae2566) (Yiran Mao) +* [Remove obsolete jazzy.yaml files.](https://github.com/google/material-components-ios/commit/fffb75e91e8ab5b979dba7a7fec661d1a058bb11) (Yiran Mao) +* [Revert "Remove obsolete jazzy.yaml files."](https://github.com/google/material-components-ios/commit/ad228a154455835c3e871109f12670387b9d926d) (Jeff Verkoeyen) +* [Update .jazzy.yaml module property.](https://github.com/google/material-components-ios/commit/977626313e702783835f7ad1aba220b99316ba3f) (Jeff Verkoeyen) +* [Updated top-level "Documentation" to "Components".](https://github.com/google/material-components-ios/commit/ac38382e86f058593de41756fb8360dc5a156a10) (Adrian Secord) +* [[Catalog and Shadow] Group shadow elevations with shadow demos](https://github.com/google/material-components-ios/commit/b8dc78d58c06c45a8616e6798839b37fd065fc05) (Junius Gunaratne) +* [[Catalog] Make catalogIsPrimaryDemo static method in demos](https://github.com/google/material-components-ios/commit/7bb181a150bc1cf707637fdf112d3a949638c809) (Junius Gunaratne) +* [[Shadow] Add swift examples to the documentation](https://github.com/google/material-components-ios/commit/adba94e106b4e576160f15380f97f720800ec373) (Ian Gordon) + +### Slider + +* [Added swift example to slider readme](https://github.com/google/material-components-ios/commit/835a6d358253253cbd3bea960fd8eebb09ddfaf3) (randallli) +* [Correct links for deploy on various deployment environment.](https://github.com/google/material-components-ios/commit/2b6f7f40a75410e57e13c20cab2d18dd09ae2566) (Yiran Mao) +* [Fix unit tests by increasing epsilon](https://github.com/google/material-components-ios/commit/8ab280fdc926fe389396a248f2d28dc24fa75c97) (randallli) +* [Fixes MDCSlider example build with required import.](https://github.com/google/material-components-ios/commit/bd6ef35fd82c4ee16d2a4d077e224499a3abe9ed) (Adrian Secord) +* [Import the umbrella header in the typical use example.](https://github.com/google/material-components-ios/commit/917f68f953132ba345fdf70fe79d80c3d68b23b6) (Jeff Verkoeyen) +* [Ran arc lint --everything --apply-patches.](https://github.com/google/material-components-ios/commit/a15bfbe35bdd6e61e17739ff4199b6a3940398a0) (Jeff Verkoeyen) +* [Remove obsolete jazzy.yaml files.](https://github.com/google/material-components-ios/commit/fffb75e91e8ab5b979dba7a7fec661d1a058bb11) (Yiran Mao) +* [Revert "Remove obsolete jazzy.yaml files."](https://github.com/google/material-components-ios/commit/ad228a154455835c3e871109f12670387b9d926d) (Jeff Verkoeyen) +* [Update .jazzy.yaml module property.](https://github.com/google/material-components-ios/commit/977626313e702783835f7ad1aba220b99316ba3f) (Jeff Verkoeyen) +* [Updated top-level "Documentation" to "Components".](https://github.com/google/material-components-ios/commit/ac38382e86f058593de41756fb8360dc5a156a10) (Adrian Secord) +* [[Catalog] Make catalogIsPrimaryDemo method in slider demo static to match other examples](https://github.com/google/material-components-ios/commit/a9a28c4cde35e476e886c1f90c44b96988c72f73) (Junius Gunaratne) +* [[Catalog] Use slider in variable names in example layout code](https://github.com/google/material-components-ios/commit/784e9a57be01dfa8f93528aa4a92de0b772b4605) (Junius Gunaratne) + +### SpritedAnimationView + +* [Remove obsolete jazzy.yaml files.](https://github.com/google/material-components-ios/commit/fffb75e91e8ab5b979dba7a7fec661d1a058bb11) (Yiran Mao) +* [Rename all images with @2x/@3x.](https://github.com/google/material-components-ios/commit/2df5344ee2a5a35d2e2c6d7d33ac79ecbdc1afba) (Louis Romero) +* [Revert "Remove obsolete jazzy.yaml files."](https://github.com/google/material-components-ios/commit/ad228a154455835c3e871109f12670387b9d926d) (Jeff Verkoeyen) +* [Update .jazzy.yaml module property.](https://github.com/google/material-components-ios/commit/977626313e702783835f7ad1aba220b99316ba3f) (Jeff Verkoeyen) +* [Updated top-level "Documentation" to "Components".](https://github.com/google/material-components-ios/commit/ac38382e86f058593de41756fb8360dc5a156a10) (Adrian Secord) +* [[Catalog & Examples] Added navigationBar example in Swift (Supplemental POC) and corrected slight mistake in Catalog by Convention logic.](https://github.com/google/material-components-ios/commit/2ab08f41337d1ef1392bec46f534fe95fb2ac79e) (Will Larche) + +### Switch + +* [Added swift example to catalog and readme.](https://github.com/google/material-components-ios/commit/1a7d800cc9a6b29320bf57d61806a85cde387f15) (randallli) +* [Correct links for deploy on various deployment environment.](https://github.com/google/material-components-ios/commit/2b6f7f40a75410e57e13c20cab2d18dd09ae2566) (Yiran Mao) +* [Remove obsolete jazzy.yaml files.](https://github.com/google/material-components-ios/commit/fffb75e91e8ab5b979dba7a7fec661d1a058bb11) (Yiran Mao) +* [Rename label from slider to switch.](https://github.com/google/material-components-ios/commit/c92a9510bc8ed6d18339b4d7928cc470cc2a4f07) (Ian Gordon) +* [Revert "Remove obsolete jazzy.yaml files."](https://github.com/google/material-components-ios/commit/ad228a154455835c3e871109f12670387b9d926d) (Jeff Verkoeyen) +* [Set the switch's ink's max ripple radius to the spec value.](https://github.com/google/material-components-ios/commit/c15fd0c5c18e442947728472b7b33381cae78f45) (Adrian Secord) +* [Update .jazzy.yaml module property.](https://github.com/google/material-components-ios/commit/977626313e702783835f7ad1aba220b99316ba3f) (Jeff Verkoeyen) +* [Updated top-level "Documentation" to "Components".](https://github.com/google/material-components-ios/commit/ac38382e86f058593de41756fb8360dc5a156a10) (Adrian Secord) +* [[Catalog & Examples] Added navigationBar example in Swift (Supplemental POC) and corrected slight mistake in Catalog by Convention logic.](https://github.com/google/material-components-ios/commit/2ab08f41337d1ef1392bec46f534fe95fb2ac79e) (Will Larche) +* [[Catalog and Switch] Update switch demo, move layout code to supplemental, update switch color](https://github.com/google/material-components-ios/commit/e700bf58ee43e35d34b565b9b04f0af4b03f1288) (Junius Gunaratne) +* [[Catalog] Make example titles consistent, use Component Name](https://github.com/google/material-components-ios/commit/ebd1fbd14c6d2b0c052e28a9bc3cf59a6e01e75e) (Junius Gunaratne) + +### Typography + +* [Correct links for deploy on various deployment environment.](https://github.com/google/material-components-ios/commit/2b6f7f40a75410e57e13c20cab2d18dd09ae2566) (Yiran Mao) +* [Remove obsolete jazzy.yaml files.](https://github.com/google/material-components-ios/commit/fffb75e91e8ab5b979dba7a7fec661d1a058bb11) (Yiran Mao) +* [Resolve iOS 8.4 crash in the Typography hero demo.](https://github.com/google/material-components-ios/commit/0859d4b2b2ba8e7e9d91fbd757ac4b0a006384fc) (Jeff Verkoeyen) +* [Revert "Remove obsolete jazzy.yaml files."](https://github.com/google/material-components-ios/commit/ad228a154455835c3e871109f12670387b9d926d) (Jeff Verkoeyen) +* [Set autoresizing masks on Read Me example.](https://github.com/google/material-components-ios/commit/b54b2c96978fdb15d44c15b565f94ab43e60a22a) (Jeff Verkoeyen) +* [Typography hero demo is now a UITableView.](https://github.com/google/material-components-ios/commit/25c20a9abf9a6c9d30397dc7387646ead5fcafc4) (Jeff Verkoeyen) +* [Update .jazzy.yaml module property.](https://github.com/google/material-components-ios/commit/977626313e702783835f7ad1aba220b99316ba3f) (Jeff Verkoeyen) +* [Updated top-level "Documentation" to "Components".](https://github.com/google/material-components-ios/commit/ac38382e86f058593de41756fb8360dc5a156a10) (Adrian Secord) +* [[Catalog and Typography] Group Typography and Font Loader examples into Typography and Fonts](https://github.com/google/material-components-ios/commit/62f57e2b290155d5bfdfafb3f27839eb8e9c1eb5) (Junius Gunaratne) +* [[Catalog] Fixing Swift example view controller initializers.](https://github.com/google/material-components-ios/commit/8e733e163a4b308186345e03d90056e4040c693b) (Jeff Verkoeyen) +* [[Catalog] Make catalogIsPrimaryDemo static method in demos](https://github.com/google/material-components-ios/commit/7bb181a150bc1cf707637fdf112d3a949638c809) (Junius Gunaratne) +* [[Catalog] Title row name should correspond to type style](https://github.com/google/material-components-ios/commit/a576d6e7ae209a1d3308f1fe6312ae0ef4c5e54d) (Junius Gunaratne) + # 4.0.1 ## API diffs diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ba550743732..a0d69ebe557 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -121,7 +121,7 @@ considerations as a basis for minimizing dependencies: - dependency-less components are much easier to drop in and, most importantly, to remove from a project. -[Reach out to the team directly](community/#questions) for advice or questions on this matter. +[Reach out to the team directly](contributing/#questions) for advice or questions on this matter. Recommendations: diff --git a/MaterialComponents.podspec b/MaterialComponents.podspec index a0f9c2bf447..30caece430c 100644 --- a/MaterialComponents.podspec +++ b/MaterialComponents.podspec @@ -2,7 +2,7 @@ load 'scripts/generated/icons.rb' Pod::Spec.new do |s| s.name = "MaterialComponents" - s.version = "4.0.1" + s.version = "5.0.0" s.authors = { 'Apple platform engineering at Google' => 'appleplatforms@google.com' } s.summary = "A collection of stand-alone production-ready UI libraries focused on design details." s.homepage = "https://github.com/google/material-components-ios" @@ -75,6 +75,44 @@ Pod::Spec.new do |s| ss.dependency "MaterialComponents/Buttons" end + s.subspec "CollectionCells" do |ss| + ss.public_header_files = "components/#{ss.base_name}/src/*.h" + ss.source_files = "components/#{ss.base_name}/src/*.{h,m}", "components/#{ss.base_name}/src/private/*.{h,m}" + ss.header_mappings_dir = "components/#{ss.base_name}/src/*" + + ss.dependency "MaterialComponents/CollectionLayoutAttributes" + ss.dependency "MaterialComponents/Ink" + ss.dependency "MaterialComponents/Typography" + ss.dependency "MaterialComponents/private/Icons/ic_check" + ss.dependency "MaterialComponents/private/Icons/ic_check_circle" + ss.dependency "MaterialComponents/private/Icons/ic_chevron_right" + ss.dependency "MaterialComponents/private/Icons/ic_info" + ss.dependency "MaterialComponents/private/Icons/ic_radio_button_unchecked" + ss.dependency "MaterialComponents/private/Icons/ic_reorder" + end + + s.subspec "CollectionLayoutAttributes" do |ss| + ss.public_header_files = "components/#{ss.base_name}/src/*.h" + ss.source_files = "components/#{ss.base_name}/src/*.{h,m}" + ss.header_mappings_dir = "components/#{ss.base_name}/src/*" + end + + s.subspec "Collections" do |ss| + ss.public_header_files = "components/#{ss.base_name}/src/*.h" + ss.source_files = "components/#{ss.base_name}/src/*.{h,m}", "components/#{ss.base_name}/src/private/*.{h,m}" + ss.header_mappings_dir = "components/#{ss.base_name}/src/*" + ss.resource_bundles = { + "Material#{ss.base_name}" => ["components/#{ss.base_name}/src/Material#{ss.base_name}.bundle/*"] + } + + ss.dependency "MaterialComponents/CollectionCells" + ss.dependency "MaterialComponents/CollectionLayoutAttributes" + ss.dependency "MaterialComponents/Ink" + ss.dependency "MaterialComponents/ShadowElevations" + ss.dependency "MaterialComponents/ShadowLayer" + ss.dependency "MaterialComponents/Typography" + end + s.subspec "FlexibleHeader" do |ss| ss.public_header_files = "components/#{ss.base_name}/src/*.h" ss.source_files = "components/#{ss.base_name}/src/*.{h,m}", "components/#{ss.base_name}/src/private/*.{h,m}" @@ -133,12 +171,6 @@ Pod::Spec.new do |s| ss.dependency "MaterialComponents/Typography" end - s.subspec "ScrollViewDelegateMultiplexer" do |ss| - ss.public_header_files = "components/#{ss.base_name}/src/*.h" - ss.source_files = "components/#{ss.base_name}/src/*.{h,m}" - ss.header_mappings_dir = "components/#{ss.base_name}/src/*" - end - s.subspec "ShadowElevations" do |ss| ss.public_header_files = "components/#{ss.base_name}/src/*.h" ss.source_files = "components/#{ss.base_name}/src/*.{h,m}" diff --git a/MaterialComponentsCatalog.podspec b/MaterialComponentsCatalog.podspec index 9a69b397615..a670be3bd63 100644 --- a/MaterialComponentsCatalog.podspec +++ b/MaterialComponentsCatalog.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "MaterialComponentsCatalog" - s.version = "4.0.1" + s.version = "5.0.0" s.authors = { 'Apple platform engineering at Google' => 'appleplatforms@google.com' } s.summary = "A collection of stand-alone production-ready UI libraries focused on design details." s.homepage = "https://github.com/google/material-components-ios" diff --git a/MaterialComponentsUnitTests.podspec b/MaterialComponentsUnitTests.podspec index b36a2bb8188..36f860d2f1a 100644 --- a/MaterialComponentsUnitTests.podspec +++ b/MaterialComponentsUnitTests.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "MaterialComponentsUnitTests" - s.version = "4.0.1" + s.version = "5.0.0" s.authors = { 'Apple platform engineering at Google' => 'appleplatforms@google.com' } s.summary = "A collection of stand-alone production-ready UI libraries focused on design details." s.homepage = "https://github.com/google/material-components-ios" diff --git a/catalog/CatalogByConvention/src/CBCCatalogExample.h b/catalog/CatalogByConvention/src/CBCCatalogExample.h index 0ea2dfd7f62..00eed851edc 100644 --- a/catalog/CatalogByConvention/src/CBCCatalogExample.h +++ b/catalog/CatalogByConvention/src/CBCCatalogExample.h @@ -26,6 +26,12 @@ /** Return a list of breadcrumbs defining the navigation path taken to reach this example. */ + (nonnull NSArray *)catalogBreadcrumbs; +/** + Return a BOOL stating whether this example should be treated as the main "Demo" in the Catalog + for this component + */ ++ (BOOL)catalogIsPrimaryDemo; + @optional /** diff --git a/catalog/CatalogByConvention/src/CBCNodeViewController.h b/catalog/CatalogByConvention/src/CBCNodeViewController.h index 20961e52f8f..729f7996e1b 100644 --- a/catalog/CatalogByConvention/src/CBCNodeViewController.h +++ b/catalog/CatalogByConvention/src/CBCNodeViewController.h @@ -22,13 +22,15 @@ An instance of CBCNodeListViewController is able to represent a non-example CBCNode instance as a UITableView. */ -@interface CBCNodeListViewController : UITableViewController +@interface CBCNodeListViewController : UIViewController /** Initializes a CBCNodeViewController instance with a non-example node. */ - (nonnull instancetype)initWithNode:(nonnull CBCNode *)node; - (nonnull instancetype)initWithStyle:(UITableViewStyle)style NS_UNAVAILABLE; +@property(nonatomic, strong, nonnull) UITableView *tableView; + /** The node that this view controller must represent. */ @property(nonatomic, strong, nonnull, readonly) CBCNode *node; @@ -65,6 +67,9 @@ FOUNDATION_EXTERN CBCNode *__nonnull CBCCreateNavigationTree(void); /** The children of this node. */ @property(nonatomic, strong, nonnull) NSArray *children; +/** Is this the most important or default demo for this component? */ +- (BOOL)isPrimaryDemo; + /** Returns YES if this is an example node. */ - (BOOL)isExample; diff --git a/catalog/CatalogByConvention/src/CBCNodeViewController.m b/catalog/CatalogByConvention/src/CBCNodeViewController.m index e8d733a9b4a..2d949525a9c 100644 --- a/catalog/CatalogByConvention/src/CBCNodeViewController.m +++ b/catalog/CatalogByConvention/src/CBCNodeViewController.m @@ -77,6 +77,10 @@ - (NSString *)createExampleDescription { return CBCDescriptionFromClass(_exampleClass); } +- (BOOL)isPrimaryDemo { + return CBCCatalogIsPrimaryDemoFromClass(_exampleClass); +} + @end @implementation CBCNodeListViewController @@ -85,7 +89,7 @@ - (instancetype)initWithNode:(CBCNode *)node { NSAssert(!_node.isExample, @"%@ cannot represent example nodes.", NSStringFromClass([self class])); - self = [super initWithStyle:UITableViewStyleGrouped]; + self = [super initWithNibName:nil bundle:nil]; if (self) { _node = node; @@ -99,6 +103,41 @@ - (instancetype)initWithStyle:(UITableViewStyle)style { return nil; } +- (void)viewDidLoad { + [super viewDidLoad]; + + self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds + style:UITableViewStyleGrouped]; + self.tableView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); + self.tableView.delegate = self; + self.tableView.dataSource = self; + [self.view addSubview:self.tableView]; +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + NSIndexPath *selectedRow = self.tableView.indexPathForSelectedRow; + if (selectedRow) { + [[self transitionCoordinator] animateAlongsideTransition:^(id context) { + [self.tableView deselectRowAtIndexPath:selectedRow animated:YES]; + } + completion:^(id context) { + if ([context isCancelled]) { + [self.tableView selectRowAtIndexPath:selectedRow + animated:NO + scrollPosition:UITableViewScrollPositionNone]; + } + }]; + } +} + +- (void)viewDidAppear:(BOOL)animated { + [super viewDidAppear:animated]; + + [self.tableView flashScrollIndicators]; +} + #pragma mark - UITableViewDataSource - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { diff --git a/catalog/CatalogByConvention/src/private/CBCRuntime.h b/catalog/CatalogByConvention/src/private/CBCRuntime.h index d0b5cf80269..49af35599fa 100644 --- a/catalog/CatalogByConvention/src/private/CBCRuntime.h +++ b/catalog/CatalogByConvention/src/private/CBCRuntime.h @@ -22,6 +22,9 @@ /** Invokes +catalogBreadcrumbs on the class and returns the corresponding array of strings. */ FOUNDATION_EXTERN NSArray *CBCCatalogBreadcrumbsFromClass(Class aClass); +/** Invokes +catalogIsPrimaryDemo on the class and returns the BOOL value. */ +FOUNDATION_EXTERN BOOL CBCCatalogIsPrimaryDemoFromClass(Class aClass); + #pragma mark Runtime enumeration /** Returns all Objective-C and Swift classes available to the runtime. */ diff --git a/catalog/CatalogByConvention/src/private/CBCRuntime.m b/catalog/CatalogByConvention/src/private/CBCRuntime.m index 325377325b7..9e6e56bcdef 100644 --- a/catalog/CatalogByConvention/src/private/CBCRuntime.m +++ b/catalog/CatalogByConvention/src/private/CBCRuntime.m @@ -26,6 +26,23 @@ return [aClass performSelector:@selector(catalogBreadcrumbs)]; } +#pragma mark Primary demo check + +BOOL CBCCatalogIsPrimaryDemoFromClass(Class aClass) { + BOOL isPrimaryDemo = NO; + + if ([aClass respondsToSelector:@selector(catalogIsPrimaryDemo)]) { + NSMethodSignature *signature = [aClass methodSignatureForSelector:@selector(catalogIsPrimaryDemo)]; + NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature]; + invocation.selector = @selector(catalogIsPrimaryDemo); + invocation.target = aClass; + [invocation invoke]; + [invocation getReturnValue:&isPrimaryDemo]; + } + + return isPrimaryDemo; +} + #pragma mark Runtime enumeration NSArray *CBCGetAllClasses(void) { @@ -87,7 +104,7 @@ NSCAssert(vc, @"expecting a initialViewController in the storyboard %@", storyboardName); return vc; } - return [[aClass alloc] initWithNibName:nil bundle:nil]; + return [[aClass alloc] init]; } NSString *CBCDescriptionFromClass(Class aClass) { diff --git a/catalog/MDCCatalog.xcodeproj/project.pbxproj b/catalog/MDCCatalog.xcodeproj/project.pbxproj index 9dbad87b251..417ecf1c128 100644 --- a/catalog/MDCCatalog.xcodeproj/project.pbxproj +++ b/catalog/MDCCatalog.xcodeproj/project.pbxproj @@ -7,15 +7,17 @@ objects = { /* Begin PBXBuildFile section */ + 0B4A5E6D1CC9307A00D2AC5D /* MDCCatalogWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0B4A5E6C1CC9307A00D2AC5D /* MDCCatalogWindow.swift */; }; 22F173D236D54720CF0F3357 /* Pods_MDCCatalog.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1B8F1AA8D28612EA405B5C1A /* Pods_MDCCatalog.framework */; }; 5D090E571C9AEB8D0061344A /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 5D090E341C9AEB8C0061344A /* Localizable.strings */; }; 6642AB811CBDBE0900F5B1D7 /* CBCRuntime.m in Sources */ = {isa = PBXBuildFile; fileRef = 6642AB801CBDBE0900F5B1D7 /* CBCRuntime.m */; }; 664524B71C6BA62A001ADBF8 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664524B61C6BA62A001ADBF8 /* AppDelegate.swift */; }; - 664524B91C6BA62A001ADBF8 /* NodeViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664524B81C6BA62A001ADBF8 /* NodeViewController.swift */; }; + 664524B91C6BA62A001ADBF8 /* MDCNodeListViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 664524B81C6BA62A001ADBF8 /* MDCNodeListViewController.swift */; }; 664524BE1C6BA62A001ADBF8 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 664524BD1C6BA62A001ADBF8 /* Assets.xcassets */; }; 664524C11C6BA62A001ADBF8 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 664524BF1C6BA62A001ADBF8 /* LaunchScreen.storyboard */; }; + 66519B071CCA980600E5423E /* MDCInkTouchController+Injection.m in Sources */ = {isa = PBXBuildFile; fileRef = 66519B061CCA980600E5423E /* MDCInkTouchController+Injection.m */; }; 666CA70D1CAEBCA9001B1884 /* CBCNodeViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 666CA70A1CAEBCA9001B1884 /* CBCNodeViewController.m */; }; - DE0B35471CAC16F2002C7357 /* MDCCatalogBarViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE0B35461CAC16F2002C7357 /* MDCCatalogBarViewController.swift */; }; + 6681FDFD1CC586660013A0C7 /* MDCCatalogTileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6681FDFC1CC586660013A0C7 /* MDCCatalogTileView.swift */; }; DE1944861CBD9E40009E0321 /* MDCCatalogTileData.m in Sources */ = {isa = PBXBuildFile; fileRef = DE1944681CBD9E40009E0321 /* MDCCatalogTileData.m */; }; DE1944871CBD9E40009E0321 /* MDCCatalogTileDataAppBar.m in Sources */ = {isa = PBXBuildFile; fileRef = DE19446A1CBD9E40009E0321 /* MDCCatalogTileDataAppBar.m */; }; DE1944881CBD9E40009E0321 /* MDCCatalogTileDataButtonBar.m in Sources */ = {isa = PBXBuildFile; fileRef = DE19446C1CBD9E40009E0321 /* MDCCatalogTileDataButtonBar.m */; }; @@ -32,18 +34,18 @@ DE1944931CBD9E40009E0321 /* MDCCatalogTileDataSwitch.m in Sources */ = {isa = PBXBuildFile; fileRef = DE1944821CBD9E40009E0321 /* MDCCatalogTileDataSwitch.m */; }; DE1944941CBD9E40009E0321 /* MDCCatalogTileDataTypography.m in Sources */ = {isa = PBXBuildFile; fileRef = DE1944841CBD9E40009E0321 /* MDCCatalogTileDataTypography.m */; }; DE309CF31C8DEB8400E73247 /* MDCCatalogComponentsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE309CF21C8DEB8400E73247 /* MDCCatalogComponentsController.swift */; }; - DE3799581CAD9AB00036D5B0 /* MDCCatalogTypicalExampleViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE3799571CAD9AB00036D5B0 /* MDCCatalogTypicalExampleViewController.swift */; }; - DE5BD5AE1CB3F7F100D8D75D /* MDCCatalogTile.swift in Sources */ = {isa = PBXBuildFile; fileRef = DE5BD5AD1CB3F7F100D8D75D /* MDCCatalogTile.swift */; }; - DEEC11E51CA9AC3A00A15FC8 /* MDCCatalogBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEEC11E41CA9AC3A00A15FC8 /* MDCCatalogBar.swift */; }; + DE7A17C51CCAC1EA00B66230 /* MDCCatalogTileDataCollections.m in Sources */ = {isa = PBXBuildFile; fileRef = DE7A17C41CCAC1EA00B66230 /* MDCCatalogTileDataCollections.m */; }; DEF64EA41C8DEE83007C4EA0 /* MDCCatalogCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DEF64EA31C8DEE83007C4EA0 /* MDCCatalogCollectionViewCell.swift */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ 097F9AF44329F8F5FC325268 /* Pods-MDCCatalog.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MDCCatalog.release.xcconfig"; path = "Pods/Target Support Files/Pods-MDCCatalog/Pods-MDCCatalog.release.xcconfig"; sourceTree = ""; }; + 0B4A5E6C1CC9307A00D2AC5D /* MDCCatalogWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MDCCatalogWindow.swift; sourceTree = ""; }; 0D185DDA19244A33A78863F7 /* Pods-MDCCatalog-MDCCatalogUnitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MDCCatalog-MDCCatalogUnitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-MDCCatalog-MDCCatalogUnitTests/Pods-MDCCatalog-MDCCatalogUnitTests.release.xcconfig"; sourceTree = ""; }; 0F092FBA334F9A6817C4B9ED /* Pods-MDCCatalog-MDCCatalogUnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MDCCatalog-MDCCatalogUnitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MDCCatalog-MDCCatalogUnitTests/Pods-MDCCatalog-MDCCatalogUnitTests.debug.xcconfig"; sourceTree = ""; }; 12970BE3EA0028A12E1427AD /* Pods.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.release.xcconfig; path = "Pods/Target Support Files/Pods/Pods.release.xcconfig"; sourceTree = ""; }; 1B8F1AA8D28612EA405B5C1A /* Pods_MDCCatalog.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_MDCCatalog.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3640411C1CBECABD00C962B2 /* CatalogByConvention.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CatalogByConvention.h; sourceTree = ""; }; 39A3426A088E725ED84D45D4 /* Pods-MDCCatalog-MDCCatalog.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MDCCatalog-MDCCatalog.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MDCCatalog-MDCCatalog/Pods-MDCCatalog-MDCCatalog.debug.xcconfig"; sourceTree = ""; }; 51A574C94F29C2B3F2198B9E /* libPods-MDCCatalog-MDCCatalogUnitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MDCCatalog-MDCCatalogUnitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 5434D10FB7D7598D60B5F56C /* Pods-MDCCatalogUnitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MDCCatalogUnitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-MDCCatalogUnitTests/Pods-MDCCatalogUnitTests.release.xcconfig"; sourceTree = ""; }; @@ -85,21 +87,22 @@ 6642AB801CBDBE0900F5B1D7 /* CBCRuntime.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CBCRuntime.m; sourceTree = ""; }; 664524B31C6BA62A001ADBF8 /* MDCCatalog.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MDCCatalog.app; sourceTree = BUILT_PRODUCTS_DIR; }; 664524B61C6BA62A001ADBF8 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; - 664524B81C6BA62A001ADBF8 /* NodeViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeViewController.swift; sourceTree = ""; }; + 664524B81C6BA62A001ADBF8 /* MDCNodeListViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MDCNodeListViewController.swift; sourceTree = ""; }; 664524BD1C6BA62A001ADBF8 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 664524C01C6BA62A001ADBF8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 664524C21C6BA62A001ADBF8 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 66519B051CCA97E100E5423E /* MDCInkTouchController+Injection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MDCInkTouchController+Injection.h"; sourceTree = ""; }; + 66519B061CCA980600E5423E /* MDCInkTouchController+Injection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MDCInkTouchController+Injection.m"; sourceTree = ""; }; 665A34D91C6BD01900962055 /* MDCCatalog-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "MDCCatalog-Bridging-Header.h"; sourceTree = ""; }; - 666CA7071CAEBCA9001B1884 /* CatalogByConvention.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CatalogByConvention.h; sourceTree = ""; }; 666CA7081CAEBCA9001B1884 /* CBCCatalogExample.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CBCCatalogExample.h; sourceTree = ""; }; 666CA7091CAEBCA9001B1884 /* CBCNodeViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CBCNodeViewController.h; sourceTree = ""; }; 666CA70A1CAEBCA9001B1884 /* CBCNodeViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CBCNodeViewController.m; sourceTree = ""; }; + 6681FDFC1CC586660013A0C7 /* MDCCatalogTileView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MDCCatalogTileView.swift; sourceTree = ""; }; 7385A3AD61219974D1545BD4 /* Pods-MDCCatalogUnitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MDCCatalogUnitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MDCCatalogUnitTests/Pods-MDCCatalogUnitTests.debug.xcconfig"; sourceTree = ""; }; 8206EEC8F0212B48C4AC4C01 /* libPods-MDCCatalogUnitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MDCCatalogUnitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 878DC2619FAA226EA1A1849A /* libPods-MDCCatalog-MDCCatalog.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-MDCCatalog-MDCCatalog.a"; sourceTree = BUILT_PRODUCTS_DIR; }; A2985B007C0718CB5E2511B3 /* libPods.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libPods.a; sourceTree = BUILT_PRODUCTS_DIR; }; BFBFBC9BA56EED442714E5F3 /* Pods-MDCCatalog.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MDCCatalog.debug.xcconfig"; path = "Pods/Target Support Files/Pods-MDCCatalog/Pods-MDCCatalog.debug.xcconfig"; sourceTree = ""; }; - DE0B35461CAC16F2002C7357 /* MDCCatalogBarViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MDCCatalogBarViewController.swift; sourceTree = ""; }; DE1944671CBD9E40009E0321 /* MDCCatalogTileData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MDCCatalogTileData.h; sourceTree = ""; }; DE1944681CBD9E40009E0321 /* MDCCatalogTileData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MDCCatalogTileData.m; sourceTree = ""; }; DE1944691CBD9E40009E0321 /* MDCCatalogTileDataAppBar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MDCCatalogTileDataAppBar.h; sourceTree = ""; }; @@ -132,9 +135,8 @@ DE1944841CBD9E40009E0321 /* MDCCatalogTileDataTypography.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MDCCatalogTileDataTypography.m; sourceTree = ""; }; DE1944851CBD9E40009E0321 /* MDCCatalogTiles.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MDCCatalogTiles.h; sourceTree = ""; }; DE309CF21C8DEB8400E73247 /* MDCCatalogComponentsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MDCCatalogComponentsController.swift; sourceTree = ""; }; - DE3799571CAD9AB00036D5B0 /* MDCCatalogTypicalExampleViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MDCCatalogTypicalExampleViewController.swift; sourceTree = ""; }; - DE5BD5AD1CB3F7F100D8D75D /* MDCCatalogTile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MDCCatalogTile.swift; sourceTree = ""; }; - DEEC11E41CA9AC3A00A15FC8 /* MDCCatalogBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MDCCatalogBar.swift; sourceTree = ""; }; + DE7A17C41CCAC1EA00B66230 /* MDCCatalogTileDataCollections.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MDCCatalogTileDataCollections.m; sourceTree = ""; }; + DE7A17C61CCAC1F700B66230 /* MDCCatalogTileDataCollections.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MDCCatalogTileDataCollections.h; sourceTree = ""; }; DEF64EA31C8DEE83007C4EA0 /* MDCCatalogCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MDCCatalogCollectionViewCell.swift; sourceTree = ""; }; DEFF1C057348FFB1866CD023 /* Pods.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = Pods.debug.xcconfig; path = "Pods/Target Support Files/Pods/Pods.debug.xcconfig"; sourceTree = ""; }; FBDDFF6A49F084B665BA398A /* Pods-MDCCatalog-MDCCatalog.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-MDCCatalog-MDCCatalog.release.xcconfig"; path = "Pods/Target Support Files/Pods-MDCCatalog-MDCCatalog/Pods-MDCCatalog-MDCCatalog.release.xcconfig"; sourceTree = ""; }; @@ -193,14 +195,14 @@ children = ( 664524B61C6BA62A001ADBF8 /* AppDelegate.swift */, 665A34D91C6BD01900962055 /* MDCCatalog-Bridging-Header.h */, - DEEC11E41CA9AC3A00A15FC8 /* MDCCatalogBar.swift */, - DE0B35461CAC16F2002C7357 /* MDCCatalogBarViewController.swift */, DEF64EA31C8DEE83007C4EA0 /* MDCCatalogCollectionViewCell.swift */, DE309CF21C8DEB8400E73247 /* MDCCatalogComponentsController.swift */, - DE5BD5AD1CB3F7F100D8D75D /* MDCCatalogTile.swift */, DE5BD5AF1CB3F8FF00D8D75D /* MDCCatalogTiles */, - DE3799571CAD9AB00036D5B0 /* MDCCatalogTypicalExampleViewController.swift */, - 664524B81C6BA62A001ADBF8 /* NodeViewController.swift */, + 6681FDFC1CC586660013A0C7 /* MDCCatalogTileView.swift */, + 0B4A5E6C1CC9307A00D2AC5D /* MDCCatalogWindow.swift */, + 664524B81C6BA62A001ADBF8 /* MDCNodeListViewController.swift */, + 66519B051CCA97E100E5423E /* MDCInkTouchController+Injection.h */, + 66519B061CCA980600E5423E /* MDCInkTouchController+Injection.m */, 665A34DE1C6BDAE700962055 /* Resources */, ); path = MDCCatalog; @@ -220,7 +222,7 @@ 666CA7061CAEBC98001B1884 /* CatalogByConvention */ = { isa = PBXGroup; children = ( - 666CA7071CAEBCA9001B1884 /* CatalogByConvention.h */, + 3640411C1CBECABD00C962B2 /* CatalogByConvention.h */, 666CA7081CAEBCA9001B1884 /* CBCCatalogExample.h */, 666CA7091CAEBCA9001B1884 /* CBCNodeViewController.h */, 666CA70A1CAEBCA9001B1884 /* CBCNodeViewController.m */, @@ -258,6 +260,8 @@ DE19446C1CBD9E40009E0321 /* MDCCatalogTileDataButtonBar.m */, DE19446D1CBD9E40009E0321 /* MDCCatalogTileDataButtons.h */, DE19446E1CBD9E40009E0321 /* MDCCatalogTileDataButtons.m */, + DE7A17C61CCAC1F700B66230 /* MDCCatalogTileDataCollections.h */, + DE7A17C41CCAC1EA00B66230 /* MDCCatalogTileDataCollections.m */, DE19446F1CBD9E40009E0321 /* MDCCatalogTileDataFlexibleHeader.h */, DE1944701CBD9E40009E0321 /* MDCCatalogTileDataFlexibleHeader.m */, DE1944711CBD9E40009E0321 /* MDCCatalogTileDataHeaderStackView.h */, @@ -459,18 +463,18 @@ DE19448E1CBD9E40009E0321 /* MDCCatalogTileDataNavigationBar.m in Sources */, DE19448F1CBD9E40009E0321 /* MDCCatalogTileDataPageControl.m in Sources */, DE1944931CBD9E40009E0321 /* MDCCatalogTileDataSwitch.m in Sources */, + DE7A17C51CCAC1EA00B66230 /* MDCCatalogTileDataCollections.m in Sources */, DE1944871CBD9E40009E0321 /* MDCCatalogTileDataAppBar.m in Sources */, - DEEC11E51CA9AC3A00A15FC8 /* MDCCatalogBar.swift in Sources */, DE309CF31C8DEB8400E73247 /* MDCCatalogComponentsController.swift in Sources */, + 6681FDFD1CC586660013A0C7 /* MDCCatalogTileView.swift in Sources */, DE19448C1CBD9E40009E0321 /* MDCCatalogTileDataInk.m in Sources */, - 664524B91C6BA62A001ADBF8 /* NodeViewController.swift in Sources */, - DE3799581CAD9AB00036D5B0 /* MDCCatalogTypicalExampleViewController.swift in Sources */, + 0B4A5E6D1CC9307A00D2AC5D /* MDCCatalogWindow.swift in Sources */, + 664524B91C6BA62A001ADBF8 /* MDCNodeListViewController.swift in Sources */, + 66519B071CCA980600E5423E /* MDCInkTouchController+Injection.m in Sources */, DE19448D1CBD9E40009E0321 /* MDCCatalogTileDataMisc.m in Sources */, DE19448B1CBD9E40009E0321 /* MDCCatalogTileDataHeaderStackView.m in Sources */, - DE5BD5AE1CB3F7F100D8D75D /* MDCCatalogTile.swift in Sources */, DE1944881CBD9E40009E0321 /* MDCCatalogTileDataButtonBar.m in Sources */, DE19448A1CBD9E40009E0321 /* MDCCatalogTileDataFlexibleHeader.m in Sources */, - DE0B35471CAC16F2002C7357 /* MDCCatalogBarViewController.swift in Sources */, DE1944921CBD9E40009E0321 /* MDCCatalogTileDataSpritedAnimationView.m in Sources */, DE1944891CBD9E40009E0321 /* MDCCatalogTileDataButtons.m in Sources */, 664524B71C6BA62A001ADBF8 /* AppDelegate.swift in Sources */, diff --git a/catalog/MDCCatalog/AppDelegate.swift b/catalog/MDCCatalog/AppDelegate.swift index 18fd32960ff..c6fe6bb76c4 100644 --- a/catalog/MDCCatalog/AppDelegate.swift +++ b/catalog/MDCCatalog/AppDelegate.swift @@ -22,7 +22,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { - self.window = UIWindow(frame: UIScreen.mainScreen().bounds) + self.window = MDCCatalogWindow(frame: UIScreen.mainScreen().bounds) let tree = CBCCreateNavigationTree() diff --git a/catalog/MDCCatalog/Assets.xcassets/App Bar.imageset/AppBar.pdf b/catalog/MDCCatalog/Assets.xcassets/App Bar.imageset/AppBar.pdf deleted file mode 100644 index cb130a1247a..00000000000 Binary files a/catalog/MDCCatalog/Assets.xcassets/App Bar.imageset/AppBar.pdf and /dev/null differ diff --git a/catalog/MDCCatalog/Assets.xcassets/App Bar.imageset/Contents.json b/catalog/MDCCatalog/Assets.xcassets/App Bar.imageset/Contents.json deleted file mode 100644 index a832b900cbb..00000000000 --- a/catalog/MDCCatalog/Assets.xcassets/App Bar.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "AppBar.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/Contents.json b/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/Contents.json index eeea76c2db5..921cbc26e3a 100644 --- a/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -1,68 +1,81 @@ { "images" : [ { - "idiom" : "iphone", "size" : "29x29", + "idiom" : "iphone", + "filename" : "logo_mdc_ios_color_29pt_2x.png", "scale" : "2x" }, { - "idiom" : "iphone", "size" : "29x29", + "idiom" : "iphone", + "filename" : "logo_mdc_ios_color_29pt_3x.png", "scale" : "3x" }, { - "idiom" : "iphone", "size" : "40x40", + "idiom" : "iphone", + "filename" : "logo_mdc_ios_color_40pt_2x.png", "scale" : "2x" }, { - "idiom" : "iphone", "size" : "40x40", + "idiom" : "iphone", + "filename" : "logo_mdc_ios_color_40pt_3x.png", "scale" : "3x" }, { - "idiom" : "iphone", "size" : "60x60", + "idiom" : "iphone", + "filename" : "logo_mdc_ios_color_40pt_3x.png", "scale" : "2x" }, { - "idiom" : "iphone", "size" : "60x60", + "idiom" : "iphone", + "filename" : "logo_mdc_ios_color_60pt_3x.png", "scale" : "3x" }, { - "idiom" : "ipad", "size" : "29x29", + "idiom" : "ipad", + "filename" : "logo_mdc_ios_color_29pt.png", "scale" : "1x" }, { - "idiom" : "ipad", "size" : "29x29", + "idiom" : "ipad", + "filename" : "logo_mdc_ios_color_29pt_2x.png", "scale" : "2x" }, { - "idiom" : "ipad", "size" : "40x40", + "idiom" : "ipad", + "filename" : "logo_mdc_ios_color_40pt.png", "scale" : "1x" }, { - "idiom" : "ipad", "size" : "40x40", + "idiom" : "ipad", + "filename" : "logo_mdc_ios_color_40pt_2x.png", "scale" : "2x" }, { - "idiom" : "ipad", "size" : "76x76", + "idiom" : "ipad", + "filename" : "logo_mdc_ios_color_76pt.png", "scale" : "1x" }, { - "idiom" : "ipad", "size" : "76x76", + "idiom" : "ipad", + "filename" : "logo_mdc_ios_color_76pt_2x.png", "scale" : "2x" }, { - "idiom" : "ipad", "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "logo_mdc_ios_color_83_5pt_2x.png", "scale" : "2x" } ], diff --git a/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_29pt.png b/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_29pt.png new file mode 100644 index 00000000000..61e685232d6 Binary files /dev/null and b/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_29pt.png differ diff --git a/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_29pt_2x.png b/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_29pt_2x.png new file mode 100644 index 00000000000..21333b511a7 Binary files /dev/null and b/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_29pt_2x.png differ diff --git a/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_29pt_3x.png b/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_29pt_3x.png new file mode 100644 index 00000000000..174f2706f7a Binary files /dev/null and b/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_29pt_3x.png differ diff --git a/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_40pt.png b/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_40pt.png new file mode 100644 index 00000000000..fc339020a60 Binary files /dev/null and b/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_40pt.png differ diff --git a/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_40pt_2x.png b/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_40pt_2x.png new file mode 100644 index 00000000000..7876f314f6f Binary files /dev/null and b/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_40pt_2x.png differ diff --git a/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_40pt_3x.png b/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_40pt_3x.png new file mode 100644 index 00000000000..964b2447b9f Binary files /dev/null and b/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_40pt_3x.png differ diff --git a/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_60pt_3x.png b/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_60pt_3x.png new file mode 100644 index 00000000000..a6b845f2e59 Binary files /dev/null and b/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_60pt_3x.png differ diff --git a/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_76pt.png b/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_76pt.png new file mode 100644 index 00000000000..48fa334abf8 Binary files /dev/null and b/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_76pt.png differ diff --git a/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_76pt_2x.png b/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_76pt_2x.png new file mode 100644 index 00000000000..e6ace063ebb Binary files /dev/null and b/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_76pt_2x.png differ diff --git a/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_83_5pt_2x.png b/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_83_5pt_2x.png new file mode 100644 index 00000000000..eea65e2c72a Binary files /dev/null and b/catalog/MDCCatalog/Assets.xcassets/AppIcon.appiconset/logo_mdc_ios_color_83_5pt_2x.png differ diff --git a/catalog/MDCCatalog/Assets.xcassets/Buttons.imageset/Button.pdf b/catalog/MDCCatalog/Assets.xcassets/Buttons.imageset/Button.pdf deleted file mode 100644 index 4f4248878f8..00000000000 Binary files a/catalog/MDCCatalog/Assets.xcassets/Buttons.imageset/Button.pdf and /dev/null differ diff --git a/catalog/MDCCatalog/Assets.xcassets/Buttons.imageset/Contents.json b/catalog/MDCCatalog/Assets.xcassets/Buttons.imageset/Contents.json deleted file mode 100644 index 7ecf57fb329..00000000000 --- a/catalog/MDCCatalog/Assets.xcassets/Buttons.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "Button.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/catalog/MDCCatalog/Assets.xcassets/Flexible Header.imageset/Contents.json b/catalog/MDCCatalog/Assets.xcassets/Flexible Header.imageset/Contents.json deleted file mode 100644 index 19cedb5e49b..00000000000 --- a/catalog/MDCCatalog/Assets.xcassets/Flexible Header.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "FlexibleHeader.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/catalog/MDCCatalog/Assets.xcassets/Flexible Header.imageset/FlexibleHeader.pdf b/catalog/MDCCatalog/Assets.xcassets/Flexible Header.imageset/FlexibleHeader.pdf deleted file mode 100644 index 58eb31ed379..00000000000 Binary files a/catalog/MDCCatalog/Assets.xcassets/Flexible Header.imageset/FlexibleHeader.pdf and /dev/null differ diff --git a/catalog/MDCCatalog/Assets.xcassets/Header Stack View.imageset/Contents.json b/catalog/MDCCatalog/Assets.xcassets/Header Stack View.imageset/Contents.json deleted file mode 100644 index 87164510c5d..00000000000 --- a/catalog/MDCCatalog/Assets.xcassets/Header Stack View.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "HeaderStackView.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/catalog/MDCCatalog/Assets.xcassets/Header Stack View.imageset/HeaderStackView.pdf b/catalog/MDCCatalog/Assets.xcassets/Header Stack View.imageset/HeaderStackView.pdf deleted file mode 100644 index 36afcfe2243..00000000000 Binary files a/catalog/MDCCatalog/Assets.xcassets/Header Stack View.imageset/HeaderStackView.pdf and /dev/null differ diff --git a/catalog/MDCCatalog/Assets.xcassets/Ink.imageset/Contents.json b/catalog/MDCCatalog/Assets.xcassets/Ink.imageset/Contents.json deleted file mode 100644 index ba320964b72..00000000000 --- a/catalog/MDCCatalog/Assets.xcassets/Ink.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "Ink.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/catalog/MDCCatalog/Assets.xcassets/Ink.imageset/Ink.pdf b/catalog/MDCCatalog/Assets.xcassets/Ink.imageset/Ink.pdf deleted file mode 100644 index c30f49aff03..00000000000 Binary files a/catalog/MDCCatalog/Assets.xcassets/Ink.imageset/Ink.pdf and /dev/null differ diff --git a/catalog/MDCCatalog/Assets.xcassets/Misc.imageset/Contents.json b/catalog/MDCCatalog/Assets.xcassets/Misc.imageset/Contents.json deleted file mode 100644 index 872d215f6a0..00000000000 --- a/catalog/MDCCatalog/Assets.xcassets/Misc.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "Misc.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/catalog/MDCCatalog/Assets.xcassets/Misc.imageset/Misc.pdf b/catalog/MDCCatalog/Assets.xcassets/Misc.imageset/Misc.pdf deleted file mode 100644 index d215ddd4358..00000000000 Binary files a/catalog/MDCCatalog/Assets.xcassets/Misc.imageset/Misc.pdf and /dev/null differ diff --git a/catalog/MDCCatalog/Assets.xcassets/Page Control.imageset/Contents.json b/catalog/MDCCatalog/Assets.xcassets/Page Control.imageset/Contents.json deleted file mode 100644 index cd6826b7b0c..00000000000 --- a/catalog/MDCCatalog/Assets.xcassets/Page Control.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "PageControl.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/catalog/MDCCatalog/Assets.xcassets/Page Control.imageset/PageControl.pdf b/catalog/MDCCatalog/Assets.xcassets/Page Control.imageset/PageControl.pdf deleted file mode 100644 index 8dcdcf9a69b..00000000000 Binary files a/catalog/MDCCatalog/Assets.xcassets/Page Control.imageset/PageControl.pdf and /dev/null differ diff --git a/catalog/MDCCatalog/Assets.xcassets/Shadows.imageset/Contents.json b/catalog/MDCCatalog/Assets.xcassets/Shadows.imageset/Contents.json deleted file mode 100644 index 02aadd138ed..00000000000 --- a/catalog/MDCCatalog/Assets.xcassets/Shadows.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "Shadows.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/catalog/MDCCatalog/Assets.xcassets/Shadows.imageset/Shadows.pdf b/catalog/MDCCatalog/Assets.xcassets/Shadows.imageset/Shadows.pdf deleted file mode 100644 index 3da78dfc977..00000000000 Binary files a/catalog/MDCCatalog/Assets.xcassets/Shadows.imageset/Shadows.pdf and /dev/null differ diff --git a/catalog/MDCCatalog/Assets.xcassets/Slider.imageset/Contents.json b/catalog/MDCCatalog/Assets.xcassets/Slider.imageset/Contents.json deleted file mode 100644 index 21778cc136f..00000000000 --- a/catalog/MDCCatalog/Assets.xcassets/Slider.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "Slider.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/catalog/MDCCatalog/Assets.xcassets/Slider.imageset/Slider.pdf b/catalog/MDCCatalog/Assets.xcassets/Slider.imageset/Slider.pdf deleted file mode 100644 index ee94fd5dd15..00000000000 Binary files a/catalog/MDCCatalog/Assets.xcassets/Slider.imageset/Slider.pdf and /dev/null differ diff --git a/catalog/MDCCatalog/Assets.xcassets/Sprited Animation View.imageset/Contents.json b/catalog/MDCCatalog/Assets.xcassets/Sprited Animation View.imageset/Contents.json deleted file mode 100644 index fbb0c5bc764..00000000000 --- a/catalog/MDCCatalog/Assets.xcassets/Sprited Animation View.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "SpritedAnimationView.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/catalog/MDCCatalog/Assets.xcassets/Sprited Animation View.imageset/SpritedAnimationView.pdf b/catalog/MDCCatalog/Assets.xcassets/Sprited Animation View.imageset/SpritedAnimationView.pdf deleted file mode 100644 index 4b30ebc8517..00000000000 Binary files a/catalog/MDCCatalog/Assets.xcassets/Sprited Animation View.imageset/SpritedAnimationView.pdf and /dev/null differ diff --git a/catalog/MDCCatalog/Assets.xcassets/Switch.imageset/Contents.json b/catalog/MDCCatalog/Assets.xcassets/Switch.imageset/Contents.json deleted file mode 100644 index 659ace1ffc5..00000000000 --- a/catalog/MDCCatalog/Assets.xcassets/Switch.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "Switch.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/catalog/MDCCatalog/Assets.xcassets/Switch.imageset/Switch.pdf b/catalog/MDCCatalog/Assets.xcassets/Switch.imageset/Switch.pdf deleted file mode 100644 index 95ab6846786..00000000000 Binary files a/catalog/MDCCatalog/Assets.xcassets/Switch.imageset/Switch.pdf and /dev/null differ diff --git a/catalog/MDCCatalog/Assets.xcassets/Typography.imageset/Contents.json b/catalog/MDCCatalog/Assets.xcassets/Typography.imageset/Contents.json deleted file mode 100644 index 99a6599bdf3..00000000000 --- a/catalog/MDCCatalog/Assets.xcassets/Typography.imageset/Contents.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "images" : [ - { - "idiom" : "universal", - "filename" : "Typography.pdf" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} \ No newline at end of file diff --git a/catalog/MDCCatalog/Assets.xcassets/Typography.imageset/Typography.pdf b/catalog/MDCCatalog/Assets.xcassets/Typography.imageset/Typography.pdf deleted file mode 100644 index 3e4ec597959..00000000000 Binary files a/catalog/MDCCatalog/Assets.xcassets/Typography.imageset/Typography.pdf and /dev/null differ diff --git a/catalog/MDCCatalog/MDCCatalog-Bridging-Header.h b/catalog/MDCCatalog/MDCCatalog-Bridging-Header.h index 40303f51e17..ef680637595 100644 --- a/catalog/MDCCatalog/MDCCatalog-Bridging-Header.h +++ b/catalog/MDCCatalog/MDCCatalog-Bridging-Header.h @@ -16,3 +16,4 @@ #import "CatalogByConvention.h" #import "MDCCatalogTiles.h" +#import "MDCInkTouchController+Injection.h" diff --git a/catalog/MDCCatalog/MDCCatalogBar.swift b/catalog/MDCCatalog/MDCCatalogBar.swift deleted file mode 100644 index 8c776fd1b53..00000000000 --- a/catalog/MDCCatalog/MDCCatalogBar.swift +++ /dev/null @@ -1,87 +0,0 @@ -/* - Copyright 2016-present Google Inc. 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. - */ - -import UIKit -import MaterialComponents - -protocol MDCCatalogBarDelegate { - func didPressExit() -} - -class MDCCatalogBar: UIView { - - var delegate: MDCCatalogBarDelegate? - var titleString = "Component" - let descriptionLabel = UILabel() - let exitLabel = UILabel() - - internal var title: String { - get { - return titleString - } - set { - titleString = newValue - descriptionLabel.text = titleString - } - } - - override init(frame: CGRect) { - super.init(frame: frame) - commonMDCCatalogBarInit() - } - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - } - - override func layoutSubviews() { - descriptionLabel.frame = CGRect( - x: 20, - y: 0, - width: self.frame.size.width - 120, - height: self.frame.size.height - ) - exitLabel.frame = CGRect( - x: self.frame.size.width - 100, - y: 0, - width: 80, - height: self.frame.size.height - ) - } - - func commonMDCCatalogBarInit() { - self.backgroundColor = UIColor(white: 0.2, alpha: 1) - descriptionLabel.text = title - descriptionLabel.textColor = UIColor.whiteColor() - descriptionLabel.font = MDCTypography.body1Font() - addSubview(descriptionLabel) - - let blueColor = UIColor(red:0.012, green:0.663, blue:0.957, alpha:1) - exitLabel.text = "Exit Demo".uppercaseString - exitLabel.textColor = blueColor - exitLabel.font = MDCTypography.buttonFont() - exitLabel.textAlignment = .Right - addSubview(exitLabel) - - let tap = UITapGestureRecognizer(target: self, action: "exitPressed") - addGestureRecognizer(tap) - } - - func exitPressed() { - delegate?.didPressExit() - } - -} diff --git a/catalog/MDCCatalog/MDCCatalogBarViewController.swift b/catalog/MDCCatalog/MDCCatalogBarViewController.swift deleted file mode 100644 index cf18a5d989b..00000000000 --- a/catalog/MDCCatalog/MDCCatalogBarViewController.swift +++ /dev/null @@ -1,70 +0,0 @@ -/* -Copyright 2016-present Google Inc. 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. -*/ - -import UIKit -import MaterialComponents - -class MDCCatalogBarViewController: UIViewController { - - internal let catalogBar = MDCCatalogBar(frame: CGRect()) - var contentViewController = UIViewController() - - init(contentViewController: UIViewController, title: String, delegate: MDCCatalogBarDelegate) { - super.init(nibName: nil, bundle: nil) - - self.contentViewController = contentViewController - self.addChildViewController(contentViewController) - - catalogBar.title = title - catalogBar.delegate = delegate - } - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - } - - override func viewDidLoad() { - self.view.addSubview(contentViewController.view) - self.view.addSubview(catalogBar) - contentViewController.didMoveToParentViewController(self) - - let catalogBarHeight = CGFloat(52) - let catalogBarRect = CGRect( - x: 0, - y: self.view.frame.size.height - catalogBarHeight, - width: self.view.frame.size.width, - height: catalogBarHeight - ) - catalogBar.frame = catalogBarRect - catalogBar.autoresizingMask = [.FlexibleTopMargin, .FlexibleWidth] - - contentViewController.view.frame = CGRect( - x: 0, - y: 0, - width: self.view.bounds.size.width, - height: self.view.bounds.size.height - catalogBarHeight - ) - contentViewController.view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight] - } - - override func childViewControllerForStatusBarHidden() -> UIViewController? { - return contentViewController - } - - override func childViewControllerForStatusBarStyle() -> UIViewController? { - return contentViewController - } -} diff --git a/catalog/MDCCatalog/MDCCatalogCollectionViewCell.swift b/catalog/MDCCatalog/MDCCatalogCollectionViewCell.swift index e7d5dfe95d8..7172f9c342e 100644 --- a/catalog/MDCCatalog/MDCCatalogCollectionViewCell.swift +++ b/catalog/MDCCatalog/MDCCatalogCollectionViewCell.swift @@ -21,7 +21,7 @@ class MDCCatalogCollectionViewCell: UICollectionViewCell { var label = UILabel() let pad = CGFloat(14) - let tile = MDCCatalogTile(frame: CGRectZero) + let tile = MDCCatalogTileView(frame: CGRectZero) override init(frame: CGRect) { super.init(frame: frame) diff --git a/catalog/MDCCatalog/MDCCatalogComponentsController.swift b/catalog/MDCCatalog/MDCCatalogComponentsController.swift index ae5f97e072b..bc8c50332cc 100644 --- a/catalog/MDCCatalog/MDCCatalogComponentsController.swift +++ b/catalog/MDCCatalog/MDCCatalogComponentsController.swift @@ -17,7 +17,7 @@ limitations under the License. import UIKit import MaterialComponents -class MDCCatalogComponentsController: UICollectionViewController { +class MDCCatalogComponentsController: UICollectionViewController, MDCInkTouchControllerDelegate { let spacing = CGFloat(1) let inset = CGFloat(16) @@ -25,6 +25,8 @@ class MDCCatalogComponentsController: UICollectionViewController { var headerViewController: MDCFlexibleHeaderViewController let imageNames = NSMutableArray() + var inkController: MDCInkTouchController? + init(collectionViewLayout ignoredLayout: UICollectionViewLayout, node: CBCNode) { self.node = node @@ -61,6 +63,11 @@ class MDCCatalogComponentsController: UICollectionViewController { override func viewDidLoad() { super.viewDidLoad() + inkController = MDCInkTouchController(view: self.collectionView!)! + inkController!.addInkView() + inkController!.delaysInkSpread = true + inkController!.delegate = self + let containerView = UIView(frame: self.headerViewController.headerView.bounds) containerView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight] @@ -86,6 +93,8 @@ class MDCCatalogComponentsController: UICollectionViewController { self.headerViewController.headerView.addSubview(containerView) + self.headerViewController.headerView.forwardTouchEventsForView(containerView) + self.headerViewController.headerView.backgroundColor = UIColor.whiteColor() self.headerViewController.headerView.trackingScrollView = self.collectionView @@ -100,13 +109,13 @@ class MDCCatalogComponentsController: UICollectionViewController { override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) - + self.collectionView?.collectionViewLayout.invalidateLayout() self.navigationController?.setNavigationBarHidden(true, animated: animated) } override func willAnimateRotationToInterfaceOrientation( toInterfaceOrientation: UIInterfaceOrientation, duration: NSTimeInterval) { - collectionView?.collectionViewLayout.invalidateLayout() + self.collectionView?.collectionViewLayout.invalidateLayout() } override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { @@ -118,10 +127,30 @@ class MDCCatalogComponentsController: UICollectionViewController { return node.children.count } + func inkViewForView(view: UIView) -> MDCInkView { + let foundInkView = MDCInkTouchController.injectedInkViewForView(view) + foundInkView.inkStyle = .Unbounded + foundInkView.inkColor = UIColor(red: 0.012, green: 0.663, blue: 0.957, alpha: 0.2) + return foundInkView + } + + // MARK: MDCInkTouchControllerDelegate + + func inkTouchController(inkTouchController: MDCInkTouchController, shouldProcessInkTouchesAtTouchLocation location: CGPoint) -> Bool { + return self.collectionView!.indexPathForItemAtPoint(location) != nil + } + + func inkTouchController(inkTouchController: MDCInkTouchController, inkViewAtTouchLocation location: CGPoint) -> MDCInkView { + if let indexPath = self.collectionView!.indexPathForItemAtPoint(location) { + let cell = self.collectionView!.cellForItemAtIndexPath(indexPath) + return self.inkViewForView(cell!) + } + return MDCInkView() + } + // MARK: UICollectionViewDelegate - override func collectionView(collectionView: UICollectionView, - cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { + override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { let cell = collectionView.dequeueReusableCellWithReuseIdentifier("MDCCatalogCollectionViewCell", forIndexPath: indexPath) cell.backgroundColor = UIColor.whiteColor() @@ -131,14 +160,18 @@ class MDCCatalogComponentsController: UICollectionViewController { catalogCell.populateView(componentName) } + // Ensure that ink animations aren't recycled. + MDCInkTouchController.injectedInkViewForView(view).cancelAllAnimationsAnimated(false) + return cell } - func collectionView(collectionView: UICollectionView, - layout collectionViewLayout: UICollectionViewLayout, - sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize { + func collectionView(collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize { let pad = CGFloat(1) - let cellWidth = (self.view.frame.size.width - 3 * pad) / 2 + var cellWidth = (self.view.frame.size.width - 3 * pad) / 2 + if (self.view.frame.size.width > self.view.frame.size.height) { + cellWidth = (self.view.frame.size.width - 4 * pad) / 3 + } return CGSize(width: cellWidth, height: cellWidth * 0.825) } @@ -149,10 +182,11 @@ class MDCCatalogComponentsController: UICollectionViewController { if node.isExample() { vc = node.createExampleViewController() } else { - vc = NodeViewController(node: node) + vc = MDCNodeListViewController(node: node) } self.navigationController?.pushViewController(vc, animated: true) } + } // UIScrollViewDelegate diff --git a/components/ScrollViewDelegateMultiplexer/examples/ObservingPageControl.m b/catalog/MDCCatalog/MDCCatalogTileDataCollections.h similarity index 63% rename from components/ScrollViewDelegateMultiplexer/examples/ObservingPageControl.m rename to catalog/MDCCatalog/MDCCatalogTileDataCollections.h index 042ce5e94d3..0fc06b8237d 100644 --- a/components/ScrollViewDelegateMultiplexer/examples/ObservingPageControl.m +++ b/catalog/MDCCatalog/MDCCatalogTileDataCollections.h @@ -1,5 +1,5 @@ /* - Copyright 2015-present Google Inc. All Rights Reserved. + Copyright 2016-present Google Inc. 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. @@ -14,14 +14,12 @@ limitations under the License. */ -#import "ObservingPageControl.h" +#import -@implementation ObservingPageControl +#import "MDCCatalogTileData.h" -#pragma mark - UIScrollViewDelegate +@interface MDCCatalogTileDataCollections : MDCCatalogTileData -- (void)scrollViewDidScroll:(UIScrollView *)scrollView { - self.currentPage = scrollView.contentOffset.x / scrollView.bounds.size.width + 0.5; -} ++ (UIImage *)drawTileImage:(CGRect)frame; @end diff --git a/catalog/MDCCatalog/MDCCatalogTileDataCollections.m b/catalog/MDCCatalog/MDCCatalogTileDataCollections.m new file mode 100644 index 00000000000..6ef8bf731ec --- /dev/null +++ b/catalog/MDCCatalog/MDCCatalogTileDataCollections.m @@ -0,0 +1,131 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import "MDCCatalogTileDataCollections.h" + +@implementation MDCCatalogTileDataCollections + ++ (UIImage*)drawTileImage:(CGRect)frame { + void (^completionBlock)() = ^() { + [self draw:CGRectMake(0, 0, frame.size.width, frame.size.height)]; + }; + return [self drawImageWithFrame:frame completionBlock:completionBlock]; +} + +/* Auto-generated code using PaintCode and formatted with clang-format. */ ++ (void)draw:(CGRect)frame { + CGContextRef context = UIGraphicsGetCurrentContext(); + + UIColor* fillColor = [UIColor colorWithRed:0.012 green:0.663 blue:0.957 alpha:1]; + UIColor* white40 = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.4]; + UIColor* blue60 = [UIColor colorWithRed:0.012 green:0.663 blue:0.957 alpha:0.6]; + UIColor* white60 = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.7]; + UIColor* white80 = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.8]; + UIColor* white30 = [UIColor colorWithRed:1 green:1 blue:1 alpha:0.3]; + + CGRect group2 = + CGRectMake(CGRectGetMinX(frame) + floor(CGRectGetWidth(frame) * 0.13021 + 0.02) + 0.48, + CGRectGetMinY(frame) + floor(CGRectGetHeight(frame) * 0.15484 - 0.38) + 0.88, + floor(CGRectGetWidth(frame) * 0.86984 - 0.03) - + floor(CGRectGetWidth(frame) * 0.13021 + 0.02) + 0.05, + floor(CGRectGetHeight(frame) * 0.67177 + 0.5) - + floor(CGRectGetHeight(frame) * 0.15484 - 0.38) - 0.88); + + { + UIBezierPath* collectionsPath = [UIBezierPath + bezierPathWithRect:CGRectMake(CGRectGetMinX(group2) + + floor(CGRectGetWidth(group2) * 0.00018 + 0.48) + 0.02, + CGRectGetMinY(group2) + + floor(CGRectGetHeight(group2) * 0.00000 + 0.48) + 0.02, + floor(CGRectGetWidth(group2) * 0.99982 + 0.47) - + floor(CGRectGetWidth(group2) * 0.00018 + 0.48), + floor(CGRectGetHeight(group2) * 0.99969 + 0.38) - + floor(CGRectGetHeight(group2) * 0.00000 + 0.48) + 0.1)]; + [fillColor setFill]; + [collectionsPath fill]; + + { + CGContextSaveGState(context); + CGContextSetAlpha(context, 0.6); + CGContextBeginTransparencyLayer(context, NULL); + + UIBezierPath* rectangle2Path = [UIBezierPath + bezierPathWithRect:CGRectMake(CGRectGetMinX(group2) + + floor(CGRectGetWidth(group2) * 0.00036 + 0.45) + 0.05, + CGRectGetMinY(group2) + + floor(CGRectGetHeight(group2) * 0.00000 + 0.48) + 0.02, + floor(CGRectGetWidth(group2) * 0.66667 - 0.2) - + floor(CGRectGetWidth(group2) * 0.00036 + 0.45) + 0.65, + floor(CGRectGetHeight(group2) * 0.99969 + 0.38) - + floor(CGRectGetHeight(group2) * 0.00000 + 0.48) + 0.1)]; + [white60 setFill]; + [rectangle2Path fill]; + + CGContextEndTransparencyLayer(context); + CGContextRestoreGState(context); + } + + UIBezierPath* rectangle3Path = [UIBezierPath + bezierPathWithRect:CGRectMake(CGRectGetMinX(group2) + + floor(CGRectGetWidth(group2) * 0.00000 + 0.5), + CGRectGetMinY(group2) + + floor(CGRectGetHeight(group2) * 0.00031 + 0.45) + 0.05, + floor(CGRectGetWidth(group2) * 0.33333 + 0.15) - + floor(CGRectGetWidth(group2) * 0.00000 + 0.5) + 0.35, + floor(CGRectGetHeight(group2) * 0.50016 + 0.4) - + floor(CGRectGetHeight(group2) * 0.00031 + 0.45) + 0.05)]; + [white80 setFill]; + [rectangle3Path fill]; + + UIBezierPath* rectangle4Path = [UIBezierPath + bezierPathWithRect:CGRectMake(CGRectGetMinX(group2) + + floor(CGRectGetWidth(group2) * 0.33333 + 0.15) + 0.35, + CGRectGetMinY(group2) + + floor(CGRectGetHeight(group2) * 0.50016 + 0.5), + floor(CGRectGetWidth(group2) * 0.66667 - 0.2) - + floor(CGRectGetWidth(group2) * 0.33333 + 0.15) + 0.35, + floor(CGRectGetHeight(group2) * 1.00000 + 0.45) - + floor(CGRectGetHeight(group2) * 0.50016 + 0.5) + 0.05)]; + [blue60 setFill]; + [rectangle4Path fill]; + + UIBezierPath* rectangle5Path = [UIBezierPath + bezierPathWithRect:CGRectMake(CGRectGetMinX(group2) + + floor(CGRectGetWidth(group2) * 0.66667 - 0.2) + 0.7, + CGRectGetMinY(group2) + + floor(CGRectGetHeight(group2) * 0.00031 + 0.45) + 0.05, + floor(CGRectGetWidth(group2) * 1.00000 + 0.45) - + floor(CGRectGetWidth(group2) * 0.66667 - 0.2) - 0.65, + floor(CGRectGetHeight(group2) * 0.50016 + 0.4) - + floor(CGRectGetHeight(group2) * 0.00031 + 0.45) + 0.05)]; + [white30 setFill]; + [rectangle5Path fill]; + + UIBezierPath* rectangle6Path = [UIBezierPath + bezierPathWithRect:CGRectMake(CGRectGetMinX(group2) + + floor(CGRectGetWidth(group2) * 0.16667 + 0.32) + 0.18, + CGRectGetMinY(group2) + + floor(CGRectGetHeight(group2) * 0.24992 + 0.45) + 0.05, + floor(CGRectGetWidth(group2) * 0.85203 + 0.02) - + floor(CGRectGetWidth(group2) * 0.16667 + 0.32) + 0.3, + floor(CGRectGetHeight(group2) * 0.74977 + 0.4) - + floor(CGRectGetHeight(group2) * 0.24992 + 0.45) + 0.05)]; + [white40 setFill]; + [rectangle6Path fill]; + } +} + +@end diff --git a/catalog/MDCCatalog/MDCCatalogTileDataNavigationBar.m b/catalog/MDCCatalog/MDCCatalogTileDataNavigationBar.m index d9674145fbd..f4e90f07cba 100644 --- a/catalog/MDCCatalog/MDCCatalogTileDataNavigationBar.m +++ b/catalog/MDCCatalog/MDCCatalogTileDataNavigationBar.m @@ -192,8 +192,8 @@ + (void)draw:(CGRect)frame { NSDictionary* labelFontAttributes = @{ NSFontAttributeName : [UIFont fontWithName:@"Roboto-Medium" size:11], - NSForegroundColorAttributeName : textForeground, - NSParagraphStyleAttributeName : labelStyle + NSForegroundColorAttributeName : textForeground, + NSParagraphStyleAttributeName : labelStyle }; CGFloat labelTextHeight = diff --git a/catalog/MDCCatalog/MDCCatalogTile.swift b/catalog/MDCCatalog/MDCCatalogTileView.swift similarity index 95% rename from catalog/MDCCatalog/MDCCatalogTile.swift rename to catalog/MDCCatalog/MDCCatalogTileView.swift index 25e371a9844..964e4dfb266 100644 --- a/catalog/MDCCatalog/MDCCatalogTile.swift +++ b/catalog/MDCCatalog/MDCCatalogTileView.swift @@ -17,7 +17,7 @@ limitations under the License. import UIKit import MaterialComponents -class MDCCatalogTile: UIView { +class MDCCatalogTileView: UIView { private var componentNameString = "Misc" var componentName:String { @@ -78,6 +78,8 @@ class MDCCatalogTile: UIView { newImage = MDCCatalogTileDataButtonBar.drawTileImage(centeredFrame) case "Buttons": newImage = MDCCatalogTileDataButtons.drawTileImage(centeredFrame) + case "Collections": + newImage = MDCCatalogTileDataCollections.drawTileImage(centeredFrame) case "Flexible Header": newImage = MDCCatalogTileDataFlexibleHeader.drawTileImage(centeredFrame) case "Header Stack View": @@ -90,7 +92,7 @@ class MDCCatalogTile: UIView { newImage = MDCCatalogTileDataMisc.drawTileImage(centeredFrame) case "Page Control": newImage = MDCCatalogTileDataPageControl.drawTileImage(centeredFrame) - case "Shadow Layer": + case "Shadow": newImage = MDCCatalogTileDataShadowLayer.drawTileImage(centeredFrame) case "Slider": newImage = MDCCatalogTileDataSlider.drawTileImage(centeredFrame) @@ -98,7 +100,7 @@ class MDCCatalogTile: UIView { newImage = MDCCatalogTileDataSpritedAnimationView.drawTileImage(centeredFrame) case "Switch": newImage = MDCCatalogTileDataSwitch.drawTileImage(centeredFrame) - case "Typography": + case "Typography and Fonts": newImage = MDCCatalogTileDataTypography.drawTileImage(centeredFrame) default: newImage = MDCCatalogTileDataMisc.drawTileImage(centeredFrame) diff --git a/catalog/MDCCatalog/MDCCatalogTiles.h b/catalog/MDCCatalog/MDCCatalogTiles.h index 1c0d8022b89..7432fd5f4b0 100644 --- a/catalog/MDCCatalog/MDCCatalogTiles.h +++ b/catalog/MDCCatalog/MDCCatalogTiles.h @@ -18,6 +18,7 @@ #import "MDCCatalogTileDataAppBar.h" #import "MDCCatalogTileDataButtonBar.h" #import "MDCCatalogTileDataButtons.h" +#import "MDCCatalogTileDataCollections.h" #import "MDCCatalogTileDataFlexibleHeader.h" #import "MDCCatalogTileDataHeaderStackView.h" #import "MDCCatalogTileDataInk.h" @@ -25,7 +26,6 @@ #import "MDCCatalogTileDataNavigationBar.h" #import "MDCCatalogTileDataPageControl.h" #import "MDCCatalogTileDataShadowLayer.h" -#import "MDCCatalogTileDataInk.h" #import "MDCCatalogTileDataSlider.h" #import "MDCCatalogTileDataSpritedAnimationView.h" #import "MDCCatalogTileDataSwitch.h" diff --git a/catalog/MDCCatalog/MDCCatalogTypicalExampleViewController.swift b/catalog/MDCCatalog/MDCCatalogTypicalExampleViewController.swift deleted file mode 100644 index b79b61c60b4..00000000000 --- a/catalog/MDCCatalog/MDCCatalogTypicalExampleViewController.swift +++ /dev/null @@ -1,60 +0,0 @@ -/* -Copyright 2016-present Google Inc. 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. -*/ - -import UIKit -import MaterialComponents - -class MDCCatalogTypicalExampleViewController: UIViewController { - let appBar = MDCAppBar() - var contentViewController = UIViewController() - - init(contentViewController: UIViewController, title: String) { - super.init(nibName: nil, bundle: nil) - assert(contentViewController.view != nil, "expecting a valid contentViewController") - self.contentViewController = contentViewController - self.addChildViewController(contentViewController) - self.title = title - - self.addChildViewController(appBar.headerViewController) - appBar.headerViewController.headerView.backgroundColor = UIColor.whiteColor() - - let headerContentView = appBar.headerViewController.headerView - let lineFrame = CGRectMake(0, headerContentView.frame.height, headerContentView.frame.width, 1) - let line = UIView(frame: lineFrame) - line.backgroundColor = UIColor(white: 0.72, alpha: 1) - line.autoresizingMask = [.FlexibleTopMargin, .FlexibleWidth] - headerContentView.addSubview(line) - } - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - } - - override func viewDidLoad() { - let headerHeight = appBar.headerViewController.headerView.minimumHeight - self.view.addSubview(contentViewController.view) - contentViewController.didMoveToParentViewController(self) - contentViewController.view.frame = - CGRectMake(0, - headerHeight, - self.view.bounds.size.width, - self.view.bounds.size.height - headerHeight) - contentViewController.view.autoresizingMask = [.FlexibleWidth, .FlexibleHeight] - - appBar.addSubviewsToParent() - } - -} diff --git a/catalog/MDCCatalog/MDCCatalogWindow.swift b/catalog/MDCCatalog/MDCCatalogWindow.swift new file mode 100644 index 00000000000..193a2070c1d --- /dev/null +++ b/catalog/MDCCatalog/MDCCatalogWindow.swift @@ -0,0 +1,109 @@ +/* +Copyright 2016-present Google Inc. 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. +*/ + +import UIKit + +/** + A custom UIWindow that displays the user's touches for recording video or demos. + + Triple tapping anywhere will toggle the visible touches. + */ +class MDCCatalogWindow: UIWindow { + var enabled = false + + private let fadeDuration: NSTimeInterval = 0.2 + private var views = [NSNumber: UIView]() + + override func sendEvent(event: UIEvent) { + let touches = event.allTouches()! + for touch in touches { + switch touch.phase { + case .Began: + if (enabled) { + beginDisplayingTouch(touch) + } + continue + case .Moved: + updateTouch(touch) + continue + case .Stationary: + continue + case .Ended: + if (touch.tapCount == 3) { + enabled = !enabled + } + fallthrough + case .Cancelled: + endDisplayingTouch(touch) + continue + } + } + + super.sendEvent(event) + } + + private func beginDisplayingTouch(touch: UITouch) { + let view = MDCTouchView() + view.center = touch.locationInView(self) + views[touch.hash] = view + self.addSubview(view) + } + + private func updateTouch(touch: UITouch) { + views[touch.hash]?.center = touch.locationInView(self) + } + + private func endDisplayingTouch(touch: UITouch) { + let view = views[touch.hash] + views[touch.hash] = nil + + UIView.animateWithDuration(fadeDuration, + animations: { + view?.alpha = 0 + }, + completion: { finished in + view?.removeFromSuperview() + }) + } +} + +/** A circular view that represents a user's touch. */ +class MDCTouchView: UIView { + private let touchCircleSize: CGFloat = 80 + private let touchCircleAlpha: CGFloat = 0.25 + private let touchCircleColor = UIColor.redColor() + private let touchCircleBorderColor = UIColor.blackColor() + private let touchCircleBorderWidth: CGFloat = 1 + + override init(frame: CGRect) { + super.init(frame: frame) + commonMDCTouchViewInit() + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + commonMDCTouchViewInit() + } + + private func commonMDCTouchViewInit() { + self.backgroundColor = touchCircleColor + self.alpha = touchCircleAlpha + self.frame.size = CGSizeMake(touchCircleSize, touchCircleSize) + self.layer.cornerRadius = touchCircleSize / 2 + self.layer.borderColor = touchCircleBorderColor.CGColor + self.layer.borderWidth = touchCircleBorderWidth + } +} diff --git a/catalog/MDCCatalog/MDCInkTouchController+Injection.h b/catalog/MDCCatalog/MDCInkTouchController+Injection.h new file mode 100644 index 00000000000..8ffd4b66992 --- /dev/null +++ b/catalog/MDCCatalog/MDCInkTouchController+Injection.h @@ -0,0 +1,36 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +@interface MDCInkTouchController (ManyInkViews) + +/** + Enumerates the given view's subviews for an instance of MDCInkView and returns it if found, or + creates and adds a new instance of MDCInkView if not. + + This method is a convenience method for adding ink to an arbitrary view without needing to subclass + the target view. Use this method in situations where you expect there to be many distinct ink views + in existence for a single ink touch controller. Example scenarios include: + + - Adding ink to individual collection view/table view cells + + This method can be used in your MDCInkTouchController delegate's + -inkTouchController:inkViewAtTouchLocation; implementation. + */ ++ (nonnull MDCInkView *)injectedInkViewForView:(nonnull UIView *)view; + +@end diff --git a/catalog/MDCCatalog/MDCInkTouchController+Injection.m b/catalog/MDCCatalog/MDCInkTouchController+Injection.m new file mode 100644 index 00000000000..cf67de3ec5d --- /dev/null +++ b/catalog/MDCCatalog/MDCInkTouchController+Injection.m @@ -0,0 +1,37 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import "MDCInkTouchController+Injection.h" + +@implementation MDCInkTouchController (ManyInkViews) + ++ (MDCInkView *)injectedInkViewForView:(UIView *)view { + MDCInkView *foundInkView = nil; + for (MDCInkView *subview in view.subviews) { + if ([subview isKindOfClass:[MDCInkView class]]) { + foundInkView = subview; + break; + } + } + if (!foundInkView) { + foundInkView = [[MDCInkView alloc] initWithFrame:view.bounds]; + foundInkView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [view addSubview:foundInkView]; + } + return foundInkView; +} + +@end diff --git a/catalog/MDCCatalog/NodeViewController.swift b/catalog/MDCCatalog/MDCNodeListViewController.swift similarity index 81% rename from catalog/MDCCatalog/NodeViewController.swift rename to catalog/MDCCatalog/MDCNodeListViewController.swift index a1266ef1c3a..7487d726d99 100644 --- a/catalog/MDCCatalog/NodeViewController.swift +++ b/catalog/MDCCatalog/MDCNodeListViewController.swift @@ -33,13 +33,12 @@ class NodeViewTableViewDemoCell: UITableViewCell { } -class NodeViewController: CBCNodeListViewController { +class MDCNodeListViewController: CBCNodeListViewController { let appBar = MDCAppBar() let sectionNames = ["Description", "Additional Examples"] let descriptionSectionHeight = CGFloat(100) let additionalExamplesSectionHeight = CGFloat(50) let rowHeight = CGFloat(50) - let footerHeight = CGFloat(20) var componentDescription = "" enum Section: Int { @@ -54,8 +53,8 @@ class NodeViewController: CBCNodeListViewController { let orderedNodes = NSMutableArray() for childNode in node.children { if childNode.isExample() { - let contentVC = childNode.createExampleViewController() - if contentVC.respondsToSelector("catalogIsPrimaryDemo") { + let isPrimaryDemo = childNode.isPrimaryDemo() + if isPrimaryDemo { orderedNodes.insertObject(childNode, atIndex: 0) componentDescription = childNode.createExampleDescription() } else { @@ -74,7 +73,6 @@ class NodeViewController: CBCNodeListViewController { line.backgroundColor = UIColor(white: 0.72, alpha: 1) line.autoresizingMask = [.FlexibleTopMargin, .FlexibleWidth] headerContentView.addSubview(line) - self.tableView.backgroundColor = UIColor.whiteColor() } override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { @@ -87,7 +85,10 @@ class NodeViewController: CBCNodeListViewController { override func viewDidLoad() { super.viewDidLoad() - self.tableView.separatorColor = UIColor.clearColor() + + self.tableView.backgroundColor = UIColor.whiteColor() + self.tableView.separatorStyle = .None + appBar.headerViewController.headerView.trackingScrollView = self.tableView appBar.addSubviewsToParent() @@ -150,10 +151,12 @@ class NodeViewController: CBCNodeListViewController { let sectionView = UIView(frame: sectionViewFrame) sectionView.backgroundColor = UIColor.whiteColor() - let lineDivider = UIView(frame: CGRectMake(0,0,tableView.frame.size.width, 1)) - lineDivider.backgroundColor = UIColor(white: 0.85, alpha: 1) - lineDivider.autoresizingMask = .FlexibleWidth - sectionView.addSubview(lineDivider) + if section == 1 { + let lineDivider = UIView(frame: CGRectMake(0, 0, tableView.frame.size.width, 1)) + lineDivider.backgroundColor = UIColor(white: 0.85, alpha: 1) + lineDivider.autoresizingMask = .FlexibleWidth + sectionView.addSubview(lineDivider) + } let label = UILabel() label.text = sectionNames[section] @@ -190,12 +193,6 @@ class NodeViewController: CBCNodeListViewController { return additionalExamplesSectionHeight } - override func tableView(tableView: UITableView, - willDisplayFooterView view: UIView, forSection section: Int) { - let footerView = view as! UITableViewHeaderFooterView - footerView.contentView.backgroundColor = UIColor.whiteColor() - } - override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { if (section == Section.Description.rawValue) { return 1 @@ -227,11 +224,6 @@ class NodeViewController: CBCNodeListViewController { return rowHeight } - override func tableView(tableView: UITableView, - heightForFooterInSection section: Int) -> CGFloat { - return footerHeight - } - // MARK: UITableViewDelegate override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { @@ -246,11 +238,37 @@ class NodeViewController: CBCNodeListViewController { if contentVC.respondsToSelector("catalogShouldHideNavigation") { vc = contentVC } else { - vc = MDCCatalogTypicalExampleViewController(contentViewController: contentVC, - title: node.title) + let container = MDCAppBarContainerViewController(contentViewController: contentVC) + + // TODO(featherless): Remove once + // https://github.com/google/material-components-ios/issues/367 is resolved. + contentVC.title = node.title + + let headerView = container.appBar.headerViewController.headerView + + headerView.backgroundColor = UIColor.whiteColor() + + let textColor = UIColor(white: 0, alpha: 0.8) + UIBarButtonItem.appearance().setTitleTextAttributes( + [NSForegroundColorAttributeName:textColor], + forState: .Normal) + + let lineFrame = CGRectMake(0, headerView.bounds.height, headerView.bounds.width, 1) + let line = UIView(frame: lineFrame) + line.backgroundColor = UIColor(white: 0.72, alpha: 1) + line.autoresizingMask = [.FlexibleTopMargin, .FlexibleWidth] + headerView.addSubview(line) + + var contentFrame = container.contentViewController.view.frame + let headerSize = headerView.sizeThatFits(container.contentViewController.view.frame.size) + contentFrame.origin.y = headerSize.height + contentFrame.size.height = self.view.bounds.size.height - headerSize.height + container.contentViewController.view.frame = contentFrame + + vc = container } } else { - vc = NodeViewController(node: node) + vc = MDCNodeListViewController(node: node) } self.navigationController?.pushViewController(vc, animated: true) } diff --git a/catalog/Podfile.lock b/catalog/Podfile.lock index 9f4155ef1b1..1bcce02f66a 100644 --- a/catalog/Podfile.lock +++ b/catalog/Podfile.lock @@ -1,24 +1,26 @@ PODS: - - MaterialComponents (4.0.1): - - MaterialComponents/AppBar (= 4.0.1) - - MaterialComponents/ButtonBar (= 4.0.1) - - MaterialComponents/Buttons (= 4.0.1) - - MaterialComponents/FlexibleHeader (= 4.0.1) - - MaterialComponents/FontDiskLoader (= 4.0.1) - - MaterialComponents/HeaderStackView (= 4.0.1) - - MaterialComponents/Ink (= 4.0.1) - - MaterialComponents/NavigationBar (= 4.0.1) - - MaterialComponents/PageControl (= 4.0.1) - - MaterialComponents/private (= 4.0.1) - - MaterialComponents/RobotoFontLoader (= 4.0.1) - - MaterialComponents/ScrollViewDelegateMultiplexer (= 4.0.1) - - MaterialComponents/ShadowElevations (= 4.0.1) - - MaterialComponents/ShadowLayer (= 4.0.1) - - MaterialComponents/Slider (= 4.0.1) - - MaterialComponents/SpritedAnimationView (= 4.0.1) - - MaterialComponents/Switch (= 4.0.1) - - MaterialComponents/Typography (= 4.0.1) - - MaterialComponents/AppBar (4.0.1): + - MaterialComponents (5.0.0): + - MaterialComponents/AppBar (= 5.0.0) + - MaterialComponents/ButtonBar (= 5.0.0) + - MaterialComponents/Buttons (= 5.0.0) + - MaterialComponents/CollectionCells (= 5.0.0) + - MaterialComponents/CollectionLayoutAttributes (= 5.0.0) + - MaterialComponents/Collections (= 5.0.0) + - MaterialComponents/FlexibleHeader (= 5.0.0) + - MaterialComponents/FontDiskLoader (= 5.0.0) + - MaterialComponents/HeaderStackView (= 5.0.0) + - MaterialComponents/Ink (= 5.0.0) + - MaterialComponents/NavigationBar (= 5.0.0) + - MaterialComponents/PageControl (= 5.0.0) + - MaterialComponents/private (= 5.0.0) + - MaterialComponents/RobotoFontLoader (= 5.0.0) + - MaterialComponents/ShadowElevations (= 5.0.0) + - MaterialComponents/ShadowLayer (= 5.0.0) + - MaterialComponents/Slider (= 5.0.0) + - MaterialComponents/SpritedAnimationView (= 5.0.0) + - MaterialComponents/Switch (= 5.0.0) + - MaterialComponents/Typography (= 5.0.0) + - MaterialComponents/AppBar (5.0.0): - MaterialComponents/FlexibleHeader - MaterialComponents/HeaderStackView - MaterialComponents/NavigationBar @@ -26,52 +28,87 @@ PODS: - MaterialComponents/ShadowElevations - MaterialComponents/ShadowLayer - MaterialComponents/Typography - - MaterialComponents/ButtonBar (4.0.1): + - MaterialComponents/ButtonBar (5.0.0): - MaterialComponents/Buttons - - MaterialComponents/Buttons (4.0.1): + - MaterialComponents/Buttons (5.0.0): - MaterialComponents/Ink - MaterialComponents/ShadowElevations - MaterialComponents/ShadowLayer - MaterialComponents/Typography - - MaterialComponents/FlexibleHeader (4.0.1) - - MaterialComponents/FontDiskLoader (4.0.1) - - MaterialComponents/HeaderStackView (4.0.1) - - MaterialComponents/Ink (4.0.1) - - MaterialComponents/NavigationBar (4.0.1): + - MaterialComponents/CollectionCells (5.0.0): + - MaterialComponents/CollectionLayoutAttributes + - MaterialComponents/Ink + - MaterialComponents/private/Icons/ic_check + - MaterialComponents/private/Icons/ic_check_circle + - MaterialComponents/private/Icons/ic_chevron_right + - MaterialComponents/private/Icons/ic_info + - MaterialComponents/private/Icons/ic_radio_button_unchecked + - MaterialComponents/private/Icons/ic_reorder + - MaterialComponents/Typography + - MaterialComponents/CollectionLayoutAttributes (5.0.0) + - MaterialComponents/Collections (5.0.0): + - MaterialComponents/CollectionCells + - MaterialComponents/CollectionLayoutAttributes + - MaterialComponents/Ink + - MaterialComponents/ShadowElevations + - MaterialComponents/ShadowLayer + - MaterialComponents/Typography + - MaterialComponents/FlexibleHeader (5.0.0) + - MaterialComponents/FontDiskLoader (5.0.0) + - MaterialComponents/HeaderStackView (5.0.0) + - MaterialComponents/Ink (5.0.0) + - MaterialComponents/NavigationBar (5.0.0): - MaterialComponents/ButtonBar - MaterialComponents/Typography - - MaterialComponents/PageControl (4.0.1) - - MaterialComponents/private (4.0.1): - - MaterialComponents/private/Color (= 4.0.1) - - MaterialComponents/private/Icons (= 4.0.1) - - MaterialComponents/private/ThumbTrack (= 4.0.1) - - MaterialComponents/private/Color (4.0.1) - - MaterialComponents/private/Icons (4.0.1): - - MaterialComponents/private/Icons/Base (= 4.0.1) - - MaterialComponents/private/Icons/ic_arrow_back (= 4.0.1) - - MaterialComponents/private/Icons/Base (4.0.1) - - MaterialComponents/private/Icons/ic_arrow_back (4.0.1): + - MaterialComponents/PageControl (5.0.0) + - MaterialComponents/private (5.0.0): + - MaterialComponents/private/Color (= 5.0.0) + - MaterialComponents/private/Icons (= 5.0.0) + - MaterialComponents/private/ThumbTrack (= 5.0.0) + - MaterialComponents/private/Color (5.0.0) + - MaterialComponents/private/Icons (5.0.0): + - MaterialComponents/private/Icons/Base (= 5.0.0) + - MaterialComponents/private/Icons/ic_arrow_back (= 5.0.0) + - MaterialComponents/private/Icons/ic_check (= 5.0.0) + - MaterialComponents/private/Icons/ic_check_circle (= 5.0.0) + - MaterialComponents/private/Icons/ic_chevron_right (= 5.0.0) + - MaterialComponents/private/Icons/ic_info (= 5.0.0) + - MaterialComponents/private/Icons/ic_radio_button_unchecked (= 5.0.0) + - MaterialComponents/private/Icons/ic_reorder (= 5.0.0) + - MaterialComponents/private/Icons/Base (5.0.0) + - MaterialComponents/private/Icons/ic_arrow_back (5.0.0): + - MaterialComponents/private/Icons/Base + - MaterialComponents/private/Icons/ic_check (5.0.0): + - MaterialComponents/private/Icons/Base + - MaterialComponents/private/Icons/ic_check_circle (5.0.0): + - MaterialComponents/private/Icons/Base + - MaterialComponents/private/Icons/ic_chevron_right (5.0.0): + - MaterialComponents/private/Icons/Base + - MaterialComponents/private/Icons/ic_info (5.0.0): + - MaterialComponents/private/Icons/Base + - MaterialComponents/private/Icons/ic_radio_button_unchecked (5.0.0): + - MaterialComponents/private/Icons/Base + - MaterialComponents/private/Icons/ic_reorder (5.0.0): - MaterialComponents/private/Icons/Base - - MaterialComponents/private/ThumbTrack (4.0.1): + - MaterialComponents/private/ThumbTrack (5.0.0): - MaterialComponents/Ink - MaterialComponents/private/Color - MaterialComponents/ShadowElevations - MaterialComponents/ShadowLayer - - MaterialComponents/RobotoFontLoader (4.0.1): + - MaterialComponents/RobotoFontLoader (5.0.0): - MaterialComponents/FontDiskLoader - MaterialComponents/Typography - - MaterialComponents/ScrollViewDelegateMultiplexer (4.0.1) - - MaterialComponents/ShadowElevations (4.0.1) - - MaterialComponents/ShadowLayer (4.0.1) - - MaterialComponents/Slider (4.0.1): + - MaterialComponents/ShadowElevations (5.0.0) + - MaterialComponents/ShadowLayer (5.0.0) + - MaterialComponents/Slider (5.0.0): - MaterialComponents/private/ThumbTrack - - MaterialComponents/SpritedAnimationView (4.0.1) - - MaterialComponents/Switch (4.0.1): + - MaterialComponents/SpritedAnimationView (5.0.0) + - MaterialComponents/Switch (5.0.0): - MaterialComponents/private/ThumbTrack - - MaterialComponents/Typography (4.0.1) - - MaterialComponentsCatalog (4.0.1): + - MaterialComponents/Typography (5.0.0) + - MaterialComponentsCatalog (5.0.0): - MaterialComponents - - MaterialComponentsUnitTests (4.0.1): + - MaterialComponentsUnitTests (5.0.0): - MaterialComponents DEPENDENCIES: @@ -88,8 +125,8 @@ EXTERNAL SOURCES: :path: ../ SPEC CHECKSUMS: - MaterialComponents: 0f57dfb792eee175d9c287e6b5778cbeb60fcf0b - MaterialComponentsCatalog: 4769fcabbbdb4b40373eb428f329dbc4c2b53dac - MaterialComponentsUnitTests: 8fa9520a8dae5ee0b7962ce449619b5be13445e6 + MaterialComponents: 5de833a54f1cd86c5826c605d8fb00db0d5c4626 + MaterialComponentsCatalog: 3c3164b20890756c6701ec09d1420c3d2114c876 + MaterialComponentsUnitTests: 37830e7944154daea9f6aea9664249635e03ce00 COCOAPODS: 0.39.0 diff --git a/community/contributor_guides/supported_versions.md b/community/contributor_guides/supported_versions.md deleted file mode 100644 index 2b5aa5b362a..00000000000 --- a/community/contributor_guides/supported_versions.md +++ /dev/null @@ -1,16 +0,0 @@ -# Supported versions - -The purpose of this document is to clearly communicate which versions of specific technologes the -core team uses to build Material components. - -## Xcode - -The core team uses **Xcode 7.2.1** and supports compilation of both Swift and Objective-C code to -the corresponding language versions. - -New Swift language features will be introduced in examples only once the team moves to a new version -of Xcode. - -## iOS - -All components are expected to support **iOS 7.0 and above**. diff --git a/components/AppBar/.jazzy.yaml b/components/AppBar/.jazzy.yaml index d9a45a8d6c9..73e0cd1ece1 100644 --- a/components/AppBar/.jazzy.yaml +++ b/components/AppBar/.jazzy.yaml @@ -1,4 +1,5 @@ -module_name: AppBar +# Auto-generated by scripts/generate_jazzy_yamls.sh. Used primarily by scripts/external/arc-jazzy-linter. +module: AppBar umbrella_header: src/MaterialAppBar.h objc: true sdk: iphonesimulator diff --git a/components/AppBar/README.md b/components/AppBar/README.md index 33766fa9e4a..982c932184c 100644 --- a/components/AppBar/README.md +++ b/components/AppBar/README.md @@ -1,7 +1,7 @@ --- title: "App Bar" layout: detail -section: documentation +section: components excerpt: "The App Bar is a flexible navigation bar designed to provide a typical Material Design navigation experience." --- # App Bar @@ -202,7 +202,7 @@ Flexible Header view's background color. Learn more by reading the section on ### UINavigationItem and the App Bar The App Bar begins mirroring the state of your view controller's `navigationItem` in the provided -`navigationBar` once you call `MDCAppBarAddViews`. +`navigationBar` once you call `addSubviewsToParent`. Learn more by reading the Navigation Bar section on [Observing UINavigationItem instances](../NavigationBar/#observing-uinavigationitem-instances). @@ -233,7 +233,7 @@ background image. This is not trivial to do with the App Bar APIs due to considerations being discussed in [Issue #184](https://github.com/google/material-components-ios/issues/184). -The heart of the limitation is that we're using a view (`headerStackView) to lay out the Navigation +The heart of the limitation is that we're using a view (`headerStackView`) to lay out the Navigation Bar. If you add a background view behind the `headerStackView` instance then `headerStackView` will end up eating all of your touch events. diff --git a/components/AppBar/examples/AppBarDelegateForwardingExample.m b/components/AppBar/examples/AppBarDelegateForwardingExample.m index 65a554143f6..7c23e50e441 100644 --- a/components/AppBar/examples/AppBarDelegateForwardingExample.m +++ b/components/AppBar/examples/AppBarDelegateForwardingExample.m @@ -35,6 +35,9 @@ - (void)viewDidLoad { self.appBar.headerViewController.headerView.trackingScrollView = self.tableView; [self.appBar addSubviewsToParent]; + + self.tableView.layoutMargins = UIEdgeInsetsZero; + self.tableView.separatorInset = UIEdgeInsetsZero; } #pragma mark - UIScrollViewDelegate @@ -76,7 +79,7 @@ - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView @implementation AppBarDelegateForwardingExample (CatalogByConvention) + (NSArray *)catalogBreadcrumbs { - return @[ @"App Bar", @"Delegate forwarding" ]; + return @[ @"App Bar", @"Delegate Forwarding" ]; } - (BOOL)catalogShouldHideNavigation { @@ -87,14 +90,14 @@ - (BOOL)catalogShouldHideNavigation { @implementation AppBarDelegateForwardingExample (TypicalUse) -- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { - self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; +- (id)init { + self = [super init]; if (self) { _appBar = [[MDCAppBar alloc] init]; _appBar.navigationBar.tintColor = [UIColor whiteColor]; [self addChildViewController:_appBar.headerViewController]; - self.title = @"Delegate forwarding"; + self.title = @"Delegate Forwarding"; UIColor *color = [UIColor colorWithRed:(CGFloat)0x03 / (CGFloat)255 green:(CGFloat)0xA9 / (CGFloat)255 @@ -136,7 +139,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"]; } - cell.textLabel.text = [NSString stringWithFormat:@"%ld", (long)indexPath.row]; + cell.layoutMargins = UIEdgeInsetsZero; return cell; } diff --git a/components/AppBar/examples/AppBarDelegateForwardingExample.swift b/components/AppBar/examples/AppBarDelegateForwardingExample.swift index c95a6efeb81..09ec54fb768 100644 --- a/components/AppBar/examples/AppBarDelegateForwardingExample.swift +++ b/components/AppBar/examples/AppBarDelegateForwardingExample.swift @@ -23,6 +23,28 @@ class AppBarDelegateForwardingExample: UITableViewController { let appBar = MDCAppBar() + convenience init() { + self.init(style: .Plain) + } + + override init(style: UITableViewStyle) { + super.init(style: style) + + self.appBar.navigationBar.tintColor = UIColor.whiteColor() + + self.addChildViewController(appBar.headerViewController) + + self.title = "Delegate Forwarding" + + let color = UIColor( + red: CGFloat(0x03) / CGFloat(255), + green: CGFloat(0xA9) / CGFloat(255), + blue: CGFloat(0xF4) / CGFloat(255), + alpha: 1) + appBar.headerViewController.headerView.backgroundColor = color + appBar.navigationBar.tintColor = UIColor.whiteColor() + } + override func viewDidLoad() { super.viewDidLoad() @@ -32,6 +54,9 @@ class AppBarDelegateForwardingExample: UITableViewController { appBar.headerViewController.headerView.trackingScrollView = self.tableView appBar.addSubviewsToParent() + + self.tableView.layoutMargins = UIEdgeInsetsZero + self.tableView.separatorInset = UIEdgeInsetsZero } // The following four methods must be forwarded to the tracking scroll view in order to implement @@ -68,7 +93,7 @@ class AppBarDelegateForwardingExample: UITableViewController { override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) - self.title = "Delegate forwarding (Swift)" + self.title = "Delegate Forwarding (Swift)" self.addChildViewController(appBar.headerViewController) @@ -89,7 +114,7 @@ class AppBarDelegateForwardingExample: UITableViewController { // MARK: Catalog by convention extension AppBarDelegateForwardingExample { class func catalogBreadcrumbs() -> [String] { - return ["App Bar", "Delegate forwarding (Swift)"] + return ["App Bar", "Delegate Forwarding (Swift)"] } func catalogShouldHideNavigation() -> Bool { return true @@ -128,7 +153,7 @@ extension AppBarDelegateForwardingExample { if cell == nil { cell = UITableViewCell(style: .Default, reuseIdentifier: "cell") } - cell!.textLabel!.text = "\(indexPath.row)" + cell!.layoutMargins = UIEdgeInsetsZero return cell! } diff --git a/components/AppBar/examples/AppBarImageryExample.m b/components/AppBar/examples/AppBarImageryExample.m index 5b30281843f..7a0b4488b80 100644 --- a/components/AppBar/examples/AppBarImageryExample.m +++ b/components/AppBar/examples/AppBarImageryExample.m @@ -55,6 +55,9 @@ - (void)viewDidLoad { self.tableView.delegate = self.appBar.headerViewController; [self.appBar addSubviewsToParent]; + + self.tableView.layoutMargins = UIEdgeInsetsZero; + self.tableView.separatorInset = UIEdgeInsetsZero; } - (UIStatusBarStyle)preferredStatusBarStyle { @@ -73,8 +76,8 @@ - (UIImage *)headerBackgroundImage { @implementation AppBarImageryExample (TypicalUse) -- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { - self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; +- (id)init { + self = [super init]; if (self) { _appBar = [[MDCAppBar alloc] init]; @@ -119,7 +122,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"]; } - cell.textLabel.text = [NSString stringWithFormat:@"%ld", (long)indexPath.row]; + cell.layoutMargins = UIEdgeInsetsZero; return cell; } diff --git a/components/AppBar/examples/AppBarImageryExample.swift b/components/AppBar/examples/AppBarImageryExample.swift index 761747487ee..85098f812c9 100644 --- a/components/AppBar/examples/AppBarImageryExample.swift +++ b/components/AppBar/examples/AppBarImageryExample.swift @@ -61,8 +61,8 @@ class AppBarImagerySwiftExample: UITableViewController { // MARK: Typical configuration - override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { - super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + init() { + super.init(nibName: nil, bundle: nil) self.title = "Imagery (Swift)" @@ -106,7 +106,7 @@ extension AppBarImagerySwiftExample { if cell == nil { cell = UITableViewCell(style: .Default, reuseIdentifier: "cell") } - cell!.textLabel!.text = "\(indexPath.row)" + cell!.layoutMargins = UIEdgeInsetsZero return cell! } } diff --git a/components/AppBar/examples/AppBarTypicalUseExample.m b/components/AppBar/examples/AppBarTypicalUseExample.m index eff634e061e..d282ed61efc 100644 --- a/components/AppBar/examples/AppBarTypicalUseExample.m +++ b/components/AppBar/examples/AppBarTypicalUseExample.m @@ -27,21 +27,13 @@ @interface AppBarTypicalUseExample : UITableViewController @implementation AppBarTypicalUseExample -- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { - self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; +- (id)init { + self = [super init]; if (self) { // Step 2: Initialize the App Bar and add the headerViewController as a child. _appBar = [[MDCAppBar alloc] init]; - _appBar.navigationBar.tintColor = [UIColor whiteColor]; [self addChildViewController:_appBar.headerViewController]; - self.title = @"App Bar"; - - UIColor *color = [UIColor colorWithRed:(CGFloat)0x03 / (CGFloat)255 - green:(CGFloat)0xA9 / (CGFloat)255 - blue:(CGFloat)0xF4 / (CGFloat)255 - alpha:1]; - _appBar.headerViewController.headerView.backgroundColor = color; } return self; } @@ -59,6 +51,17 @@ - (void)viewDidLoad { // Step 3: Register the App Bar views. [self.appBar addSubviewsToParent]; + + // Optional: Change the App Bar's background color and tint color. + UIColor *color = [UIColor colorWithRed:(CGFloat)0x03 / (CGFloat)255 + green:(CGFloat)0xA9 / (CGFloat)255 + blue:(CGFloat)0xF4 / (CGFloat)255 + alpha:1]; + _appBar.headerViewController.headerView.backgroundColor = color; + _appBar.navigationBar.tintColor = [UIColor whiteColor]; + + self.tableView.layoutMargins = UIEdgeInsetsZero; + self.tableView.separatorInset = UIEdgeInsetsZero; } // Optional step: If you allow the header view to hide the status bar you must implement this @@ -94,7 +97,7 @@ + (NSString *)catalogDescription { " navigation experience."; } -- (BOOL)catalogIsPrimaryDemo { ++ (BOOL)catalogIsPrimaryDemo { return YES; } @@ -119,7 +122,7 @@ - (UITableViewCell *)tableView:(UITableView *)tableView cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"]; } - cell.textLabel.text = [NSString stringWithFormat:@"%ld", (long)indexPath.row]; + cell.layoutMargins = UIEdgeInsetsZero; return cell; } diff --git a/components/AppBar/examples/AppBarTypicalUseExample.swift b/components/AppBar/examples/AppBarTypicalUseExample.swift index 0bfaa556b8f..099fec05da4 100644 --- a/components/AppBar/examples/AppBarTypicalUseExample.swift +++ b/components/AppBar/examples/AppBarTypicalUseExample.swift @@ -22,8 +22,8 @@ class AppBarTypicalUseSwiftExample: UITableViewController { // Step 1: Create and initialize an App Bar. let appBar = MDCAppBar() - override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { - super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + init() { + super.init(nibName: nil, bundle: nil) self.title = "App Bar (Swift)" @@ -56,6 +56,9 @@ class AppBarTypicalUseSwiftExample: UITableViewController { // Step 3: Register the App Bar views. appBar.addSubviewsToParent() + + self.tableView.layoutMargins = UIEdgeInsetsZero + self.tableView.separatorInset = UIEdgeInsetsZero } // Optional step: If you allow the header view to hide the status bar you must implement this @@ -82,7 +85,7 @@ class AppBarTypicalUseSwiftExample: UITableViewController { // MARK: Catalog by convention extension AppBarTypicalUseSwiftExample { class func catalogBreadcrumbs() -> [String] { - return ["App Bar", "Basic (Swift)"] + return ["App Bar", "App Bar (Swift)"] } func catalogShouldHideNavigation() -> Bool { return true @@ -105,7 +108,7 @@ extension AppBarTypicalUseSwiftExample { if cell == nil { cell = UITableViewCell(style: .Default, reuseIdentifier: "cell") } - cell!.textLabel!.text = "\(indexPath.row)" + cell!.layoutMargins = UIEdgeInsetsZero return cell! } diff --git a/components/AppBar/src/MDCAppBar.m b/components/AppBar/src/MDCAppBar.m index f27217a4344..3aa4ff21a5c 100644 --- a/components/AppBar/src/MDCAppBar.m +++ b/components/AppBar/src/MDCAppBar.m @@ -78,6 +78,10 @@ - (void)addHeaderViewControllerToParentViewController:(nonnull UIViewController - (void)addSubviewsToParent { MDCFlexibleHeaderViewController *fhvc = self.headerViewController; + NSAssert(fhvc.parentViewController, + @"headerViewController does not have a parentViewController. " + @"Use [self addChildViewController:appBar.headerViewController]. " + @"This warning only appears in DEBUG builds"); if (fhvc.view.superview == fhvc.parentViewController.view) { return; } diff --git a/components/AppBar/src/MDCAppBarContainerViewController.m b/components/AppBar/src/MDCAppBarContainerViewController.m index ef6f9d60332..6af9de3bba0 100644 --- a/components/AppBar/src/MDCAppBarContainerViewController.m +++ b/components/AppBar/src/MDCAppBarContainerViewController.m @@ -35,7 +35,8 @@ - (instancetype)initWithContentViewController:(UIViewController *)contentViewCon [self addChildViewController:_appBar.headerViewController]; - self.contentViewController = contentViewController; + _contentViewController = contentViewController; + [self addChildViewController:contentViewController]; } return self; } @@ -80,30 +81,4 @@ - (MDCFlexibleHeaderViewController *)headerViewController { return self.appBar.headerViewController; } -- (void)setContentViewController:(UIViewController *)contentViewController { - if (_contentViewController == contentViewController) { - return; - } - - // Teardown of the old controller - - [_contentViewController willMoveToParentViewController:nil]; - if ([_contentViewController isViewLoaded]) { - [_contentViewController.view removeFromSuperview]; - } - [_contentViewController removeFromParentViewController]; - - // Setup of the new controller - - _contentViewController = contentViewController; - - [self addChildViewController:contentViewController]; - - NSAssert(![self isViewLoaded], - @"View should not have been loaded at this point." - @" Verify that the view is not being accessed anywhere in %@ or %@.", - NSStringFromSelector(_cmd), - NSStringFromSelector(@selector(initWithContentViewController:))); -} - @end diff --git a/components/ButtonBar/.jazzy.yaml b/components/ButtonBar/.jazzy.yaml index 968497ec668..04760b74d4b 100644 --- a/components/ButtonBar/.jazzy.yaml +++ b/components/ButtonBar/.jazzy.yaml @@ -1,4 +1,5 @@ -module_name: ButtonBar +# Auto-generated by scripts/generate_jazzy_yamls.sh. Used primarily by scripts/external/arc-jazzy-linter. +module: ButtonBar umbrella_header: src/MaterialButtonBar.h objc: true sdk: iphonesimulator diff --git a/components/ButtonBar/README.md b/components/ButtonBar/README.md index fd36979f838..c982f5c7d27 100644 --- a/components/ButtonBar/README.md +++ b/components/ButtonBar/README.md @@ -1,7 +1,7 @@ --- title: "Button Bar" layout: detail -section: documentation +section: components excerpt: "The Button Bar component is a view that facilitates the creation and layout of a horizontally-aligned list of buttons." --- # Button Bar diff --git a/components/ButtonBar/examples/ButtonBarTypicalUseExample.m b/components/ButtonBar/examples/ButtonBarTypicalUseExample.m index 974c766a06e..da43345c0a1 100644 --- a/components/ButtonBar/examples/ButtonBarTypicalUseExample.m +++ b/components/ButtonBar/examples/ButtonBarTypicalUseExample.m @@ -92,7 +92,7 @@ + (NSArray *)catalogBreadcrumbs { return @[ @"Button Bar", @"Button Bar" ]; } -- (BOOL)catalogIsPrimaryDemo { ++ (BOOL)catalogIsPrimaryDemo { return YES; } @@ -107,10 +107,10 @@ + (NSString *)catalogDescription { @implementation ButtonBarTypicalUseExample (GeneralApplicationLogic) -- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { - self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; +- (id)init { + self = [super init]; if (self) { - self.title = @"Typical use"; + self.title = @"Button Bar"; } return self; } diff --git a/components/ButtonBar/examples/ButtonBarTypicalUseExample.swift b/components/ButtonBar/examples/ButtonBarTypicalUseExample.swift index 73245d2a9f9..d9a050d537a 100644 --- a/components/ButtonBar/examples/ButtonBarTypicalUseExample.swift +++ b/components/ButtonBar/examples/ButtonBarTypicalUseExample.swift @@ -70,10 +70,10 @@ class ButtonBarTypicalUseSwiftExample: UIViewController { } // MARK: Typical application code (not Material-specific) - override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { - super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + init() { + super.init(nibName: nil, bundle: nil) - self.title = "Typical use" + self.title = "Button Bar" } required init?(coder aDecoder: NSCoder) { @@ -84,7 +84,7 @@ class ButtonBarTypicalUseSwiftExample: UIViewController { // MARK: Catalog by convention extension ButtonBarTypicalUseSwiftExample { class func catalogBreadcrumbs() -> [String] { - return ["Button Bar", "Demo (Swift)"] + return ["Button Bar", "Button Bar (Swift)"] } } diff --git a/components/ButtonBar/src/private/MDCAppBarButtonBarBuilder.m b/components/ButtonBar/src/private/MDCAppBarButtonBarBuilder.m index 26c9ee97c73..3491135cbea 100644 --- a/components/ButtonBar/src/private/MDCAppBarButtonBarBuilder.m +++ b/components/ButtonBar/src/private/MDCAppBarButtonBarBuilder.m @@ -202,7 +202,15 @@ - (void)updateButton:(UIButton *)button barMetrics:(UIBarMetrics)barMetrics { NSString *title = item.title ?: @""; if ([UIButton instancesRespondToSelector:@selector(setAttributedTitle:forState:)]) { - NSDictionary *attributes = [item titleTextAttributesForState:state]; + NSMutableDictionary *attributes = [NSMutableDictionary dictionary]; + + // UIBarButtonItem's appearance proxy values don't appear to come "for free" like they do with + // typical UIView instances, so we're attempting to recreate the behavior here. + NSArray *appearanceProxies = @[ [item.class appearance] ]; + for (UIBarButtonItem *appearance in appearanceProxies) { + [attributes addEntriesFromDictionary:[appearance titleTextAttributesForState:state]]; + } + [attributes addEntriesFromDictionary:[item titleTextAttributesForState:state]]; if ([attributes count] > 0) { [button setAttributedTitle:[[NSAttributedString alloc] initWithString:title attributes:attributes] diff --git a/components/ButtonBar/tests/unit/ButtonBarIssue370Tests.swift b/components/ButtonBar/tests/unit/ButtonBarIssue370Tests.swift new file mode 100644 index 00000000000..3ed7689888b --- /dev/null +++ b/components/ButtonBar/tests/unit/ButtonBarIssue370Tests.swift @@ -0,0 +1,113 @@ +/* +Copyright 2016-present Google Inc. 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. +*/ + +import XCTest +import MaterialComponents + +// Tests confirming that the Button Bar respects UI appearance for bar button item +// titleTextAttributes on iOS 9 and above. +// +// Based on issue https://github.com/google/material-components-ios/issues/370 +class ButtonBarIssue370Tests: XCTestCase { + + var buttonBar: MDCButtonBar! + let globalAttributes = [NSForegroundColorAttributeName:UIColor.blueColor()] + let directAttributes = [NSForegroundColorAttributeName:UIColor.blueColor()] + let fontAttributes = [NSFontAttributeName:UIFont.systemFontOfSize(12)] + + override func setUp() { + buttonBar = MDCButtonBar() + } + + override func tearDown() { + UIBarButtonItem.appearance().setTitleTextAttributes(nil, forState: .Normal) + } + + func testDirectOnly() { + let item = UIBarButtonItem(title: "Text", style: .Plain, target: nil, action: nil) + item.setTitleTextAttributes(directAttributes, forState: .Normal) + buttonBar.items = [item] + + forEachButton { button in + let attributes = button.titleLabel?.attributedText?.attributesAtIndex(0, effectiveRange: nil) + XCTAssertTrue(NSDictionary(dictionary: self.directAttributes).isEqualToDictionary(attributes!)) + } + } + + func testGlobalAppearanceOnly() { + UIBarButtonItem.appearance().setTitleTextAttributes(globalAttributes, forState: .Normal) + + if UIBarButtonItem.appearance().titleTextAttributesForState(.Normal) == nil { + // This feature is not supported on this OS + return + } + + let item = UIBarButtonItem(title: "Text", style: .Plain, target: nil, action: nil) + buttonBar.items = [item] + + forEachButton { button in + let attributes = button.titleLabel?.attributedText?.attributesAtIndex(0, effectiveRange: nil) + XCTAssertTrue(NSDictionary(dictionary: self.globalAttributes).isEqualToDictionary(attributes!)) + } + } + + func testGlobalAppearanceAndDirectOverwriting() { + UIBarButtonItem.appearance().setTitleTextAttributes(globalAttributes, forState: .Normal) + + let item = UIBarButtonItem(title: "Text", style: .Plain, target: nil, action: nil) + + // Should take priority. + item.setTitleTextAttributes(directAttributes, forState: .Normal) + + buttonBar.items = [item] + + forEachButton { button in + let attributes = button.titleLabel?.attributedText?.attributesAtIndex(0, effectiveRange: nil) + XCTAssertTrue(NSDictionary(dictionary: self.directAttributes).isEqualToDictionary(attributes!)) + } + } + + func testGlobalAppearanceAndDirectMerging() { + UIBarButtonItem.appearance().setTitleTextAttributes(fontAttributes, forState: .Normal) + + if UIBarButtonItem.appearance().titleTextAttributesForState(.Normal) == nil { + // This feature is not supported on this OS + return + } + + let item = UIBarButtonItem(title: "Text", style: .Plain, target: nil, action: nil) + item.setTitleTextAttributes(directAttributes, forState: .Normal) + buttonBar.items = [item] + + var composite: [String: AnyObject] = fontAttributes + for (key, value) in directAttributes { + composite[key] = value + } + + self.forEachButton { button in + let attributes = button.titleLabel?.attributedText?.attributesAtIndex(0, effectiveRange: nil) + XCTAssertTrue(NSDictionary(dictionary: composite).isEqualToDictionary(attributes!)) + } + } + + func forEachButton(work: MDCButton -> Void) { + for view in buttonBar.subviews { + if let button = view as? MDCButton { + work(button) + } + } + } +} diff --git a/components/Buttons/.jazzy.yaml b/components/Buttons/.jazzy.yaml index 713c075a245..4ae5b0524f6 100644 --- a/components/Buttons/.jazzy.yaml +++ b/components/Buttons/.jazzy.yaml @@ -1,4 +1,5 @@ -module_name: Buttons +# Auto-generated by scripts/generate_jazzy_yamls.sh. Used primarily by scripts/external/arc-jazzy-linter. +module: Buttons umbrella_header: src/MaterialButtons.h objc: true sdk: iphonesimulator diff --git a/components/Buttons/README.md b/components/Buttons/README.md index e0fe33d678e..6832132ad05 100644 --- a/components/Buttons/README.md +++ b/components/Buttons/README.md @@ -1,7 +1,7 @@ --- title: "Buttons" layout: detail -section: documentation +section: components excerpt: "Buttons is a collection of Material Design buttons, including a flat button, a raised button and a floating action button." --- # Buttons diff --git a/components/Buttons/examples/ButtonsSimpleExampleViewController.m b/components/Buttons/examples/ButtonsSimpleExampleViewController.m deleted file mode 100644 index eb25f27432f..00000000000 --- a/components/Buttons/examples/ButtonsSimpleExampleViewController.m +++ /dev/null @@ -1,233 +0,0 @@ -/* - Copyright 2016-present Google Inc. 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. - */ - -#import "ButtonsSimpleExampleViewController.h" - -#import "MaterialButtons.h" -#import "MaterialTypography.h" - -@implementation ButtonsSimpleExampleViewController - -- (void)viewDidLoad { - [super viewDidLoad]; - self.view.backgroundColor = [UIColor whiteColor]; - - // Raised button and label - - MDCRaisedButton *raisedButton = [[MDCRaisedButton alloc] init]; - [raisedButton setTitle:@"Button" forState:UIControlStateNormal]; - [raisedButton sizeToFit]; - [raisedButton addTarget:self - action:@selector(didTap:) - forControlEvents:UIControlEventTouchUpInside]; - raisedButton.translatesAutoresizingMaskIntoConstraints = NO; - [self.view addSubview:raisedButton]; - - UILabel *raisedButtonLabel = [[UILabel alloc] init]; - raisedButtonLabel.text = @"Raised"; - raisedButtonLabel.font = [MDCTypography captionFont]; - raisedButtonLabel.alpha = [MDCTypography captionFontOpacity]; - [raisedButtonLabel sizeToFit]; - raisedButtonLabel.translatesAutoresizingMaskIntoConstraints = NO; - [self.view addSubview:raisedButtonLabel]; - - // Disabled raised button and label - - MDCRaisedButton *disabledRaisedButton = [[MDCRaisedButton alloc] init]; - [disabledRaisedButton setTitle:@"Button" forState:UIControlStateNormal]; - [disabledRaisedButton sizeToFit]; - [disabledRaisedButton addTarget:self - action:@selector(didTap:) - forControlEvents:UIControlEventTouchUpInside]; - [disabledRaisedButton setEnabled:NO]; - disabledRaisedButton.translatesAutoresizingMaskIntoConstraints = NO; - [self.view addSubview:disabledRaisedButton]; - - UILabel *disabledRaisedButtonLabel = [[UILabel alloc] init]; - disabledRaisedButtonLabel.text = @"Disabled Raised"; - disabledRaisedButtonLabel.font = [MDCTypography captionFont]; - disabledRaisedButtonLabel.alpha = [MDCTypography captionFontOpacity]; - [disabledRaisedButtonLabel sizeToFit]; - disabledRaisedButtonLabel.translatesAutoresizingMaskIntoConstraints = NO; - [self.view addSubview:disabledRaisedButtonLabel]; - - // Flat button and label - - MDCFlatButton *flatButton = [[MDCFlatButton alloc] init]; - [flatButton setTitle:@"Button" forState:UIControlStateNormal]; - [flatButton setCustomTitleColor:[UIColor grayColor]]; - [flatButton sizeToFit]; - [flatButton addTarget:self - action:@selector(didTap:) - forControlEvents:UIControlEventTouchUpInside]; - flatButton.translatesAutoresizingMaskIntoConstraints = NO; - [self.view addSubview:flatButton]; - - UILabel *flatButtonLabel = [[UILabel alloc] init]; - flatButtonLabel.text = @"Flat"; - flatButtonLabel.font = [MDCTypography captionFont]; - flatButtonLabel.alpha = [MDCTypography captionFontOpacity]; - [flatButtonLabel sizeToFit]; - flatButtonLabel.translatesAutoresizingMaskIntoConstraints = NO; - [self.view addSubview:flatButtonLabel]; - - // Disabled flat button and label - - MDCFlatButton *disabledFlatButton = [[MDCFlatButton alloc] init]; - [disabledFlatButton setTitle:@"Button" forState:UIControlStateNormal]; - [disabledFlatButton setCustomTitleColor:[UIColor grayColor]]; - [disabledFlatButton sizeToFit]; - [disabledFlatButton addTarget:self - action:@selector(didTap:) - forControlEvents:UIControlEventTouchUpInside]; - [disabledFlatButton setEnabled:NO]; - disabledFlatButton.translatesAutoresizingMaskIntoConstraints = NO; - [self.view addSubview:disabledFlatButton]; - - UILabel *disabledFlatButtonLabel = [[UILabel alloc] init]; - disabledFlatButtonLabel.text = @"Disabled Flat"; - disabledFlatButtonLabel.font = [MDCTypography captionFont]; - disabledFlatButtonLabel.alpha = [MDCTypography captionFontOpacity]; - [disabledFlatButtonLabel sizeToFit]; - disabledFlatButtonLabel.translatesAutoresizingMaskIntoConstraints = NO; - [self.view addSubview:disabledFlatButtonLabel]; - - // Floating action button and label - - MDCFloatingButton *floatingButton = [[MDCFloatingButton alloc] init]; - [floatingButton setTitle:@"+" forState:UIControlStateNormal]; - [floatingButton sizeToFit]; - [floatingButton addTarget:self - action:@selector(didTap:) - forControlEvents:UIControlEventTouchUpInside]; - floatingButton.translatesAutoresizingMaskIntoConstraints = NO; - [self.view addSubview:floatingButton]; - - UILabel *floatingButtonLabel = [[UILabel alloc] init]; - floatingButtonLabel.text = @"Floating Action"; - floatingButtonLabel.font = [MDCTypography captionFont]; - floatingButtonLabel.alpha = [MDCTypography captionFontOpacity]; - [floatingButtonLabel sizeToFit]; - floatingButtonLabel.translatesAutoresizingMaskIntoConstraints = NO; - [self.view addSubview:floatingButtonLabel]; - - NSDictionary *views = @{ - @"raised" : raisedButton, - @"raisedLabel" : raisedButtonLabel, - @"disabledRaised" : disabledRaisedButton, - @"disabledRaisedLabel" : disabledRaisedButtonLabel, - @"flat" : flatButton, - @"flatLabel" : flatButtonLabel, - @"disabledFlat" : disabledFlatButton, - @"disabledFlatLabel" : disabledFlatButtonLabel, - @"floating" : floatingButton, - @"floatingLabel" : floatingButtonLabel - }; - - NSDictionary *metrics = @{ @"smallVMargin" : @24.0, - @"largeVMargin" : @56.0, - @"smallHMargin" : @24.0, - @"buttonHeight" : @(raisedButton.bounds.size.height), - @"fabHeight" : @(floatingButton.bounds.size.height) }; - - // Vertical column of buttons - NSString *buttonLayoutConstraints = - @"V:[raised]-smallVMargin-" - "[disabledRaised]-largeVMargin-" - "[flat]-smallVMargin-" - "[disabledFlat]-largeVMargin-" - "[floating]"; - - // Vertical column of labels - NSString *labelLayoutConstraints = - @"V:[raisedLabel(buttonHeight)]-smallVMargin-" - "[disabledRaisedLabel(buttonHeight)]-largeVMargin-" - "[flatLabel(buttonHeight)]-smallVMargin-" - "[disabledFlatLabel(buttonHeight)]-largeVMargin-" - "[floatingLabel(fabHeight)]"; - - // Horizontal alignment between the two columns - NSString *columnConstraints = @"[raisedLabel(100)]-smallHMargin-[raised]"; - - // Center view horizontally on the left edge of one of the buttons - [self.view addConstraint: - [NSLayoutConstraint constraintWithItem:flatButton - attribute:NSLayoutAttributeLeft - relatedBy:NSLayoutRelationEqual - toItem:self.view - attribute:NSLayoutAttributeCenterX - multiplier:1.f - constant:12.f]]; - - // Center view vertically on the flat button (it's the middlemost) - [self.view addConstraint: - [NSLayoutConstraint constraintWithItem:flatButton - attribute:NSLayoutAttributeBottom - relatedBy:NSLayoutRelationEqual - toItem:self.view - attribute:NSLayoutAttributeCenterY - multiplier:1.f - constant:0.f]]; - - // Center buttons in their column - [self.view addConstraints: - [NSLayoutConstraint constraintsWithVisualFormat:buttonLayoutConstraints - options:NSLayoutFormatAlignAllCenterX - metrics:metrics - views:views]]; - // Left align labels in their column - [self.view addConstraints: - [NSLayoutConstraint constraintsWithVisualFormat:labelLayoutConstraints - options:NSLayoutFormatAlignAllLeft - metrics:metrics - views:views]]; - - // Vertically align first element in label column to first element in button column - [self.view addConstraint: - [NSLayoutConstraint constraintWithItem:raisedButton - attribute:NSLayoutAttributeCenterY - relatedBy:NSLayoutRelationEqual - toItem:raisedButtonLabel - attribute:NSLayoutAttributeCenterY - multiplier:1.f - constant:0.f]]; - - // Position label column left of button column, wide enough to accommodate label text - [self.view addConstraints: - [NSLayoutConstraint constraintsWithVisualFormat:columnConstraints - options:0 - metrics:metrics - views:views]]; -} - -- (void)didTap:(id)sender { - NSLog(@"%@ was tapped.", NSStringFromClass([sender class])); -} - -+ (NSArray *)catalogBreadcrumbs { - return @[ @"Buttons", @"Buttons" ]; -} - -+ (NSString *)catalogDescription { - return @"Buttons is a collection of Material Design buttons, including a flat button, a raised" - " button and a floating action button."; -} - -- (BOOL)catalogIsPrimaryDemo { - return YES; -} - -@end diff --git a/components/Buttons/examples/ButtonsStoryboardAndProgrammatic.swift b/components/Buttons/examples/ButtonsStoryboardAndProgrammatic.swift index b92814bdafb..d0fbe341cf3 100644 --- a/components/Buttons/examples/ButtonsStoryboardAndProgrammatic.swift +++ b/components/Buttons/examples/ButtonsStoryboardAndProgrammatic.swift @@ -35,8 +35,8 @@ class ButtonsStoryboardAndProgrammaticController: UIViewController { @IBOutlet weak var storyboardFlat: MDCFlatButton! @IBOutlet weak var storyboardFloating: MDCFloatingButton! - override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { - super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + init() { + super.init(nibName: nil, bundle: nil) } required init?(coder aDecoder: NSCoder) { diff --git a/components/Buttons/examples/ButtonsTypicalUse.m b/components/Buttons/examples/ButtonsTypicalUse.m new file mode 100644 index 00000000000..17bb5b025b2 --- /dev/null +++ b/components/Buttons/examples/ButtonsTypicalUse.m @@ -0,0 +1,107 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import "ButtonsTypicalUseSupplemental.h" + +#import "MaterialButtons.h" +#import "MaterialTypography.h" + +@interface ButtonsTypicalUseViewController () + +@end + +@implementation ButtonsTypicalUseViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.backgroundColor = [UIColor whiteColor]; + + // Raised button + + MDCRaisedButton *raisedButton = [[MDCRaisedButton alloc] init]; + [raisedButton setTitle:@"Button" forState:UIControlStateNormal]; + [raisedButton sizeToFit]; + [raisedButton addTarget:self + action:@selector(didTap:) + forControlEvents:UIControlEventTouchUpInside]; + raisedButton.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:raisedButton]; + + // Disabled raised button + + MDCRaisedButton *disabledRaisedButton = [[MDCRaisedButton alloc] init]; + [disabledRaisedButton setTitle:@"Button" forState:UIControlStateNormal]; + [disabledRaisedButton sizeToFit]; + [disabledRaisedButton addTarget:self + action:@selector(didTap:) + forControlEvents:UIControlEventTouchUpInside]; + [disabledRaisedButton setEnabled:NO]; + disabledRaisedButton.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:disabledRaisedButton]; + + // Flat button + + MDCFlatButton *flatButton = [[MDCFlatButton alloc] init]; + [flatButton setTitle:@"Button" forState:UIControlStateNormal]; + [flatButton setCustomTitleColor:[UIColor grayColor]]; + [flatButton sizeToFit]; + [flatButton addTarget:self + action:@selector(didTap:) + forControlEvents:UIControlEventTouchUpInside]; + flatButton.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:flatButton]; + + // Disabled flat + + MDCFlatButton *disabledFlatButton = [[MDCFlatButton alloc] init]; + [disabledFlatButton setTitle:@"Button" forState:UIControlStateNormal]; + [disabledFlatButton setCustomTitleColor:[UIColor grayColor]]; + [disabledFlatButton sizeToFit]; + [disabledFlatButton addTarget:self + action:@selector(didTap:) + forControlEvents:UIControlEventTouchUpInside]; + [disabledFlatButton setEnabled:NO]; + disabledFlatButton.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:disabledFlatButton]; + + // Floating action button + + MDCFloatingButton *floatingButton = [[MDCFloatingButton alloc] init]; + [floatingButton setTitle:@"+" forState:UIControlStateNormal]; + [floatingButton sizeToFit]; + [floatingButton addTarget:self + action:@selector(didTap:) + forControlEvents:UIControlEventTouchUpInside]; + floatingButton.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:floatingButton]; + + NSDictionary *views = @{ @"raised" : raisedButton, + @"disabledRaised" : disabledRaisedButton, + @"flat" : flatButton, + @"disabledFlat" : disabledFlatButton, + @"floating" : floatingButton }; + + self.views = [NSMutableDictionary dictionary]; + [self.views addEntriesFromDictionary:views]; + + [self setupExampleViews]; +} + +- (void)didTap:(id)sender { + NSLog(@"%@ was tapped.", NSStringFromClass([sender class])); +} + +@end diff --git a/components/Buttons/examples/supplemental/ButtonsTypicalUseSupplemental.h b/components/Buttons/examples/supplemental/ButtonsTypicalUseSupplemental.h new file mode 100644 index 00000000000..1541578fe2e --- /dev/null +++ b/components/Buttons/examples/supplemental/ButtonsTypicalUseSupplemental.h @@ -0,0 +1,22 @@ +/* IMPORTANT: + This file contains supplemental code used to populate the demos with dummy data or instructions. + It is not necessary to import this file to implement any Material Design Components. + */ + +#import + +@class ButtonsTypicalUseViewController; + +@interface ButtonsTypicalUseViewController : UIViewController + +@property(nonatomic, strong) NSMutableDictionary *views; +@property(nonatomic, strong) NSMutableArray *portraitLayoutConstraints; +@property(nonatomic, strong) NSMutableArray *landscapeLayoutConstraints; + +@end + +@interface ButtonsTypicalUseViewController (Supplemental) + +- (void)setupExampleViews; + +@end diff --git a/components/Buttons/examples/supplemental/ButtonsTypicalUseSupplemental.m b/components/Buttons/examples/supplemental/ButtonsTypicalUseSupplemental.m new file mode 100644 index 00000000000..f1f2a9784eb --- /dev/null +++ b/components/Buttons/examples/supplemental/ButtonsTypicalUseSupplemental.m @@ -0,0 +1,366 @@ +/* IMPORTANT: + This file contains supplemental code used to populate the examples with dummy data and/or + instructions. It is not necessary to import this file to implement any Material Design Components. + */ + +#import + +#import "ButtonsTypicalUseSupplemental.h" +#import "MaterialButtons.h" +#import "MaterialTypography.h" + +#pragma mark - ButtonsTypicalUseViewController + +@implementation ButtonsTypicalUseViewController (CatalogByConvention) + ++ (NSArray *)catalogBreadcrumbs { + return @[ @"Buttons", @"Buttons" ]; +} + ++ (NSString *)catalogDescription { + return @"Buttons is a collection of Material Design buttons, including a flat button, a raised" + " button and a floating action button."; +} + ++ (BOOL)catalogIsPrimaryDemo { + return YES; +} + +@end + +@implementation ButtonsTypicalUseViewController (Supplemental) + +- (void)addLabels { + UILabel *raisedButtonLabel = [[UILabel alloc] init]; + raisedButtonLabel.text = @"Raised"; + raisedButtonLabel.font = [MDCTypography captionFont]; + raisedButtonLabel.alpha = [MDCTypography captionFontOpacity]; + [raisedButtonLabel sizeToFit]; + raisedButtonLabel.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:raisedButtonLabel]; + + UILabel *disabledRaisedButtonLabel = [[UILabel alloc] init]; + disabledRaisedButtonLabel.text = @"Disabled Raised"; + disabledRaisedButtonLabel.font = [MDCTypography captionFont]; + disabledRaisedButtonLabel.alpha = [MDCTypography captionFontOpacity]; + [disabledRaisedButtonLabel sizeToFit]; + disabledRaisedButtonLabel.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:disabledRaisedButtonLabel]; + + UILabel *flatButtonLabel = [[UILabel alloc] init]; + flatButtonLabel.text = @"Flat"; + flatButtonLabel.font = [MDCTypography captionFont]; + flatButtonLabel.alpha = [MDCTypography captionFontOpacity]; + [flatButtonLabel sizeToFit]; + flatButtonLabel.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:flatButtonLabel]; + + UILabel *disabledFlatButtonLabel = [[UILabel alloc] init]; + disabledFlatButtonLabel.text = @"Disabled Flat"; + disabledFlatButtonLabel.font = [MDCTypography captionFont]; + disabledFlatButtonLabel.alpha = [MDCTypography captionFontOpacity]; + [disabledFlatButtonLabel sizeToFit]; + disabledFlatButtonLabel.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:disabledFlatButtonLabel]; + + UILabel *floatingButtonLabel = [[UILabel alloc] init]; + floatingButtonLabel.text = @"Floating Action"; + floatingButtonLabel.font = [MDCTypography captionFont]; + floatingButtonLabel.alpha = [MDCTypography captionFontOpacity]; + [floatingButtonLabel sizeToFit]; + floatingButtonLabel.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:floatingButtonLabel]; + + NSDictionary *views = @{ @"raisedLabel" : raisedButtonLabel, + @"disabledRaisedLabel" : disabledRaisedButtonLabel, + @"flatLabel" : flatButtonLabel, + @"disabledFlatLabel" : disabledFlatButtonLabel, + @"floatingLabel" : floatingButtonLabel }; + [self.views addEntriesFromDictionary:views]; +} + +- (void)setupExampleViews { + [self addLabels]; + + UILabel *raisedButtonLabel = self.views[@"raisedLabel"]; + MDCRaisedButton *raisedButton = self.views[@"raised"]; + UILabel *disabledRaisedButtonLabel = self.views[@"disabledRaisedLabel"]; + MDCRaisedButton *disabledRaisedButton = self.views[@"disabledRaised"]; + + UILabel *flatButtonLabel = self.views[@"flatLabel"]; + MDCFlatButton *flatButton = self.views[@"flat"]; + UILabel *disabledFlatButtonLabel = self.views[@"disabledFlatLabel"]; + MDCFlatButton *disabledFlatButton = self.views[@"disabledFlat"]; + + UILabel *floatingButtonLabel = self.views[@"floatingLabel"]; + MDCFloatingButton *floatingButton = self.views[@"floating"]; + + self.portraitLayoutConstraints = [NSMutableArray array]; + + NSDictionary *metrics = @{ @"smallVMargin" : @24.0, + @"largeVMargin" : @56.0, + @"smallHMargin" : @24.0, + @"buttonHeight" : @(raisedButton.bounds.size.height), + @"fabHeight" : @(floatingButton.bounds.size.height) }; + + // Vertical column of buttons + NSString *buttonLayoutConstraints = + @"V:[raised]-smallVMargin-" + "[disabledRaised]-largeVMargin-" + "[flat]-smallVMargin-" + "[disabledFlat]-largeVMargin-" + "[floating]"; + + // Vertical column of labels + NSString *labelLayoutConstraints = + @"V:[raisedLabel(buttonHeight)]-smallVMargin-" + "[disabledRaisedLabel(buttonHeight)]-largeVMargin-" + "[flatLabel(buttonHeight)]-smallVMargin-" + "[disabledFlatLabel(buttonHeight)]-largeVMargin-" + "[floatingLabel(fabHeight)]"; + + // Horizontal alignment between the two columns + NSString *columnConstraints = @"[raisedLabel(100)]-smallHMargin-[raised]"; + + // Center view horizontally on the left edge of one of the buttons + NSLayoutConstraint *horizontalConstraint = + [NSLayoutConstraint constraintWithItem:flatButton + attribute:NSLayoutAttributeLeft + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeCenterX + multiplier:1.f + constant:12.f]; + [self.portraitLayoutConstraints addObject:horizontalConstraint]; + + // Center view vertically on the flat button (it's the middlemost) + NSLayoutConstraint *verticalConstraint = + [NSLayoutConstraint constraintWithItem:flatButton + attribute:NSLayoutAttributeBottom + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeCenterY + multiplier:1.f + constant:0.f]; + [self.portraitLayoutConstraints addObject:verticalConstraint]; + + // Center buttons in their column + NSArray *buttonConstraints = + [NSLayoutConstraint constraintsWithVisualFormat:buttonLayoutConstraints + options:NSLayoutFormatAlignAllCenterX + metrics:metrics + views:self.views]; + [self.portraitLayoutConstraints addObjectsFromArray:buttonConstraints]; + + // Left align labels in their column + NSArray *labelConstraints = + [NSLayoutConstraint constraintsWithVisualFormat:labelLayoutConstraints + options:NSLayoutFormatAlignAllLeft + metrics:metrics + views:self.views]; + [self.portraitLayoutConstraints addObjectsFromArray:labelConstraints]; + + // Vertically align first element in label column to first element in button column + NSLayoutConstraint *labelColConstraint = + [NSLayoutConstraint constraintWithItem:raisedButton + attribute:NSLayoutAttributeCenterY + relatedBy:NSLayoutRelationEqual + toItem:raisedButtonLabel + attribute:NSLayoutAttributeCenterY + multiplier:1.f + constant:0.f]; + [self.portraitLayoutConstraints addObject:labelColConstraint]; + + // Position label column left of button column, wide enough to accommodate label text + NSArray *labelLeftColConstraints = + [NSLayoutConstraint constraintsWithVisualFormat:columnConstraints + options:0 + metrics:metrics + views:self.views]; + [self.portraitLayoutConstraints addObjectsFromArray:labelLeftColConstraints]; + + self.landscapeLayoutConstraints = [NSMutableArray array]; + + NSLayoutConstraint *landscapeRaiseButtonConstraint = + [NSLayoutConstraint constraintWithItem:raisedButtonLabel + attribute:NSLayoutAttributeCenterY + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeCenterY + multiplier:0.5 + constant:0]; + [self.landscapeLayoutConstraints addObject:landscapeRaiseButtonConstraint]; + + NSDictionary *views = @{ @"raisedButtonLabel" : raisedButtonLabel, + @"raisedButton" : raisedButton, + @"flatButtonLabel" : flatButtonLabel, + @"flatButton" : flatButton }; + NSString *horizontalConstraints = @"H:|-(50)-[raisedButtonLabel(100)]-[raisedButton]-(50)-" + "[flatButtonLabel(100)]-[flatButton]-(50)-|"; + NSDictionary *horizontalMetrics = @{ @"raisedButtonLabel" : @(50), + @"raisedButton" : @(raisedButton.frame.size.width), + @"flatButtonLabel" : @(200), + @"flatButton" : @(flatButton.frame.size.width) }; + + NSArray *landscapeHorizontalMetricsConstraints = + [NSLayoutConstraint constraintsWithVisualFormat:horizontalConstraints + options:NSLayoutFormatAlignAllCenterY + metrics:horizontalMetrics + views:views]; + [self.landscapeLayoutConstraints addObjectsFromArray:landscapeHorizontalMetricsConstraints]; + + NSDictionary *raisedButtonLabelViews = + @{ @"raisedButtonLabel" : raisedButtonLabel, + @"disabledRaisedButtonLabel" : disabledRaisedButtonLabel }; + NSString *raisedButtonLabelConstraints = @"V:[raisedButtonLabel]-[disabledRaisedButtonLabel]"; + + NSDictionary *metricsY = + @{ @"raisedButtonLabel" : @(raisedButtonLabel.frame.size.height), + @"disabledRaisedButtonLabel" : @(disabledRaisedButtonLabel.frame.size.height) }; + + NSArray *landscapeRaisedButtonMetricsConstraints = + [NSLayoutConstraint constraintsWithVisualFormat:raisedButtonLabelConstraints + options:NSLayoutFormatAlignAllCenterX + metrics:metricsY + views:raisedButtonLabelViews]; + [self.landscapeLayoutConstraints addObjectsFromArray:landscapeRaisedButtonMetricsConstraints]; + + NSArray *landscapeRaisedButtonConstraints = + [NSLayoutConstraint constraintsWithVisualFormat:raisedButtonLabelConstraints + options:NSLayoutFormatAlignAllLeft + metrics:metricsY + views:raisedButtonLabelViews]; + [self.landscapeLayoutConstraints addObjectsFromArray:landscapeRaisedButtonConstraints]; + + NSLayoutConstraint *landscapeDisabledRaiseButtonConstraint = + [NSLayoutConstraint constraintWithItem:disabledRaisedButton + attribute:NSLayoutAttributeCenterY + relatedBy:NSLayoutRelationEqual + toItem:disabledRaisedButtonLabel + attribute:NSLayoutAttributeCenterY + multiplier:1.f + constant:0.f]; + [self.landscapeLayoutConstraints addObject:landscapeDisabledRaiseButtonConstraint]; + + NSDictionary *raisedButtonViews = @{ @"raisedButton" : raisedButton, + @"disabledRaisedButton" : disabledRaisedButton }; + NSString *raisedButtonConstraints = @"V:[raisedButton]-[disabledRaisedButton]"; + NSDictionary *raisedButtonMetrics = + @{ @"raisedButton" : @(raisedButton.frame.size.height), + @"disabledRaisedButton" : @(disabledRaisedButton.frame.size.height) }; + NSArray *landscapeMetricsY2Constraints = + [NSLayoutConstraint constraintsWithVisualFormat:raisedButtonConstraints + options:NSLayoutFormatAlignAllCenterX + metrics:raisedButtonMetrics + views:raisedButtonViews]; + [self.landscapeLayoutConstraints addObjectsFromArray:landscapeMetricsY2Constraints]; + + NSArray *landscapeMetricsY2LeftConstraints = + [NSLayoutConstraint constraintsWithVisualFormat:raisedButtonConstraints + options:NSLayoutFormatAlignAllLeft + metrics:raisedButtonMetrics + views:raisedButtonViews]; + [self.landscapeLayoutConstraints addObjectsFromArray:landscapeMetricsY2LeftConstraints]; + + NSDictionary *flatButtonLabelViews = @{ @"flatButtonLabel" : flatButtonLabel, + @"disabledFlatButtonLabel" : disabledFlatButtonLabel }; + NSString *flatButtonLabelConstraints = @"V:[flatButtonLabel]-[disabledFlatButtonLabel]"; + NSDictionary *flatButtonLabelMetrics = + @{ @"flatButtonLabel" : @(flatButtonLabel.frame.size.height), + @"disabledFlatButtonLabel" : @(disabledFlatButtonLabel.frame.size.height) }; + NSArray *landscapeMetricsY3Constraints = + [NSLayoutConstraint constraintsWithVisualFormat:flatButtonLabelConstraints + options:NSLayoutFormatAlignAllCenterX + metrics:flatButtonLabelMetrics + views:flatButtonLabelViews]; + [self.landscapeLayoutConstraints addObjectsFromArray:landscapeMetricsY3Constraints]; + + NSArray *landscapeMetricsY3LeftConstraints = + [NSLayoutConstraint constraintsWithVisualFormat:flatButtonLabelConstraints + options:NSLayoutFormatAlignAllLeft + metrics:flatButtonLabelMetrics + views:flatButtonLabelViews]; + [self.landscapeLayoutConstraints addObjectsFromArray:landscapeMetricsY3LeftConstraints]; + + NSDictionary *flatButtonViews = @{ @"flatButton" : flatButton, + @"disabledFlatButton" : disabledFlatButton }; + NSString *flatButtonConstraints = @"V:[flatButton]-[disabledFlatButton]"; + NSDictionary *flatButtonMetrics = @{ @"flatButton" : @(flatButton.frame.size.height), + @"disabledFlatButton" : @(disabledFlatButton.frame.size.height) }; + + NSArray *landscapeMetricsY4Constraints = + [NSLayoutConstraint constraintsWithVisualFormat:flatButtonConstraints + options:NSLayoutFormatAlignAllCenterX + metrics:flatButtonMetrics + views:flatButtonViews]; + [self.landscapeLayoutConstraints addObjectsFromArray:landscapeMetricsY4Constraints]; + + NSArray *landscapeMetricsY4LeftConstraints = + [NSLayoutConstraint constraintsWithVisualFormat:flatButtonConstraints + options:NSLayoutFormatAlignAllLeft + metrics:flatButtonMetrics + views:flatButtonViews]; + [self.landscapeLayoutConstraints addObjectsFromArray:landscapeMetricsY4LeftConstraints]; + + NSLayoutConstraint *landscapeMetricsCenterConstraint = + [NSLayoutConstraint constraintWithItem:disabledFlatButton + attribute:NSLayoutAttributeCenterY + relatedBy:NSLayoutRelationEqual + toItem:disabledFlatButtonLabel + attribute:NSLayoutAttributeCenterY + multiplier:1.f + constant:0.f]; + [self.landscapeLayoutConstraints addObject:landscapeMetricsCenterConstraint]; + + NSLayoutConstraint *landscapeEqualCenterYConstraint = + [NSLayoutConstraint constraintWithItem:floatingButton + attribute:NSLayoutAttributeCenterY + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeCenterY + multiplier:1.5 + constant:0]; + [self.landscapeLayoutConstraints addObject:landscapeEqualCenterYConstraint]; + + NSLayoutConstraint *landscapeEqualCenterXConstraint = + [NSLayoutConstraint constraintWithItem:floatingButton + attribute:NSLayoutAttributeCenterX + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeCenterX + multiplier:1 + constant:50]; + [self.landscapeLayoutConstraints addObject:landscapeEqualCenterXConstraint]; + + NSLayoutConstraint *landscapeFloatingButtonLabelCenterConstraint = + [NSLayoutConstraint constraintWithItem:floatingButton + attribute:NSLayoutAttributeCenterY + relatedBy:NSLayoutRelationEqual + toItem:floatingButtonLabel + attribute:NSLayoutAttributeCenterY + multiplier:1.f + constant:0.f]; + [self.landscapeLayoutConstraints addObject:landscapeFloatingButtonLabelCenterConstraint]; + + NSLayoutConstraint *landscapeFloatingButtonCenterEqualConstraint = + [NSLayoutConstraint constraintWithItem:floatingButtonLabel + attribute:NSLayoutAttributeCenterX + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeCenterX + multiplier:1 + constant:-50]; + [self.landscapeLayoutConstraints addObject:landscapeFloatingButtonCenterEqualConstraint]; +} + +- (void)viewWillLayoutSubviews { + if (self.view.frame.size.width < self.view.frame.size.height) { + [self.view removeConstraints:self.landscapeLayoutConstraints]; + [self.view addConstraints:self.portraitLayoutConstraints]; + } else { + [self.view removeConstraints:self.portraitLayoutConstraints]; + [self.view addConstraints:self.landscapeLayoutConstraints]; + } +} + +@end diff --git a/components/Buttons/src/MDCButton.m b/components/Buttons/src/MDCButton.m index 65c58645cf1..b2c03f0d549 100644 --- a/components/Buttons/src/MDCButton.m +++ b/components/Buttons/src/MDCButton.m @@ -154,7 +154,10 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { } if ([aDecoder containsValueForKey:MDCButtonShouldRaiseOnTouchKey]) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" self.shouldRaiseOnTouch = [aDecoder decodeBoolForKey:MDCButtonShouldRaiseOnTouchKey]; +#pragma clang diagnostic pop } if ([aDecoder containsValueForKey:MDCButtonUppercaseTitleKey]) { diff --git a/components/Buttons/src/MDCFlatButton.m b/components/Buttons/src/MDCFlatButton.m index a88059b250e..6c38f4dfa3e 100644 --- a/components/Buttons/src/MDCFlatButton.m +++ b/components/Buttons/src/MDCFlatButton.m @@ -46,8 +46,12 @@ - (instancetype)initWithCoder:(NSCoder *)aDecoder { } - (void)commonMDCFlatButtonInit { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wdeprecated-declarations" self.shouldRaiseOnTouch = NO; - [self setBackgroundColor:nil forState:UIControlStateNormal]; +#pragma clang diagnostic pop + [self setBackgroundColor:nil + forState:UIControlStateNormal]; self.inkColor = [UIColor colorWithWhite:0 alpha:0.06f]; } diff --git a/components/CollectionCells/.jazzy.yaml b/components/CollectionCells/.jazzy.yaml new file mode 100644 index 00000000000..3e16f474889 --- /dev/null +++ b/components/CollectionCells/.jazzy.yaml @@ -0,0 +1,5 @@ +# Auto-generated by scripts/generate_jazzy_yamls.sh. Used primarily by scripts/external/arc-jazzy-linter. +module: CollectionCells +umbrella_header: src/MaterialCollectionCells.h +objc: true +sdk: iphonesimulator diff --git a/components/CollectionCells/README.md b/components/CollectionCells/README.md new file mode 100644 index 00000000000..40c08773afe --- /dev/null +++ b/components/CollectionCells/README.md @@ -0,0 +1,40 @@ +--- +title: "Collection Cells" +layout: detail +section: components +excerpt: "Collection view cell classes that adhere to Material design layout and styling." +--- +# Collection Cells + +![Collections](docs/assets/collections_screenshot.png) + + +Collection view cell classes that adhere to Material design layout and styling. + + +### Material Design Specifications + + + +### API Documentation + + + +- - - + +## Installation + +### Requirements + +- Xcode 7.0 or higher. +- iOS SDK version 7.0 or higher. + +## Usage + +Please see the [Collections](../Collections) component for more information about using Collection +Cells. We do not presently support using Collection Cells as a standalone component. diff --git a/components/CollectionCells/docs/assets/collections_screenshot.png b/components/CollectionCells/docs/assets/collections_screenshot.png new file mode 100644 index 00000000000..2c0b1d8a6f7 Binary files /dev/null and b/components/CollectionCells/docs/assets/collections_screenshot.png differ diff --git a/components/CollectionCells/examples/CollectionCellsLayoutExample.m b/components/CollectionCells/examples/CollectionCellsLayoutExample.m new file mode 100644 index 00000000000..e69b1f95c88 --- /dev/null +++ b/components/CollectionCells/examples/CollectionCellsLayoutExample.m @@ -0,0 +1,198 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "CollectionCellsLayoutExample.h" + +#import "MaterialSwitch.h" +#import "MaterialTypography.h" + +@interface SimpleModel : NSObject +@property(nonatomic, strong, nullable) NSString *text; +@property(nonatomic, strong, nullable) NSString *detailText; +@property(nonatomic) NSInteger textLines; +@property(nonatomic) NSInteger detailTextLines; ++ (instancetype)modelWithTextArray:(NSArray *)textArray textLineArray:(NSArray *)textLineArray; +@end + +@implementation SimpleModel +- (instancetype)initWithTextArray:(NSArray *)textArray textLineArray:(NSArray *)textLineArray { + self = [super init]; + if (self) { + _text = textArray[0]; + _detailText = textArray[1]; + _textLines = [textLineArray[0] integerValue]; + _detailTextLines = [textLineArray[1] integerValue]; + } + return self; +} + ++ (instancetype)modelWithTextArray:(NSArray *)textArray textLineArray:(NSArray *)textLineArray { + return [[self alloc] initWithTextArray:textArray textLineArray:textLineArray]; +} + +@end + +static NSString *const kReusableIdentifierItem = @"itemCellIdentifier"; +static NSString *const kExampleText = + @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Mauris tempus, enim non tincidunt " + "rhoncus, lacus sapien sagittis mi, id gravida risus turpis ut libero. "; +static NSString *const kExampleDetailText = + @"Pellentesque non quam ornare, porta urna sed, malesuada felis. Praesent at gravida felis, " + "non facilisis enim. Proin dapibus laoreet lorem, in viverra leo dapibus a."; + +#define RGBCOLOR(r, g, b) [UIColor colorWithRed:(r) / 255.0f green:(g) / 255.0f blue:(b) / 255.0f alpha:1] +#define HEXCOLOR(hex) RGBCOLOR((((hex) >> 16) & 0xFF), (((hex) >> 8) & 0xFF), ((hex)&0xFF)) + +@implementation CollectionCellsLayoutExample { + NSMutableArray *_content; +} + ++ (NSArray *)catalogBreadcrumbs { + return @[ @"Collection Cells", @"Cell Layout Example" ]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + // Register cell class. + [self.collectionView registerClass:[MDCCollectionViewTextCell class] + forCellWithReuseIdentifier:kReusableIdentifierItem]; + + // Populate content. + _content = [NSMutableArray array]; + [_content addObject:[SimpleModel modelWithTextArray:@[ @"Show in Editing Mode", @"" ] + textLineArray:@[ @(1), @(0) ]]]; + [_content addObject:[SimpleModel modelWithTextArray:@[ @"Single line text", @"" ] + textLineArray:@[ @(1), @(0) ]]]; + [_content addObject:[SimpleModel modelWithTextArray:@[ kExampleText, @"" ] + textLineArray:@[ @(2), @(0) ]]]; + [_content addObject:[SimpleModel modelWithTextArray:@[ kExampleText, @"" ] + textLineArray:@[ @(3), @(0) ]]]; + [_content addObject:[SimpleModel modelWithTextArray:@[ @"", @"Detail text" ] + textLineArray:@[ @(0), @(1) ]]]; + [_content addObject:[SimpleModel modelWithTextArray:@[ kExampleText, kExampleDetailText ] + textLineArray:@[ @(1), @(1) ]]]; + [_content addObject:[SimpleModel modelWithTextArray:@[ kExampleText, kExampleDetailText ] + textLineArray:@[ @(1), @(2) ]]]; + [_content addObject:[SimpleModel modelWithTextArray:@[ kExampleText, @"Detail text" ] + textLineArray:@[ @(2), @(1) ]]]; + + // Customize collection view settings. + self.styler.cellStyle = MDCCollectionViewCellStyleCard; +} + +#pragma mark - + +- (NSInteger)collectionView:(UICollectionView *)collectionView + numberOfItemsInSection:(NSInteger)section { + return [_content count]; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath { + MDCCollectionViewTextCell *cell = + [collectionView dequeueReusableCellWithReuseIdentifier:kReusableIdentifierItem + forIndexPath:indexPath]; + SimpleModel *model = _content[indexPath.item]; + cell.textLabel.text = model.text; + cell.textLabel.numberOfLines = model.textLines; + cell.detailTextLabel.text = model.detailText; + cell.detailTextLabel.numberOfLines = model.detailTextLines; + + // Add accessory views. + if (indexPath.item == 0) { + // Add switch as accessory view. + MDCSwitch *editingSwitch = [[MDCSwitch alloc] initWithFrame:CGRectZero]; + [editingSwitch addTarget:self + action:@selector(didSwitch:) + forControlEvents:UIControlEventValueChanged]; + cell.accessoryView = editingSwitch; + } + + if (indexPath.item == 2) { + cell.accessoryType = MDCCollectionViewCellAccessoryCheckmark; + } else if (indexPath.item == 5 || indexPath.item == 6) { + cell.imageView.image = + [self imageWithSize:CGSizeMake(40, 40) + color:HEXCOLOR(0x80CBC4) + cornerRadius:20]; + } + + return cell; +} + +#pragma mark - + +- (CGFloat)collectionView:(UICollectionView *)collectionView + cellHeightAtIndexPath:(NSIndexPath *)indexPath { + SimpleModel *model = _content[indexPath.item]; + NSInteger numberOfLines = model.textLines + model.detailTextLines; + if (numberOfLines == 1) { + return MDCCellDefaultOneLineHeight; + } else if (numberOfLines == 2) { + return MDCCellDefaultTwoLineHeight; + } else if (numberOfLines == 3) { + return MDCCellDefaultThreeLineHeight; + } + return MDCCellDefaultOneLineHeight; +} + +#pragma mark - + +- (BOOL)collectionViewAllowsEditing:(UICollectionView *)collectionView { + return YES; +} + +- (BOOL)collectionViewAllowsReordering:(UICollectionView *)collectionView { + return YES; +} + +- (BOOL)collectionView:(UICollectionView *)collectionView + canEditItemAtIndexPath:(NSIndexPath *)indexPath { + return (indexPath.item != 0); +} + +- (BOOL)collectionView:(UICollectionView *)collectionView + canMoveItemAtIndexPath:(NSIndexPath *)indexPath { + return (indexPath.item != 0); +} + +#pragma mark UIControlEvents + +- (void)didSwitch:(id)sender { + MDCSwitch *switchControl = sender; + [self.editor setEditing:switchControl.isOn animated:YES]; +} + +#pragma mark - Private helper methods + +- (UIImage *)imageWithSize:(CGSize)size color:(UIColor *)color cornerRadius:(CGFloat)cornerRadius { + // Create a colored image. + UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, size.width, size.height)]; + view.backgroundColor = color; + view.layer.cornerRadius = cornerRadius; + UIGraphicsBeginImageContextWithOptions(CGSizeMake(size.width, size.height), NO, 0); + [view.layer renderInContext:UIGraphicsGetCurrentContext()]; + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + return image; +} + +@end diff --git a/components/CollectionCells/examples/CollectionCellsTextExample.m b/components/CollectionCells/examples/CollectionCellsTextExample.m new file mode 100644 index 00000000000..6639a1ce8c2 --- /dev/null +++ b/components/CollectionCells/examples/CollectionCellsTextExample.m @@ -0,0 +1,102 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "CollectionCellsTextExample.h" + +#import "MaterialTypography.h" + +static NSString *const kReusableIdentifierItem = @"itemCellIdentifier"; +static NSString *const kExampleDetailText = + @"Pellentesque non quam ornare, porta urna sed, malesuada felis. Praesent at gravida felis, " + "non facilisis enim. Proin dapibus laoreet lorem, in viverra leo dapibus a."; + +@implementation CollectionCellsTextExample { + NSMutableArray *_content; +} + ++ (NSArray *)catalogBreadcrumbs { + return @[ @"Collection Cells", @"Cell Text Example" ]; +} + ++ (BOOL)catalogIsPrimaryDemo { + return YES; +} + ++ (NSString *)catalogDescription { + return @"Material Collection Cells enables a native collection view cell to have Material " + "design layout and styling. It also provides editing and extensive customization " + "capabilities."; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + // Register cell class. + [self.collectionView registerClass:[MDCCollectionViewTextCell class] + forCellWithReuseIdentifier:kReusableIdentifierItem]; + + // Populate content with array of text, details text, and number of lines. + _content = [NSMutableArray array]; + [_content addObject:@[ @"Single line text", + @"", + @(MDCCellDefaultOneLineHeight) ]]; + [_content addObject:@[ @"", + @"Single line detail text", + @(MDCCellDefaultOneLineHeight) ]]; + [_content addObject:@[ @"Two line text", + @"Here is the detail text", + @(MDCCellDefaultTwoLineHeight) ]]; + [_content addObject:@[ @"Two line text (truncated)", + kExampleDetailText, + @(MDCCellDefaultTwoLineHeight) ]]; + [_content addObject:@[ @"Three line text (wrapped)", + kExampleDetailText, + @(MDCCellDefaultThreeLineHeight) ]]; +} + +#pragma mark - + +- (NSInteger)collectionView:(UICollectionView *)collectionView + numberOfItemsInSection:(NSInteger)section { + return [_content count]; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath { + MDCCollectionViewTextCell *cell = + [collectionView dequeueReusableCellWithReuseIdentifier:kReusableIdentifierItem + forIndexPath:indexPath]; + cell.textLabel.text = _content[indexPath.item][0]; + cell.detailTextLabel.text = _content[indexPath.item][1]; + + if (indexPath.item == 4) { + cell.detailTextLabel.numberOfLines = 2; + } + return cell; +} + +#pragma mark - + +- (CGFloat)collectionView:(UICollectionView *)collectionView + cellHeightAtIndexPath:(NSIndexPath *)indexPath { + return [_content[indexPath.item][2] integerValue]; +} + +@end diff --git a/components/CollectionCells/examples/resources/CollectionCellsExample.xcassets/ic_description.imageset/Contents.json b/components/CollectionCells/examples/resources/CollectionCellsExample.xcassets/ic_description.imageset/Contents.json new file mode 100644 index 00000000000..8c45308acec --- /dev/null +++ b/components/CollectionCells/examples/resources/CollectionCellsExample.xcassets/ic_description.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images": [ + { + "filename": "ic_description.png", + "idiom": "universal", + "scale": "1x" + }, + { + "filename": "ic_description_2x.png", + "idiom": "universal", + "scale": "2x" + }, + { + "filename": "ic_description_3x.png", + "idiom": "universal", + "scale": "3x" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/components/CollectionCells/examples/resources/CollectionCellsExample.xcassets/ic_description.imageset/ic_description.png b/components/CollectionCells/examples/resources/CollectionCellsExample.xcassets/ic_description.imageset/ic_description.png new file mode 100644 index 00000000000..a39e27f47b6 Binary files /dev/null and b/components/CollectionCells/examples/resources/CollectionCellsExample.xcassets/ic_description.imageset/ic_description.png differ diff --git a/components/CollectionCells/examples/resources/CollectionCellsExample.xcassets/ic_description.imageset/ic_description_2x.png b/components/CollectionCells/examples/resources/CollectionCellsExample.xcassets/ic_description.imageset/ic_description_2x.png new file mode 100644 index 00000000000..8470bc03fe5 Binary files /dev/null and b/components/CollectionCells/examples/resources/CollectionCellsExample.xcassets/ic_description.imageset/ic_description_2x.png differ diff --git a/components/CollectionCells/examples/resources/CollectionCellsExample.xcassets/ic_description.imageset/ic_description_3x.png b/components/CollectionCells/examples/resources/CollectionCellsExample.xcassets/ic_description.imageset/ic_description_3x.png new file mode 100644 index 00000000000..dd23b93ac24 Binary files /dev/null and b/components/CollectionCells/examples/resources/CollectionCellsExample.xcassets/ic_description.imageset/ic_description_3x.png differ diff --git a/components/CollectionCells/examples/supplemental/CollectionCellsLayoutExample.h b/components/CollectionCells/examples/supplemental/CollectionCellsLayoutExample.h new file mode 100644 index 00000000000..7aa134b9f2a --- /dev/null +++ b/components/CollectionCells/examples/supplemental/CollectionCellsLayoutExample.h @@ -0,0 +1,22 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +#import "MaterialCollections.h" + +@interface CollectionCellsLayoutExample : MDCCollectionViewController +@end diff --git a/components/CollectionCells/examples/supplemental/CollectionCellsTextExample.h b/components/CollectionCells/examples/supplemental/CollectionCellsTextExample.h new file mode 100644 index 00000000000..74c321a87c1 --- /dev/null +++ b/components/CollectionCells/examples/supplemental/CollectionCellsTextExample.h @@ -0,0 +1,22 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +#import "MaterialCollections.h" + +@interface CollectionCellsTextExample : MDCCollectionViewController +@end diff --git a/components/CollectionCells/src/MDCCollectionViewCell+Ink.h b/components/CollectionCells/src/MDCCollectionViewCell+Ink.h new file mode 100644 index 00000000000..e7a206ff2d4 --- /dev/null +++ b/components/CollectionCells/src/MDCCollectionViewCell+Ink.h @@ -0,0 +1,30 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import "MDCCollectionViewCell.h" + +#import "MaterialInk.h" + +/** + This class adds an ink view as a cell subview. Must be used with an ink controller in order to + fully implement ink behavior. + */ +@interface MDCCollectionViewCell (Ink) + +/** View containing the ink effect. */ +@property(nonatomic, strong, nullable) MDCInkView *inkView; + +@end diff --git a/components/CollectionCells/src/MDCCollectionViewCell+Ink.m b/components/CollectionCells/src/MDCCollectionViewCell+Ink.m new file mode 100644 index 00000000000..69760ae6a65 --- /dev/null +++ b/components/CollectionCells/src/MDCCollectionViewCell+Ink.m @@ -0,0 +1,40 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "MDCCollectionViewCell+Ink.h" + +#import + +@implementation MDCCollectionViewCell (Ink) + +@dynamic inkView; + +- (MDCInkView *)inkView { + return objc_getAssociatedObject(self, _cmd); +} + +- (void)setInkView:(MDCInkView *)inkView { + if (!self.inkView) { + [self addSubview:inkView]; + } + objc_setAssociatedObject(self, @selector(inkView), inkView, OBJC_ASSOCIATION_RETAIN_NONATOMIC); +} + +@end diff --git a/components/CollectionCells/src/MDCCollectionViewCell.h b/components/CollectionCells/src/MDCCollectionViewCell.h new file mode 100644 index 00000000000..6580b48b3c5 --- /dev/null +++ b/components/CollectionCells/src/MDCCollectionViewCell.h @@ -0,0 +1,90 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +/** The available cell accessory view types. Based on UITableViewCellAccessoryType. */ +typedef NS_ENUM(NSUInteger, MDCCollectionViewCellAccessoryType) { + /** Default value. No accessory view shown. */ + MDCCollectionViewCellAccessoryNone, + + /** A chevron accessory view. */ + MDCCollectionViewCellAccessoryDisclosureIndicator, + + /** A checkmark accessory view. */ + MDCCollectionViewCellAccessoryCheckmark, + + /** An info button accessory view. */ + MDCCollectionViewCellAccessoryDetailButton +}; + +/** + The MDCCollectionViewCell class provides an implementation of UICollectionViewCell that + supports Material design layout and styling. + */ +@interface MDCCollectionViewCell : UICollectionViewCell + +/** The accessory type for this cell. Default is MDCCollectionViewCellAccessoryNone. */ +@property(nonatomic) MDCCollectionViewCellAccessoryType accessoryType; + +/** If set, use custom view and ignore accessoryType. Defaults to nil. */ +@property(nonatomic, strong, nullable) UIView *accessoryView; + +/** + The accessory inset for this cell. Only left/right insets are valid as top/bottom insets will + be ignored. These insets are used for both accessories and editing mask controls. + Defaults to {0, 16.0f, 0, 16.0f}. + */ +@property(nonatomic) UIEdgeInsets accessoryInset; + +/** + Whether to hide the separator for this cell. If not set, the |shouldHideSeparators| property of + the collection view style manager will be used. Defaults to NO. + */ +@property(nonatomic) BOOL shouldHideSeparator; + +/** + The separator inset for this cell. Only left/right insets are valid as top/bottom insets will be + ignored. If this property is not changed, the |separatorInset| property of the collection view + style manager will be used instead. Defaults to UIEdgeInsetsZero. + */ +@property(nonatomic) UIEdgeInsets separatorInset; + +/** + A boolean value indicating whether a cell permits interactions with subviews of its content while + the cell is in editing mode. If NO, then tapping anywhere in the cell will select it instead of + permitting the tapped subview to receive the touch. Defaults to NO. + */ +@property(nonatomic) BOOL allowsCellInteractionsWhileEditing; + +/** + A boolean value indicating whether the a cell is being edited. Setting is not animated. + + When set, the cell will shows/hide editing controls with/without animation. + */ +@property(nonatomic, getter=isEditing) BOOL editing; + +/** + Set the editing state with optional animations. + + When set, the cell will shows/hide editing controls with/without animation. + + @param editing YES if editing; otherwise, NO. + @param animated YES the transition will be animated; otherwise, NO. + */ +- (void)setEditing:(BOOL)editing animated:(BOOL)animated; + +@end diff --git a/components/CollectionCells/src/MDCCollectionViewCell.m b/components/CollectionCells/src/MDCCollectionViewCell.m new file mode 100644 index 00000000000..f4fd9127757 --- /dev/null +++ b/components/CollectionCells/src/MDCCollectionViewCell.m @@ -0,0 +1,365 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "MDCCollectionViewCell.h" + +#import "MaterialCollectionLayoutAttributes.h" +#import "MaterialIcons+ic_check.h" +#import "MaterialIcons+ic_check_circle.h" +#import "MaterialIcons+ic_chevron_right.h" +#import "MaterialIcons+ic_info.h" +#import "MaterialIcons+ic_radio_button_unchecked.h" +#import "MaterialIcons+ic_reorder.h" + +#define RGBCOLOR(r, g, b) [UIColor colorWithRed:(r) / 255.0f green:(g) / 255.0f blue:(b) / 255.0f alpha:1] +#define HEXCOLOR(hex) RGBCOLOR((((hex) >> 16) & 0xFF), (((hex) >> 8) & 0xFF), ((hex)&0xFF)) + +static CGFloat kEditingControlAppearanceOffset = 16.0f; + +// Default accessory insets. +static const UIEdgeInsets kAccessoryInsetDefault = {0, 16.0f, 0, 16.0f}; + +// Default editing icon colors. +static const uint32_t kCellGrayColor = 0x626262; +static const uint32_t kCellRedColor = 0xF44336; + +@implementation MDCCollectionViewCell { + MDCCollectionViewLayoutAttributes *_attr; + BOOL _usesCellSeparatorHiddenOverride; + BOOL _usesCellSeparatorInsetOverride; + CAShapeLayer *_separatorLayer; + UIView *_separatorView; + UIImageView *_backgroundImageView; + UIImageView *_editingReorderImageView; + UIImageView *_editingSelectorImageView; +} + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self commonMDCCollectionViewCellInit]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super initWithCoder:aDecoder]; + if (self) { + [self commonMDCCollectionViewCellInit]; + } + return self; +} + +- (void)commonMDCCollectionViewCellInit { + // Separator defaults. + _separatorView = [[UIImageView alloc] initWithFrame:CGRectZero]; + [self addSubview:_separatorView]; + + // Accessory defaults. + _accessoryType = MDCCollectionViewCellAccessoryNone; + _accessoryInset = kAccessoryInsetDefault; +} + +#pragma mark - Layout + +- (void)prepareForReuse { + [super prepareForReuse]; + + // Reset properties. + _usesCellSeparatorHiddenOverride = NO; + _usesCellSeparatorInsetOverride = NO; + _separatorView.hidden = YES; + + [self drawSeparatorIfNeeded]; + [self updateInterfaceForEditing]; + + // Reset cells hidden during swipe deletion. + self.hidden = NO; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + self.contentView.frame = [self contentViewFrame]; + _accessoryView.frame = [self accessoryFrame]; + + // Animate editing controls. + [UIView animateWithDuration:0.3 + animations:^{ + _editingReorderImageView.alpha = + _attr.shouldShowReorderStateMask ? 1.0f : 0.0f; + _editingReorderImageView.transform = + _attr.shouldShowReorderStateMask + ? CGAffineTransformMakeTranslation(kEditingControlAppearanceOffset, 0) + : CGAffineTransformIdentity; + + _editingSelectorImageView.alpha = + _attr.shouldShowSelectorStateMask ? 1.0f : 0.0f; + _editingSelectorImageView.transform = + _attr.shouldShowSelectorStateMask + ? CGAffineTransformMakeTranslation(-kEditingControlAppearanceOffset, 0) + : CGAffineTransformIdentity; + }]; +} + +- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes { + [super applyLayoutAttributes:layoutAttributes]; + if ([layoutAttributes isKindOfClass:[MDCCollectionViewLayoutAttributes class]]) { + _attr = (MDCCollectionViewLayoutAttributes *)layoutAttributes; + + if (_attr.representedElementCategory == UICollectionElementCategoryCell) { + [self setEditing:_attr.editing]; + } + + // Create image view to hold cell background image with shadowing. + if (!_backgroundImageView) { + _backgroundImageView = [[UIImageView alloc] initWithFrame:self.bounds]; + self.backgroundView = _backgroundImageView; + } + _backgroundImageView.image = _attr.backgroundImage; + + // Draw separator if needed. + [self drawSeparatorIfNeeded]; + + self.contentView.frame = [self contentViewFrame]; + _accessoryView.frame = [self accessoryFrame]; + + // Animate cell on appearance settings. + [self updateAppearanceAnimation]; + } +} + +#pragma mark - Accessory Views + +- (void)setAccessoryType:(MDCCollectionViewCellAccessoryType)accessoryType { + _accessoryType = accessoryType; + + UIImageView *accessoryImageView = nil; + if (!_accessoryView && accessoryType != MDCCollectionViewCellAccessoryNone) { + // Add accessory view. + accessoryImageView = [[UIImageView alloc] initWithFrame:CGRectZero]; + _accessoryView = accessoryImageView; + _accessoryView.userInteractionEnabled = NO; + [self addSubview:_accessoryView]; + } + + switch (_accessoryType) { + case MDCCollectionViewCellAccessoryDisclosureIndicator: { + accessoryImageView.image = + [UIImage imageWithContentsOfFile:[MDCIcons pathFor_ic_chevron_right]]; + break; + } + case MDCCollectionViewCellAccessoryCheckmark: { + UIImage *image = [UIImage imageWithContentsOfFile:[MDCIcons pathFor_ic_check]]; + accessoryImageView.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + break; + } + case MDCCollectionViewCellAccessoryDetailButton: { + UIImage *image = [UIImage imageWithContentsOfFile:[MDCIcons pathFor_ic_info]]; + accessoryImageView.image = [image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + break; + } + case MDCCollectionViewCellAccessoryNone: + accessoryImageView.image = nil; + break; + } + [_accessoryView sizeToFit]; +} + +- (void)setAccessoryView:(UIView *)accessoryView { + if (!_accessoryView) { + [self addSubview:accessoryView]; + } + _accessoryView = accessoryView; +} + +- (CGRect)accessoryFrame { + CGSize size = _accessoryView.frame.size; + return CGRectMake(CGRectGetWidth(self.bounds) - size.width - _accessoryInset.right, + (CGRectGetHeight(self.bounds) - size.height) / 2, + size.width, + size.height); +} + +#pragma mark - Separator + +- (void)setShouldHideSeparator:(BOOL)shouldHideSeparator { + _usesCellSeparatorHiddenOverride = YES; + _shouldHideSeparator = shouldHideSeparator; + [self drawSeparatorIfNeeded]; +} + +- (void)setSeparatorInset:(UIEdgeInsets)separatorInset { + _usesCellSeparatorInsetOverride = YES; + _separatorInset = separatorInset; + [self drawSeparatorIfNeeded]; +} + +- (void)drawSeparatorIfNeeded { + // Determine separator spec from attributes and cell overrides. Don't draw separator for bottom + // cell or in grid layout cells. Separators are added here as cell subviews instead of decoration + // views registered with the layout to overcome inability to animate decoration views in + // coordination with cell animations. + BOOL isHidden = + _usesCellSeparatorHiddenOverride ? _shouldHideSeparator : _attr.shouldHideSeparators; + UIEdgeInsets separatorInset = + _usesCellSeparatorInsetOverride ? _separatorInset : _attr.separatorInset; + BOOL isBottom = _attr.sectionOrdinalPosition & MDCCollectionViewOrdinalPositionVerticalBottom; + BOOL isGrid = _attr.isGridLayout; + + BOOL hideSeparator = isBottom || isHidden || isGrid; + if (hideSeparator != _separatorView.hidden) { + _separatorView.hidden = hideSeparator; + } + + if (!hideSeparator) { + CGFloat borderWidth = (1.0f / [[UIScreen mainScreen] scale]); + CGRect separatorFrame = + CGRectMake(borderWidth, + CGRectGetHeight(self.bounds) - _attr.separatorLineHeight, + CGRectGetWidth(self.bounds) - borderWidth, + _attr.separatorLineHeight); + _separatorView.frame = UIEdgeInsetsInsetRect(separatorFrame, separatorInset); + _separatorView.backgroundColor = _attr.separatorColor; + } +} + +#pragma mark - Editing + +- (void)setEditing:(BOOL)editing { + [self setEditing:editing animated:NO]; +} + +- (void)setEditing:(BOOL)editing animated:(BOOL)animated { + if (_editing == editing) { + return; + } + _editing = editing; + [self updateInterfaceForEditing]; +} + +- (void)updateInterfaceForEditing { + self.contentView.userInteractionEnabled = [self shouldEnableCellInteractions]; + + if (_editing) { + // Disable implicit animations when setting initial positioning of these subviews. + [CATransaction begin]; + [CATransaction setDisableActions:YES]; + + // Create reorder editing controls. + if (_attr.shouldShowReorderStateMask && !_editingReorderImageView) { + UIImage *reorderImage = [UIImage imageWithContentsOfFile:[MDCIcons pathFor_ic_reorder]]; + reorderImage = [reorderImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + _editingReorderImageView = [[UIImageView alloc] initWithImage:reorderImage]; + _editingReorderImageView.tintColor = HEXCOLOR(kCellGrayColor); + _editingReorderImageView.alpha = 0.0f; + _editingReorderImageView.frame = + CGRectMake(0, + (CGRectGetHeight(self.bounds) - reorderImage.size.height) / 2, + reorderImage.size.width, + reorderImage.size.height); + [self addSubview:_editingReorderImageView]; + } + + // Create selector editing controls. + if (_attr.shouldShowSelectorStateMask && !_editingSelectorImageView) { + UIImage *selectorImage = + [UIImage imageWithContentsOfFile:[MDCIcons pathFor_ic_radio_button_unchecked]]; + selectorImage = [selectorImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + _editingSelectorImageView = [[UIImageView alloc] initWithImage:selectorImage]; + _editingSelectorImageView.tintColor = HEXCOLOR(kCellGrayColor); + _editingSelectorImageView.alpha = 0.0f; + _editingSelectorImageView.frame = + CGRectMake(CGRectGetWidth(self.bounds) - selectorImage.size.width, + (CGRectGetHeight(self.bounds) - selectorImage.size.height) / 2, + selectorImage.size.width, + selectorImage.size.height); + _editingSelectorImageView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin; + [self addSubview:_editingSelectorImageView]; + } + [CATransaction commit]; + } + + // Update accessory view. + _accessoryView.alpha = _attr.shouldShowSelectorStateMask ? 0.0f : 1.0f; + _accessoryInset.right = _attr.shouldShowSelectorStateMask + ? kAccessoryInsetDefault.right + kEditingControlAppearanceOffset + : kAccessoryInsetDefault.right; +} + +#pragma mark - Selecting + +- (void)setSelected:(BOOL)selected { + [super setSelected:selected]; + if (selected) { + _editingSelectorImageView.image = + [UIImage imageWithContentsOfFile:[MDCIcons pathFor_ic_check_circle]]; + _editingSelectorImageView.tintColor = HEXCOLOR(kCellRedColor); + } else { + _editingSelectorImageView.image = + [UIImage imageWithContentsOfFile:[MDCIcons pathFor_ic_radio_button_unchecked]]; + _editingSelectorImageView.tintColor = HEXCOLOR(kCellGrayColor); + } + _editingSelectorImageView.image = + [_editingSelectorImageView.image imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; +} + +#pragma mark - Cell Appearance Animation + +- (void)updateAppearanceAnimation { + if (_attr.animateCellsOnAppearanceDelay > 0) { + // Intially hide content view and separator. + self.contentView.alpha = 0; + _separatorView.alpha = 0; + + // Animate fade-in after delay. + if (!_attr.willAnimateCellsOnAppearance) { + [UIView animateWithDuration:_attr.animateCellsOnAppearanceDuration + delay:_attr.animateCellsOnAppearanceDelay + options:UIViewAnimationOptionCurveEaseOut + animations:^{ + self.contentView.alpha = 1; + _separatorView.alpha = 1; + } + completion:nil]; + } + } +} + +#pragma mark - Private + +- (CGRect)contentViewFrame { + CGFloat leftPadding = + _attr.shouldShowReorderStateMask + ? CGRectGetWidth(_editingReorderImageView.bounds) + kEditingControlAppearanceOffset + : 0.f; + + CGFloat rightPadding = + _attr.shouldShowSelectorStateMask + ? CGRectGetWidth(_editingSelectorImageView.bounds) + kEditingControlAppearanceOffset + : 0.f; + return UIEdgeInsetsInsetRect(self.bounds, UIEdgeInsetsMake(0, leftPadding, 0, rightPadding)); +} + +- (BOOL)shouldEnableCellInteractions { + return !_editing || _allowsCellInteractionsWhileEditing; +} + +@end diff --git a/components/CollectionCells/src/MDCCollectionViewTextCell.h b/components/CollectionCells/src/MDCCollectionViewTextCell.h new file mode 100644 index 00000000000..8fbf1bfcb28 --- /dev/null +++ b/components/CollectionCells/src/MDCCollectionViewTextCell.h @@ -0,0 +1,75 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import "MDCCollectionViewCell.h" + +/** Default cell height for single line of text. Defaults to 48.0f. */ +extern const CGFloat MDCCellDefaultOneLineHeight; + +/** Default cell height for single line of text with avatar. Defaults to 56.0f. */ +extern const CGFloat MDCCellDefaultOneLineWithAvatarHeight; + +/** Default cell height for two lines of text. Defaults to 72.0f. */ +extern const CGFloat MDCCellDefaultTwoLineHeight; + +/** Default cell height for three lines of text. Defaults to 88.0f. */ +extern const CGFloat MDCCellDefaultThreeLineHeight; + +/** + The MDCCollectionViewTextCell class provides an implementation of UICollectionViewCell that + supports Material design layout and styling. It provides two labels for text as well as an + image view. The default layout specifications can be found at the following link. + + @see http://www.google.com/design/spec/components/lists.html#lists-specs + */ +@interface MDCCollectionViewTextCell : MDCCollectionViewCell + +/** + A text label. Typically this will be the first line of text in the cell. + + Default text label properties: + - text defaults to nil. + - font defaults to [MDCTypography subheadFont]. + - textColor defaults to [UIColor colorWithWhite:0 alpha:MDCTypography subheadFontOpacity]]. + - shadowColor defaults to nil. + - shadowOffset defaults to CGSizeZero. + - textAlignment defaults to NSTextAlignmentLeft. + - lineBreakMode defaults to NSLineBreakByTruncatingTail. + - numberOfLines defaults to 1. + */ +@property(nonatomic, readonly, strong, nullable) UILabel *textLabel; + +/** + A detail text label. Typically this will be the second line of text in the cell. + + Default detail text label properties: + - text defaults to nil. + - font defaults to [MDCTypography body1Font]. + - textColor defaults to [UIColor colorWithWhite:0 alpha:MDCTypography captionFontOpacity]]. + - shadowColor defaults to nil. + - shadowOffset defaults to CGSizeZero. + - textAlignment defaults to NSTextAlignmentLeft. + - lineBreakMode defaults to NSLineBreakByTruncatingTail. + - numberOfLines defaults to 1. + */ +@property(nonatomic, readonly, strong, nullable) UILabel *detailTextLabel; + +/** + An image view placed left side of cell. Default left padding is 16.0f. + */ +@property(nonatomic, readonly, strong, nullable) UIImageView *imageView; + +@end diff --git a/components/CollectionCells/src/MDCCollectionViewTextCell.m b/components/CollectionCells/src/MDCCollectionViewTextCell.m new file mode 100644 index 00000000000..d403e12c36d --- /dev/null +++ b/components/CollectionCells/src/MDCCollectionViewTextCell.m @@ -0,0 +1,197 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "MDCCollectionViewTextCell.h" + +#import "MaterialTypography.h" + +#import + +// Default cell heights. +const CGFloat MDCCellDefaultOneLineHeight = 48.0f; +const CGFloat MDCCellDefaultOneLineWithAvatarHeight = 56.0f; +const CGFloat MDCCellDefaultTwoLineHeight = 72.0f; +const CGFloat MDCCellDefaultThreeLineHeight = 88.0f; + +// Default cell fonts. +#define kCellDefaultTextFont [MDCTypography subheadFont] +#define kCellDefaultDetailTextFont [MDCTypography body1Font] + +// Default cell font opacity. +#define kCellDefaultTextOpacity [MDCTypography subheadFontOpacity] +#define kCellDefaultDetailTextFontOpacity [MDCTypography captionFontOpacity] + +// Cell padding top/bottom. +static const CGFloat kCellTwoLinePaddingTop = 20; +static const CGFloat kCellTwoLinePaddingBottom = 20; +static const CGFloat kCellThreeLinePaddingTop = 16; +static const CGFloat kCellThreeLinePaddingBottom = 20; +// Cell padding left/right. +static const CGFloat kCellTextNoImagePaddingLeft = 16; +static const CGFloat kCellTextNoImagePaddingRight = 16; +static const CGFloat kCellTextWithImagePaddingLeft = 72; +// Cell image view padding. +static const CGFloat kCellImagePaddingLeft = 16; + +@implementation MDCCollectionViewTextCell { + UIView *_contentWrapper; +} + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self commonMDCCollectionViewTextCellInit]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super initWithCoder:aDecoder]; + if (self) { + [self commonMDCCollectionViewTextCellInit]; + } + return self; +} + +- (void)commonMDCCollectionViewTextCellInit { + _contentWrapper = [[UIView alloc] initWithFrame:self.contentView.bounds]; + _contentWrapper.autoresizingMask = UIViewAutoresizingFlexibleWidth; + _contentWrapper.clipsToBounds = YES; + [self.contentView addSubview:_contentWrapper]; + + // Text label. + _textLabel = [[UILabel alloc] initWithFrame:CGRectZero]; + _textLabel.font = kCellDefaultTextFont; + _textLabel.textColor = [UIColor colorWithWhite:0 alpha:kCellDefaultTextOpacity]; + _textLabel.shadowColor = nil; + _textLabel.shadowOffset = CGSizeZero; + _textLabel.textAlignment = NSTextAlignmentLeft; + _textLabel.lineBreakMode = NSLineBreakByTruncatingTail; + [_contentWrapper addSubview:_textLabel]; + + // Detail text label. + _detailTextLabel = [[UILabel alloc] initWithFrame:CGRectZero]; + _detailTextLabel.font = kCellDefaultDetailTextFont; + _detailTextLabel.textColor = [UIColor colorWithWhite:0 alpha:kCellDefaultDetailTextFontOpacity]; + _detailTextLabel.shadowColor = nil; + _detailTextLabel.shadowOffset = CGSizeZero; + _detailTextLabel.textAlignment = NSTextAlignmentLeft; + _detailTextLabel.lineBreakMode = NSLineBreakByTruncatingTail; + [_contentWrapper addSubview:_detailTextLabel]; + + // Image view. + _imageView = [[UIImageView alloc] initWithFrame:CGRectZero]; + [self.contentView addSubview:_imageView]; +} + +#pragma mark - Layout + +- (void)layoutSubviews { + [super layoutSubviews]; + [self applyMetrics]; +} + +- (CGRect)contentWrapperFrame { + CGFloat leftPadding = + _imageView.image ? kCellTextWithImagePaddingLeft : kCellTextNoImagePaddingLeft; + CGFloat rightPadding = kCellTextNoImagePaddingRight; + if (self.accessoryView && !self.isEditing) { + rightPadding += CGRectGetWidth(self.accessoryView.bounds) + kCellTextNoImagePaddingRight; + } + return UIEdgeInsetsInsetRect(self.contentView.bounds, + UIEdgeInsetsMake(0, leftPadding, 0, rightPadding)); +} + +- (void)applyMetrics { + // Set content wrapper frame. + _contentWrapper.frame = [self contentWrapperFrame]; + CGFloat boundsHeight = CGRectGetHeight(_contentWrapper.bounds); + + // Image layout. + [_imageView sizeToFit]; + CGRect imageFrame = _imageView.frame; + imageFrame.origin.x = kCellImagePaddingLeft; + imageFrame.origin.y = + (CGRectGetHeight(self.contentView.frame) / 2) - (imageFrame.size.height / 2); + + // Text layout. + CGRect textFrame = CGRectZero; + textFrame.size = [self frameSizeForLabel:_textLabel]; + CGRect detailFrame = CGRectZero; + detailFrame.size = [self frameSizeForLabel:_detailTextLabel]; + + if ([self numberOfAllVisibleTextLines] == 1) { + // Alignment for single line. + textFrame.origin.y = (boundsHeight / 2) - (textFrame.size.height / 2); + detailFrame.origin.y = (boundsHeight / 2) - (detailFrame.size.height / 2); + + } else if ([self numberOfAllVisibleTextLines] == 2) { + if (!CGRectIsEmpty(textFrame) && !CGRectIsEmpty(detailFrame)) { + // Alignment for two lines. + textFrame.origin.y = + kCellTwoLinePaddingTop + _textLabel.font.ascender - textFrame.size.height; + detailFrame.origin.y = + boundsHeight - kCellTwoLinePaddingBottom - + detailFrame.size.height - _detailTextLabel.font.descender; + } else { + // Since single wrapped label, just center. + textFrame.origin.y = (boundsHeight / 2) - (textFrame.size.height / 2); + detailFrame.origin.y = (boundsHeight / 2) - (detailFrame.size.height / 2); + } + + } else if ([self numberOfAllVisibleTextLines] == 3) { + if (!CGRectIsEmpty(textFrame) && !CGRectIsEmpty(detailFrame)) { + // Alignment for three lines. + textFrame.origin.y = + kCellThreeLinePaddingTop + _textLabel.font.ascender - _textLabel.font.lineHeight; + detailFrame.origin.y = + boundsHeight - kCellThreeLinePaddingBottom - + detailFrame.size.height - _detailTextLabel.font.descender; + imageFrame.origin.y = kCellThreeLinePaddingTop; + } else { + // Since single wrapped label, just center. + textFrame.origin.y = (boundsHeight / 2) - (textFrame.size.height / 2); + detailFrame.origin.y = (boundsHeight / 2) - (detailFrame.size.height / 2); + } + } + _textLabel.frame = textFrame; + _detailTextLabel.frame = detailFrame; + _imageView.frame = imageFrame; +} + +- (NSInteger)numberOfAllVisibleTextLines { + return [self numberOfLinesForLabel:_textLabel] + [self numberOfLinesForLabel:_detailTextLabel]; +} + +- (NSInteger)numberOfLinesForLabel:(UILabel *)label { + CGSize size = [self frameSizeForLabel:label]; + return (NSInteger)floor(size.height / label.font.lineHeight); +} + +- (CGSize)frameSizeForLabel:(UILabel *)label { + CGFloat width = MIN(CGRectGetWidth(_contentWrapper.bounds), + [label.text sizeWithAttributes:@{NSFontAttributeName : label.font}].width); + CGFloat height = [label textRectForBounds:_contentWrapper.bounds + limitedToNumberOfLines:label.numberOfLines] + .size.height; + return CGSizeMake(width, height); +} + +@end diff --git a/components/CollectionCells/src/MaterialCollectionCells.h b/components/CollectionCells/src/MaterialCollectionCells.h new file mode 100644 index 00000000000..bc72418e497 --- /dev/null +++ b/components/CollectionCells/src/MaterialCollectionCells.h @@ -0,0 +1,19 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import "MDCCollectionViewCell+Ink.h" +#import "MDCCollectionViewCell.h" +#import "MDCCollectionViewTextCell.h" diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/Contents.json b/components/CollectionCells/src/MaterialCollectionCells.xcassets/Contents.json new file mode 100644 index 00000000000..da4a164c918 --- /dev/null +++ b/components/CollectionCells/src/MaterialCollectionCells.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_check.imageset/Contents.json b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_check.imageset/Contents.json new file mode 100644 index 00000000000..370e9ec2629 --- /dev/null +++ b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_check.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images": [ + { + "filename": "mdc_cell_check.png", + "idiom": "universal", + "scale": "1x" + }, + { + "filename": "mdc_cell_check_2x.png", + "idiom": "universal", + "scale": "2x" + }, + { + "filename": "mdc_cell_check_3x.png", + "idiom": "universal", + "scale": "3x" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_check.imageset/mdc_cell_check.png b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_check.imageset/mdc_cell_check.png new file mode 100644 index 00000000000..1c14c9c4459 Binary files /dev/null and b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_check.imageset/mdc_cell_check.png differ diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_check.imageset/mdc_cell_check@2x.png b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_check.imageset/mdc_cell_check@2x.png new file mode 100644 index 00000000000..64a4944f753 Binary files /dev/null and b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_check.imageset/mdc_cell_check@2x.png differ diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_check.imageset/mdc_cell_check@3x.png b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_check.imageset/mdc_cell_check@3x.png new file mode 100644 index 00000000000..b26a2c05e3f Binary files /dev/null and b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_check.imageset/mdc_cell_check@3x.png differ diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_chevron_right.imageset/Contents.json b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_chevron_right.imageset/Contents.json new file mode 100644 index 00000000000..787f9812ceb --- /dev/null +++ b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_chevron_right.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images": [ + { + "filename": "mdc_cell_chevron_right.png", + "idiom": "universal", + "scale": "1x" + }, + { + "filename": "mdc_cell_chevron_right_2x.png", + "idiom": "universal", + "scale": "2x" + }, + { + "filename": "mdc_cell_chevron_right_3x.png", + "idiom": "universal", + "scale": "3x" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_chevron_right.imageset/mdc_cell_chevron_right.png b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_chevron_right.imageset/mdc_cell_chevron_right.png new file mode 100644 index 00000000000..c11a2a5e2d3 Binary files /dev/null and b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_chevron_right.imageset/mdc_cell_chevron_right.png differ diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_chevron_right.imageset/mdc_cell_chevron_right@2x.png b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_chevron_right.imageset/mdc_cell_chevron_right@2x.png new file mode 100644 index 00000000000..23338b8b5b2 Binary files /dev/null and b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_chevron_right.imageset/mdc_cell_chevron_right@2x.png differ diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_chevron_right.imageset/mdc_cell_chevron_right@3x.png b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_chevron_right.imageset/mdc_cell_chevron_right@3x.png new file mode 100644 index 00000000000..f97c51b8ed7 Binary files /dev/null and b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_chevron_right.imageset/mdc_cell_chevron_right@3x.png differ diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_delete.imageset/Contents.json b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_delete.imageset/Contents.json new file mode 100644 index 00000000000..720abaa989a --- /dev/null +++ b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_delete.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "mdc_cell_delete.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "mdc_cell_delete@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "mdc_cell_delete@3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_delete.imageset/mdc_cell_delete.png b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_delete.imageset/mdc_cell_delete.png new file mode 100644 index 00000000000..0dc34999bf1 Binary files /dev/null and b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_delete.imageset/mdc_cell_delete.png differ diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_delete.imageset/mdc_cell_delete@2x.png b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_delete.imageset/mdc_cell_delete@2x.png new file mode 100644 index 00000000000..330b1cc0e90 Binary files /dev/null and b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_delete.imageset/mdc_cell_delete@2x.png differ diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_delete.imageset/mdc_cell_delete@3x.png b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_delete.imageset/mdc_cell_delete@3x.png new file mode 100644 index 00000000000..f0e4f05c563 Binary files /dev/null and b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_delete.imageset/mdc_cell_delete@3x.png differ diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_info.imageset/Contents.json b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_info.imageset/Contents.json new file mode 100644 index 00000000000..e2bf98ee712 --- /dev/null +++ b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_info.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images": [ + { + "filename": "mdc_cell_info.png", + "idiom": "universal", + "scale": "1x" + }, + { + "filename": "mdc_cell_info_2x.png", + "idiom": "universal", + "scale": "2x" + }, + { + "filename": "mdc_cell_info_3x.png", + "idiom": "universal", + "scale": "3x" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_info.imageset/mdc_cell_info.png b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_info.imageset/mdc_cell_info.png new file mode 100644 index 00000000000..5ef3dc0809e Binary files /dev/null and b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_info.imageset/mdc_cell_info.png differ diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_info.imageset/mdc_cell_info@2x.png b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_info.imageset/mdc_cell_info@2x.png new file mode 100644 index 00000000000..46ed12a89bd Binary files /dev/null and b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_info.imageset/mdc_cell_info@2x.png differ diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_info.imageset/mdc_cell_info@3x.png b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_info.imageset/mdc_cell_info@3x.png new file mode 100644 index 00000000000..a81eeb9ee7e Binary files /dev/null and b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_info.imageset/mdc_cell_info@3x.png differ diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_reorder.imageset/Contents.json b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_reorder.imageset/Contents.json new file mode 100644 index 00000000000..e106cefc973 --- /dev/null +++ b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_reorder.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "mdc_cell_reorder.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "mdc_cell_reorder@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "mdc_cell_reorder@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_reorder.imageset/mdc_cell_reorder.png b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_reorder.imageset/mdc_cell_reorder.png new file mode 100644 index 00000000000..9556cbb1eae Binary files /dev/null and b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_reorder.imageset/mdc_cell_reorder.png differ diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_reorder.imageset/mdc_cell_reorder@2x.png b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_reorder.imageset/mdc_cell_reorder@2x.png new file mode 100644 index 00000000000..3ffe68f1c70 Binary files /dev/null and b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_reorder.imageset/mdc_cell_reorder@2x.png differ diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_reorder.imageset/mdc_cell_reorder@3x.png b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_reorder.imageset/mdc_cell_reorder@3x.png new file mode 100644 index 00000000000..146b67cee23 Binary files /dev/null and b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_reorder.imageset/mdc_cell_reorder@3x.png differ diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_selected.imageset/Contents.json b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_selected.imageset/Contents.json new file mode 100644 index 00000000000..6145b9a8789 --- /dev/null +++ b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_selected.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "mdc_cell_selected.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "mdc_cell_selected@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "mdc_cell_selected@3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_selected.imageset/mdc_cell_selected.png b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_selected.imageset/mdc_cell_selected.png new file mode 100644 index 00000000000..19baf52bca8 Binary files /dev/null and b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_selected.imageset/mdc_cell_selected.png differ diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_selected.imageset/mdc_cell_selected@2x.png b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_selected.imageset/mdc_cell_selected@2x.png new file mode 100644 index 00000000000..007abb509e2 Binary files /dev/null and b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_selected.imageset/mdc_cell_selected@2x.png differ diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_selected.imageset/mdc_cell_selected@3x.png b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_selected.imageset/mdc_cell_selected@3x.png new file mode 100644 index 00000000000..81375ad1d08 Binary files /dev/null and b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_selected.imageset/mdc_cell_selected@3x.png differ diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_unselected.imageset/Contents.json b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_unselected.imageset/Contents.json new file mode 100644 index 00000000000..3647b5769c2 --- /dev/null +++ b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_unselected.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x", + "filename" : "mdc_cell_unselected.png" + }, + { + "idiom" : "universal", + "scale" : "2x", + "filename" : "mdc_cell_unselected@2x.png" + }, + { + "idiom" : "universal", + "scale" : "3x", + "filename" : "mdc_cell_unselected@3x.png" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_unselected.imageset/mdc_cell_unselected.png b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_unselected.imageset/mdc_cell_unselected.png new file mode 100644 index 00000000000..451ea2ae8ff Binary files /dev/null and b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_unselected.imageset/mdc_cell_unselected.png differ diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_unselected.imageset/mdc_cell_unselected@2x.png b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_unselected.imageset/mdc_cell_unselected@2x.png new file mode 100644 index 00000000000..44f17d39606 Binary files /dev/null and b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_unselected.imageset/mdc_cell_unselected@2x.png differ diff --git a/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_unselected.imageset/mdc_cell_unselected@3x.png b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_unselected.imageset/mdc_cell_unselected@3x.png new file mode 100644 index 00000000000..833f3713b30 Binary files /dev/null and b/components/CollectionCells/src/MaterialCollectionCells.xcassets/mdc_cell_unselected.imageset/mdc_cell_unselected@3x.png differ diff --git a/components/CollectionLayoutAttributes/.jazzy.yaml b/components/CollectionLayoutAttributes/.jazzy.yaml new file mode 100644 index 00000000000..207bdfcb143 --- /dev/null +++ b/components/CollectionLayoutAttributes/.jazzy.yaml @@ -0,0 +1,5 @@ +# Auto-generated by scripts/generate_jazzy_yamls.sh. Used primarily by scripts/external/arc-jazzy-linter. +module: CollectionLayoutAttributes +umbrella_header: src/MaterialCollectionLayoutAttributes.h +objc: true +sdk: iphonesimulator diff --git a/components/CollectionLayoutAttributes/README.md b/components/CollectionLayoutAttributes/README.md new file mode 100644 index 00000000000..54d4c2e9b14 --- /dev/null +++ b/components/CollectionLayoutAttributes/README.md @@ -0,0 +1,94 @@ +--- +title: "Collection Layout Attributes" +layout: detail +section: components +excerpt: "Allows passing layout attributes to the cells and supplementary views." +--- +# Collection Layout Attributes + +Allows passing layout attributes to the cells and supplementary views. + + +### API Documentation + + + +- - - + +## Installation + +### Requirements + +- Xcode 7.0 or higher. +- iOS SDK version 7.0 or higher. + +### Installation with CocoaPods + +To add this component to your Xcode project using CocoaPods, add the following to your `Podfile`: + +~~~ +pod 'MaterialComponents/CollectionLayoutAttributes' +~~~ + +Then, run the following command: + +~~~ bash +$ pod install +~~~ + +- - - + +## Usage + +### Importing + +Before using Collection Layout Attributes, you'll need to import it: + + +#### Objective-C +~~~ objc +#import "MaterialCollectionLayoutAttributes.h" +~~~ + +#### Swift +~~~ swift +import MaterialComponents.MaterialCollectionLayoutAttributes +~~~ + + +The `MDCCollectionViewLayoutAttributes` class allows passing properties to a cell from a collection +view layout. Override the `-applyLayoutAttributes` method of any `UICollectionReusableView` or +`UICollectionViewCell` subclasses, then apply any of the properties of the attributes class. + + +#### Objective-C +~~~ objc +- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes { + [super applyLayoutAttributes:layoutAttributes]; + if ([layoutAttributes isKindOfClass:[MDCCollectionViewLayoutAttributes class]]) { + MDCCollectionViewLayoutAttributes *attr = (MDCCollectionViewLayoutAttributes *)layoutAttributes; + if (attr.representedElementCategory == UICollectionElementCategoryCell) { + + // Example to set a background image to the cell background view. + self.backgroundView = [[UIImageView alloc] initWithImage:attr.backgroundImage]; + } + } +} +~~~ + +#### Swift +~~~ swift +override func applyLayoutAttributes(layoutAttributes: UICollectionViewLayoutAttributes) { + super.applyLayoutAttributes(layoutAttributes) + if let attr = layoutAttributes as? MDCCollectionViewLayoutAttributes { + if (attr.representedElementCategory == .Cell) { + + // Example to set a background image to the cell background view. + self.backgroundView = UIImageView(image: attr.backgroundImage) + } + } +} +~~~ + diff --git a/components/CollectionLayoutAttributes/src/MDCCollectionViewLayoutAttributes.h b/components/CollectionLayoutAttributes/src/MDCCollectionViewLayoutAttributes.h new file mode 100644 index 00000000000..156bb07b93d --- /dev/null +++ b/components/CollectionLayoutAttributes/src/MDCCollectionViewLayoutAttributes.h @@ -0,0 +1,115 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +/** Types of cell ordinal positions available within a collectionView. */ +typedef NS_OPTIONS(NSUInteger, MDCCollectionViewOrdinalPosition) { + /** Cell visually has top edge within section. */ + MDCCollectionViewOrdinalPositionVerticalTop = 1 << 0, + + /** Cell visually has no top/bottom edges within section. */ + MDCCollectionViewOrdinalPositionVerticalCenter = 1 << 1, + + /** Cell visually has bottom edge within section. */ + MDCCollectionViewOrdinalPositionVerticalBottom = 1 << 2, + + /** + Cell visually has both bottom/top edges within section. Typically for a single or inlaid cell. + */ + MDCCollectionViewOrdinalPositionVerticalTopBottom = + (MDCCollectionViewOrdinalPositionVerticalTop | + MDCCollectionViewOrdinalPositionVerticalBottom), + + /** Cell visually has left edge within section. */ + MDCCollectionViewOrdinalPositionHorizontalLeft = 1 << 10, + + /** Cell visually has no left/right edges within section. */ + MDCCollectionViewOrdinalPositionHorizontalCenter = 1 << 11, + + /** Cell visually has right edge within section. */ + MDCCollectionViewOrdinalPositionHorizontalRight = 1 << 12 +}; + +/** + The MDCCollectionViewLayoutAttributes class allows passing layout attributes to the cells and + supplementary views. + */ +@interface MDCCollectionViewLayoutAttributes : UICollectionViewLayoutAttributes + +#pragma mark - Cell Styling + +/** A boolean value indicating whether the collectionView is being edited. Defaults to NO. */ +@property(nonatomic, getter=isEditing) BOOL editing; + +/** + A boolean value indicating whether the collectionView cell should be displayed with reorder + state mask. Defaults to NO. + */ +@property(nonatomic, assign) BOOL shouldShowReorderStateMask; + +/** + A boolean value indicating whether the collectionView cell should be displayed with selector + state mask. Defaults to NO. + */ +@property(nonatomic, assign) BOOL shouldShowSelectorStateMask; + +/** + A Boolean value indicating whether the collection view cell should allow the grid background + decoration view to be drawn at the specified index. + */ +@property(nonatomic, assign) BOOL shouldShowGridBackground; + +/** The image for use as the cells background image. */ +@property(nonatomic, strong, nullable) UIImage *backgroundImage; + +/** + A boolean value indicating whether the collectionView cell style is set to + MDCCollectionViewCellLayoutTypeGrid. + */ +@property(nonatomic, assign) BOOL isGridLayout; + +/** The ordinal position within the collectionView section. */ +@property(nonatomic, assign) MDCCollectionViewOrdinalPosition sectionOrdinalPosition; + +#pragma mark - Cell Separator + +/** Separator color. Defaults to #E0E0E0. */ +@property(nonatomic, strong, nullable) UIColor *separatorColor; + +/** Separator inset. Defaults to UIEdgeInsetsZero. */ +@property(nonatomic) UIEdgeInsets separatorInset; + +/** Separator line height. Defaults to 1.0f */ +@property(nonatomic) CGFloat separatorLineHeight; + +/** Whether to hide the cell separators. Defaults to NO. */ +@property(nonatomic) BOOL shouldHideSeparators; + +#pragma mark - Cell Appearance Animation + +/** Whether cells will animation on appearance. */ +@property(nonatomic, assign) BOOL willAnimateCellsOnAppearance; + +/** + The cell appearance animation duration. Defaults to MDCCollectionViewAnimatedAppearanceDuration. + */ +@property(nonatomic, assign) NSTimeInterval animateCellsOnAppearanceDuration; + +/** The cell delay used to stagger fade-in during appearance animation. Defaults to 0. */ +@property(nonatomic, assign) NSTimeInterval animateCellsOnAppearanceDelay; + +@end diff --git a/components/CollectionLayoutAttributes/src/MDCCollectionViewLayoutAttributes.m b/components/CollectionLayoutAttributes/src/MDCCollectionViewLayoutAttributes.m new file mode 100644 index 00000000000..71d3fdd2015 --- /dev/null +++ b/components/CollectionLayoutAttributes/src/MDCCollectionViewLayoutAttributes.m @@ -0,0 +1,86 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "MDCCollectionViewLayoutAttributes.h" + +@implementation MDCCollectionViewLayoutAttributes + +- (id)copyWithZone:(NSZone *)zone { + MDCCollectionViewLayoutAttributes *attributes = + (MDCCollectionViewLayoutAttributes *)[super copyWithZone:zone]; + attributes->_editing = _editing; + attributes->_shouldShowReorderStateMask = _shouldShowReorderStateMask; + attributes->_shouldShowSelectorStateMask = _shouldShowSelectorStateMask; + attributes->_shouldShowGridBackground = _shouldShowGridBackground; + attributes->_sectionOrdinalPosition = _sectionOrdinalPosition; + attributes->_backgroundImage = _backgroundImage; + attributes->_isGridLayout = _isGridLayout; + attributes->_separatorColor = _separatorColor; + attributes->_separatorInset = _separatorInset; + attributes->_separatorLineHeight = _separatorLineHeight; + attributes->_shouldHideSeparators = _shouldHideSeparators; + attributes->_willAnimateCellsOnAppearance = _willAnimateCellsOnAppearance; + attributes->_animateCellsOnAppearanceDuration = _animateCellsOnAppearanceDuration; + attributes->_animateCellsOnAppearanceDelay = _animateCellsOnAppearanceDelay; + return attributes; +} + +- (BOOL)isEqual:(id)object { + if (object == self) { + return YES; + } + if (!object || ![[object class] isEqual:[self class]]) { + return NO; + } + + // Compare custom properties that affect layout. + MDCCollectionViewLayoutAttributes *otherAttrs = (MDCCollectionViewLayoutAttributes *)object; + if ((otherAttrs.editing != self.editing) || + (otherAttrs.shouldShowReorderStateMask != self.shouldShowReorderStateMask) || + (otherAttrs.shouldShowSelectorStateMask != self.shouldShowSelectorStateMask) || + (otherAttrs.shouldShowGridBackground != self.shouldShowGridBackground) || + (otherAttrs.sectionOrdinalPosition != self.sectionOrdinalPosition) || + ![otherAttrs.backgroundImage isEqual:self.backgroundImage] || + (otherAttrs.isGridLayout != self.isGridLayout) || + ![otherAttrs.separatorColor isEqual:self.separatorColor] || + (!UIEdgeInsetsEqualToEdgeInsets(otherAttrs.separatorInset, self.separatorInset)) || + (otherAttrs.separatorLineHeight != self.separatorLineHeight) || + (otherAttrs.shouldHideSeparators != self.shouldHideSeparators) || + (!CGRectEqualToRect(otherAttrs.frame, self.frame))) { + return NO; + } + + return [super isEqual:object]; +} + +- (NSUInteger)hash { + return (NSUInteger)self.editing ^ + (NSUInteger)self.shouldShowReorderStateMask ^ + (NSUInteger)self.shouldShowSelectorStateMask ^ + (NSUInteger)self.shouldShowGridBackground ^ + (NSUInteger)self.sectionOrdinalPosition ^ + (NSUInteger)self.backgroundImage ^ + (NSUInteger)self.isGridLayout ^ + (NSUInteger)self.separatorColor ^ + (NSUInteger)self.separatorLineHeight ^ + (NSUInteger)self.shouldHideSeparators; +} + +@end diff --git a/components/ScrollViewDelegateMultiplexer/src/MaterialScrollViewDelegateMultiplexer.h b/components/CollectionLayoutAttributes/src/MaterialCollectionLayoutAttributes.h similarity index 83% rename from components/ScrollViewDelegateMultiplexer/src/MaterialScrollViewDelegateMultiplexer.h rename to components/CollectionLayoutAttributes/src/MaterialCollectionLayoutAttributes.h index e3c9af1b0e5..78edd8d9a75 100644 --- a/components/ScrollViewDelegateMultiplexer/src/MaterialScrollViewDelegateMultiplexer.h +++ b/components/CollectionLayoutAttributes/src/MaterialCollectionLayoutAttributes.h @@ -1,5 +1,5 @@ /* - Copyright 2015-present Google Inc. All Rights Reserved. + Copyright 2016-present Google Inc. 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. @@ -14,4 +14,4 @@ limitations under the License. */ -#import "MDCScrollViewDelegateMultiplexer.h" +#import "MDCCollectionViewLayoutAttributes.h" diff --git a/components/CollectionLayoutAttributes/tests/unit/CollectionLayoutAttributesTests.m b/components/CollectionLayoutAttributes/tests/unit/CollectionLayoutAttributesTests.m new file mode 100644 index 00000000000..23b313614e1 --- /dev/null +++ b/components/CollectionLayoutAttributes/tests/unit/CollectionLayoutAttributesTests.m @@ -0,0 +1,92 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +#import "MaterialCollectionLayoutAttributes.h" + +@interface CollectionLayoutAttributesTests : XCTestCase { + MDCCollectionViewLayoutAttributes *_attributes; +} +@end + +@implementation CollectionLayoutAttributesTests + +- (void)setUp { + NSIndexPath *indexPath = [NSIndexPath indexPathWithIndex:0]; + _attributes = + [MDCCollectionViewLayoutAttributes layoutAttributesForCellWithIndexPath:indexPath]; + _attributes.editing = YES; + _attributes.shouldShowReorderStateMask = YES; + _attributes.shouldShowSelectorStateMask = YES; + _attributes.shouldShowGridBackground = YES; + _attributes.backgroundImage = [[UIImage alloc] init]; + _attributes.isGridLayout = YES; + _attributes.sectionOrdinalPosition = (MDCCollectionViewOrdinalPosition)NSUIntegerMax; + _attributes.separatorColor = [UIColor purpleColor]; + _attributes.separatorInset = UIEdgeInsetsMake(3, 1, 4, 5); + _attributes.separatorLineHeight = 42; + _attributes.shouldHideSeparators = YES; + _attributes.willAnimateCellsOnAppearance = YES; + _attributes.animateCellsOnAppearanceDuration = 3.145926; + _attributes.animateCellsOnAppearanceDelay = 42; +} + +- (void)testCopying { + // Given + + // When + MDCCollectionViewLayoutAttributes *copy = [_attributes copy]; + + // Then + XCTAssertEqual(_attributes.editing, copy.editing); + XCTAssertEqual(_attributes.shouldShowReorderStateMask, copy.shouldShowReorderStateMask); + XCTAssertEqual(_attributes.shouldShowSelectorStateMask, copy.shouldShowSelectorStateMask); + XCTAssertEqual(_attributes.shouldShowGridBackground, copy.shouldShowGridBackground); + XCTAssertEqualObjects(_attributes.backgroundImage, copy.backgroundImage); + XCTAssertEqual(_attributes.isGridLayout, copy.isGridLayout); + XCTAssertEqual(_attributes.sectionOrdinalPosition, copy.sectionOrdinalPosition); + XCTAssertEqualObjects(_attributes.separatorColor, copy.separatorColor); + XCTAssertTrue(UIEdgeInsetsEqualToEdgeInsets(_attributes.separatorInset, copy.separatorInset)); + XCTAssertEqual(_attributes.separatorLineHeight, copy.separatorLineHeight); + XCTAssertEqual(_attributes.shouldHideSeparators, copy.shouldHideSeparators); + XCTAssertEqual(_attributes.willAnimateCellsOnAppearance, copy.willAnimateCellsOnAppearance); + XCTAssertEqual(_attributes.animateCellsOnAppearanceDuration, + copy.animateCellsOnAppearanceDuration); + XCTAssertEqual(_attributes.animateCellsOnAppearanceDelay, copy.animateCellsOnAppearanceDelay); +} + +- (void)testEqualAfterCopying { + // Given + + // When + MDCCollectionViewLayoutAttributes *copy = [_attributes copy]; + + // Then + XCTAssertEqualObjects(_attributes, copy); +} + +- (void)testEqualHashesAfterCopying { + // Given + + // When + MDCCollectionViewLayoutAttributes *copy = [_attributes copy]; + + // Then + XCTAssertEqual(_attributes.hash, copy.hash); +} + +@end diff --git a/components/Collections/.jazzy.yaml b/components/Collections/.jazzy.yaml new file mode 100644 index 00000000000..9759e4bc93c --- /dev/null +++ b/components/Collections/.jazzy.yaml @@ -0,0 +1,5 @@ +# Auto-generated by scripts/generate_jazzy_yamls.sh. Used primarily by scripts/external/arc-jazzy-linter. +module: Collections +umbrella_header: src/MaterialCollections.h +objc: true +sdk: iphonesimulator diff --git a/components/Collections/README.md b/components/Collections/README.md new file mode 100644 index 00000000000..5c4cdbfefde --- /dev/null +++ b/components/Collections/README.md @@ -0,0 +1,311 @@ +--- +title: "Collections" +layout: detail +section: components +excerpt: "Collection view classes that adhere to Material design layout and styling." +--- +# Collections + +![Collections](docs/assets/collections_screenshot.png) + + +Collection view classes that adhere to Material design layout and styling. + + +### Material Design Specifications + + + +### API Documentation + + + +- - - + +## Installation + +### Requirements + +- Xcode 7.0 or higher. +- iOS SDK version 7.0 or higher. + +### Installation with CocoaPods + +To add this component to your Xcode project using CocoaPods, add the following to your `Podfile`: + +~~~ +pod 'MaterialComponents/Collections' +~~~ + +Then, run the following command: + +~~~ bash +$ pod install +~~~ + +- - - + +## Usage + +### Importing + +Before using Collections, you'll need to import it: + + +#### Objective-C +~~~ objc +#import "MaterialCollections.h" +~~~ + +#### Swift +~~~ swift +import MaterialComponents.MaterialCollections +~~~ + + +### Use `MDCCollectionViewController` as a view controller + +The following four steps will allow you to get a basic example of a `MDCCollectionViewController` +subclass up and running. + +Step 1: **Subclass `MDCCollectionViewController` in your view controller interface**. + + +#### Objective-C +~~~ objc +#import "MaterialCollections.h" + +@interface MyCollectionsExample : MDCCollectionViewController +@end +~~~ + +#### Swift +~~~ swift +import MaterialComponents.MaterialCollections + +class MyCollectionsExample: MDCCollectionViewController { +} +~~~ + + +Step 2: **Setup your data**. + + +#### Objective-C +~~~ objc +colors = @[ @"red", @"blue", @"green", @"black", @"yellow", @"purple" ]; +~~~ + +#### Swift +~~~ swift +let colors = [ "red", "blue", "green", "black", "yellow", "purple" ] +~~~ + + +Step 3: **Register a cell class**. + + +#### Objective-C +~~~ objc +[self.collectionView registerClass:[MDCCollectionViewTextCell class] + forCellWithReuseIdentifier:kReusableIdentifierItem]; +~~~ + +#### Swift +~~~ swift +self.collectionView?.registerClass(MDCCollectionViewTextCell.self, + forCellWithReuseIdentifier: reusableIdentifierItem) +~~~ + + +Step 4: **Override `UICollectionViewDataSource` protocol required methods**. + + +#### Objective-C +~~~ objc +- (NSInteger)collectionView:(UICollectionView *)collectionView + numberOfItemsInSection:(NSInteger)section { + return colors.count; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath { + MDCCollectionViewTextCell *cell = + [collectionView dequeueReusableCellWithReuseIdentifier:kReusableIdentifierItem + forIndexPath:indexPath]; + cell.textLabel.text = colors[indexPath.item]; + return cell; +} +~~~ + +#### Swift +~~~ swift +override func collectionView(collectionView: UICollectionView, + numberOfItemsInSection section: Int) -> Int { + return colors.count +} + +override func collectionView(collectionView: UICollectionView, + cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { + var cell = collectionView.dequeueReusableCellWithReuseIdentifier(reusableIdentifierItem, + forIndexPath: indexPath) + if let cell = cell as? MDCCollectionViewTextCell { + cell.textLabel?.text = colors[indexPath.item] + } + + return cell +} +~~~ + + +- - - + +### Styling the collection view + +The collection view controller provides a `styler` property that conforms to the +`MDCCollectionViewStyling` protocol. By using this property, styling can be easily set for the +collection view items/sections. In addition, by overriding `MDCCollectionViewStyleDelegate` +protocol methods in a collection view controller subclass, specific cells/sections can be styled +differently. + +### Cell Styles + +The styler allows setting the cell style as Default, Grouped, or Card Style. Choose to +either set the styler `cellStyle` property directly, or use the protocol method +`collectionView:cellStyleForSection:` to style per section. + + +#### Objective-C +~~~ objc +// Set for entire collection view. +self.styler.cellStyle = MDCCollectionViewCellStyleCard; + +// Or set for specific sections. +- (MDCCollectionViewCellStyle)collectionView:(UICollectionView *)collectionView + cellStyleForSection:(NSInteger)section { + if (section == 2) { + return MDCCollectionViewCellStyleCard; + } + return MDCCollectionViewCellStyleGrouped; +} +~~~ + +#### Swift +~~~ swift +// Set for entire collection view. +self.styler.cellStyle = .Card + +// Or set for specific sections. +override func collectionView(collectionView: UICollectionView, + cellStyleForSection section: Int) -> MDCCollectionViewCellStyle { + if section == 2 { + return .Card + } + return .Grouped +} +~~~ + + +### Cell Height + +The styling delegate protocol can be used to override the default cell height of `48.0f`. + + +#### Objective-C +~~~ objc +- (CGFloat)collectionView:(UICollectionView *)collectionView + cellHeightAtIndexPath:(NSIndexPath *)indexPath { + if (indexPath.item == 0) { + return 80.0f; + } + return 48.0f; +} +~~~ + +#### Swift +~~~ swift +override func collectionView(collectionView: UICollectionView, + cellHeightAtIndexPath indexPath: NSIndexPath) -> CGFloat { + if indexPath.item == 0 { + return 80.0 + } + return 48.0 +} +~~~ + + +### Cell Layout + +The styler allows setting the cell layout as List, Grid, or Custom. + + +#### Objective-C +~~~ objc +// Set as list layout. +self.styler.cellLayoutType = MDCCollectionViewCellLayoutTypeList; + +// Or set as grid layout. +self.styler.cellLayoutType = MDCCollectionViewCellLayoutTypeGrid; +self.styler.gridPadding = 8; +self.styler.gridColumnCount = 2; +~~~ + +#### Swift +~~~ swift +// Set as list layout. +self.styler.cellLayoutType = .List + +// Or set as grid layout. +self.styler.cellLayoutType = .Grid +self.styler.gridPadding = 8 +self.styler.gridColumnCount = 2 +~~~ + + +### Cell Separators + +The styler allows customizing cell separators for the entire collection view. Individual +cell customization is also available by using an `MDCCollectionViewCell` cell or a subclass of it. +Learn more by reading the section on [Cell Separators](../CollectionCells/#cell-separators) in the +[CollectionCells](../CollectionCells) component. + + +#### Objective-C +~~~ objc +// Set separator color. +self.styler.separatorColor = [UIColor redColor]; + +// Set separator insets. +self.styler.separatorInset = UIEdgeInsetsMake(0, 16, 0, 16); + +// Set separator line height. +self.styler.separatorLineHeight = 1.0f; + +// Whether to hide separators. +self.styler.shouldHideSeparators = NO; +~~~ + +#### Swift +~~~ swift +// Set separator color. +self.styler.separatorColor = UIColor.redColor() + +// Set separator insets. +self.styler.separatorInset = UIEdgeInsetsMake(0, 16, 0, 16) + +// Set separator line height. +self.styler.separatorLineHeight = 1.0 + +// Whether to hide separators. +self.styler.shouldHideSeparators = false +~~~ + diff --git a/components/Collections/docs/assets/collections_screenshot.png b/components/Collections/docs/assets/collections_screenshot.png new file mode 100644 index 00000000000..2c0b1d8a6f7 Binary files /dev/null and b/components/Collections/docs/assets/collections_screenshot.png differ diff --git a/components/Collections/examples/CollectionsAppearanceAnimationExample.m b/components/Collections/examples/CollectionsAppearanceAnimationExample.m new file mode 100644 index 00000000000..9f438945986 --- /dev/null +++ b/components/Collections/examples/CollectionsAppearanceAnimationExample.m @@ -0,0 +1,80 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "CollectionsAppearanceAnimationExample.h" + +#import "MaterialTypography.h" + +static const NSInteger kSectionCount = 10; +static const NSInteger kSectionItemCount = 5; +static NSString *const kReusableIdentifierItem = @"itemCellIdentifier"; + +@implementation CollectionsAppearanceAnimationExample { + NSMutableArray *_content; +} + ++ (NSArray *)catalogBreadcrumbs { + return @[ @"Collections", @"Appearance Animation Example" ]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + // Register cell class. + [self.collectionView registerClass:[MDCCollectionViewTextCell class] + forCellWithReuseIdentifier:kReusableIdentifierItem]; + + // Populate content. + _content = [NSMutableArray array]; + for (NSInteger i = 0; i < kSectionCount; i++) { + NSMutableArray *items = [NSMutableArray array]; + for (NSInteger j = 0; j < kSectionItemCount; j++) { + NSString *itemString = [NSString stringWithFormat:@"Section-%zd Item-%zd", i, j]; + [items addObject:itemString]; + } + [_content addObject:items]; + } + + // Customize collection view settings. + self.styler.cellStyle = MDCCollectionViewCellStyleCard; + self.styler.shouldAnimateCellsOnAppearance = YES; +} + +#pragma mark - + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return [_content count]; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView + numberOfItemsInSection:(NSInteger)section { + return [_content[section] count]; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath { + MDCCollectionViewTextCell *cell = + [collectionView dequeueReusableCellWithReuseIdentifier:kReusableIdentifierItem + forIndexPath:indexPath]; + cell.textLabel.text = _content[indexPath.section][indexPath.item]; + return cell; +} + +@end diff --git a/components/Collections/examples/CollectionsCellAccessoryExample.m b/components/Collections/examples/CollectionsCellAccessoryExample.m new file mode 100644 index 00000000000..53596506ffb --- /dev/null +++ b/components/Collections/examples/CollectionsCellAccessoryExample.m @@ -0,0 +1,123 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "CollectionsCellAccessoryExample.h" + +#import "MaterialSwitch.h" + +static NSString *const kReusableIdentifierItem = @"itemCellIdentifier"; + +@implementation CollectionsCellAccessoryExample { + NSArray *_accessoryTypes; + NSMutableArray *_content; +} + ++ (NSArray *)catalogBreadcrumbs { + return @[ @"Collections", @"Cell Accessory Example" ]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + // Register cell class. + [self.collectionView registerClass:[MDCCollectionViewTextCell class] + forCellWithReuseIdentifier:kReusableIdentifierItem]; + + // Array of available accessory types. + _accessoryTypes = @[ @(MDCCollectionViewCellAccessoryDisclosureIndicator), + @(MDCCollectionViewCellAccessoryCheckmark), + @(MDCCollectionViewCellAccessoryDetailButton), + @(MDCCollectionViewCellAccessoryNone), + @(MDCCollectionViewCellAccessoryNone) ]; + + // Populate content. + _content = [NSMutableArray array]; + [_content addObject:@[ @"Enable Editing" ]]; + [_content addObject:@[ @"Disclosure Indicator", + @"Checkmark", + @"Detail Button", + @"Custom Accessory View", + @"No Accessory View" ]]; + + // Customize collection view settings. + self.styler.cellStyle = MDCCollectionViewCellStyleCard; +} + +#pragma mark - + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return [_content count]; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView + numberOfItemsInSection:(NSInteger)section { + return [_content[section] count]; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath { + MDCCollectionViewTextCell *cell = + [collectionView dequeueReusableCellWithReuseIdentifier:kReusableIdentifierItem + forIndexPath:indexPath]; + cell.textLabel.text = _content[indexPath.section][indexPath.item]; + + // Add accessory views. + if (indexPath.section == 0) { + // Add switch as accessory view. + MDCSwitch *editingSwitch = [[MDCSwitch alloc] initWithFrame:CGRectZero]; + [editingSwitch addTarget:self + action:@selector(didSwitch:) + forControlEvents:UIControlEventValueChanged]; + cell.accessoryView = editingSwitch; + } else { + cell.accessoryType = [_accessoryTypes[indexPath.item] unsignedIntegerValue]; + } + + return cell; +} + +#pragma mark - + +- (BOOL)collectionViewAllowsEditing:(UICollectionView *)collectionView { + return NO; +} + +- (BOOL)collectionViewAllowsReordering:(UICollectionView *)collectionView { + return NO; +} + +- (BOOL)collectionView:(UICollectionView *)collectionView + canEditItemAtIndexPath:(NSIndexPath *)indexPath { + return (indexPath.section != 0); +} + +- (BOOL)collectionView:(UICollectionView *)collectionView + canMoveItemAtIndexPath:(NSIndexPath *)indexPath { + return (indexPath.section != 0); +} + +#pragma mark - UIControlEvents + +- (void)didSwitch:(id)sender { + MDCSwitch *switchControl = sender; + [self.editor setEditing:switchControl.isOn animated:YES]; +} + +@end diff --git a/components/Collections/examples/CollectionsCellColorExample.m b/components/Collections/examples/CollectionsCellColorExample.m new file mode 100644 index 00000000000..2672b3aeccd --- /dev/null +++ b/components/Collections/examples/CollectionsCellColorExample.m @@ -0,0 +1,86 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "CollectionsCellColorExample.h" + +static NSString *const kReusableIdentifierItem = @"itemCellIdentifier"; + +@implementation CollectionsCellColorExample { + NSMutableArray *_content; + NSArray *_cellBackgroundColors; +} + ++ (NSArray *)catalogBreadcrumbs { + return @[ @"Collections", @"Cell Color Example" ]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + // Register cell class. + [self.collectionView registerClass:[MDCCollectionViewTextCell class] + forCellWithReuseIdentifier:kReusableIdentifierItem]; + + // Array of cell background colors. + _cellBackgroundColors = @[ [UIColor colorWithWhite:0 alpha:0.2], + [UIColor colorWithRed:(CGFloat)0x39 / (CGFloat)255 + green:(CGFloat)0xA4 / (CGFloat)255 + blue:(CGFloat)0xDD / (CGFloat)255 + alpha:1], + [UIColor whiteColor] ]; + + // Populate content. + _content = [NSMutableArray array]; + [_content addObject:@[ @"[UIColor colorWithWhite:0 alpha:0.2]", + @"Custom Blue Color", + @"Default White Color" ]]; + + // Customize collection view settings. + self.styler.cellStyle = MDCCollectionViewCellStyleCard; +} + +#pragma mark - + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return [_content count]; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView + numberOfItemsInSection:(NSInteger)section { + return [_content[section] count]; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath { + MDCCollectionViewTextCell *cell = + [collectionView dequeueReusableCellWithReuseIdentifier:kReusableIdentifierItem + forIndexPath:indexPath]; + cell.textLabel.text = _content[indexPath.section][indexPath.item]; + return cell; +} + +#pragma mark - + +- (UIColor *)collectionView:(UICollectionView *)collectionView + cellBackgroundColorAtIndexPath:(NSIndexPath *)indexPath { + return _cellBackgroundColors[indexPath.item]; +} + +@end diff --git a/components/Collections/examples/CollectionsCellSeparatorExample.m b/components/Collections/examples/CollectionsCellSeparatorExample.m new file mode 100644 index 00000000000..555f923e853 --- /dev/null +++ b/components/Collections/examples/CollectionsCellSeparatorExample.m @@ -0,0 +1,87 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "CollectionsCellSeparatorExample.h" + +static const NSInteger kSectionCount = 10; +static const NSInteger kSectionItemCount = 3; +static NSString *const kReusableIdentifierItem = @"itemCellIdentifier"; + +@implementation CollectionsCellSeparatorExample { + NSMutableArray *_content; +} + ++ (NSArray *)catalogBreadcrumbs { + return @[ @"Collections", @"Cell Separator Example" ]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + // Register cell class. + [self.collectionView registerClass:[MDCCollectionViewTextCell class] + forCellWithReuseIdentifier:kReusableIdentifierItem]; + + // Populate content. + _content = [NSMutableArray array]; + for (NSInteger i = 0; i < kSectionCount; i++) { + NSMutableArray *items = [NSMutableArray array]; + for (NSInteger j = 0; j < kSectionItemCount; j++) { + NSString *itemString = [NSString stringWithFormat:@"Section-%zd Item-%zd", i, j]; + [items addObject:itemString]; + } + [_content addObject:items]; + } + + // Customize collection view settings. + self.styler.cellStyle = MDCCollectionViewCellStyleCard; +} + +#pragma mark - + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return [_content count]; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView + numberOfItemsInSection:(NSInteger)section { + return [_content[section] count]; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath { + MDCCollectionViewTextCell *cell = + [collectionView dequeueReusableCellWithReuseIdentifier:kReusableIdentifierItem + forIndexPath:indexPath]; + cell.textLabel.text = _content[indexPath.section][indexPath.item]; + + // Customize separators. + if (indexPath.section == 0) { + cell.separatorInset = UIEdgeInsetsMake(0, 72, 0, 0); + } else if (indexPath.section == 1) { + cell.separatorInset = UIEdgeInsetsMake(0, 0, 0, 72); + } else if (indexPath.section == 2) { + cell.shouldHideSeparator = YES; + } + + return cell; +} + +@end diff --git a/components/Collections/examples/CollectionsContainerExample.m b/components/Collections/examples/CollectionsContainerExample.m new file mode 100644 index 00000000000..8480d4a2fed --- /dev/null +++ b/components/Collections/examples/CollectionsContainerExample.m @@ -0,0 +1,98 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "CollectionsContainerExample.h" + +#import "MaterialCollections.h" + +static const NSInteger kSectionCount = 2; +static const NSInteger kSectionItemCount = 2; +static NSString *const kReusableIdentifierItem = @"itemCellIdentifier"; + +@implementation CollectionsContainerExample { + MDCCollectionViewController *_collectionsController; + NSMutableArray *_content; +} + ++ (NSArray *)catalogBreadcrumbs { + return @[ @"Collections", @"Collections in a Container" ]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.view.backgroundColor = [UIColor whiteColor]; + + // Create gray view to contain collection view. + UIView *container = + [[UIView alloc] initWithFrame:CGRectMake(30, + 200, + self.view.bounds.size.width - 60, + self.view.bounds.size.height - 200 - 30)]; + container.backgroundColor = [UIColor lightGrayColor]; + container.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [self.view addSubview:container]; + + // Create collection view controller. + _collectionsController = [[MDCCollectionViewController alloc] init]; + _collectionsController.collectionView.dataSource = self; + [container addSubview:_collectionsController.view]; + [_collectionsController.view setFrame:container.bounds]; + + // Register cell class. + [_collectionsController.collectionView registerClass:[MDCCollectionViewTextCell class] + forCellWithReuseIdentifier:kReusableIdentifierItem]; + + // Populate content. + _content = [NSMutableArray array]; + for (NSInteger i = 0; i < kSectionCount; i++) { + NSMutableArray *items = [NSMutableArray array]; + for (NSInteger j = 0; j < kSectionItemCount; j++) { + NSString *itemString = [NSString stringWithFormat:@"Section-%zd Item-%zd", i, j]; + [items addObject:itemString]; + } + [_content addObject:items]; + } + + // Customize collection view settings. + _collectionsController.styler.cellStyle = MDCCollectionViewCellStyleCard; +} + +#pragma mark - + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return [_content count]; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView + numberOfItemsInSection:(NSInteger)section { + return [_content[section] count]; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath { + MDCCollectionViewTextCell *cell = + [collectionView dequeueReusableCellWithReuseIdentifier:kReusableIdentifierItem + forIndexPath:indexPath]; + cell.textLabel.text = _content[indexPath.section][indexPath.item]; + return cell; +} + +@end diff --git a/components/Collections/examples/CollectionsEditingExample.m b/components/Collections/examples/CollectionsEditingExample.m new file mode 100644 index 00000000000..82cab2bb0ca --- /dev/null +++ b/components/Collections/examples/CollectionsEditingExample.m @@ -0,0 +1,132 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "CollectionsEditingExample.h" + +static const NSInteger kSectionCount = 10; +static const NSInteger kSectionItemCount = 5; +static NSString *const kReusableIdentifierItem = @"itemCellIdentifier"; + +@implementation CollectionsEditingExample { + NSMutableArray *_content; +} + ++ (NSArray *)catalogBreadcrumbs { + return @[ @"Collections", @"Cell Editing Example" ]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + // Add button to toggle edit mode. + [self updatedRightBarButtonItem:NO]; + + // Register cell class. + [self.collectionView registerClass:[MDCCollectionViewTextCell class] + forCellWithReuseIdentifier:kReusableIdentifierItem]; + + // Populate content. + _content = [NSMutableArray array]; + for (NSInteger i = 0; i < kSectionCount; i++) { + NSMutableArray *items = [NSMutableArray array]; + for (NSInteger j = 0; j < kSectionItemCount; j++) { + NSString *itemString = [NSString stringWithFormat:@"Section-%zd Item-%zd", i, j]; + [items addObject:itemString]; + } + [_content addObject:items]; + } + + // Customize collection view settings. + self.styler.cellStyle = MDCCollectionViewCellStyleCard; +} + +- (void)updatedRightBarButtonItem:(BOOL)isEditing { + self.navigationItem.rightBarButtonItem = + [[UIBarButtonItem alloc] initWithTitle:isEditing ? @"Cancel" : @"Edit" + style:UIBarButtonItemStyleDone + target:self + action:@selector(toggleEditMode:)]; +} + +- (void)toggleEditMode:(id)sender { + BOOL isEditing = self.editor.isEditing; + [self updatedRightBarButtonItem:!isEditing]; + [self.editor setEditing:!isEditing animated:YES]; +} + +#pragma mark - + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return [_content count]; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView + numberOfItemsInSection:(NSInteger)section { + return [_content[section] count]; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath { + MDCCollectionViewTextCell *cell = + [collectionView dequeueReusableCellWithReuseIdentifier:kReusableIdentifierItem + forIndexPath:indexPath]; + cell.textLabel.text = _content[indexPath.section][indexPath.item]; + return cell; +} + +#pragma mark - + +- (BOOL)collectionViewAllowsEditing:(UICollectionView *)collectionView { + return YES; +} + +- (BOOL)collectionViewAllowsReordering:(UICollectionView *)collectionView { + return YES; +} + +- (BOOL)collectionViewAllowsSwipeToDismissItem:(UICollectionView *)collectionView { + return self.editor.isEditing; +} + +- (void)collectionView:(UICollectionView *)collectionView + willDeleteItemsAtIndexPaths:(NSArray *)indexPaths { + // Remove these index paths from our data. + for (NSIndexPath *indexPath in indexPaths) { + [_content[indexPath.section] removeObjectAtIndex:indexPath.item]; + } +} + +- (void)collectionView:(UICollectionView *)collectionView + willMoveItemAtIndexPath:(NSIndexPath *)indexPath + toIndexPath:(NSIndexPath *)newIndexPath { + if (indexPath.section == newIndexPath.section) { + // Exchange data within same section. + [_content[indexPath.section] exchangeObjectAtIndex:indexPath.item + withObjectAtIndex:newIndexPath.item]; + } else { + // Since moving to different section, first remove data from index path and insert + // at new index path. + id movedObject = [_content[indexPath.section] objectAtIndex:indexPath.item]; + [_content[indexPath.section] removeObjectAtIndex:indexPath.item]; + [_content[newIndexPath.section] insertObject:movedObject atIndex:newIndexPath.item]; + } +} + +@end diff --git a/components/Collections/examples/CollectionsGridExample.m b/components/Collections/examples/CollectionsGridExample.m new file mode 100644 index 00000000000..ea1b7992f80 --- /dev/null +++ b/components/Collections/examples/CollectionsGridExample.m @@ -0,0 +1,142 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "CollectionsGridExample.h" + +static const NSInteger kSectionCount = 10; +static const NSInteger kSectionItemCount = 4; +static NSString *const kReusableIdentifierItem = @"itemCellIdentifier"; + +@implementation CollectionsGridExample { + NSMutableArray *_content; + UIAlertController *_actionController; +} + ++ (NSArray *)catalogBreadcrumbs { + return @[ @"Collections", @"Grid Example" ]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + // Add button to update styles. + self.navigationItem.rightBarButtonItem = + [[UIBarButtonItem alloc] initWithTitle:@"Update Styles" + style:UIBarButtonItemStylePlain + target:self + action:@selector(presentActionController:)]; + + // Register cell class. + [self.collectionView registerClass:[MDCCollectionViewTextCell class] + forCellWithReuseIdentifier:kReusableIdentifierItem]; + + // Populate content. + _content = [NSMutableArray array]; + for (NSInteger i = 0; i < kSectionCount; i++) { + NSMutableArray *items = [NSMutableArray array]; + for (NSInteger j = 0; j < kSectionItemCount; j++) { + NSString *itemString = [NSString stringWithFormat:@"Section-%zd Item-%zd", i, j]; + [items addObject:itemString]; + } + [_content addObject:items]; + } + + // Customize collection view settings. + self.styler.cellStyle = MDCCollectionViewCellStyleCard; + self.styler.cellLayoutType = MDCCollectionViewCellLayoutTypeGrid; + self.styler.gridPadding = 8; + self.styler.gridColumnCount = 2; +} + +#pragma mark - + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return [_content count]; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView + numberOfItemsInSection:(NSInteger)section { + return [_content[section] count]; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath { + MDCCollectionViewTextCell *cell = + [collectionView dequeueReusableCellWithReuseIdentifier:kReusableIdentifierItem + forIndexPath:indexPath]; + cell.textLabel.text = _content[indexPath.section][indexPath.item]; + return cell; +} + +#pragma mark - Update Styles + +- (void)toggleCellLayoutType { + // Toggles between list and grid layout. + BOOL isListLayout = (self.styler.cellLayoutType == MDCCollectionViewCellLayoutTypeList); + self.styler.cellLayoutType = + isListLayout ? MDCCollectionViewCellLayoutTypeGrid : MDCCollectionViewCellLayoutTypeList; + [self.collectionView performBatchUpdates:nil completion:nil]; +} + +- (void)toggleCellStyle { + // Toggles between card and grouped styles. + BOOL isCardStyle = (self.styler.cellStyle == MDCCollectionViewCellStyleCard); + self.styler.cellStyle = + isCardStyle ? MDCCollectionViewCellStyleGrouped : MDCCollectionViewCellStyleCard; + [self.collectionView performBatchUpdates:nil completion:nil]; +} + +#pragma mark - Action Controller + +- (void)presentActionController:(id)sender { + _actionController = + [UIAlertController alertControllerWithTitle:@"Update Styles" + message:nil + preferredStyle:UIAlertControllerStyleActionSheet]; + + [_actionController addAction: + [UIAlertAction actionWithTitle:@"Toggle List/Grid Layout" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + [self toggleCellLayoutType]; + }]]; + + [_actionController addAction: + [UIAlertAction actionWithTitle:@"Toggle Card/Grouped Style" + style:UIAlertActionStyleDefault + handler:^(UIAlertAction *action) { + [self toggleCellStyle]; + }]]; + + [_actionController addAction: + [UIAlertAction actionWithTitle:@"Cancel" + style:UIAlertActionStyleCancel + handler:^(UIAlertAction *action) { + [self dismissActionController]; + }]]; + + [self presentViewController:_actionController animated:YES completion:nil]; +} + +- (void)dismissActionController { + [self dismissViewControllerAnimated:YES completion:nil]; +} + +@end diff --git a/components/Collections/examples/CollectionsHeaderFooterExample.m b/components/Collections/examples/CollectionsHeaderFooterExample.m new file mode 100644 index 00000000000..768f2d84534 --- /dev/null +++ b/components/Collections/examples/CollectionsHeaderFooterExample.m @@ -0,0 +1,133 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "CollectionsHeaderFooterExample.h" + +#import "MaterialTypography.h" + +static const NSInteger kSectionCount = 3; +static const NSInteger kSectionItemCount = 2; +static NSString *const kReusableIdentifierItem = @"itemCellIdentifier"; + +@implementation CollectionsHeaderFooterExample { + NSMutableArray *_content; +} + ++ (NSArray *)catalogBreadcrumbs { + return @[ @"Collections", @"Header / Footer Demo" ]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + // Register cell. + [self.collectionView registerClass:[MDCCollectionViewTextCell class] + forCellWithReuseIdentifier:kReusableIdentifierItem]; + + // Register header. + [self.collectionView registerClass:[MDCCollectionViewTextCell class] + forSupplementaryViewOfKind:UICollectionElementKindSectionHeader + withReuseIdentifier:UICollectionElementKindSectionHeader]; + + // Register footer. + [self.collectionView registerClass:[MDCCollectionViewTextCell class] + forSupplementaryViewOfKind:UICollectionElementKindSectionFooter + withReuseIdentifier:UICollectionElementKindSectionFooter]; + + // Populate content. + _content = [NSMutableArray array]; + for (NSInteger i = 0; i < kSectionCount; i++) { + NSMutableArray *items = [NSMutableArray array]; + for (NSInteger j = 0; j < kSectionItemCount; j++) { + NSString *itemString = [NSString stringWithFormat:@"Section-%zd Item-%zd", i, j]; + [items addObject:itemString]; + } + [_content addObject:items]; + } + + // Customize collection view settings. + self.styler.cellStyle = MDCCollectionViewCellStyleCard; +} + +#pragma mark - + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return [_content count]; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView + numberOfItemsInSection:(NSInteger)section { + return [_content[section] count]; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath { + MDCCollectionViewTextCell *cell = + [collectionView dequeueReusableCellWithReuseIdentifier:kReusableIdentifierItem + forIndexPath:indexPath]; + cell.textLabel.text = _content[indexPath.section][indexPath.item]; + return cell; +} + +- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView + viewForSupplementaryElementOfKind:(NSString *)kind + atIndexPath:(NSIndexPath *)indexPath { + MDCCollectionViewTextCell *supplementaryView = + [collectionView dequeueReusableSupplementaryViewOfKind:kind + withReuseIdentifier:kind + forIndexPath:indexPath]; + + if ([kind isEqualToString:UICollectionElementKindSectionHeader]) { + if (indexPath.section == 0) { + supplementaryView.textLabel.text = @"Section with only header"; + } + supplementaryView.textLabel.text = @"Section header"; + + } else if ([kind isEqualToString:UICollectionElementKindSectionFooter]) { + if (indexPath.section == 1) { + supplementaryView.textLabel.text = @"Section with only footer"; + } + supplementaryView.textLabel.text = @"Section footer"; + } + + return supplementaryView; +} + +#pragma mark - + +- (CGSize)collectionView:(UICollectionView *)collectionView + layout:(UICollectionViewLayout *)collectionViewLayout + referenceSizeForHeaderInSection:(NSInteger)section { + if (section == 0 || section == 2) { + return CGSizeMake(collectionView.bounds.size.width, MDCCellDefaultOneLineHeight); + } + return CGSizeZero; +} + +- (CGSize)collectionView:(UICollectionView *)collectionView + layout:(UICollectionViewLayout *)collectionViewLayout + referenceSizeForFooterInSection:(NSInteger)section { + if (section > 0) { + return CGSizeMake(collectionView.bounds.size.width, MDCCellDefaultOneLineHeight); + } + return CGSizeZero; +} + +@end diff --git a/components/Collections/examples/CollectionsInlayExample.m b/components/Collections/examples/CollectionsInlayExample.m new file mode 100644 index 00000000000..023aeb2ed93 --- /dev/null +++ b/components/Collections/examples/CollectionsInlayExample.m @@ -0,0 +1,77 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "CollectionsInlayExample.h" + +static NSString *const kReusableIdentifierItem = @"itemCellIdentifier"; + +@implementation CollectionsInlayExample { + NSArray *_colors; +} + ++ (NSArray *)catalogBreadcrumbs { + return @[ @"Collections", @"Cell Inlay Example" ]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + _colors = @[ @"red", @"blue", @"green", @"black", @"yellow", @"purple" ]; + + // Register cell class. + [self.collectionView registerClass:[MDCCollectionViewTextCell class] + forCellWithReuseIdentifier:kReusableIdentifierItem]; + + // Customize collection view settings. + self.styler.cellStyle = MDCCollectionViewCellStyleCard; + self.styler.allowsItemInlay = YES; + self.styler.allowsMultipleItemInlays = YES; +} + +#pragma mark - + +- (NSInteger)collectionView:(UICollectionView *)collectionView + numberOfItemsInSection:(NSInteger)section { + return _colors.count; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath { + MDCCollectionViewTextCell *cell = + [collectionView dequeueReusableCellWithReuseIdentifier:kReusableIdentifierItem + forIndexPath:indexPath]; + cell.textLabel.text = _colors[indexPath.item]; + return cell; +} + +#pragma mark - + +- (void)collectionView:(UICollectionView *)collectionView + didSelectItemAtIndexPath:(NSIndexPath *)indexPath { + [super collectionView:collectionView didSelectItemAtIndexPath:indexPath]; + BOOL isInlaid = [self.styler isItemInlaidAtIndexPath:indexPath]; + if (isInlaid) { + [self.styler removeInlayFromItemAtIndexPath:indexPath animated:YES]; + } else { + [self.styler applyInlayToItemAtIndexPath:indexPath animated:YES]; + } +} + +@end diff --git a/components/Collections/examples/CollectionsSimpleDemo.m b/components/Collections/examples/CollectionsSimpleDemo.m new file mode 100644 index 00000000000..94b5e734077 --- /dev/null +++ b/components/Collections/examples/CollectionsSimpleDemo.m @@ -0,0 +1,90 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "CollectionsSimpleDemo.h" + +#import "MaterialTypography.h" + +static const NSInteger kSectionCount = 10; +static const NSInteger kSectionItemCount = 5; +static NSString *const kReusableIdentifierItem = @"itemCellIdentifier"; + +@implementation CollectionsSimpleDemo { + NSMutableArray *_content; +} + ++ (NSArray *)catalogBreadcrumbs { + return @[ @"Collections", @"Simple Demo" ]; +} + ++ (BOOL)catalogIsPrimaryDemo { + return YES; +} + ++ (NSString *)catalogDescription { + return @"Material Collections enables a native collection view controller to have Material " + "design layout and styling. It also provides editing and extensive customization " + "capabilities."; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + self.title = @"Simple Demo"; + + // Register cell class. + [self.collectionView registerClass:[MDCCollectionViewTextCell class] + forCellWithReuseIdentifier:kReusableIdentifierItem]; + + // Populate content. + _content = [NSMutableArray array]; + for (NSInteger i = 0; i < kSectionCount; i++) { + NSMutableArray *items = [NSMutableArray array]; + for (NSInteger j = 0; j < kSectionItemCount; j++) { + NSString *itemString = [NSString stringWithFormat:@"Section-%zd Item-%zd", i, j]; + [items addObject:itemString]; + } + [_content addObject:items]; + } + + // Customize collection view settings. + self.styler.cellStyle = MDCCollectionViewCellStyleCard; +} + +#pragma mark - + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return [_content count]; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView + numberOfItemsInSection:(NSInteger)section { + return [_content[section] count]; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath { + MDCCollectionViewTextCell *cell = + [collectionView dequeueReusableCellWithReuseIdentifier:kReusableIdentifierItem + forIndexPath:indexPath]; + cell.textLabel.text = _content[indexPath.section][indexPath.item]; + return cell; +} + +@end diff --git a/components/Collections/examples/CollectionsSimpleSwiftDemo.swift b/components/Collections/examples/CollectionsSimpleSwiftDemo.swift new file mode 100644 index 00000000000..53ab1e4de61 --- /dev/null +++ b/components/Collections/examples/CollectionsSimpleSwiftDemo.swift @@ -0,0 +1,61 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +import UIKit + +import MaterialComponents.MaterialCollections + +class CollectionsSimpleSwiftDemo: MDCCollectionViewController { + + let reusableIdentifierItem = "itemCellIdentifier" + let colors = [ "red", "blue", "green", "black", "yellow", "purple" ] + + override func viewDidLoad() { + super.viewDidLoad() + + // Register cell class. + self.collectionView?.registerClass(MDCCollectionViewTextCell.self, + forCellWithReuseIdentifier: reusableIdentifierItem) + + // Customize collection view settings. + self.styler.cellStyle = .Card + } + + // MARK: UICollectionViewDataSource + + override func collectionView(collectionView: UICollectionView, + numberOfItemsInSection section: Int) -> Int { + return colors.count + } + + override func collectionView(collectionView: UICollectionView, + cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reusableIdentifierItem, + forIndexPath: indexPath) + if let cell = cell as? MDCCollectionViewTextCell { + cell.textLabel?.text = colors[indexPath.item] + } + + return cell + } +} + +// MARK: Catalog by convention +extension CollectionsSimpleSwiftDemo { + class func catalogBreadcrumbs() -> Array { + return [ "Collections", "Simple Swift Demo"] + } +} diff --git a/components/Collections/examples/CollectionsSwipeToDismissRowExample.m b/components/Collections/examples/CollectionsSwipeToDismissRowExample.m new file mode 100644 index 00000000000..57e8d4476cb --- /dev/null +++ b/components/Collections/examples/CollectionsSwipeToDismissRowExample.m @@ -0,0 +1,104 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "CollectionsSwipeToDismissRowExample.h" + +static const NSInteger kSectionCount = 10; +static const NSInteger kSectionItemCount = 5; +static NSString *const kReusableIdentifierItem = @"itemCellIdentifier"; + +@implementation CollectionsSwipeToDismissRowExample { + NSMutableArray *_content; +} + ++ (NSArray *)catalogBreadcrumbs { + return @[ @"Collections", @"Swipe-To-Dismiss-Row Example" ]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + // Register cell class. + [self.collectionView registerClass:[MDCCollectionViewTextCell class] + forCellWithReuseIdentifier:kReusableIdentifierItem]; + + // Populate content. + _content = [NSMutableArray array]; + for (NSInteger i = 0; i < kSectionCount; i++) { + NSMutableArray *items = [NSMutableArray array]; + for (NSInteger j = 0; j < kSectionItemCount; j++) { + NSString *itemString = [NSString stringWithFormat:@"Section-%zd Item-%zd", i, j]; + [items addObject:itemString]; + } + [_content addObject:items]; + } + + // Insert cell that cannot be removed. + [_content insertObject:@[ @"This cell cannot be deleted." ] atIndex:0]; + + // Customize collection view settings. + self.styler.cellStyle = MDCCollectionViewCellStyleCard; +} + +#pragma mark - + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return [_content count]; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView + numberOfItemsInSection:(NSInteger)section { + return [_content[section] count]; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath { + MDCCollectionViewTextCell *cell = + [collectionView dequeueReusableCellWithReuseIdentifier:kReusableIdentifierItem + forIndexPath:indexPath]; + cell.textLabel.text = _content[indexPath.section][indexPath.item]; + return cell; +} + +#pragma mark - + +- (BOOL)collectionViewAllowsSwipeToDismissItem:(UICollectionView *)collectionView { + return YES; +} + +- (BOOL)collectionView:(UICollectionView *)collectionView + canSwipeToDismissItemAtIndexPath:(NSIndexPath *)indexPath { + // In this example we are allowing all items to be dismissed + // except this first section. + if (indexPath.section == 0) { + return NO; + } + return YES; +} + +- (void)collectionView:(UICollectionView *)collectionView + willDeleteItemsAtIndexPaths:(NSArray *)indexPaths { + // Remove these swiped index paths from our data. + for (NSIndexPath *indexPath in indexPaths) { + [_content[indexPath.section] removeObjectAtIndex:indexPath.item]; + } +} + +@end diff --git a/components/Collections/examples/CollectionsSwipeToDismissSectionExample.m b/components/Collections/examples/CollectionsSwipeToDismissSectionExample.m new file mode 100644 index 00000000000..0616258025d --- /dev/null +++ b/components/Collections/examples/CollectionsSwipeToDismissSectionExample.m @@ -0,0 +1,104 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "CollectionsSwipeToDismissSectionExample.h" + +static const NSInteger kSectionCount = 10; +static const NSInteger kSectionItemCount = 5; +static NSString *const kReusableIdentifierItem = @"itemCellIdentifier"; + +@implementation CollectionsSwipeToDismissSectionExample { + NSMutableArray *_content; +} + ++ (NSArray *)catalogBreadcrumbs { + return @[ @"Collections", @"Swipe-To-Dismiss-Section Example" ]; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + // Register cell class. + [self.collectionView registerClass:[MDCCollectionViewTextCell class] + forCellWithReuseIdentifier:kReusableIdentifierItem]; + + // Populate content. + _content = [NSMutableArray array]; + for (NSInteger i = 0; i < kSectionCount; i++) { + NSMutableArray *items = [NSMutableArray array]; + for (NSInteger j = 0; j < kSectionItemCount; j++) { + NSString *itemString = [NSString stringWithFormat:@"Section-%zd Item-%zd", i, j]; + [items addObject:itemString]; + } + [_content addObject:items]; + } + + // Insert section of cells that cannot be removed. + [_content insertObject:@[ @"This cell cannot be deleted.", + @"This cell cannot be deleted." ] + atIndex:0]; + + // Customize collection view settings. + self.styler.cellStyle = MDCCollectionViewCellStyleCard; +} + +#pragma mark - + +- (NSInteger)numberOfSectionsInCollectionView:(UICollectionView *)collectionView { + return [_content count]; +} + +- (NSInteger)collectionView:(UICollectionView *)collectionView + numberOfItemsInSection:(NSInteger)section { + return [_content[section] count]; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView + cellForItemAtIndexPath:(NSIndexPath *)indexPath { + MDCCollectionViewTextCell *cell = + [collectionView dequeueReusableCellWithReuseIdentifier:kReusableIdentifierItem + forIndexPath:indexPath]; + cell.textLabel.text = _content[indexPath.section][indexPath.item]; + return cell; +} + +#pragma mark - + +- (BOOL)collectionViewAllowsSwipeToDismissSection:(UICollectionView *)collectionView { + return YES; +} + +- (BOOL)collectionView:(UICollectionView *)collectionView + canSwipeToDismissSection:(NSInteger)section { + // In this example we are allowing all sections to be dismissed + // except this first section. + if (section == 0) { + return NO; + } + return YES; +} + +- (void)collectionView:(UICollectionView *)collectionView + willDeleteSections:(NSIndexSet *)sections { + // Remove these swiped sections from our data. + [_content removeObjectsAtIndexes:sections]; +} + +@end diff --git a/components/Collections/examples/supplemental/CollectionsAppearanceAnimationExample.h b/components/Collections/examples/supplemental/CollectionsAppearanceAnimationExample.h new file mode 100644 index 00000000000..e880f2a17b3 --- /dev/null +++ b/components/Collections/examples/supplemental/CollectionsAppearanceAnimationExample.h @@ -0,0 +1,22 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +#import "MaterialCollections.h" + +@interface CollectionsAppearanceAnimationExample : MDCCollectionViewController +@end diff --git a/components/Collections/examples/supplemental/CollectionsCellAccessoryExample.h b/components/Collections/examples/supplemental/CollectionsCellAccessoryExample.h new file mode 100644 index 00000000000..1d3506177c7 --- /dev/null +++ b/components/Collections/examples/supplemental/CollectionsCellAccessoryExample.h @@ -0,0 +1,22 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +#import "MaterialCollections.h" + +@interface CollectionsCellAccessoryExample : MDCCollectionViewController +@end diff --git a/components/Collections/examples/supplemental/CollectionsCellColorExample.h b/components/Collections/examples/supplemental/CollectionsCellColorExample.h new file mode 100644 index 00000000000..f00b6ec0cfb --- /dev/null +++ b/components/Collections/examples/supplemental/CollectionsCellColorExample.h @@ -0,0 +1,22 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +#import "MaterialCollections.h" + +@interface CollectionsCellColorExample : MDCCollectionViewController +@end diff --git a/components/Collections/examples/supplemental/CollectionsCellSeparatorExample.h b/components/Collections/examples/supplemental/CollectionsCellSeparatorExample.h new file mode 100644 index 00000000000..80e93b11ec9 --- /dev/null +++ b/components/Collections/examples/supplemental/CollectionsCellSeparatorExample.h @@ -0,0 +1,22 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +#import "MaterialCollections.h" + +@interface CollectionsCellSeparatorExample : MDCCollectionViewController +@end diff --git a/components/Buttons/examples/ButtonsSimpleExampleViewController.h b/components/Collections/examples/supplemental/CollectionsContainerExample.h similarity index 87% rename from components/Buttons/examples/ButtonsSimpleExampleViewController.h rename to components/Collections/examples/supplemental/CollectionsContainerExample.h index 0b6b48e099b..4740f5bda2f 100644 --- a/components/Buttons/examples/ButtonsSimpleExampleViewController.h +++ b/components/Collections/examples/supplemental/CollectionsContainerExample.h @@ -16,6 +16,5 @@ #import -@interface ButtonsSimpleExampleViewController : UIViewController - +@interface CollectionsContainerExample : UIViewController @end diff --git a/components/Collections/examples/supplemental/CollectionsEditingExample.h b/components/Collections/examples/supplemental/CollectionsEditingExample.h new file mode 100644 index 00000000000..02231a72680 --- /dev/null +++ b/components/Collections/examples/supplemental/CollectionsEditingExample.h @@ -0,0 +1,22 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +#import "MaterialCollections.h" + +@interface CollectionsEditingExample : MDCCollectionViewController +@end diff --git a/components/Collections/examples/supplemental/CollectionsGridExample.h b/components/Collections/examples/supplemental/CollectionsGridExample.h new file mode 100644 index 00000000000..ce128440aba --- /dev/null +++ b/components/Collections/examples/supplemental/CollectionsGridExample.h @@ -0,0 +1,22 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +#import "MaterialCollections.h" + +@interface CollectionsGridExample : MDCCollectionViewController +@end diff --git a/components/Collections/examples/supplemental/CollectionsHeaderFooterExample.h b/components/Collections/examples/supplemental/CollectionsHeaderFooterExample.h new file mode 100644 index 00000000000..c5b507807da --- /dev/null +++ b/components/Collections/examples/supplemental/CollectionsHeaderFooterExample.h @@ -0,0 +1,22 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +#import "MaterialCollections.h" + +@interface CollectionsHeaderFooterExample : MDCCollectionViewController +@end diff --git a/components/Collections/examples/supplemental/CollectionsInlayExample.h b/components/Collections/examples/supplemental/CollectionsInlayExample.h new file mode 100644 index 00000000000..7296bcb3528 --- /dev/null +++ b/components/Collections/examples/supplemental/CollectionsInlayExample.h @@ -0,0 +1,22 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +#import "MaterialCollections.h" + +@interface CollectionsInlayExample : MDCCollectionViewController +@end diff --git a/components/ScrollViewDelegateMultiplexer/examples/ObservingPageControl.h b/components/Collections/examples/supplemental/CollectionsSimpleDemo.h similarity index 78% rename from components/ScrollViewDelegateMultiplexer/examples/ObservingPageControl.h rename to components/Collections/examples/supplemental/CollectionsSimpleDemo.h index 8e26ca7ce91..581c00618ed 100644 --- a/components/ScrollViewDelegateMultiplexer/examples/ObservingPageControl.h +++ b/components/Collections/examples/supplemental/CollectionsSimpleDemo.h @@ -1,5 +1,5 @@ /* - Copyright 2015-present Google Inc. All Rights Reserved. + Copyright 2016-present Google Inc. 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. @@ -16,5 +16,7 @@ #import -@interface ObservingPageControl : UIPageControl +#import "MaterialCollections.h" + +@interface CollectionsSimpleDemo : MDCCollectionViewController @end diff --git a/components/Collections/examples/supplemental/CollectionsSwipeToDismissRowExample.h b/components/Collections/examples/supplemental/CollectionsSwipeToDismissRowExample.h new file mode 100644 index 00000000000..d50dbea26e6 --- /dev/null +++ b/components/Collections/examples/supplemental/CollectionsSwipeToDismissRowExample.h @@ -0,0 +1,22 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +#import "MaterialCollections.h" + +@interface CollectionsSwipeToDismissRowExample : MDCCollectionViewController +@end diff --git a/components/Collections/examples/supplemental/CollectionsSwipeToDismissSectionExample.h b/components/Collections/examples/supplemental/CollectionsSwipeToDismissSectionExample.h new file mode 100644 index 00000000000..382f7a797ef --- /dev/null +++ b/components/Collections/examples/supplemental/CollectionsSwipeToDismissSectionExample.h @@ -0,0 +1,22 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +#import "MaterialCollections.h" + +@interface CollectionsSwipeToDismissSectionExample : MDCCollectionViewController +@end diff --git a/components/Collections/src/MDCCollectionViewController.h b/components/Collections/src/MDCCollectionViewController.h new file mode 100644 index 00000000000..f77efb9fc2f --- /dev/null +++ b/components/Collections/src/MDCCollectionViewController.h @@ -0,0 +1,67 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +#import "MDCCollectionViewEditing.h" +#import "MDCCollectionViewEditingDelegate.h" +#import "MDCCollectionViewStyling.h" +#import "MDCCollectionViewStylingDelegate.h" + +/** + Controller that implements a collection view that adheres to Material design layout + and animation styling. + */ +@interface MDCCollectionViewController : UICollectionViewController < + /** Allows for editing notifications/permissions. */ + MDCCollectionViewEditingDelegate, + + /** Allows for styling updates. */ + MDCCollectionViewStylingDelegate, + + /** Adheres to flow layout. */ + UICollectionViewDelegateFlowLayout> + +/** The collection view styler. */ +@property(nonatomic, strong, readonly, nonnull) id styler; + +/** The collection view editing manager. */ +@property(nonatomic, strong, readonly, nonnull) id editor; + +#pragma mark - Subclassing + +/** + The following methods require a call to super in their overriding implementations to allow + this collection view controller to properly configure the collection view when in editing mode. + */ + +- (BOOL)collectionView:(nonnull UICollectionView *)collectionView + shouldSelectItemAtIndexPath:(nonnull NSIndexPath *)indexPath NS_REQUIRES_SUPER; + +- (BOOL)collectionView:(nonnull UICollectionView *)collectionView + shouldDeselectItemAtIndexPath:(nonnull NSIndexPath *)indexPath NS_REQUIRES_SUPER; + +- (void)collectionView:(nonnull UICollectionView *)collectionView + didSelectItemAtIndexPath:(nonnull NSIndexPath *)indexPath NS_REQUIRES_SUPER; + +- (void)collectionView:(nonnull UICollectionView *)collectionView + didDeselectItemAtIndexPath:(nonnull NSIndexPath *)indexPath NS_REQUIRES_SUPER; + +- (void)collectionViewWillBeginEditing:(nonnull UICollectionView *)collectionView NS_REQUIRES_SUPER; + +- (void)collectionViewWillEndEditing:(nonnull UICollectionView *)collectionView NS_REQUIRES_SUPER; + +@end diff --git a/components/Collections/src/MDCCollectionViewController.m b/components/Collections/src/MDCCollectionViewController.m new file mode 100644 index 00000000000..bbd34f3e07c --- /dev/null +++ b/components/Collections/src/MDCCollectionViewController.m @@ -0,0 +1,561 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "MDCCollectionViewController.h" + +#import "MDCCollectionViewFlowLayout.h" +#import "MaterialCollectionCells.h" +#import "MaterialInk.h" +#import "private/MDCCollectionInfoBarView.h" +#import "private/MDCCollectionStringResources.h" +#import "private/MDCCollectionViewEditor.h" +#import "private/MDCCollectionViewStyler.h" + +#import + +@interface MDCCollectionViewController () + +@end + +@implementation MDCCollectionViewController { + MDCInkTouchController *_inkTouchController; + MDCCollectionInfoBarView *_headerInfoBar; + MDCCollectionInfoBarView *_footerInfoBar; + BOOL _headerInfoBarDismissed; +} + +@synthesize collectionViewLayout = _collectionViewLayout; + +- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout { + self = [super initWithCollectionViewLayout:layout]; + if (self) { + [self commonMDCCollectionViewControllerInit:self.collectionViewLayout]; + } + return self; +} + +- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + self = [super initWithCollectionViewLayout:self.collectionViewLayout]; + if (self) { + [self commonMDCCollectionViewControllerInit:self.collectionViewLayout]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super initWithCollectionViewLayout:self.collectionViewLayout]; + if (self) { + [self commonMDCCollectionViewControllerInit:self.collectionViewLayout]; + } + return self; +} + +- (void)commonMDCCollectionViewControllerInit:(UICollectionViewLayout *)layout { + _collectionViewLayout = layout; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + self.collectionView.backgroundColor = [UIColor whiteColor]; + self.collectionView.alwaysBounceVertical = YES; + + _styler = [[MDCCollectionViewStyler alloc] initWithCollectionView:self.collectionView]; + _styler.delegate = self; + + _editor = [[MDCCollectionViewEditor alloc] initWithCollectionView:self.collectionView]; + _editor.delegate = self; + + // Set up ink touch controller. + _inkTouchController = [[MDCInkTouchController alloc] initWithView:self.collectionView]; + _inkTouchController.delegate = self; + _inkTouchController.delaysInkSpread = YES; +} + +- (void)viewDidLayoutSubviews { + [super viewDidLayoutSubviews]; + _styler.shouldInvalidateLayout = NO; +} + +- (UICollectionViewLayout *)collectionViewLayout { + if (!_collectionViewLayout) { + _collectionViewLayout = [[MDCCollectionViewFlowLayout alloc] init]; + } + return _collectionViewLayout; +} + +#pragma mark - + +- (void)updateControllerWithInfoBar:(MDCCollectionInfoBarView *)infoBar { + // Updates info bar styling for header/footer. + if ([infoBar.kind isEqualToString:MDCCollectionInfoBarKindHeader]) { + _headerInfoBar = infoBar; + _headerInfoBar.message = MDCCollectionStringResources(infoBarGestureHintString); + _headerInfoBar.style = MDCCollectionInfoBarViewStyleHUD; + [self updateHeaderInfoBarIfNecessary]; + } else if ([infoBar.kind isEqualToString:MDCCollectionInfoBarKindFooter]) { + _footerInfoBar = infoBar; + _footerInfoBar.message = MDCCollectionStringResources(deleteButtonString); + _footerInfoBar.style = MDCCollectionInfoBarViewStyleActionable; + [self updateFooterInfoBarIfNecessary]; + } +} + +- (void)didTapInfoBar:(MDCCollectionInfoBarView *)infoBar { + if ([infoBar isEqual:_footerInfoBar]) { + [self deleteIndexPaths:self.collectionView.indexPathsForSelectedItems]; + } +} + +- (void)infoBar:(MDCCollectionInfoBarView *)infoBar + willShowAnimated:(BOOL)animated + willAutoDismiss:(BOOL)willAutoDismiss { + if ([infoBar.kind isEqualToString:MDCCollectionInfoBarKindFooter]) { + [self updateContentWithBottomInset:MDCCollectionInfoBarFooterHeight]; + } +} + +- (void)infoBar:(MDCCollectionInfoBarView *)infoBar + willDismissAnimated:(BOOL)animated + willAutoDismiss:(BOOL)willAutoDismiss { + if ([infoBar.kind isEqualToString:MDCCollectionInfoBarKindHeader]) { + _headerInfoBarDismissed = willAutoDismiss; + } else { + [self updateContentWithBottomInset:-MDCCollectionInfoBarFooterHeight]; + } +} + +#pragma mark - + +- (MDCCollectionViewCellStyle)collectionView:(UICollectionView *)collectionView + cellStyleForSection:(NSInteger)section { + return _styler.cellStyle; +} + +#pragma mark - + +- (CGSize)collectionView:(UICollectionView *)collectionView + layout:(UICollectionViewLayout *)collectionViewLayout + sizeForItemAtIndexPath:(NSIndexPath *)indexPath { + UICollectionViewLayoutAttributes *attr = + [collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath]; + CGSize size = [self sizeWithAttribute:attr]; + size = [self inlaidSizeAtIndexPath:indexPath withSize:size]; + return size; +} + +- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView + layout:(UICollectionViewLayout *)collectionViewLayout + insetForSectionAtIndex:(NSInteger)section { + return [self insetsAtSectionIndex:section]; +} + +- (CGFloat)collectionView:(UICollectionView *)collectionView + layout:(UICollectionViewLayout *)collectionViewLayout + minimumLineSpacingForSectionAtIndex:(NSInteger)section { + if ([collectionViewLayout isKindOfClass:[UICollectionViewFlowLayout class]]) { + if (_styler.cellLayoutType == MDCCollectionViewCellLayoutTypeGrid) { + return _styler.gridPadding; + } + return [(UICollectionViewFlowLayout *)collectionViewLayout minimumLineSpacing]; + } + return 0; +} + +- (CGSize)sizeWithAttribute:(UICollectionViewLayoutAttributes *)attr { + CGFloat height = MDCCellDefaultOneLineHeight; + if ([_styler.delegate respondsToSelector: + @selector(collectionView:cellHeightAtIndexPath:)]) { + height = [_styler.delegate collectionView:self.collectionView + cellHeightAtIndexPath:attr.indexPath]; + } + + CGFloat width = [self cellWidthAtSectionIndex:attr.indexPath.section]; + return CGSizeMake(width, height); +} + +- (CGFloat)cellWidthAtSectionIndex:(NSInteger)section { + CGFloat bounds = CGRectGetWidth(UIEdgeInsetsInsetRect(self.collectionView.bounds, + self.collectionView.contentInset)); + UIEdgeInsets sectionInsets = [self insetsAtSectionIndex:section]; + CGFloat insets = sectionInsets.left + sectionInsets.right; + if (_styler.cellLayoutType == MDCCollectionViewCellLayoutTypeGrid) { + CGFloat cellWidth = + bounds - insets - (_styler.gridPadding * (_styler.gridColumnCount - 1)); + return cellWidth / _styler.gridColumnCount; + } + return bounds - insets; +} + +- (UIEdgeInsets)insetsAtSectionIndex:(NSInteger)section { + // Determine insets based on cell style. + CGFloat inset = (CGFloat)floor(MDCCollectionViewCellStyleCardSectionInset); + UIEdgeInsets insets = UIEdgeInsetsZero; + NSInteger numberOfSections = self.collectionView.numberOfSections; + BOOL isTop = (section == 0); + BOOL isBottom = (section == numberOfSections - 1); + MDCCollectionViewCellStyle cellStyle = [_styler cellStyleAtSectionIndex:section]; + BOOL isCardStyle = cellStyle == MDCCollectionViewCellStyleCard; + BOOL isGroupedStyle = cellStyle == MDCCollectionViewCellStyleGrouped; + // Set left/right insets. + if (isCardStyle) { + insets.left = inset; + insets.right = inset; + } + // Set top/bottom insets. + if (isCardStyle || isGroupedStyle) { + insets.top = (CGFloat)floor((isTop) ? inset : inset / 2.0f); + insets.bottom = (CGFloat)floor((isBottom) ? inset : inset / 2.0f); + } + return insets; +} + +- (CGSize)inlaidSizeAtIndexPath:(NSIndexPath *)indexPath + withSize:(CGSize)size { + // If object is inlaid, return its adjusted size. + UICollectionView *collectionView = self.collectionView; + if ([_styler isItemInlaidAtIndexPath:indexPath]) { + CGFloat inset = MDCCollectionViewCellStyleCardSectionInset; + UIEdgeInsets inlayInsets = UIEdgeInsetsZero; + BOOL prevCellIsInlaid = NO; + BOOL nextCellIsInlaid = NO; + + BOOL hasSectionHeader = NO; + if ([self respondsToSelector: + @selector(collectionView:layout:referenceSizeForHeaderInSection:)]) { + CGSize headerSize = [self collectionView:collectionView + layout:_collectionViewLayout + referenceSizeForHeaderInSection:indexPath.section]; + hasSectionHeader = !CGSizeEqualToSize(headerSize, CGSizeZero); + } + + BOOL hasSectionFooter = NO; + if ([self respondsToSelector: + @selector(collectionView:layout:referenceSizeForFooterInSection:)]) { + CGSize footerSize = [self collectionView:collectionView + layout:_collectionViewLayout + referenceSizeForFooterInSection:indexPath.section]; + hasSectionFooter = !CGSizeEqualToSize(footerSize, CGSizeZero); + } + + // Check if previous cell is inlaid. + if (indexPath.item > 0 || hasSectionHeader) { + NSIndexPath *prevIndexPath = + [NSIndexPath indexPathForItem:(indexPath.item - 1) + inSection:indexPath.section]; + prevCellIsInlaid = [_styler isItemInlaidAtIndexPath:prevIndexPath]; + inlayInsets.top = prevCellIsInlaid ? inset / 2 : inset; + } + + // Check if next cell is inlaid. + if (indexPath.item < [collectionView numberOfItemsInSection:indexPath.section] - 1 || + hasSectionFooter) { + NSIndexPath *nextIndexPath = + [NSIndexPath indexPathForItem:(indexPath.item + 1) + inSection:indexPath.section]; + nextCellIsInlaid = [_styler isItemInlaidAtIndexPath:nextIndexPath]; + inlayInsets.bottom = nextCellIsInlaid ? inset / 2 : inset; + } + + // Apply top/bottom height adjustments to inlaid object. + size.height += inlayInsets.top + inlayInsets.bottom; + } + return size; +} + +#pragma mark - + +- (BOOL)inkTouchControllerShouldProcessInkTouches:(MDCInkTouchController *)inkTouchController + atTouchLocation:(CGPoint)location { + NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:location]; + if (indexPath) { + if ([_styler.delegate respondsToSelector: + @selector(collectionView:hidesInkViewAtIndexPath:)]) { + return [_styler.delegate collectionView:self.collectionView + hidesInkViewAtIndexPath:indexPath]; + } + } + return YES; +} + +- (MDCInkView *)inkTouchController:(MDCInkTouchController *)inkTouchController + inkViewAtTouchLocation:(CGPoint)location { + NSIndexPath *indexPath = [self.collectionView indexPathForItemAtPoint:location]; + UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath]; + + if ([cell isKindOfClass:[MDCCollectionViewCell class]]) { + MDCCollectionViewCell *inkCell = (MDCCollectionViewCell *)cell; + if ([inkCell respondsToSelector:@selector(inkView)]) { + // Set cell ink. + MDCInkView *ink = [cell performSelector:@selector(inkView)]; + if (!ink) { + ink = [[MDCInkView alloc] initWithFrame:inkCell.bounds]; + inkCell.inkView = ink; + } + return ink; + } + } + return nil; +} + +#pragma mark - + +- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView + viewForSupplementaryElementOfKind:(NSString *)kind + atIndexPath:(NSIndexPath *)indexPath { + // Editing info bar. + if ([kind isEqualToString:MDCCollectionInfoBarKindHeader] || + [kind isEqualToString:MDCCollectionInfoBarKindFooter]) { + NSString *identifier = NSStringFromClass([MDCCollectionInfoBarView class]); + identifier = [identifier stringByAppendingFormat:@".%@", kind]; + [collectionView registerClass:[MDCCollectionInfoBarView class] + forSupplementaryViewOfKind:kind + withReuseIdentifier:identifier]; + + UICollectionReusableView *supplementaryView = + [collectionView dequeueReusableSupplementaryViewOfKind:kind + withReuseIdentifier:identifier + forIndexPath:indexPath]; + + // Update info bar. + if ([supplementaryView isKindOfClass:[MDCCollectionInfoBarView class]]) { + MDCCollectionInfoBarView *infoBar = (MDCCollectionInfoBarView *)supplementaryView; + infoBar.delegate = self; + infoBar.kind = kind; + [self updateControllerWithInfoBar:infoBar]; + } + return supplementaryView; + } + return nil; +} + +#pragma mark - + +- (BOOL)collectionView:(UICollectionView *)collectionView + shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath { + if (_editor.isEditing) { + if ([self collectionView:collectionView canEditItemAtIndexPath:indexPath]) { + return [self collectionView:collectionView canSelectItemDuringEditingAtIndexPath:indexPath]; + } + return NO; + } + return YES; +} + +- (BOOL)collectionView:(UICollectionView *)collectionView + shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath { + return collectionView.allowsMultipleSelection; +} + +- (void)collectionView:(UICollectionView *)collectionView + didSelectItemAtIndexPath:(NSIndexPath *)indexPath { + [self updateFooterInfoBarIfNecessary]; +} + +- (void)collectionView:(UICollectionView *)collectionView + didDeselectItemAtIndexPath:(NSIndexPath *)indexPath { + [self updateFooterInfoBarIfNecessary]; +} + +#pragma mark - + +- (BOOL)collectionViewAllowsEditing:(UICollectionView *)collectionView { + return NO; +} + +- (void)collectionViewWillBeginEditing:(UICollectionView *)collectionView { + // Inlay all items. + _styler.allowsItemInlay = YES; + _styler.allowsMultipleItemInlays = YES; + [_styler applyInlayToAllItemsAnimated:YES]; + [self updateHeaderInfoBarIfNecessary]; +} + +- (void)collectionViewWillEndEditing:(UICollectionView *)collectionView { + // Remove inlay of all items. + [_styler removeInlayFromAllItemsAnimated:YES]; + [self updateFooterInfoBarIfNecessary]; +} + +- (BOOL)collectionView:(UICollectionView *)collectionView + canEditItemAtIndexPath:(NSIndexPath *)indexPath { + return [self collectionViewAllowsEditing:collectionView]; +} + +- (BOOL)collectionView:(UICollectionView *)collectionView + canSelectItemDuringEditingAtIndexPath:(NSIndexPath *)indexPath { + if ([self collectionViewAllowsEditing:collectionView]) { + return [self collectionView:collectionView canEditItemAtIndexPath:indexPath]; + } + return NO; +} + +#pragma mark - Item Moving + +- (BOOL)collectionViewAllowsReordering:(UICollectionView *)collectionView { + return NO; +} + +- (BOOL)collectionView:(UICollectionView *)collectionView + canMoveItemAtIndexPath:(NSIndexPath *)indexPath { + return ([self collectionViewAllowsEditing:collectionView] && + [self collectionViewAllowsReordering:collectionView]); +} + +- (BOOL)collectionView:(UICollectionView *)collectionView + canMoveItemAtIndexPath:(NSIndexPath *)indexPath + toIndexPath:(NSIndexPath *)newIndexPath { + // First ensure both source and target items can be moved. + return ([self collectionView:collectionView canMoveItemAtIndexPath:indexPath] && + [self collectionView:collectionView + canMoveItemAtIndexPath:newIndexPath]); +} + +- (void)collectionView:(UICollectionView *)collectionView + didMoveItemAtIndexPath:(NSIndexPath *)indexPath + toIndexPath:(NSIndexPath *)newIndexPath { + [collectionView moveItemAtIndexPath:indexPath toIndexPath:newIndexPath]; +} + +#pragma mark - Swipe-To-Dismiss-Items + +- (BOOL)collectionViewAllowsSwipeToDismissItem:(UICollectionView *)collectionView { + return NO; +} + +- (BOOL)collectionView:(UICollectionView *)collectionView + canSwipeToDismissItemAtIndexPath:(NSIndexPath *)indexPath { + return [self collectionViewAllowsSwipeToDismissItem:collectionView]; +} + +- (void)collectionView:(UICollectionView *)collectionView + didEndSwipeToDismissItemAtIndexPath:(NSIndexPath *)indexPath { + [self deleteIndexPaths:@[ indexPath ]]; +} + +#pragma mark - Swipe-To-Dismiss-Sections + +- (BOOL)collectionViewAllowsSwipeToDismissSection:(UICollectionView *)collectionView { + return NO; +} + +- (BOOL)collectionView:(UICollectionView *)collectionView + canSwipeToDismissSection:(NSInteger)section { + return [self collectionViewAllowsSwipeToDismissSection:collectionView]; +} + +- (void)collectionView:(UICollectionView *)collectionView + didEndSwipeToDismissSection:(NSInteger)section { + [self deleteSections:[NSIndexSet indexSetWithIndex:section]]; +} + +#pragma mark - Private + +- (void)deleteIndexPaths:(NSArray *)indexPaths { + if ([self respondsToSelector:@selector(collectionView:willDeleteItemsAtIndexPaths:)]) { + void (^batchUpdates)() = ^{ + // Notify delegate to delete data. + [self collectionView:self.collectionView willDeleteItemsAtIndexPaths:indexPaths]; + + // Delete index paths. + [self.collectionView deleteItemsAtIndexPaths:indexPaths]; + }; + + void (^completionBlock)(BOOL finished) = ^(BOOL finished) { + [self updateFooterInfoBarIfNecessary]; + // Notify delegate of deletion. + if ([self respondsToSelector:@selector(collectionView:didDeleteItemsAtIndexPaths:)]) { + [self collectionView:self.collectionView didDeleteItemsAtIndexPaths:indexPaths]; + } + }; + + // Animate deletion. + [self.collectionView performBatchUpdates:batchUpdates completion:completionBlock]; + } +} + +- (void)deleteSections:(NSIndexSet *)sections { + if ([self respondsToSelector:@selector(collectionView:willDeleteSections:)]) { + void (^batchUpdates)() = ^{ + // Notify delegate to delete data. + [self collectionView:self.collectionView willDeleteSections:sections]; + + // Delete sections. + [self.collectionView deleteSections:sections]; + }; + + void (^completionBlock)(BOOL finished) = ^(BOOL finished) { + [self updateFooterInfoBarIfNecessary]; + // Notify delegate of deletion. + if ([self respondsToSelector:@selector(collectionView:didDeleteSections:)]) { + [self collectionView:self.collectionView didDeleteSections:sections]; + } + }; + + // Animate deletion. + [self.collectionView performBatchUpdates:batchUpdates completion:completionBlock]; + } +} + +- (void)updateHeaderInfoBarIfNecessary { + if (_editor.isEditing) { + // Show HUD only once before autodissmissing. + BOOL allowsSwipeToDismissItem = NO; + if ([self respondsToSelector:@selector(collectionViewAllowsSwipeToDismissItem:)]) { + allowsSwipeToDismissItem = [self collectionViewAllowsSwipeToDismissItem:self.collectionView]; + } + + if (!_headerInfoBar.isVisible && !_headerInfoBarDismissed && allowsSwipeToDismissItem) { + [_headerInfoBar showAnimated:YES]; + } else { + [_headerInfoBar dismissAnimated:YES]; + } + } +} + +- (void)updateFooterInfoBarIfNecessary { + NSInteger selectedItemCount = [self.collectionView.indexPathsForSelectedItems count]; + if (_editor.isEditing) { + // Invalidate layout to add info bar if necessary. + [self.collectionView.collectionViewLayout invalidateLayout]; + if (_footerInfoBar) { + if (selectedItemCount > 0 && !_footerInfoBar.isVisible) { + [_footerInfoBar showAnimated:YES]; + } else if (selectedItemCount == 0 && _footerInfoBar.isVisible) { + [_footerInfoBar dismissAnimated:YES]; + } + } + } else if (selectedItemCount == 0 && _footerInfoBar.isVisible) { + [_footerInfoBar dismissAnimated:YES]; + } +} + +- (void)updateContentWithBottomInset:(CGFloat)inset { + // Update bottom inset to account for footer info bar. + UIEdgeInsets contentInset = self.collectionView.contentInset; + contentInset.bottom += inset; + [UIView animateWithDuration:MDCCollectionInfoBarAnimationDuration + animations:^{ + self.collectionView.contentInset = contentInset; + }]; +} + +@end diff --git a/components/Collections/src/MDCCollectionViewEditing.h b/components/Collections/src/MDCCollectionViewEditing.h new file mode 100644 index 00000000000..f9fb1490478 --- /dev/null +++ b/components/Collections/src/MDCCollectionViewEditing.h @@ -0,0 +1,60 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +@protocol MDCCollectionViewEditingDelegate; + +/** The MDCCollectionViewEditing protocol defines the editing state for a UICollectionView. */ +@protocol MDCCollectionViewEditing + +/** The associated collection view. */ +@property(nonatomic, readonly, weak, nullable) UICollectionView *collectionView; + +/** The delegate will be informed of editing state changes. */ +@property(nonatomic, weak, nullable) id delegate; + +/** The index path of the cell being moved or reordered, if any. */ +@property(nonatomic, readonly, strong, nullable) NSIndexPath *reorderingCellIndexPath; + +/** The index path of the cell being dragged for dismissal, if any. */ +@property(nonatomic, readonly, strong, nullable) NSIndexPath *dismissingCellIndexPath; + +/** The index of the section being dragged for dismissal, or NSNotFound if none. */ +@property(nonatomic, readonly, assign) NSInteger dismissingSection; + +/** + A Boolean value indicating whether the a visible cell within the collectionView is being + edited. + + When set, all rows show or hide editing controls without animation. To animate the state change see + @c setEditing:animated:. Setting the editing state of this class does not propagate to the parent + view controller's editing state. + */ +@property(nonatomic, getter=isEditing) BOOL editing; + +/** + Set the editing state with optional animations. + + When set, row shows or hides editing controls with/without animation. Setting the editing + state of this class does not propagate to the parent view controller's editing state. + + @param editing YES if editing; otherwise, NO. + @param animated YES the transition will be animated; otherwise, NO. + */ +- (void)setEditing:(BOOL)editing animated:(BOOL)animated; + +@end diff --git a/components/Collections/src/MDCCollectionViewEditingDelegate.h b/components/Collections/src/MDCCollectionViewEditingDelegate.h new file mode 100644 index 00000000000..bda7364657f --- /dev/null +++ b/components/Collections/src/MDCCollectionViewEditingDelegate.h @@ -0,0 +1,329 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +/** + A delegate protocol that provides editing notifications for three types of collection view + gestural interations: + + - Cells that are dragging/moved vertically for reordering. + - Individual cells being swiped horizontally for dismissal. + - Entire cell sections being swiped horizontally for dismissal. + */ +@protocol MDCCollectionViewEditingDelegate + +@optional + +#pragma mark - CollectionView Item Editing + +/** + If YES, the collectionView will allow editing of its items. Permissions for individual + index paths are set by implementing the delegate -collectionView:canEditItemAtIndexPath method. + If not implemented, will default to NO. + */ +- (BOOL)collectionViewAllowsEditing:(nonnull UICollectionView *)collectionView; + +/** + Sent to the receiver when the collection view will begin editing. Typically this method will be + called from a collection view editing manager that has had its editing property set to YES. + This will be called before any animations to editing state will begin. + + @param collectionView The collection view. + */ +- (void)collectionViewWillBeginEditing:(nonnull UICollectionView *)collectionView; + +/** + Sent to the receiver when the collection view did begin editing. Typically this method will be + called from a collection view editing manager that has had its editing property set to YES. + This is called after animations to editing state have completed. + + @param collectionView The collection view. + */ +- (void)collectionViewDidBeginEditing:(nonnull UICollectionView *)collectionView; + +/** + Sent to the receiver when the collection view will end editing. Typically this method will be + called from a collection view editing manager that has had its editing property set to NO. + This will be called before any animations from editing state begin. + + @param collectionView The collection view. + */ +- (void)collectionViewWillEndEditing:(nonnull UICollectionView *)collectionView; + +/** + Sent to the receiver when the collection view did end editing. Typically this method will be + called from a collection view editing manager that has had its editing property set to NO. + This is called after animations from editing state have completed. + + @param collectionView The collection view. + */ +- (void)collectionViewDidEndEditing:(nonnull UICollectionView *)collectionView; + +/** + Sent to the receiver for permission to edit an item at this collection view index path. + Returning NO here will prevent editing the designated index path. If not implemented, will + default to NO. + + @param collectionView The collection view. + @param indexPath The index path of the collection view. + @return if the collection view index path can be edited. + */ +- (BOOL)collectionView:(nonnull UICollectionView *)collectionView + canEditItemAtIndexPath:(nonnull NSIndexPath *)indexPath; + +/** + Sent to the receiver for permission to select an item at this collection view index path + during collection view editing. Returning NO here will prevent selecting the designated + index path. If not implemented, will default to NO. + + + @param collectionView The collection view. + @param indexPath The index path of the collection view. + @return if the collection view index path can be selected. + */ +- (BOOL)collectionView:(nonnull UICollectionView *)collectionView + canSelectItemDuringEditingAtIndexPath:(nonnull NSIndexPath *)indexPath; + +#pragma mark - CollectionView Item Moving + +/** + If YES, the collectionView will allow reordering of its items. Permissions for individual + index paths are be set by the -collectionView:canMoveItemAtIndexPath and/or the + -collectionView:canMoveItemAtIndexPath:toIndexPath methods. If not implemented, will default + to NO. + + */ +- (BOOL)collectionViewAllowsReordering:(nonnull UICollectionView *)collectionView; + +/** + Sent to the receiver for permission to move an item at this collection view index path. + Returning NO here will prevent moving the designated index path. If not implemented, will + default to NO. + + @param collectionView The collection view. + @param indexPath The index path of the collection view. + @return if the collection view index path can be moved. + */ +- (BOOL)collectionView:(nonnull UICollectionView *)collectionView + canMoveItemAtIndexPath:(nonnull NSIndexPath *)indexPath; + +/** + Sent to the receiver for permission to move an item at this collection view index path to + a new index path. Returning NO here will prevent moving the designated index path. If not + implemented, will default to NO. + + @param collectionView The collection view. + @param indexPath The current index path of the collection view. + @param newIndexPath The propsed new index path of the collection view. + @return if the collection view index path can be moved. + */ +- (BOOL)collectionView:(nonnull UICollectionView *)collectionView + canMoveItemAtIndexPath:(nonnull NSIndexPath *)indexPath + toIndexPath:(nonnull NSIndexPath *)newIndexPath; + +/** + Sent to the receiver when the collection view will move an item from its previous index path + to the new index path. + + @param collectionView The collection view. + @param indexPath The previous index path of the collection view. + @param newIndexPath The new index path of the collection view. + */ +- (void)collectionView:(nonnull UICollectionView *)collectionView + willMoveItemAtIndexPath:(nonnull NSIndexPath *)indexPath + toIndexPath:(nonnull NSIndexPath *)newIndexPath; + +/** + Sent to the receiver when the collection view did move an item from its previous index path + to the new index path. + + @param collectionView The collection view. + @param indexPath The previous index path of the collection view. + @param newIndexPath The new index path of the collection view. + */ +- (void)collectionView:(nonnull UICollectionView *)collectionView + didMoveItemAtIndexPath:(nonnull NSIndexPath *)indexPath + toIndexPath:(nonnull NSIndexPath *)newIndexPath; + +/** + Sent to the receiver when a collection view item at specified index path will begin dragging. + + @param collectionView The collection view. + @param indexPath The index path of the collection view. + */ +- (void)collectionView:(nonnull UICollectionView *)collectionView + willBeginDraggingItemAtIndexPath:(nonnull NSIndexPath *)indexPath; + +/** + Sent to the receiver when a collection view item at specified index path has finished dragging. + + @param collectionView The collection view. + @param indexPath The index path of the collection view. + */ +- (void)collectionView:(nonnull UICollectionView *)collectionView + didEndDraggingItemAtIndexPath:(nonnull NSIndexPath *)indexPath; + +#pragma mark - CollectionView Item Deletions + +/** + Sent to the receiver when an array of index paths will be deleted from the collection view. + + @param collectionView The collection view. + @param indexPaths An array of index paths to be deleted from the collection view. + */ +- (void)collectionView:(nonnull UICollectionView *)collectionView + willDeleteItemsAtIndexPaths:(nonnull NSArray *)indexPaths; + +/** + Sent to the receiver after an array of index paths did get deleted from the collection view. + + @param collectionView The collection view. + @param indexPaths An array of index paths deleted from the collection view. + */ +- (void)collectionView:(nonnull UICollectionView *)collectionView + didDeleteItemsAtIndexPaths:(nonnull NSArray *)indexPaths; + +#pragma mark - CollectionView Section Deletions + +/** + Sent to the receiver when an array of index paths will be deleted from the collection view. + + @param collectionView The collection view. + @param sections An index set of sections to deleted from the collection view. + */ +- (void)collectionView:(nonnull UICollectionView *)collectionView + willDeleteSections:(nonnull NSIndexSet *)sections; + +/** + Sent to the receiver after an array of index paths did get deleted from the collection view. + + @param collectionView The collection view. + @param sections An index set of sections deleted from the collection view. + */ +- (void)collectionView:(nonnull UICollectionView *)collectionView + didDeleteSections:(nonnull NSIndexSet *)sections; + +#pragma mark - CollectionView Swipe To Dismiss Items + +/** + If YES, the collectionView will allow swiping to dismiss an item. If allowed, swiping is enabled + for all items (excluding headers, and footers). Permissions for individual items can be set by + implementing the protocol -collectionView:canSwipeToDismissItemAtIndexPath method. If not + implemented, will default to NO. + + @param collectionView The collection view being swiped for dismissal. + @return if the collection view can swipe to dismiss an item. + */ +- (BOOL)collectionViewAllowsSwipeToDismissItem:(nonnull UICollectionView *)collectionView; + +/** + Sent to the receiver when a collection view index path begins to swipe for dismissal. The + delegate method -collectionViewAllowsSwipeToDismissItem must return true in order for this + subsequent delegate method to be called. The collection view is NOT required to be in + edit mode to allow swipe-to-dismiss items. Returning NO here will prevent swiping the + designated item at index path. If not implemented, will default to NO. + + + @param collectionView The collection view being swiped for dismissal. + @param indexPath The index path of the collection view being swiped for dismissal. + @return if the collection view index path can be swiped for dismissal. + */ +- (BOOL)collectionView:(nonnull UICollectionView *)collectionView + canSwipeToDismissItemAtIndexPath:(nonnull NSIndexPath *)indexPath; + +/** + Sent to the receiver when the collection view index path begins to swipe for dismissal. + + @param collectionView The collection view being swiped for dismissal. + @param indexPath The index path of the collection view being swiped for dismissal. + */ +- (void)collectionView:(nonnull UICollectionView *)collectionView + willBeginSwipeToDismissItemAtIndexPath:(nonnull NSIndexPath *)indexPath; + +/** + Sent to the receiver after the collection view item has been dismissed. + + @param collectionView The collection view being swiped for dismissal. + @param indexPath The index path of the collection view being swiped for dismissal. + */ +- (void)collectionView:(nonnull UICollectionView *)collectionView + didEndSwipeToDismissItemAtIndexPath:(nonnull NSIndexPath *)indexPath; + +/** + Sent to the receiver when the collection view index path has reset without being dismissed. + + @param collectionView The collection view being swiped for dismissal. + @param indexPath The index path of the collection view being swiped for dismissal. + */ +- (void)collectionView:(nonnull UICollectionView *)collectionView + didCancelSwipeToDismissItemAtIndexPath:(nonnull NSIndexPath *)indexPath; + +#pragma mark - CollectionView Swipe To Dismiss Sections + +/** + If YES, the collectionView will allow swiping to dismiss a section. If allowed, swiping is enabled + for all section items, headers, and footers. Permissions for individual sections can be + set by implementing the protocol -collectionView:canSwipeToDismissSection method. If not + implemented, will default to NO. + + @param collectionView The collection view being swiped for dismissal. + @return if the collection view can swipe to dismiss a section. + */ +- (BOOL)collectionViewAllowsSwipeToDismissSection:(nonnull UICollectionView *)collectionView; + +/** + Sent to the receiver when a collection view section begins to swipe for dismissal. The + collection view property |allowsSwipeToDismissSection| must be true in order for this + subsequent delegate method to be called. The collection view is NOT required to be in + edit mode to allow swipe-to-dismiss sections. Returning NO here will prevent swiping the + designated section. If not implemented, will default to NO. + + @param collectionView The collection view being swiped for dismissal. + @param section The section of the collection view being swiped for dismissal. + @return if the collection view section can be swiped for dismissal. + */ +- (BOOL)collectionView:(nonnull UICollectionView *)collectionView + canSwipeToDismissSection:(NSInteger)section; + +/** + Sent to the receiver when the collection view section begins to swipe for dismissal. + + @param collectionView The collection view being swiped for dismissal. + @param section The section of the collection view being swiped for dismissal. + */ +- (void)collectionView:(nonnull UICollectionView *)collectionView + willBeginSwipeToDismissSection:(NSInteger)section; + +/** + Sent to the receiver after the collection view section has been dismissed. + + @param collectionView The collection view being swiped for dismissal. + @param section The section of the collection view being swiped for dismissal. + */ +- (void)collectionView:(nonnull UICollectionView *)collectionView + didEndSwipeToDismissSection:(NSInteger)section; + +/** + Sent to the receiver when the collection view section has reset without being dismissed. + + @param collectionView The collection view being swiped for dismissal. + @param section The section of the collection view being swiped for dismissal. + */ +- (void)collectionView:(nonnull UICollectionView *)collectionView + didCancelSwipeToDismissSection:(NSInteger)section; + +@end diff --git a/components/Collections/src/MDCCollectionViewFlowLayout.h b/components/Collections/src/MDCCollectionViewFlowLayout.h new file mode 100644 index 00000000000..3181e3e0e9b --- /dev/null +++ b/components/Collections/src/MDCCollectionViewFlowLayout.h @@ -0,0 +1,28 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +/** + The MDCCollectionViewFlowLayout class provides a Material design layout implementation of a + UICollectionViewFlowLayout layout. + + Learn more at the + [Material spec](https://www.google.com/design/spec/components/lists.html#lists-specs) + */ +@interface MDCCollectionViewFlowLayout : UICollectionViewFlowLayout + +@end diff --git a/components/Collections/src/MDCCollectionViewFlowLayout.m b/components/Collections/src/MDCCollectionViewFlowLayout.m new file mode 100644 index 00000000000..d063dd7a442 --- /dev/null +++ b/components/Collections/src/MDCCollectionViewFlowLayout.m @@ -0,0 +1,751 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "MDCCollectionViewFlowLayout.h" + +#import "MDCCollectionViewController.h" +#import "MDCCollectionViewEditingDelegate.h" +#import "MDCCollectionViewStyling.h" +#import "MaterialCollectionLayoutAttributes.h" +#import "private/MDCCollectionGridBackgroundView.h" +#import "private/MDCCollectionInfoBarView.h" +#import "private/MDCCollectionViewEditor.h" + +#import + +/** The grid background decoration view kind. */ +NSString *const kMDCCollectionDecorationView = @"MDCCollectionDecorationView"; + +static const NSInteger kMDCSupplementaryViewZIndex = 99; + +@implementation MDCCollectionViewFlowLayout { + NSMutableArray *_deletedIndexPaths; + NSMutableArray *_insertedIndexPaths; + NSMutableIndexSet *_deletedSections; + NSMutableIndexSet *_insertedSections; + NSMutableIndexSet *_headerSections; + NSMutableIndexSet *_footerSections; +} + +- (instancetype)init { + self = [super init]; + if (self) { + // Defaults. + self.minimumLineSpacing = 0; + self.minimumInteritemSpacing = 0; + self.scrollDirection = UICollectionViewScrollDirectionVertical; + self.sectionInset = UIEdgeInsetsZero; + + // Register decoration view for grid background. + [self registerClass:[MDCCollectionGridBackgroundView class] + forDecorationViewOfKind:kMDCCollectionDecorationView]; + } + return self; +} + +- (id)editor { + if ([self.collectionView.delegate isKindOfClass:[MDCCollectionViewController class]]) { + MDCCollectionViewController *controller = + (MDCCollectionViewController *)self.collectionView.delegate; + return controller.editor; + } + return nil; +} + +- (id)styler { + if ([self.collectionView.delegate isKindOfClass:[MDCCollectionViewController class]]) { + MDCCollectionViewController *controller = + (MDCCollectionViewController *)self.collectionView.delegate; + return controller.styler; + } + return nil; +} + +#pragma mark - UICollectionViewLayout (SubclassingHooks) + +- (NSArray *)layoutAttributesForElementsInRect:(CGRect)rect { + // If performing appearance animation, increase bounds height in order to retrieve additional + // offscreen attributes needed during animation. + rect = [self boundsForAppearanceAnimationWithInitialBounds:rect]; + NSMutableArray *attributes = [[super layoutAttributesForElementsInRect:rect] mutableCopy]; + + // Store index path sections of any headers/footers within these attributes. + [self storeSupplementaryViewsWithAttributes:attributes]; + + // Set layout attributes. + for (MDCCollectionViewLayoutAttributes *attr in attributes) { + [self updateAttribute:attr]; + } + + // Add info bar header/footer supplementary view if necessary. + [self addInfoBarAttributesIfNecessary:attributes]; + + // Begin cell appearance animation if necessary. + [self beginCellAppearanceAnimationIfNecessary:attributes]; + + // Add a grid background decoration view for each section if necessary. + [self addDecorationViewIfNecessary:attributes]; + + return attributes; +} + +- (BOOL)shouldInvalidateLayoutForBoundsChange:(CGRect)newBounds { + if (!CGSizeEqualToSize(self.collectionView.bounds.size, newBounds.size) || + self.editor.isEditing) { + [self invalidateLayout]; + return YES; + } + return [super shouldInvalidateLayoutForBoundsChange:newBounds]; +} + +#pragma mark - UICollectionViewLayout (UISubclassingHooks) + ++ (Class)layoutAttributesClass { + return [MDCCollectionViewLayoutAttributes class]; +} + +- (UICollectionViewLayoutAttributes *)layoutAttributesForItemAtIndexPath:(NSIndexPath *)indexPath { + MDCCollectionViewLayoutAttributes *attr = + (MDCCollectionViewLayoutAttributes *)[super layoutAttributesForItemAtIndexPath:indexPath]; + [self updateAttribute:attr]; + return attr; +} + +- (UICollectionViewLayoutAttributes *) + layoutAttributesForSupplementaryViewOfKind:(NSString *)kind + atIndexPath:(NSIndexPath *)indexPath { + UICollectionViewLayoutAttributes *attr; + + if ([kind isEqualToString:UICollectionElementKindSectionHeader] || + [kind isEqualToString:UICollectionElementKindSectionFooter]) { + // Update section headers/Footers attributes. + attr = [super layoutAttributesForSupplementaryViewOfKind:kind atIndexPath:indexPath]; + if (!attr) { + attr = + [MDCCollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:kind + withIndexPath:indexPath]; + } + [self updateAttribute:(MDCCollectionViewLayoutAttributes *)attr]; + + } else { + // Update editing info bar attributes. + attr = [UICollectionViewLayoutAttributes layoutAttributesForSupplementaryViewOfKind:kind + withIndexPath:indexPath]; + + // Force the info bar supplementary views to stay fixed to their respective positions + // at top/bottom of the collectionView bounds. + CGFloat offsetY = 0; + CGRect currentBounds = self.collectionView.bounds; + attr.zIndex = kMDCSupplementaryViewZIndex; + + if ([kind isEqualToString:MDCCollectionInfoBarKindHeader]) { + attr.size = CGSizeMake(CGRectGetWidth(currentBounds), MDCCollectionInfoBarHeaderHeight); + // Allow header to move upwards with scroll, but prevent from moving downwards with scroll. + CGFloat insetTop = self.collectionView.contentInset.top; + CGFloat boundsY = currentBounds.origin.y; + CGFloat maxOffsetY = MAX(boundsY + insetTop, 0); + offsetY = boundsY + (attr.size.height / 2) + insetTop - maxOffsetY; + } else if ([kind isEqualToString:MDCCollectionInfoBarKindFooter]) { + attr.size = CGSizeMake(CGRectGetWidth(currentBounds), MDCCollectionInfoBarFooterHeight); + offsetY = currentBounds.origin.y + currentBounds.size.height - (attr.size.height / 2); + } + attr.center = CGPointMake(CGRectGetMidX(currentBounds), offsetY); + } + return attr; +} + +- (UICollectionViewLayoutAttributes *) + layoutAttributesForDecorationViewOfKind:(NSString *)elementKind + atIndexPath:(NSIndexPath *)indexPath { + MDCCollectionViewLayoutAttributes *decorationAttr = + [MDCCollectionViewLayoutAttributes layoutAttributesForDecorationViewOfKind:elementKind + withIndexPath:indexPath]; + // Determine section frame by summing all of its item frames. + CGRect sectionFrame = CGRectNull; + for (NSInteger i = 0; i < [self numberOfItemsInSection:indexPath.section]; ++i) { + indexPath = [NSIndexPath indexPathForItem:i inSection:indexPath.section]; + UICollectionViewLayoutAttributes *attribute = + [self layoutAttributesForItemAtIndexPath:indexPath]; + if (!CGRectIsNull(attribute.frame)) { + sectionFrame = CGRectUnion(sectionFrame, attribute.frame); + } + } + decorationAttr.frame = sectionFrame; + decorationAttr.zIndex = -1; + return decorationAttr; +} + +- (CGPoint)targetContentOffsetForProposedContentOffset:(CGPoint)proposedContentOffset { + // Return current contentOffset to prevent any layout animations from jumping to new offset. + return [super targetContentOffsetForProposedContentOffset:self.collectionView.contentOffset]; +} + +#pragma mark - UICollectionViewLayout (UIUpdateSupportHooks) + +- (void)prepareForCollectionViewUpdates:(NSArray *)updateItems { + [super prepareForCollectionViewUpdates:updateItems]; + _deletedIndexPaths = [NSMutableArray array]; + _insertedIndexPaths = [NSMutableArray array]; + _deletedSections = [NSMutableIndexSet indexSet]; + _insertedSections = [NSMutableIndexSet indexSet]; + + for (UICollectionViewUpdateItem *item in updateItems) { + if (item.updateAction == UICollectionUpdateActionDelete) { + // Store deleted sections or indexPaths. + if (item.indexPathBeforeUpdate.item == NSNotFound) { + [_deletedSections addIndex:item.indexPathBeforeUpdate.section]; + } else { + [_deletedIndexPaths addObject:item.indexPathBeforeUpdate]; + } + + } else if (item.updateAction == UICollectionUpdateActionInsert) { + // Store inserted sections or indexPaths. + if (item.indexPathAfterUpdate.item == NSNotFound) { + [_insertedSections addIndex:item.indexPathAfterUpdate.section]; + } else { + [_insertedIndexPaths addObject:item.indexPathAfterUpdate]; + } + } + } +} + +- (void)finalizeCollectionViewUpdates { + [super finalizeCollectionViewUpdates]; + _deletedIndexPaths = nil; + _insertedIndexPaths = nil; + _deletedSections = nil; + _insertedSections = nil; +} + +- (UICollectionViewLayoutAttributes *)initialLayoutAttributesForAppearingItemAtIndexPath: + (NSIndexPath *)itemIndexPath { + UICollectionViewLayoutAttributes *attr = + [super initialLayoutAttributesForAppearingItemAtIndexPath:itemIndexPath]; + // Adding new section or item. + if ([_insertedSections containsIndex:itemIndexPath.section] || + [_insertedIndexPaths containsObject:itemIndexPath]) { + attr.transform = CGAffineTransformMakeTranslation(0, -CGRectGetHeight(attr.bounds) / 2); + } else { + attr.alpha = 1; + } + return attr; +} + +- (UICollectionViewLayoutAttributes *) + initialLayoutAttributesForAppearingSupplementaryElementOfKind:(NSString *)elementKind + atIndexPath:(NSIndexPath *)elementIndexPath { + UICollectionViewLayoutAttributes *attr = + [super initialLayoutAttributesForAppearingSupplementaryElementOfKind:elementKind + atIndexPath:elementIndexPath]; + if ([elementKind isEqualToString:UICollectionElementKindSectionHeader] || + [elementKind isEqualToString:UICollectionElementKindSectionFooter]) { + // Adding new section header or footer. + if ([_insertedSections containsIndex:elementIndexPath.section]) { + attr.transform = CGAffineTransformMakeTranslation(0, -CGRectGetHeight(attr.bounds) / 2); + } else { + attr.alpha = 1; + } + } + return attr; +} + +- (UICollectionViewLayoutAttributes *)finalLayoutAttributesForDisappearingItemAtIndexPath: + (NSIndexPath *)itemIndexPath { + UICollectionViewLayoutAttributes *attr = + [super finalLayoutAttributesForDisappearingItemAtIndexPath:itemIndexPath]; + // Deleting section or item. + if ([_deletedSections containsIndex:itemIndexPath.section] || + [_deletedIndexPaths containsObject:itemIndexPath]) { + attr.transform = CGAffineTransformMakeTranslation(0, -CGRectGetHeight(attr.bounds) / 2); + } else { + attr.alpha = 1; + } + return attr; +} + +- (UICollectionViewLayoutAttributes *) + finalLayoutAttributesForDisappearingSupplementaryElementOfKind:(NSString *)elementKind + atIndexPath:(NSIndexPath *)elementIndexPath { + UICollectionViewLayoutAttributes *attr = + [super finalLayoutAttributesForDisappearingSupplementaryElementOfKind:elementKind + atIndexPath:elementIndexPath]; + if ([elementKind isEqualToString:UICollectionElementKindSectionHeader] || + [elementKind isEqualToString:UICollectionElementKindSectionFooter]) { + // Deleting section header or footer. + if ([_deletedSections containsIndex:elementIndexPath.section]) { + attr.transform = CGAffineTransformMakeTranslation(0, -CGRectGetHeight(attr.bounds) / 2); + } else { + attr.alpha = 1; + } + } + return attr; +} + +#pragma mark - Header/Footer Caching + +- (void)storeSupplementaryViewsWithAttributes:(NSArray *)attributes { + _headerSections = [NSMutableIndexSet indexSet]; + _footerSections = [NSMutableIndexSet indexSet]; + + // Store index path sections for headers/footers. + for (MDCCollectionViewLayoutAttributes *attr in attributes) { + if ([attr.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]) { + [_headerSections addIndex:attr.indexPath.section]; + } else if ([attr.representedElementKind isEqualToString:UICollectionElementKindSectionFooter]) { + [_footerSections addIndex:attr.indexPath.section]; + } + } +} + +#pragma mark - Private + +- (MDCCollectionViewLayoutAttributes *)updateAttribute:(MDCCollectionViewLayoutAttributes *)attr { + if (attr.representedElementKind == UICollectionElementCategoryCell) { + attr.editing = self.editor.isEditing; + } + attr.isGridLayout = NO; + if (self.styler.cellLayoutType == MDCCollectionViewCellLayoutTypeList) { + attr.sectionOrdinalPosition = [self ordinalPositionForListElementWithAttribute:attr]; + } else if (self.styler.cellLayoutType == MDCCollectionViewCellLayoutTypeGrid) { + attr.sectionOrdinalPosition = [self ordinalPositionForGridElementWithAttribute:attr]; + attr.isGridLayout = YES; + } + + [self updateCellStateMaskWithAttribute:attr]; + + if (attr.representedElementCategory == UICollectionElementCategorySupplementaryView) { + attr = [self updateSupplementaryViewAttribute:attr]; + } + + // Set cell background. + attr.backgroundImage = [self.styler backgroundImageForCellLayoutAttributes:attr]; + + // Set separator styling. + attr.separatorColor = self.styler.separatorColor; + attr.separatorInset = self.styler.separatorInset; + attr.separatorLineHeight = self.styler.separatorLineHeight; + attr.shouldHideSeparators = self.styler.shouldHideSeparators; + + // Set inlay and hidden state if necessary. + [self inlayAttributeIfNecessary:attr]; + [self hideAttributeIfNecessary:attr]; + + return attr; +} + +- (MDCCollectionViewLayoutAttributes *)updateSupplementaryViewAttribute: + (MDCCollectionViewLayoutAttributes *)attr { + // In vertical scrolling, supplementary views only respect their height and ignore their width + // value. The opposite is true for horizontal scrolling. Therefore we must manually set insets + // on both the backgroundView and contentView in order to match the insets of the collection + // view rows. + CGRect insetFrame = attr.frame; + UIEdgeInsets insets = [self insetsAtSectionIndex:attr.indexPath.section]; + if (self.scrollDirection == UICollectionViewScrollDirectionVertical) { + insetFrame = CGRectInset(insetFrame, insets.left / 2 + insets.right / 2, 0); + if (attr.representedElementKind == UICollectionElementKindSectionHeader) { + insetFrame.origin.y += insets.top; + } else if (attr.representedElementKind == UICollectionElementKindSectionFooter) { + insetFrame.origin.y -= insets.bottom; + } + attr.frame = insetFrame; + } + return attr; +} + +- (UIEdgeInsets)insetsAtSectionIndex:(NSInteger)section { + // Determine insets based on cell style. + CGFloat inset = (CGFloat)floor(MDCCollectionViewCellStyleCardSectionInset); + UIEdgeInsets insets = UIEdgeInsetsZero; + NSInteger numberOfSections = self.collectionView.numberOfSections; + BOOL isTop = (section == 0); + BOOL isBottom = (section == numberOfSections - 1); + MDCCollectionViewCellStyle cellStyle = [self.styler cellStyleAtSectionIndex:section]; + BOOL isCardStyle = cellStyle == MDCCollectionViewCellStyleCard; + BOOL isGroupedStyle = cellStyle == MDCCollectionViewCellStyleGrouped; + // Set left/right insets. + if (isCardStyle) { + insets.left = inset; + insets.right = inset; + } + // Set top/bottom insets. + if (isCardStyle || isGroupedStyle) { + insets.top = (CGFloat)floor((isTop) ? inset : inset / 2.0f); + insets.bottom = (CGFloat)floor((isBottom) ? inset : inset / 2.0f); + } + return insets; +} + +- (MDCCollectionViewOrdinalPosition)ordinalPositionForListElementWithAttribute: + (MDCCollectionViewLayoutAttributes *)attr { + // Returns the ordinal position of cells and supplementary views within a list layout. This is + // used to determine the layout attributes applied to their styling. + MDCCollectionViewOrdinalPosition position = 0; + NSIndexPath *indexPath = attr.indexPath; + NSInteger numberOfItemsInSection = [self numberOfItemsInSection:indexPath.section]; + BOOL isTop = NO; + BOOL isBottom = NO; + BOOL hasSectionHeader = [_headerSections containsIndex:indexPath.section]; + BOOL hasSectionFooter = [_footerSections containsIndex:indexPath.section]; + BOOL hasSectionItems = YES; + + BOOL hidesHeaderBackground = NO; + if ([self.styler.delegate + respondsToSelector:@selector(collectionView:shouldHideHeaderBackgroundForSection:)]) { + hidesHeaderBackground = + [self.styler.delegate collectionView:self.styler.collectionView + shouldHideHeaderBackgroundForSection:indexPath.section]; + } + + BOOL hidesFooterBackground = NO; + if ([self.styler.delegate + respondsToSelector:@selector(collectionView:shouldHideFooterBackgroundForSection:)]) { + hidesFooterBackground = + [self.styler.delegate collectionView:self.styler.collectionView + shouldHideFooterBackgroundForSection:indexPath.section]; + } + + if (attr.representedElementCategory == UICollectionElementCategoryCell) { + isTop = (indexPath.item == 0) && (!hasSectionHeader || hidesHeaderBackground); + isBottom = + (indexPath.item == numberOfItemsInSection - 1) && + (!hasSectionFooter || hidesFooterBackground); + } else if (attr.representedElementCategory == UICollectionElementCategorySupplementaryView) { + NSString *kind = attr.representedElementKind; + BOOL isElementHeader = ([kind isEqualToString:UICollectionElementKindSectionHeader]); + BOOL isElementFooter = ([kind isEqualToString:UICollectionElementKindSectionFooter]); + isTop = (isElementFooter && !hasSectionItems && !hasSectionHeader) || isElementHeader; + isBottom = (isElementHeader && !hasSectionItems && !hasSectionFooter) || isElementFooter; + } + + if (attr.editing || [self.styler isItemInlaidAtIndexPath:attr.indexPath]) { + isTop = YES; + isBottom = YES; + } + + if (!isTop && !isBottom) { + position |= MDCCollectionViewOrdinalPositionVerticalCenter; + } else { + position |= isTop ? MDCCollectionViewOrdinalPositionVerticalTop : position; + position |= isBottom ? MDCCollectionViewOrdinalPositionVerticalBottom : position; + } + return position; +} + +- (MDCCollectionViewOrdinalPosition)ordinalPositionForGridElementWithAttribute: + (MDCCollectionViewLayoutAttributes *)attr { + // Returns the ordinal position of cells and supplementary views within a grid layout. This is + // used to determine the layout attributes applied to their styling. + MDCCollectionViewOrdinalPosition position = 0; + NSIndexPath *indexPath = attr.indexPath; + NSInteger numberOfItemsInSection = [self numberOfItemsInSection:indexPath.section]; + NSInteger gridColumnCount = self.styler.gridColumnCount; + NSInteger maxRowIndex = (NSInteger)(floor(numberOfItemsInSection / gridColumnCount) - 1); + NSInteger maxColumnIndex = gridColumnCount - 1; + NSInteger ordinalRow = (NSInteger)(floor(indexPath.item / gridColumnCount)); + NSInteger ordinalColumn = (NSInteger)(floor(indexPath.item % gridColumnCount)); + + // Set vertical ordinal position. + if (ordinalRow > 0 && ordinalRow < maxRowIndex) { + position = position | MDCCollectionViewOrdinalPositionVerticalCenter; + } else { + position = (ordinalRow == 0) + ? position | MDCCollectionViewOrdinalPositionVerticalTop + : position; + position = (ordinalRow == maxRowIndex) + ? position | MDCCollectionViewOrdinalPositionVerticalBottom + : position; + } + + // Set horizontal ordinal position. + if (ordinalColumn > 0 && ordinalColumn < maxColumnIndex) { + position = position | MDCCollectionViewOrdinalPositionHorizontalCenter; + } else { + position = (ordinalColumn == 0) + ? position | MDCCollectionViewOrdinalPositionHorizontalLeft + : position; + position = (ordinalColumn == maxColumnIndex) + ? position | MDCCollectionViewOrdinalPositionHorizontalRight + : position; + } + return position; +} + +- (void)updateCellStateMaskWithAttribute:(MDCCollectionViewLayoutAttributes *)attr { + attr.shouldShowSelectorStateMask = NO; + attr.shouldShowSelectorStateMask = NO; + + // Determine proper state to show cell if editing. + if (attr.editing) { + if ([self.collectionView.dataSource + conformsToProtocol:@protocol(MDCCollectionViewEditingDelegate)]) { + id editingDelegate = + (id)self.collectionView.dataSource; + + // Check if delegate can select during editing. + if ([editingDelegate respondsToSelector: + @selector(collectionView:canSelectItemDuringEditingAtIndexPath:)]) { + attr.shouldShowSelectorStateMask = [editingDelegate collectionView:self.collectionView + canSelectItemDuringEditingAtIndexPath:attr.indexPath]; + } + + // Check if delegate can reorder. + if ([editingDelegate respondsToSelector:@selector(collectionView:canMoveItemAtIndexPath:)]) { + attr.shouldShowReorderStateMask = [editingDelegate collectionView:self.collectionView + canMoveItemAtIndexPath:attr.indexPath]; + } + } + } +} + +- (void)inlayAttributeIfNecessary:(MDCCollectionViewLayoutAttributes *)attr { + // Inlay this attribute if necessary. + CGFloat inset = MDCCollectionViewCellStyleCardSectionInset; + UIEdgeInsets inlayInsets = UIEdgeInsetsZero; + NSInteger item = attr.indexPath.item; + NSArray *inlaidIndexPaths = [self.styler indexPathsForInlaidItems]; + + // Update ordinal position for index paths adjacent to inlaid index path. + for (NSIndexPath *inlaidIndexPath in inlaidIndexPaths) { + if (inlaidIndexPath.section == attr.indexPath.section) { + NSInteger numberOfItemsInSection = [self numberOfItemsInSection:inlaidIndexPath.section]; + if (attr.representedElementCategory == UICollectionElementCategoryCell) { + if (item == inlaidIndexPath.item) { + // Get previous and next index paths to the inlaid index path. + BOOL prevAttrIsInlaid = NO; + BOOL nextAttrIsInlaid = NO; + BOOL hasSectionHeader = [_headerSections containsIndex:inlaidIndexPath.section]; + BOOL hasSectionFooter = [_footerSections containsIndex:inlaidIndexPath.section]; + + if (inlaidIndexPath.item > 0 || hasSectionHeader) { + NSIndexPath *prevIndexPath = [NSIndexPath indexPathForItem:(inlaidIndexPath.item - 1) + inSection:inlaidIndexPath.section]; + prevAttrIsInlaid = [self.styler isItemInlaidAtIndexPath:prevIndexPath]; + inlayInsets.top = prevAttrIsInlaid ? inset / 2 : inset; + } + + if (inlaidIndexPath.item < numberOfItemsInSection - 1 || hasSectionFooter) { + NSIndexPath *nextIndexPath = [NSIndexPath indexPathForItem:(inlaidIndexPath.item + 1) + inSection:inlaidIndexPath.section]; + nextAttrIsInlaid = [self.styler isItemInlaidAtIndexPath:nextIndexPath]; + inlayInsets.bottom = nextAttrIsInlaid ? inset / 2 : inset; + } + + // Is attribute to be inlaid. + attr.frame = UIEdgeInsetsInsetRect(attr.frame, inlayInsets); + attr.sectionOrdinalPosition = MDCCollectionViewOrdinalPositionVerticalTopBottom; + } else if (item == inlaidIndexPath.item - 1) { + // Is previous to inlaid attribute. + if (attr.sectionOrdinalPosition & MDCCollectionViewOrdinalPositionVerticalTop) { + attr.sectionOrdinalPosition = MDCCollectionViewOrdinalPositionVerticalTopBottom; + } else if (attr.sectionOrdinalPosition & + MDCCollectionViewOrdinalPositionVerticalCenter) { + attr.sectionOrdinalPosition = MDCCollectionViewOrdinalPositionVerticalBottom; + } + } else if (item == inlaidIndexPath.item + 1) { + // Is next to inlaid attribute. + if (attr.sectionOrdinalPosition & MDCCollectionViewOrdinalPositionVerticalCenter) { + attr.sectionOrdinalPosition = MDCCollectionViewOrdinalPositionVerticalTop; + } else if (attr.sectionOrdinalPosition & + MDCCollectionViewOrdinalPositionVerticalBottom) { + attr.sectionOrdinalPosition = MDCCollectionViewOrdinalPositionVerticalBottom; + } + } + + } else if (attr.representedElementCategory == UICollectionElementCategorySupplementaryView) { + // If header/footer attribute, update if adjacent to inlaid index path. + NSString *kind = attr.representedElementKind; + BOOL isElementHeader = ([kind isEqualToString:UICollectionElementKindSectionHeader]); + BOOL isElementFooter = ([kind isEqualToString:UICollectionElementKindSectionFooter]); + if (isElementHeader && inlaidIndexPath.item == 0) { + attr.sectionOrdinalPosition = MDCCollectionViewOrdinalPositionVerticalTopBottom; + } else if (isElementFooter && inlaidIndexPath.item == numberOfItemsInSection - 1) { + attr.sectionOrdinalPosition = MDCCollectionViewOrdinalPositionVerticalTopBottom; + } + } + } + } +} + +- (void)hideAttributeIfNecessary:(MDCCollectionViewLayoutAttributes *)attr { + if (self.editor) { + // Hide the attribute if the editor is either currently handling a cell item or section swipe + // for dismissal, or is reordering a cell item. + BOOL isCell = attr.representedElementCategory == UICollectionElementCategoryCell; + if (attr.indexPath.section == self.editor.dismissingSection || + ([attr.indexPath isEqual:self.editor.dismissingCellIndexPath] && isCell) || + ([attr.indexPath isEqual:self.editor.reorderingCellIndexPath] && isCell)) { + attr.hidden = YES; + } + } +} + +- (void)addInfoBarAttributesIfNecessary:(NSMutableArray *)attributes { + if (self.editor.isEditing && [attributes count] > 0) { + NSIndexPath *indexPath = [NSIndexPath indexPathForItem:0 inSection:0]; + + // Add header info bar if editing. + [attributes addObject: + [self layoutAttributesForSupplementaryViewOfKind:MDCCollectionInfoBarKindHeader + atIndexPath:indexPath]]; + + // Add footer info bar if editing and item(s) are selected. + NSInteger selectedItemCount = [self.collectionView.indexPathsForSelectedItems count]; + if (selectedItemCount > 0) { + [attributes addObject: + [self layoutAttributesForSupplementaryViewOfKind:MDCCollectionInfoBarKindFooter + atIndexPath:indexPath]]; + } + } +} + +- (void)addDecorationViewIfNecessary:(NSMutableArray *)attributes { + // If necessary, adds a decoration view to a section drawn below its items. This will only happen + // for a grid layout when it is either A) grouped-style or B) card-style with zero padding. When + // this happens, the background for those items will not be drawn, and instead this decoration + // view will extend to the bounds of the sum of its respective section item frames. Shadowing and + // border will be applied to this decoration view as per the style manager settings. + if (self.styler.cellLayoutType == MDCCollectionViewCellLayoutTypeGrid) { + NSMutableSet *sectionSet = [NSMutableSet set]; + BOOL shouldShowGridBackground = NO; + NSMutableArray *decorationAttributes = [NSMutableArray array]; + for (MDCCollectionViewLayoutAttributes *attr in attributes) { + NSInteger section = attr.indexPath.section; + + // Only add one decoration view per section. + if (![sectionSet containsObject:@(section)]) { + NSIndexPath *decorationIndexPath = [NSIndexPath indexPathForItem:0 inSection:section]; + MDCCollectionViewLayoutAttributes *decorationAttr = + (MDCCollectionViewLayoutAttributes *) + [self layoutAttributesForDecorationViewOfKind:kMDCCollectionDecorationView + atIndexPath:decorationIndexPath]; + shouldShowGridBackground = [self shouldShowGridBackgroundWithAttribute:decorationAttr]; + decorationAttr.shouldShowGridBackground = shouldShowGridBackground; + decorationAttr.backgroundImage = shouldShowGridBackground + ? [self.styler backgroundImageForCellLayoutAttributes:decorationAttr] + : nil; + [decorationAttributes addObject:decorationAttr]; + [sectionSet addObject:@(section)]; + } + if (shouldShowGridBackground) { + attr.backgroundImage = nil; + } + } + [attributes addObjectsFromArray:decorationAttributes]; + } +} + +- (BOOL)shouldShowGridBackgroundWithAttribute:(MDCCollectionViewLayoutAttributes *)attr { + // Determine whether to show grid background. + if (self.styler.cellLayoutType == MDCCollectionViewCellLayoutTypeGrid) { + if (self.styler.cellStyle == MDCCollectionViewCellStyleGrouped || + (self.styler.cellStyle == MDCCollectionViewCellStyleCard && + self.styler.gridPadding == 0)) { + return YES; + } + } + return NO; +} + +- (NSInteger)numberOfItemsInSection:(NSInteger)section { + return [self.collectionView numberOfItemsInSection:section]; +} + +#pragma mark - Cell Appearance Animation + +- (CGRect)boundsForAppearanceAnimationWithInitialBounds:(CGRect)initialBounds { + // Increase initial bounds by 25% allowing offscreen attributes to be included in the + // appearance animation. + if (self.styler.shouldAnimateCellsOnAppearance && + self.styler.willAnimateCellsOnAppearance) { + CGRect newBounds = initialBounds; + newBounds.size.height += (newBounds.size.height / 4); + return newBounds; + } + return initialBounds; +} + +- (void)beginCellAppearanceAnimationIfNecessary:(NSMutableArray *)attributes { + // Here we want to assign a delay to each attribute such that the animation will fade-in from the + // top downwards in a staggered manner. However, the array of attributes we receive here are not + // in the correct order and must be sorted and re-ordered to properly assign these delays. + // + // First we will sort the array of attributes by index path to ensure proper ordering. Secondly + // we will manipulate the array to bring any headers before their first respective cell items. + // + // When completed, we will end up with an array of attributes in the form of + // header -> item -> footer ... repeated for each section. Now we can use this ordered array + // to assign delays based on their proper ordinal position from top down. + NSInteger attributeCount = attributes.count; + NSTimeInterval duration = self.styler.animateCellsOnAppearanceDuration; + if (self.styler.shouldAnimateCellsOnAppearance && attributeCount > 0) { + // First sort by index path. + NSArray *sortedByIndexPath = + [attributes sortedArrayUsingComparator: + ^NSComparisonResult(MDCCollectionViewLayoutAttributes *attr1, + MDCCollectionViewLayoutAttributes *attr2) { + return [attr1.indexPath compare:attr2.indexPath]; + }]; + + // Next create new array containing attributes in the form of header -> item -> footer. + NSMutableArray *sortedAttributes = [NSMutableArray array]; + [sortedByIndexPath enumerateObjectsUsingBlock:^(MDCCollectionViewLayoutAttributes *attr, + NSUInteger idx, BOOL *stop) { + if (sortedAttributes.count > 0) { + // Check if current attribute is a header and previous attribute is an item. If so, + // insert the current header attribute before the cell. + MDCCollectionViewLayoutAttributes *prevAttr = [sortedAttributes objectAtIndex:idx - 1]; + BOOL prevAttrIsItem = + prevAttr.representedElementCategory == UICollectionElementCategoryCell; + BOOL attrIsHeader = + [attr.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]; + if (attrIsHeader && prevAttrIsItem) { + [sortedAttributes insertObject:attr atIndex:idx - 1]; + return; + } + } + [sortedAttributes addObject:attr]; + }]; + + // Now assign delays and add padding to frame Y coordinate which gets removed during animation. + [sortedAttributes enumerateObjectsUsingBlock:^(MDCCollectionViewLayoutAttributes *attr, + NSUInteger idx, BOOL *stop) { + attr.willAnimateCellsOnAppearance = self.styler.willAnimateCellsOnAppearance; + attr.animateCellsOnAppearanceDuration = self.styler.animateCellsOnAppearanceDuration; + attr.animateCellsOnAppearanceDelay = + (attributeCount > 0) ? ((CGFloat)idx / attributeCount) * duration : 0; + + if (self.styler.willAnimateCellsOnAppearance) { + CGRect frame = attr.frame; + frame.origin.y += self.styler.animateCellsOnAppearancePadding; + attr.frame = frame; + } + }]; + + // Call asynchronously to allow the current layout cycle to complete before issuing animations. + if (self.styler.willAnimateCellsOnAppearance) { + dispatch_async(dispatch_get_main_queue(), ^{ + [self.styler beginCellAppearanceAnimation]; + }); + } + } +} + +@end diff --git a/components/Collections/src/MDCCollectionViewStyling.h b/components/Collections/src/MDCCollectionViewStyling.h new file mode 100644 index 00000000000..a5448c47290 --- /dev/null +++ b/components/Collections/src/MDCCollectionViewStyling.h @@ -0,0 +1,265 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +@protocol MDCCollectionViewStylingDelegate; +@class MDCCollectionViewLayoutAttributes; + +/** The default section insets. Should be an even number to allow even division. */ +extern const CGFloat MDCCollectionViewCellStyleCardSectionInset; + +/** The styles in which the collectionView cells can be shown. */ +typedef NS_ENUM(NSUInteger, MDCCollectionViewCellStyle) { + /** Cells displayed full width without section padding. */ + MDCCollectionViewCellStyleDefault, + + /** Cells displayed full width with section padding. */ + MDCCollectionViewCellStyleGrouped, + + /** Cells displayed card style with sections padding. */ + MDCCollectionViewCellStyleCard, +}; + +/** The layout types in which the collectionView cells can be shown. */ +typedef NS_ENUM(NSUInteger, MDCCollectionViewCellLayoutType) { + /** Cells displayed in list layout. */ + MDCCollectionViewCellLayoutTypeList, + + /** Cells displayed in grid layout. */ + MDCCollectionViewCellLayoutTypeGrid, + + /** + Cells displayed in custom defined layout. A new UICollectionViewLayout must be + provided and set on the collection view. + */ + MDCCollectionViewCellLayoutTypeCustom +}; + +/** + The MDCCollectionViewStyling protocol defines the stylable properties for a Material collection + view. + */ +@protocol MDCCollectionViewStyling + +/** The associated collection view. */ +@property(nonatomic, readonly, weak, nullable) UICollectionView *collectionView; + +/** The delegate is sent messages when styles change. */ +@property(nonatomic, weak, nullable) id delegate; + +/** Indicates whether the collection view layout should be invalidated. */ +@property(nonatomic, assign) BOOL shouldInvalidateLayout; + +#pragma mark - Cell Styling + +/** The cell background color. Defaults to white color. */ +@property(nonatomic, strong, nonnull) UIColor *cellBackgroundColor; + +/** + The cell layout type. Defaults to MDCCollectionViewCellLayoutTypeList if not set. Not animated. + Defaults to MDCCollectionViewCellLayoutTypeList. + */ +@property(nonatomic, assign) MDCCollectionViewCellLayoutType cellLayoutType; + +/** + The grid column count. Requires cellLayoutType to be set to MDCCollectionViewCellLayoutTypeGrid. + Not animated. + */ +@property(nonatomic, assign) NSInteger gridColumnCount; + +/** + The grid padding property is used when the cellLayoutType is MDCCollectionViewCellLayoutTypeGrid + and the gridColumnCount is greater than or equal to 2. This value ensure that the left, center, + and right padding are equivalent. This prevents double padding at center. Not animated. + */ +@property(nonatomic, assign) CGFloat gridPadding; + +/** The cell style. Not animated. @c setCellStyle:animated: for animated layout type changes. */ +@property(nonatomic, assign) MDCCollectionViewCellStyle cellStyle; + +/** + Updates the cell style with/without animation. + + @param animated YES the transition will be animated; otherwise, NO. + */ +- (void)setCellStyle:(MDCCollectionViewCellStyle)cellStyle animated:(BOOL)animated; + +/** + The collection view cell style at the specified section index. + + @param section The collection view section. + @return The cell style at specified section. + */ +- (MDCCollectionViewCellStyle)cellStyleAtSectionIndex:(NSInteger)section; + +/** + The collection view cell background image view (utilized to render the background color and + shadows) edge outsets as determined for a cell and its layout attributes. + + @param attr The cell's layout attributes. + @return Edge outsets as detemined by cell style at this index. + */ +- (UIEdgeInsets)backgroundImageViewOutsetsForCellWithAttribute: + (nonnull MDCCollectionViewLayoutAttributes *)attr; + +/** + Returns an image for use with the given cell style and ordinal position within section. + + The returned image is cached internally after the first request. Changing any of the display + properties will invalidate the cached images. + + @param attr The cell's layout attributes. + @return Image as determined by cell style and section ordinal position. + */ +- (nonnull UIImage *)backgroundImageForCellLayoutAttributes: + (nonnull MDCCollectionViewLayoutAttributes *)attr; + +#pragma mark - Cell Separator + +/** Separator color. Defaults to #E0E0E0. */ +@property(nonatomic, strong, nullable) UIColor *separatorColor; + +/** Separator inset. Defaults to UIEdgeInsetsZero. */ +@property(nonatomic) UIEdgeInsets separatorInset; + +/** Separator line height. Defaults to 1.0f */ +@property(nonatomic) CGFloat separatorLineHeight; + +/* Whether to hide the cell separators. Defaults to NO. */ +@property(nonatomic) BOOL shouldHideSeparators; + +#pragma mark - Item Inlaying + +/** + Whether inlaying a collection view item is allowed. When an item is inlaid, it will be given + padding at its edges to make it inset from other sibling items. A typical use case for inlaying + is when setting a collection view into editing mode, in which each cell gets inlaid so that they + can individually be selected/unselected/swiped for deletion. + + If YES, call the -inlayItemAtIndexPath:animated method to inlay the item at the specified + index path. Remove the inlay by calling the -removeInlayAtIndexPath:animated: method. + Not animated. Defaults to YES. + */ +@property(nonatomic, assign) BOOL allowsItemInlay; + +/** Whether inlaying multiple items is allowed. Not animated. Defaults to NO. */ +@property(nonatomic, assign) BOOL allowsMultipleItemInlays; + +/** + Returns an array of collection view index paths for items that have their respective frames + inlaid. + + @return An array of index paths. + */ +- (nullable NSArray *)indexPathsForInlaidItems; + +/** + A Boolean value indicating if the collection view item at the specified index path has its + frame inlaid. + + @param indexPath The collection view index path. + @return YES if the cells are Material styled cards. Otherwise No. + */ +- (BOOL)isItemInlaidAtIndexPath:(nonnull NSIndexPath *)indexPath; + +/** + Inlays the item at the specified index path. + + Property allowsItemInlay must be set to YES, otherwise no-op. + + @param indexPath The specified index path. + @param animated YES the transition will be animated; otherwise, NO. + */ +- (void)applyInlayToItemAtIndexPath:(nonnull NSIndexPath *)indexPath animated:(BOOL)animated; + +/** + Removes the inlaid state of the item at the specified index path. + + @param indexPath The specified index path. + @param animated YES the transition will be animated; otherwise, NO. + */ +- (void)removeInlayFromItemAtIndexPath:(nonnull NSIndexPath *)indexPath animated:(BOOL)animated; + +/** + Inlays items at all available index paths. + + Property allowsItemInlay must be set to YES, otherwise no-op. + + @param animated YES the transition will be animated; otherwise, NO. + */ +- (void)applyInlayToAllItemsAnimated:(BOOL)animated; + +/** + Removes the inlaid state of items at all available index paths. + + @param animated YES the transition will be animated; otherwise, NO. + */ +- (void)removeInlayFromAllItemsAnimated:(BOOL)animated; + +/** + Resets the internal storage array of all item index paths for inlaid items. When modifying + items in the collection view model, a call to this method will ensure that all inserted + and/or deleted index paths are reflected properly in the return array from the + method -indexPathsForInlaidItems. Calling this is not animated. + */ +- (void)resetIndexPathsForInlaidItems; + +#pragma mark - Cell Appearance Animation + +/** + Whether cells should animate on appearance. This property typically should be set in the + -viewDidLoad method of the controller. If YES, cells will animate the following properties: + + A) Cell heights will begin expanded by the padding specified by animateCellsOnAppearancePadding. + It will then animate to original height. + + B) Cell frame will begin with positive y-offset as specified by animateCellsOnAppearancePadding. + It will animate upwards to original y coordinate. + + C) Cell contentView and separatorView will begin hidden by setting alpha to 0. It will then + animate with a fade-in transition in a staggered fashion from the top cell down to last + visible cell. + */ +@property(nonatomic, assign) BOOL shouldAnimateCellsOnAppearance; + +/** Whether cells should calculate their initial properties before animation on appearance. */ +@property(nonatomic, readonly, assign) BOOL willAnimateCellsOnAppearance; + +/* + The cell appearance animation padding. This value is ignored unless + shouldAnimateCellsOnAppearance is set to YES. Its value is set at initialization time by the + constant MDCCollectionViewAnimatedAppearancePadding. + */ +@property(nonatomic, readonly, assign) CGFloat animateCellsOnAppearancePadding; + +/* + The cell appearance animation duration. This value is ignored unless + shouldAnimateCellsOnAppearance is set to YES. Its value is set at initialization time by the + constant MDCCollectionViewAnimatedAppearanceDuration. + */ +@property(nonatomic, readonly, assign) NSTimeInterval animateCellsOnAppearanceDuration; + +/** + This method should only be called from MDCCollectionViewFlowLayout class once all visible + collection view attributes are available. By calling -updateLayoutAnimated within this method's + animation block, it will trigger the collection view to query both the item and supplementary + view sizing methods of the UICollectionViewDelegateFlowLayout protocol, resulting in an + animated resizing of the cells by the height specified in animateCellsOnAppearancePadding. + */ +- (void)beginCellAppearanceAnimation; + +@end diff --git a/components/Collections/src/MDCCollectionViewStylingDelegate.h b/components/Collections/src/MDCCollectionViewStylingDelegate.h new file mode 100644 index 00000000000..b013b77baef --- /dev/null +++ b/components/Collections/src/MDCCollectionViewStylingDelegate.h @@ -0,0 +1,139 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +#import "MDCCollectionViewStyling.h" + +@class MDCInkTouchController; +@class MDCInkView; + +/** A delegate protocol which allows setting collection view cell styles. */ +@protocol MDCCollectionViewStylingDelegate +@optional + +#pragma mark - Styling + +/** + Asks the delegate for the cell height at the specified collection view index path. The style + controller will make padding/insets adjustments to this value as needed depending on the cell + style and editing mode. + + @param collectionView The collection view. + @param indexPath The item's index path. + @return The cell height at the specified index path. + */ +- (CGFloat)collectionView:(nonnull UICollectionView *)collectionView + cellHeightAtIndexPath:(nonnull NSIndexPath *)indexPath; + +/** + Asks the delegate for the cell style at the specified collection view section index. All + remaining sections to have their cells styled per the style managers |cellStyle| property. + + @param collectionView The collection view. + @param section The collection view section. + @return The cell style to be used at the specified index. + */ +- (MDCCollectionViewCellStyle)collectionView:(nonnull UICollectionView *)collectionView + cellStyleForSection:(NSInteger)section; + +/** + Asks the delegate for the cell background color at the specified collection view index path. + + @param collectionView The collection view. + @param indexPath The item's index path. + @return The cell background color at the specified index path. + */ +- (nullable UIColor *)collectionView:(nonnull UICollectionView *)collectionView + cellBackgroundColorAtIndexPath:(nonnull NSIndexPath *)indexPath; + +/** + Asks the delegate whether the specified item should hide its background image/shadowing. + + @param collectionView The collection view. + @param indexPath The item's index path. + @return if the item background should be hidden at the specified index path. + */ +- (BOOL)collectionView:(nonnull UICollectionView *)collectionView + shouldHideItemBackgroundAtIndexPath:(nonnull NSIndexPath *)indexPath; + +/** + Asks the delegate whether the specified header should hide its background image/shadowing. + + @param collectionView The collection view. + @param section The collection view section. + @return if the header background should be hidden at the specified section. + */ +- (BOOL)collectionView:(nonnull UICollectionView *)collectionView + shouldHideHeaderBackgroundForSection:(NSInteger)section; + +/** + Asks the delegate whether the specified footer should hide its background image/shadowing. + + @param collectionView The collection view. + @param section The collection view section. + @return if the footer background should be hidden at the specified section. + */ +- (BOOL)collectionView:(nonnull UICollectionView *)collectionView + shouldHideFooterBackgroundForSection:(NSInteger)section; + +#pragma mark - Item inlaying + +/** + Allows the receiver to receive notification that items at the specified index paths did + inlay their frame. + + @param collectionView The collection view. + @param indexPaths An array of index paths. + */ +- (void)collectionView:(nonnull UICollectionView *)collectionView + didApplyInlayToItemAtIndexPaths:(nonnull NSArray *)indexPaths; + +/** + Allows the receiver to receive notification that items at the specified index paths did + remove the inlay of their frames. + + @param collectionView The collection view. + @param indexPaths An array of index paths. + */ +- (void)collectionView:(nonnull UICollectionView *)collectionView + didRemoveInlayFromItemAtIndexPaths:(nonnull NSArray *)indexPaths; + +#pragma mark - Ink Touches + +/** + Asks the delegate whether the inkVIew for a specified index path will is hidden. + + @param collectionView The collection view. + @param indexPath The collection view index path. + @return if the ink view should be hidden at the specified index path. + */ +- (BOOL)collectionView:(nonnull UICollectionView *)collectionView + hidesInkViewAtIndexPath:(nonnull NSIndexPath *)indexPath; + +/** + Allows the receiver to set the ink view at the specified collection view index path. + + @param collectionView The collection view. + @param inkTouchController The ink controller of the ink view. + @param indexPath The collection view index path. + @return The inkView to be used at the specified index path. + */ +- (nonnull MDCInkView *)collectionView:(nonnull UICollectionView *)collectionView + inkTouchController:(nonnull MDCInkTouchController *)inkTouchController + inkViewAtIndexPath:(nonnull NSIndexPath *)indexPath; + +@end diff --git a/components/Collections/src/MaterialCollections.bundle/Resources/en.lproj/MaterialCollections.strings b/components/Collections/src/MaterialCollections.bundle/Resources/en.lproj/MaterialCollections.strings new file mode 100644 index 00000000000..4e9cd34d774 --- /dev/null +++ b/components/Collections/src/MaterialCollections.bundle/Resources/en.lproj/MaterialCollections.strings @@ -0,0 +1,4 @@ +/* Label for a button indicating a collectionView row can be deleted. (40 chars.) */ +"DeleteButton" = "Delete"; +/* Label for info bar indicating available editing gestures for collectionView. (40 chars.) */ +"InfoBarGestureHint" = "Swipe an item to delete"; diff --git a/components/Collections/src/MaterialCollections.h b/components/Collections/src/MaterialCollections.h new file mode 100644 index 00000000000..a1455611957 --- /dev/null +++ b/components/Collections/src/MaterialCollections.h @@ -0,0 +1,23 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import "MDCCollectionViewController.h" +#import "MDCCollectionViewEditing.h" +#import "MDCCollectionViewEditingDelegate.h" +#import "MDCCollectionViewFlowLayout.h" +#import "MDCCollectionViewStyling.h" +#import "MDCCollectionViewStylingDelegate.h" +#import "MaterialCollectionCells.h" diff --git a/components/Collections/src/private/MDCCollectionGridBackgroundView.h b/components/Collections/src/private/MDCCollectionGridBackgroundView.h new file mode 100644 index 00000000000..e4499df244f --- /dev/null +++ b/components/Collections/src/private/MDCCollectionGridBackgroundView.h @@ -0,0 +1,29 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +/** + The MDCCollectionGridBackgroundView class provides an implementation of UICollectionReusableView + that displays a background view under cells at each section of a collection view in grid layout. + + This will only happen for a grid layout when it is either A) grouped-style or B) card-style with + zero padding. When this happens, the background for the section cells will not be drawn, and + instead this view will extend to the bounds of the sum of its respective section item frames. + */ +@interface MDCCollectionGridBackgroundView : UICollectionReusableView + +@end diff --git a/components/Collections/src/private/MDCCollectionGridBackgroundView.m b/components/Collections/src/private/MDCCollectionGridBackgroundView.m new file mode 100644 index 00000000000..12e3ec3fa46 --- /dev/null +++ b/components/Collections/src/private/MDCCollectionGridBackgroundView.m @@ -0,0 +1,60 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "MDCCollectionGridBackgroundView.h" + +#import "MaterialCollectionLayoutAttributes.h" + +@implementation MDCCollectionGridBackgroundView { + UIImageView *_backgroundImageView; +} + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self commonMDCCollectionGridBackgroundViewInit]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super initWithCoder:aDecoder]; + if (self) { + [self commonMDCCollectionGridBackgroundViewInit]; + } + return self; +} + +- (void)commonMDCCollectionGridBackgroundViewInit { + _backgroundImageView = [[UIImageView alloc] initWithFrame:self.bounds]; + _backgroundImageView.autoresizingMask = + UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + [self addSubview:_backgroundImageView]; +} + +- (void)applyLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes { + [super applyLayoutAttributes:layoutAttributes]; + NSAssert([layoutAttributes isKindOfClass:[MDCCollectionViewLayoutAttributes class]], + @"LayoutAttributes must be a subclass of MDCCollectionViewLayoutAttributes."); + MDCCollectionViewLayoutAttributes *attr = (MDCCollectionViewLayoutAttributes *)layoutAttributes; + _backgroundImageView.image = attr.backgroundImage; +} + +@end diff --git a/components/Collections/src/private/MDCCollectionInfoBarView.h b/components/Collections/src/private/MDCCollectionInfoBarView.h new file mode 100644 index 00000000000..ef71d5de345 --- /dev/null +++ b/components/Collections/src/private/MDCCollectionInfoBarView.h @@ -0,0 +1,184 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +@class MDCCollectionInfoBarView; + +/** The info bar supplementary view kind when shown in header. */ +extern NSString *__nonnull const MDCCollectionInfoBarKindHeader; + +/** The info bar supplementary view kind when shown in footer. */ +extern NSString *__nonnull const MDCCollectionInfoBarKindFooter; + +/** The animation duration for the info bar. */ +extern const CGFloat MDCCollectionInfoBarAnimationDuration; + +/** Height for the info bar when shown in header. */ +extern const CGFloat MDCCollectionInfoBarHeaderHeight; + +/** Height for the info bar when shown in footer. */ +extern const CGFloat MDCCollectionInfoBarFooterHeight; + +/** The available styles that the info bar can be shown within a collectionView. */ +typedef NS_ENUM(NSUInteger, MDCCollectionInfoBarViewStyle) { + MDCCollectionInfoBarViewStyleActionable, + MDCCollectionInfoBarViewStyleHUD +}; + +/** Delegate protocol for the MDCCollectionInfoBarView. */ +@protocol MDCCollectionInfoBarViewDelegate +@required +/** + Allows the receiver to update the info bar after it has been created. + + @param infoBar The MDCCollectionInfoBarView info bar. + */ +- (void)updateControllerWithInfoBar:(nonnull MDCCollectionInfoBarView *)infoBar; + +@optional +/** + Allows the receiver to get notifed when a tap gesture has been performed on the info bar. + + @param infoBar The MDCCollectionInfoBarView info bar. + */ +- (void)didTapInfoBar:(nonnull MDCCollectionInfoBarView *)infoBar; + +/** + Allows the receiver to get notifed when an info bar will show. + + @param infoBar The MDCCollectionInfoBarView info bar. + @param animated YES the transition will be animated; otherwise, NO. + @param willAutoDismiss YES the info bar will be auto-dismissed after the time interval + set in |autoDismissAfterDuration|. + */ +- (void)infoBar:(nonnull MDCCollectionInfoBarView *)infoBar + willShowAnimated:(BOOL)animated + willAutoDismiss:(BOOL)willAutoDismiss; + +/** + Allows the receiver to get notifed after an info bar did show. + + @param infoBar The MDCCollectionInfoBarView info bar. + @param animated YES the transition was animated; otherwise, NO. + @param willAutoDismiss YES the info bar will be auto-dismissed after the time interval + set in |autoDismissAfterDuration|. + */ +- (void)infoBar:(nonnull MDCCollectionInfoBarView *)infoBar + didShowAnimated:(BOOL)animated + willAutoDismiss:(BOOL)willAutoDismiss; + +/** + Allows the receiver to get notifed when an info bar will dismiss. + + @param infoBar The MDCCollectionInfoBarView info bar. + @param animated YES the transition will be animated; otherwise, NO. + @param willAutoDismiss YES the info bar will be auto-dismissed after the time interval + set in |autoDismissAfterDuration|. + */ +- (void)infoBar:(nonnull MDCCollectionInfoBarView *)infoBar + willDismissAnimated:(BOOL)animated + willAutoDismiss:(BOOL)willAutoDismiss; + +/** + Allows the receiver to get notifed after an info bar has been dismissed. + + @param infoBar The MDCCollectionInfoBarView info bar. + @param animated YES the transition was animated; otherwise, NO. + @param didAutoDismiss YES the info bar was auto-dismissed. + */ +- (void)infoBar:(nonnull MDCCollectionInfoBarView *)infoBar + didDismissAnimated:(BOOL)animated + didAutoDismiss:(BOOL)didAutoDismiss; + +@end + +/** + The MDCCollectionInfoBarView class provides an implementation of + UICollectionReusableView that displays an animated view in either the header + or footer. + */ +@interface MDCCollectionInfoBarView : UICollectionReusableView + +/** A delegate through which the MDCCollectionInfoBarView may inform of updates. */ +@property(nonatomic, weak, nullable) id delegate; + +/** The background view containing the message label. This view is animatable on show/dismiss. */ +@property(nonatomic, readonly, strong, nullable) UIView *backgroundView; + +/** Whether the background view should be shown with a shadow. */ +@property(nonatomic, assign) BOOL shouldApplyBackgroundViewShadow; + +/** + The color assigned to the background view. + + Defaults to #448AFF for header, and white for footer. + */ +@property(nonatomic, strong, null_resettable) UIColor *tintColor; + +/** + The color assigned to the info bar message label. + + Defaults to white for header, and #F44336 for footer. + */ +@property(nonatomic, strong, nullable) UIColor *titleColor; + +/** The info bar message label. */ +@property(nonatomic, readonly, strong, nullable) UILabel *titleLabel; + +/** The info bar message text. */ +@property(nonatomic, strong, nullable) NSString *message; + +/** The horizontal position of bar message. */ +@property(nonatomic, assign) NSTextAlignment textAlignment; + +/** Whether the background view can receive a tap gesture. */ +@property(nonatomic, assign) BOOL allowsTap; + +/** The optional style that the info bar can be shown. */ +@property(nonatomic, assign) MDCCollectionInfoBarViewStyle style; + +/** + The info bar supplementary view kind as set by the collectionView model + |collectionView:viewForSupplementaryElementOfKind:atIndexPath| method. Possible values + are MDCCollectionInfoBarKindHeader or MDCCollectionInfoBarKindFooter. + */ +@property(nonatomic, strong, nonnull) NSString *kind; + +/** + The desired duration after which the info bar will be automatically dismissed. If set to + 0, autoDismiss will be ignored. + */ +@property(nonatomic, assign) NSTimeInterval autoDismissAfterDuration; + +/** Whether the info bar is currently being shown. */ +@property(nonatomic, readonly, assign) BOOL isVisible; + +/** + Shows the info bar with/without animation. + + @param animated YES the transition will be animated; otherwise, NO. + */ +- (void)showAnimated:(BOOL)animated; + +/** + Dismisses the info bar with/without animation. + + @param animated YES the transition will be animated; otherwise, NO. + */ +- (void)dismissAnimated:(BOOL)animated; + +@end diff --git a/components/Collections/src/private/MDCCollectionInfoBarView.m b/components/Collections/src/private/MDCCollectionInfoBarView.m new file mode 100644 index 00000000000..962210fd389 --- /dev/null +++ b/components/Collections/src/private/MDCCollectionInfoBarView.m @@ -0,0 +1,247 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "MDCCollectionInfoBarView.h" + +#import "MaterialShadowLayer.h" +#import "MaterialTypography.h" + +NSString *const MDCCollectionInfoBarKindHeader = @"MDCCollectionInfoBarKindHeader"; +NSString *const MDCCollectionInfoBarKindFooter = @"MDCCollectionInfoBarKindFooter"; + +const CGFloat MDCCollectionInfoBarAnimationDuration = 0.3f; +const CGFloat MDCCollectionInfoBarHeaderHeight = 48.0f; +const CGFloat MDCCollectionInfoBarFooterHeight = 48.0f; + +static const CGFloat MDCCollectionInfoBarLabelHorizontalPadding = 16.0f; + +// Colors derived from http://www.google.com/design/spec/style/color.html#color-color-palette . +static const uint32_t kCollectionInfoBarBlueColor = 0x448AFF; // Blue A200 +static const uint32_t kCollectionInfoBarRedColor = 0xF44336; // Red 500 + +// Creates a UIColor from a 24-bit RGB color encoded as an integer. +static inline UIColor *ColorFromRGB(uint32_t rgbValue) { + return [UIColor colorWithRed:((CGFloat)((rgbValue & 0xFF0000) >> 16)) / 255 + green:((CGFloat)((rgbValue & 0x00FF00) >> 8)) / 255 + blue:((CGFloat)((rgbValue & 0x0000FF) >> 0)) / 255 + alpha:1]; +} + +@interface ShadowedView : UIView +@end + +@implementation ShadowedView ++ (Class)layerClass { + return [MDCShadowLayer class]; +} +@end + +@implementation MDCCollectionInfoBarView { + CGFloat _backgroundTransformY; + UITapGestureRecognizer *_tapGesture; +} + +- (instancetype)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + [self commonMDCCollectionInfoBarViewInit]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super initWithCoder:aDecoder]; + if (self) { + [self commonMDCCollectionInfoBarViewInit]; + } + return self; +} + +- (void)commonMDCCollectionInfoBarViewInit { + self.backgroundColor = [UIColor clearColor]; + self.userInteractionEnabled = NO; + _backgroundView = [[ShadowedView alloc] initWithFrame:self.bounds]; + _backgroundView.autoresizingMask = UIViewAutoresizingFlexibleWidth; + _backgroundView.hidden = YES; + + _titleLabel = + [[UILabel alloc] initWithFrame:CGRectInset(self.bounds, + MDCCollectionInfoBarLabelHorizontalPadding, + 0)]; + _titleLabel.backgroundColor = [UIColor clearColor]; + _titleLabel.textAlignment = NSTextAlignmentCenter; + _titleLabel.font = [MDCTypography body1Font]; + _titleLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth; + [_backgroundView addSubview:_titleLabel]; + + [self addSubview:_backgroundView]; + + _tapGesture = + [[UITapGestureRecognizer alloc] initWithTarget:self + action:@selector(handleTapGesture:)]; + [_backgroundView addGestureRecognizer:_tapGesture]; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + if (_shouldApplyBackgroundViewShadow) { + [self setShouldApplyBackgroundViewShadow:_shouldApplyBackgroundViewShadow]; + } +} + +- (void)setTintColor:(UIColor *)tintColor { + _tintColor = tintColor; + _backgroundView.backgroundColor = _tintColor; +} + +- (void)setMessage:(NSString *)message { + _message = message; + _titleLabel.text = message; +} + +- (void)setKind:(NSString *)kind { + _kind = kind; + if ([kind isEqualToString:MDCCollectionInfoBarKindHeader]) { + _backgroundTransformY = -MDCCollectionInfoBarHeaderHeight; + } else { + _backgroundTransformY = MDCCollectionInfoBarFooterHeight; + } + _backgroundView.transform = CGAffineTransformMakeTranslation(0, _backgroundTransformY); +} + +- (void)setShouldApplyBackgroundViewShadow:(BOOL)shouldApplyBackgroundViewShadow { + _shouldApplyBackgroundViewShadow = shouldApplyBackgroundViewShadow; + MDCShadowLayer *shadowLayer = (MDCShadowLayer *)_backgroundView.layer; + shadowLayer.elevation = shouldApplyBackgroundViewShadow ? 1 : 0; +} + +- (void)setTextAlignment:(NSTextAlignment)textAlignment { + _textAlignment = textAlignment; + _titleLabel.textAlignment = textAlignment; +} + +- (void)setStyle:(MDCCollectionInfoBarViewStyle)style { + _style = style; + if (style == MDCCollectionInfoBarViewStyleHUD) { + self.allowsTap = NO; + self.shouldApplyBackgroundViewShadow = NO; + self.textAlignment = NSTextAlignmentLeft; + self.tintColor = ColorFromRGB(kCollectionInfoBarBlueColor); + self.titleLabel.textColor = [UIColor whiteColor]; + self.autoDismissAfterDuration = 1.0f; + self.backgroundView.alpha = 0.9f; + } else if (style == MDCCollectionInfoBarViewStyleActionable) { + self.allowsTap = YES; + self.shouldApplyBackgroundViewShadow = YES; + self.textAlignment = NSTextAlignmentCenter; + self.tintColor = [UIColor whiteColor]; + self.titleLabel.textColor = ColorFromRGB(kCollectionInfoBarRedColor); + self.autoDismissAfterDuration = 0.0f; + self.backgroundView.alpha = 1.0f; + self.isAccessibilityElement = YES; + self.accessibilityTraits = UIAccessibilityTraitButton; + self.accessibilityLabel = self.message; + } +} + +- (BOOL)isVisible { + return !_backgroundView.hidden; +} + +- (void)showAnimated:(BOOL)animated { + _backgroundView.hidden = NO; + // Notify delegate. + if ([_delegate respondsToSelector:@selector(infoBar:willShowAnimated:willAutoDismiss:)]) { + [_delegate infoBar:self willShowAnimated:animated willAutoDismiss:[self shouldAutoDismiss]]; + } + + NSTimeInterval duration = (animated) ? MDCCollectionInfoBarAnimationDuration : 0.0f; + [UIView animateWithDuration:duration + delay:0 + options:UIViewAnimationOptionCurveEaseOut + animations:^{ + _backgroundView.transform = CGAffineTransformIdentity; + } + completion:^(BOOL finished) { + self.userInteractionEnabled = _allowsTap; + + // Notify delegate. + if ([_delegate respondsToSelector: + @selector(infoBar:didShowAnimated:willAutoDismiss:)]) { + [_delegate infoBar:self + didShowAnimated:animated + willAutoDismiss:[self shouldAutoDismiss]]; + } + + [self autoDismissIfNecessaryWithAnimation:animated]; + }]; +} + +- (void)dismissAnimated:(BOOL)animated { + // Notify delegate. + if ([_delegate respondsToSelector:@selector(infoBar:willDismissAnimated:willAutoDismiss:)]) { + [_delegate infoBar:self willDismissAnimated:animated willAutoDismiss:[self shouldAutoDismiss]]; + } + + NSTimeInterval duration = (animated) ? MDCCollectionInfoBarAnimationDuration : 0.0f; + [UIView animateWithDuration:duration + delay:0 + options:UIViewAnimationOptionCurveEaseIn + animations:^{ + _backgroundView.transform = + CGAffineTransformMakeTranslation(0, _backgroundTransformY); + } + completion:^(BOOL finished) { + self.userInteractionEnabled = NO; + _backgroundView.hidden = YES; + + // Notify delegate. + if ([_delegate respondsToSelector: + @selector(infoBar:didDismissAnimated:didAutoDismiss:)]) { + [_delegate infoBar:self + didDismissAnimated:animated + didAutoDismiss:[self shouldAutoDismiss]]; + } + }]; +} + +#pragma mark - Private + +- (void)handleTapGesture:(UITapGestureRecognizer *)recognizer { + if ([_delegate respondsToSelector:@selector(didTapInfoBar:)]) { + [_delegate didTapInfoBar:self]; + } +} + +- (BOOL)shouldAutoDismiss { + return (_autoDismissAfterDuration > 0); +} + +- (void)autoDismissIfNecessaryWithAnimation:(BOOL)animation { + if ([self shouldAutoDismiss]) { + dispatch_time_t popTime = + dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_autoDismissAfterDuration * NSEC_PER_SEC)); + dispatch_after(popTime, dispatch_get_main_queue(), ^{ + [self dismissAnimated:animation]; + }); + } +} + +@end diff --git a/components/Collections/src/private/MDCCollectionStringResources.h b/components/Collections/src/private/MDCCollectionStringResources.h new file mode 100644 index 00000000000..8abde92d310 --- /dev/null +++ b/components/Collections/src/private/MDCCollectionStringResources.h @@ -0,0 +1,36 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +/** + Shorthand for returning a resource from MDCCollectionStringResources's singleton. + */ +#define MDCCollectionStringResources(sel) [[MDCCollectionStringResources sharedInstance] sel] + +/** String resources that are used for collection views. */ +@interface MDCCollectionStringResources : NSObject + +/** Returns the shared resources singleton instance. */ ++ (nonnull instancetype)sharedInstance; + +/** Returns cell delete string. */ +- (nonnull NSString *)deleteButtonString; + +/** Returns cell gesture hint string. */ +- (nonnull NSString *)infoBarGestureHintString; + +@end diff --git a/components/Collections/src/private/MDCCollectionStringResources.m b/components/Collections/src/private/MDCCollectionStringResources.m new file mode 100644 index 00000000000..bc9c4068a45 --- /dev/null +++ b/components/Collections/src/private/MDCCollectionStringResources.m @@ -0,0 +1,76 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "MDCCollectionStringResources.h" + +#import "MaterialCollectionsStrings.h" +#import "MaterialCollectionsStrings_table.h" + +// The Bundle for string resources. +static NSString *const kBundleName = @"MaterialCollections.bundle"; + +@implementation MDCCollectionStringResources + ++ (instancetype)sharedInstance { + static MDCCollectionStringResources *sharedInstance; + @synchronized(self) { + if (sharedInstance == nil) { + sharedInstance = [[MDCCollectionStringResources alloc] init]; + } + } + return sharedInstance; +} + +- (NSString *)stringForId:(MaterialCollectionsStringId)stringID { + NSString *stringKey = kMaterialCollectionsStringTable[stringID]; + NSBundle *bundle = [[self class] bundle]; + NSString *tableName = [kBundleName stringByDeletingPathExtension]; + return [bundle localizedStringForKey:stringKey value:nil table:tableName]; +} + +- (NSString *)deleteButtonString { + return [self stringForId:kStr_MaterialCollectionsDeleteButton]; +} + +- (NSString *)infoBarGestureHintString { + return [self stringForId:kStr_MaterialCollectionsInfoBarGestureHint]; +} + +#pragma mark - Resource bundle + ++ (NSBundle *)bundle { + static NSBundle *bundle = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + bundle = [NSBundle bundleWithPath:[self bundlePathWithName:kBundleName]]; + }); + + return bundle; +} + ++ (NSString *)bundlePathWithName:(NSString *)bundleName { + // In iOS 8+, we could be included by way of a dynamic framework, and our resource bundles may + // not be in the main .app bundle, but rather in a nested framework, so figure out where we live + // and use that as the search location. + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSString *resourcePath = [(nil == bundle ? [NSBundle mainBundle] : bundle)resourcePath]; + return [resourcePath stringByAppendingPathComponent:bundleName]; +} +@end diff --git a/components/Collections/src/private/MDCCollectionViewEditor.h b/components/Collections/src/private/MDCCollectionViewEditor.h new file mode 100644 index 00000000000..99d6e430f7d --- /dev/null +++ b/components/Collections/src/private/MDCCollectionViewEditor.h @@ -0,0 +1,34 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import "MDCCollectionViewEditing.h" + +/** + The MDCCollectionViewEditingManager class provides an implementation for a UICollectionView to + set its editing properties. + */ +@interface MDCCollectionViewEditor : NSObject + +/** + Initialize the controller with a collection view. + + Designated initializer. + + @param collectionView The controller's collection view. + */ +- (instancetype)initWithCollectionView:(UICollectionView *)collectionView NS_DESIGNATED_INITIALIZER; + +@end diff --git a/components/Collections/src/private/MDCCollectionViewEditor.m b/components/Collections/src/private/MDCCollectionViewEditor.m new file mode 100644 index 00000000000..05ac9528257 --- /dev/null +++ b/components/Collections/src/private/MDCCollectionViewEditor.m @@ -0,0 +1,741 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "MDCCollectionViewEditor.h" + +#import "MDCCollectionViewEditingDelegate.h" +#import "MaterialShadowLayer.h" + +#import + +// Distance from center before we start fading the item. +static const CGFloat kMDCDismissalDistanceBeforeFading = 50.0f; + +// Minimum alpha for an item being dismissed. +static const CGFloat kMDCDismissalMinimumAlpha = 0.5f; + +// Final angle of the arc the item travels through during dismissal. +static const CGFloat kMDCDismissalArcAngle = (CGFloat)(M_PI / 6.0f); + +// The centroid for the item dismissal arc. +static const CGFloat kMDCDismissalArcYOffset = 400.0f; + +// Simple linear friction applied to swipe velocity. +static const CGFloat kMDCDismissalSwipeFriction = 0.05f; + +// Animation duration for dismissal / restore. +static const NSTimeInterval kMDCDismissalAnimationDuration = 0.3; +static const NSTimeInterval kMDCRestoreAnimationDuration = 0.2; + +/** A view that uses an MDCShadowLayer as its sublayer. */ +@interface ShadowedSnapshotView : UIView +@end + +@implementation ShadowedSnapshotView ++ (Class)layerClass { + return [MDCShadowLayer class]; +} +@end + +@interface MDCCollectionViewEditor () +@end + +@implementation MDCCollectionViewEditor { + UILongPressGestureRecognizer *_longPressGestureRecognizer; + UIPanGestureRecognizer *_panGestureRecognizer; + CGPoint _selectedCellLocation; + CGPoint _initialCellLocation; + ShadowedSnapshotView *_cellSnapshot; +} + +@synthesize collectionView = _collectionView; +@synthesize delegate = _delegate; +@synthesize reorderingCellIndexPath = _reorderingCellIndexPath; +@synthesize dismissingCellIndexPath = _dismissingCellIndexPath; +@synthesize dismissingSection = _dismissingSection; +@synthesize editing = _editing; + +#pragma mark - Public + +- (instancetype)init { + [self doesNotRecognizeSelector:_cmd]; + return [self initWithCollectionView:nil]; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + [self doesNotRecognizeSelector:_cmd]; + return [self initWithCollectionView:nil]; +} + +- (instancetype)initWithCollectionView:(UICollectionView *)collectionView { + self = [super init]; + if (self) { + _collectionView = collectionView; + _dismissingSection = NSNotFound; + + // Setup gestures to handle collectionView editing. + + SEL longPressSelector = @selector(handleLongPressGesture:); + _longPressGestureRecognizer = + [[UILongPressGestureRecognizer alloc] initWithTarget:self + action:longPressSelector]; + _longPressGestureRecognizer.delegate = self; + [_collectionView addGestureRecognizer:_longPressGestureRecognizer]; + + SEL panSelector = @selector(handlePanGesture:); + _panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:panSelector]; + _panGestureRecognizer.delegate = self; + [_collectionView addGestureRecognizer:_panGestureRecognizer]; + } + return self; +} + +- (void)dealloc { + _longPressGestureRecognizer.delegate = nil; + _panGestureRecognizer.delegate = nil; + + // Remove gesture recognizers to prevent duplicates when re-initializing this controller. + [_collectionView removeGestureRecognizer:_longPressGestureRecognizer]; + [_collectionView removeGestureRecognizer:_panGestureRecognizer]; +} + +- (void)setEditing:(BOOL)editing { + [self setEditing:editing animated:NO]; +} + +- (void)setEditing:(BOOL)editing animated:(BOOL)animated { + _editing = editing; + _collectionView.allowsMultipleSelection = editing; + + // Clear any selected indexPaths. + for (NSIndexPath *indexPath in [_collectionView indexPathsForSelectedItems]) { + [_collectionView deselectItemAtIndexPath:indexPath animated:NO]; + } + + [CATransaction begin]; + if (editing) { + // Notify delegate will begin editing. + if ([_delegate respondsToSelector:@selector(collectionViewWillBeginEditing:)]) { + [_delegate collectionViewWillBeginEditing:_collectionView]; + } + + [CATransaction setCompletionBlock:^{ + // Notify delegate did begin editing. + if ([_delegate respondsToSelector:@selector(collectionViewDidBeginEditing:)]) { + [_delegate collectionViewDidBeginEditing:_collectionView]; + } + }]; + + } else { + // Notify delegate will end editing. + if ([_delegate respondsToSelector:@selector(collectionViewWillEndEditing:)]) { + [_delegate collectionViewWillEndEditing:_collectionView]; + } + + [CATransaction setCompletionBlock:^{ + // Notify delegate did end editing. + if ([_delegate respondsToSelector:@selector(collectionViewDidEndEditing:)]) { + [_delegate collectionViewDidEndEditing:_collectionView]; + } + }]; + } + [CATransaction commit]; +} + +#pragma mark - Private + +- (NSArray *)attributesAtSection:(NSInteger)section { + UICollectionViewLayout *layout = _collectionView.collectionViewLayout; + NSIndexPath *indexPath; + NSMutableArray *sectionAttributes = [NSMutableArray array]; + + // Get all item attributes at section. + NSInteger numberOfItemsInSection = [_collectionView numberOfItemsInSection:section]; + + for (NSInteger i = 0; i < numberOfItemsInSection; ++i) { + indexPath = [NSIndexPath indexPathForItem:i inSection:section]; + UICollectionViewLayoutAttributes *attribute = + [layout layoutAttributesForItemAtIndexPath:indexPath]; + [sectionAttributes addObject:attribute]; + } + + // Headers/footers require section but ignore item of index path, so set to zero. + indexPath = [NSIndexPath indexPathForItem:0 inSection:section]; + + // Get header attributes at section. + UICollectionViewLayoutAttributes *headerAttribute = + [layout layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionHeader + atIndexPath:indexPath]; + [sectionAttributes addObject:headerAttribute]; + + // Get footer attributes at section. + UICollectionViewLayoutAttributes *footerAttribute = + [layout layoutAttributesForSupplementaryViewOfKind:UICollectionElementKindSectionFooter + atIndexPath:indexPath]; + [sectionAttributes addObject:footerAttribute]; + + return sectionAttributes; +} + +- (NSInteger)swipedSectionAtLocation:(CGPoint)location { + // Returns section index being swiped for dismissal, or NSNotFound for an invalid swipes + // where location does not return an item or section header/footer. + CGRect contentFrame = _collectionView.frame; + contentFrame.size = _collectionView.contentSize; + NSArray *visibleAttributes = + [_collectionView.collectionViewLayout layoutAttributesForElementsInRect:contentFrame]; + for (UICollectionViewLayoutAttributes *attribute in visibleAttributes) { + if (!CGRectIsNull(attribute.frame) && CGRectContainsPoint(attribute.frame, location)) { + return attribute.indexPath.section; + } + } + return NSNotFound; +} + +#pragma mark - Snapshotting + +- (UIView *)snapshotWithIndexPath:(NSIndexPath *)indexPath { + // Here we will take a snapshot of the collectionView item. + if (_cellSnapshot) { + [_cellSnapshot removeFromSuperview]; + _cellSnapshot = nil; + } + + // Create snapshot. + UICollectionViewLayoutAttributes *attributes = + [_collectionView.collectionViewLayout layoutAttributesForItemAtIndexPath:indexPath]; + _cellSnapshot = [[ShadowedSnapshotView alloc] initWithFrame:attributes.frame]; + UICollectionViewCell *cell = [_collectionView cellForItemAtIndexPath:indexPath]; + [_cellSnapshot addSubview:[cell snapshotViewAfterScreenUpdates:NO]]; + + // Invalidate layout here to force attributes to now be hidden. + [_collectionView.collectionViewLayout invalidateLayout]; + return _cellSnapshot; +} + +- (UIView *)snapshotWithSection:(NSInteger)section { + // Here we will take a snapshot of the collectionView section items, header, and footer. + if (_cellSnapshot) { + [_cellSnapshot removeFromSuperview]; + _cellSnapshot = nil; + } + + // The snapshot frame encompasses all of the section items, header, and footer attribute frames. + NSArray *sectionAttributes = [self attributesAtSection:section]; + CGRect snapshotFrame = CGRectNull; + for (UICollectionViewLayoutAttributes *attribute in sectionAttributes) { + if (!CGRectIsNull(attribute.frame)) { + snapshotFrame = CGRectUnion(snapshotFrame, attribute.frame); + } + } + + // Create snapshot. + _cellSnapshot = [[ShadowedSnapshotView alloc] initWithFrame:snapshotFrame]; + UIImageView *snapshotView = + [[UIImageView alloc] initWithImage:[self snapshotWithRect:snapshotFrame]]; + [_cellSnapshot addSubview:snapshotView]; + + // Invalidate layout here to force attributes to now be hidden. + [_collectionView.collectionViewLayout invalidateLayout]; + return _cellSnapshot; +} + +- (UIImage *)snapshotWithRect:(CGRect)rect { + // Here we will take a snapshot of a rect within the collectionView. + UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0.0); + CGContextRef cx = UIGraphicsGetCurrentContext(); + CGContextTranslateCTM(cx, -rect.origin.x, -rect.origin.y); + + // Save original collection view properties. + id savedDelegate = _collectionView.delegate; + _collectionView.delegate = nil; + CGPoint savedContentOffset = _collectionView.contentOffset; + BOOL savedClipsToBounds = _collectionView.clipsToBounds; + _collectionView.clipsToBounds = NO; + + // Hide any scroll indicators. + BOOL showsHorizontalScrollIndicator = _collectionView.showsHorizontalScrollIndicator; + BOOL showsVerticalScrollIndicator = _collectionView.showsVerticalScrollIndicator; + _collectionView.showsHorizontalScrollIndicator = NO; + _collectionView.showsVerticalScrollIndicator = NO; + + // Render snapshot. + [_collectionView.layer renderInContext:cx]; + _collectionView.layer.rasterizationScale = [UIScreen mainScreen].scale; + _collectionView.layer.shouldRasterize = YES; + UIImage *screenshotImage = UIGraphicsGetImageFromCurrentImageContext(); + + // Reset collection view. + _collectionView.contentOffset = savedContentOffset; + _collectionView.showsHorizontalScrollIndicator = showsHorizontalScrollIndicator; + _collectionView.showsVerticalScrollIndicator = showsVerticalScrollIndicator; + _collectionView.delegate = savedDelegate; + _collectionView.clipsToBounds = savedClipsToBounds; + _collectionView.layer.shouldRasterize = NO; + + UIGraphicsEndImageContext(); + return screenshotImage; +} + +- (void)applyLayerShadowing:(CALayer *)layer { + MDCShadowLayer *shadowLayer = (MDCShadowLayer *)_cellSnapshot.layer; + shadowLayer.shadowMaskEnabled = NO; + shadowLayer.elevation = 3; +} + +#pragma mark - UIGestureRecognizerDelegate + +- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer { + if ([gestureRecognizer isEqual:_longPressGestureRecognizer]) { + // Only allow longpress if collectionView is editing. + return self.isEditing; + + } else if ([gestureRecognizer isEqual:_panGestureRecognizer]) { + // Only allow panning if collectionView is editing or dismissing item/section. + BOOL allowsSwipeToDismissItem = NO; + if ([_delegate respondsToSelector:@selector(collectionViewAllowsSwipeToDismissItem:)]) { + allowsSwipeToDismissItem = [_delegate collectionViewAllowsSwipeToDismissItem:_collectionView]; + } + + BOOL allowsSwipeToDismissSection = NO; + if ([_delegate respondsToSelector:@selector(collectionViewAllowsSwipeToDismissSection:)]) { + allowsSwipeToDismissSection = + [_delegate collectionViewAllowsSwipeToDismissSection:_collectionView]; + } + return (self.isEditing || allowsSwipeToDismissItem || allowsSwipeToDismissSection); + } + return YES; +} + +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer + shouldRecognizeSimultaneouslyWithGestureRecognizer: + (UIGestureRecognizer *)otherGestureRecognizer { + return YES; +} + +- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer + shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { + // Prevent panning to dismiss when scrolling. + return ([gestureRecognizer isEqual:_panGestureRecognizer] && + [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]); +} + +#pragma mark - LongPress Gesture Handling + +- (void)handleLongPressGesture:(UILongPressGestureRecognizer *)recognizer { + switch (recognizer.state) { + case UIGestureRecognizerStateBegan: { + _initialCellLocation = [recognizer locationInView:_collectionView]; + _selectedCellLocation = [recognizer locationInView:_collectionView]; + _reorderingCellIndexPath = [_collectionView indexPathForItemAtPoint:_selectedCellLocation]; + + if ([_delegate respondsToSelector:@selector(collectionView:canMoveItemAtIndexPath:)] && + ![_delegate collectionView:_collectionView + canMoveItemAtIndexPath:_reorderingCellIndexPath]) { + _reorderingCellIndexPath = nil; + return; + } + + // Notify delegate dragging has began. + if ([_delegate respondsToSelector: + @selector(collectionView:willBeginDraggingItemAtIndexPath:)]) { + [_delegate collectionView:_collectionView + willBeginDraggingItemAtIndexPath:_reorderingCellIndexPath]; + } + + // Create cell snapshot with shadowing. + [_collectionView addSubview:[self snapshotWithIndexPath:_reorderingCellIndexPath]]; + [self applyLayerShadowing:_cellSnapshot.layer]; + + // Disable scrolling. + [_collectionView setScrollEnabled:NO]; + break; + } + case UIGestureRecognizerStateCancelled: + case UIGestureRecognizerStateEnded: { + NSIndexPath *currentIndexPath = _reorderingCellIndexPath; + if (currentIndexPath) { + UICollectionViewLayoutAttributes *attributes = + [_collectionView.collectionViewLayout + layoutAttributesForItemAtIndexPath:currentIndexPath]; + + void (^completionBlock)(BOOL finished) = ^(BOOL finished) { + // Notify delegate dragging has finished. + if ([_delegate respondsToSelector: + @selector(collectionView:didEndDraggingItemAtIndexPath:)]) { + [_delegate collectionView:_collectionView + didEndDraggingItemAtIndexPath:_reorderingCellIndexPath]; + } + [self restoreEditingItem]; + + // Re-enable scrolling. + [_collectionView setScrollEnabled:YES]; + }; + + [UIView animateWithDuration:kMDCDismissalAnimationDuration + delay:0 + options:UIViewAnimationOptionBeginFromCurrentState + animations:^{ + _cellSnapshot.frame = attributes.frame; + } + completion:completionBlock]; + } + break; + } + default: + break; + } +} + +#pragma mark - Pan Gesture Handling + +- (void)handlePanGesture:(UIPanGestureRecognizer *)recognizer { + if (_reorderingCellIndexPath) { + [self panToReorderWithRecognizer:recognizer]; + } else { + [self panToDismissWithRecognizer:recognizer]; + } +} + +- (void)panToReorderWithRecognizer:(UIPanGestureRecognizer *)recognizer { + if (recognizer.state == UIGestureRecognizerStateChanged) { + // Transform snapshot position when panning. + _selectedCellLocation = [recognizer locationInView:_collectionView]; + CGFloat change = _selectedCellLocation.y - _initialCellLocation.y; + CGAffineTransform transform = CGAffineTransformMakeTranslation(0, change); + _cellSnapshot.layer.transform = CATransform3DMakeAffineTransform(transform); + + // Determine moved index paths. + NSIndexPath *newIndexPath = [_collectionView indexPathForItemAtPoint:_selectedCellLocation]; + NSIndexPath *previousIndexPath = _reorderingCellIndexPath; + if ((newIndexPath == nil) || [newIndexPath isEqual:previousIndexPath]) { + return; + } + + // Check delegate for permission to move item. + if ([_delegate respondsToSelector: + @selector(collectionView:canMoveItemAtIndexPath:toIndexPath:)]) { + if ([_delegate collectionView:_collectionView + canMoveItemAtIndexPath:previousIndexPath + toIndexPath:newIndexPath]) { + _reorderingCellIndexPath = newIndexPath; + + // Notify delegate that item will move. + if ([_delegate respondsToSelector: + @selector(collectionView:willMoveItemAtIndexPath:toIndexPath:)]) { + [_delegate collectionView:_collectionView + willMoveItemAtIndexPath:previousIndexPath + toIndexPath:newIndexPath]; + + // Notify delegate item did move. + if ([_delegate respondsToSelector: + @selector(collectionView:didMoveItemAtIndexPath:toIndexPath:)]) { + [_delegate collectionView:_collectionView + didMoveItemAtIndexPath:previousIndexPath + toIndexPath:newIndexPath]; + } + } + } else { + // Exit if delegate will not allow this indexPath to move. + return; + } + } + } +} + +- (void)panToDismissWithRecognizer:(UIPanGestureRecognizer *)recognizer { + CGPoint translation = [recognizer translationInView:_collectionView]; + CGPoint velocity = [recognizer velocityInView:_collectionView]; + CGPoint location = [recognizer locationInView:_collectionView]; + + switch (recognizer.state) { + case UIGestureRecognizerStateBegan: { + if (fabs(velocity.y) > fabs(velocity.x)) { + // Exit if panning vertically. + return [self exitPanToDismissWithRecognizer:recognizer]; + } + + BOOL allowsSwipeToDismissSection = NO; + if ([_delegate respondsToSelector:@selector(collectionViewAllowsSwipeToDismissSection:)]) { + allowsSwipeToDismissSection = + [_delegate collectionViewAllowsSwipeToDismissSection:_collectionView]; + } + + BOOL allowsSwipeToDismissItem = NO; + if ([_delegate respondsToSelector:@selector(collectionViewAllowsSwipeToDismissItem:)]) { + allowsSwipeToDismissItem = + [_delegate collectionViewAllowsSwipeToDismissItem:_collectionView]; + } + + if (allowsSwipeToDismissSection && !self.isEditing) { + // Determine panned section to dismiss. + _dismissingSection = [self swipedSectionAtLocation:location]; + if (_dismissingSection == NSNotFound) { + return [self exitPanToDismissWithRecognizer:recognizer]; + } + + // Check delegate for permission to swipe section. + if ([_delegate respondsToSelector:@selector(collectionView:canSwipeToDismissSection:)]) { + if ([_delegate collectionView:_collectionView + canSwipeToDismissSection:_dismissingSection]) { + // Notify delegate. + if ([_delegate respondsToSelector: + @selector(collectionView:willBeginSwipeToDismissSection:)]) { + [_delegate collectionView:_collectionView + willBeginSwipeToDismissSection:_dismissingSection]; + } + } else { + // Cannot swipe so exit. + return [self exitPanToDismissWithRecognizer:recognizer]; + } + } + + // Create section snapshot. + [_collectionView insertSubview:[self snapshotWithSection:_dismissingSection] atIndex:0]; + break; + + } else if (allowsSwipeToDismissItem) { + // Determine panned index path to dismiss. + _dismissingCellIndexPath = [_collectionView indexPathForItemAtPoint:location]; + if (!_dismissingCellIndexPath) { + return [self exitPanToDismissWithRecognizer:recognizer]; + } + + // Check delegate for permission to swipe item. + if ([_delegate respondsToSelector: + @selector(collectionView:canSwipeToDismissItemAtIndexPath:)]) { + if ([_delegate collectionView:_collectionView + canSwipeToDismissItemAtIndexPath:_dismissingCellIndexPath]) { + // Notify delegate. + if ([_delegate respondsToSelector: + @selector(collectionView:willBeginSwipeToDismissItemAtIndexPath:)]) { + [_delegate collectionView:_collectionView + willBeginSwipeToDismissItemAtIndexPath:_dismissingCellIndexPath]; + } + } else { + // Cannot swipe so exit. + return [self exitPanToDismissWithRecognizer:recognizer]; + } + } + + // Create item snapshot. + [_collectionView insertSubview:[self snapshotWithIndexPath:_dismissingCellIndexPath] + atIndex:0]; + break; + } + } + + case UIGestureRecognizerStateChanged: { + // Update the tracked item's position and alpha. + CGAffineTransform transform; + CGFloat alpha; + // The item is fully opaque until it pans at least |kMDCDismissalDistanceBeforeFading| points. + CGFloat panDistance = (CGFloat)fabs(translation.x) - kMDCDismissalDistanceBeforeFading; + if (panDistance > 0) { + transform = [self transformItemDismissalToTranslationX:translation.x]; + alpha = [self dismissalAlphaForTranslationX:translation.x]; + } else { + // Pan the item. + transform = CGAffineTransformMakeTranslation(translation.x, 0); + alpha = 1; + } + _cellSnapshot.layer.transform = CATransform3DMakeAffineTransform(transform); + _cellSnapshot.alpha = alpha; + break; + } + + case UIGestureRecognizerStateEnded: { + // Check the final translation, including the final velocity, to determine + // if the item should be dismissed. + CGFloat momentumX = velocity.x * kMDCDismissalSwipeFriction; + CGFloat translationX = translation.x + momentumX; + + if (fabs(translationX) > [self distanceThresholdForDismissal]) { + // |translationX| is only guaranteed to be over the dismissal threshold; + // make sure the view animates all the way off the screen. + translationX = (CGFloat)copysign(MAX(fabs(translationX), + CGRectGetWidth(_collectionView.bounds)), + translationX); + [self animateFinalItemDismissalToTranslationX:translationX]; + } else { + [self restorePanningItemIfNecessaryWithMomentumX:momentumX]; + } + break; + } + default: { + [self restorePanningItemIfNecessaryWithMomentumX:0]; + break; + } + } +} + +- (void)exitPanToDismissWithRecognizer:(UIPanGestureRecognizer *)recognizer { + // To exit, disable the recognizer immediately which forces it to drop out of the current + // loop and prevent any state updates. Then re-enable to allow future panning. + recognizer.enabled = NO; + recognizer.enabled = YES; +} + +#pragma mark - Dismissal animation. + +- (CGAffineTransform)transformItemDismissalToTranslationX:(CGFloat)translationX { + // Returns a transform that can be applied to the snapshot during dismissal. The + // translation will pan along an arc facing downwards. + CGFloat panDistance = (CGFloat)fabs(translationX) - kMDCDismissalDistanceBeforeFading; + CGFloat panRatio = panDistance / CGRectGetWidth(_collectionView.bounds); + CGFloat arcAngle = (translationX > 0 ? kMDCDismissalArcAngle : -kMDCDismissalArcAngle); + + CGAffineTransform initialTranslation = + CGAffineTransformMakeTranslation(0, -kMDCDismissalArcYOffset); + CGAffineTransform rotation = CGAffineTransformMakeRotation(panRatio * arcAngle); + + // Modify the X translation to take account of the rotation angle. + // This makes the item continue to track the finger as it follows the arc. + CGFloat correctedXTranslation = (CGFloat)(translationX * (1 - cos(kMDCDismissalArcAngle) / 2)); + if (translationX > 0) { + correctedXTranslation = MAX(kMDCDismissalDistanceBeforeFading, correctedXTranslation); + } else { + correctedXTranslation = MIN(-kMDCDismissalDistanceBeforeFading, correctedXTranslation); + } + + // Reverse |initialTranslation| and add |translation.x| to ensure that + // we pan along with the arc. + CGAffineTransform reverseTranslation = + CGAffineTransformMakeTranslation(correctedXTranslation, kMDCDismissalArcYOffset); + + CGAffineTransform arcTransform = CGAffineTransformConcat(initialTranslation, rotation); + return CGAffineTransformConcat(arcTransform, reverseTranslation); +} + +- (void)animateFinalItemDismissalToTranslationX:(CGFloat)translationX { + // Called at the end of a pan gesture that results in the item being dismissed. + // Animation that moves the dismissed item to the final location and fades it out. + CGAffineTransform transform = [self transformItemDismissalToTranslationX:translationX]; + + // Notify delegate of dismissed section. + if (_dismissingSection != NSNotFound) { + if ([_delegate respondsToSelector:@selector(collectionView:didEndSwipeToDismissSection:)]) { + [_delegate collectionView:_collectionView didEndSwipeToDismissSection:_dismissingSection]; + } + } + + // Notify delegate of dismissed item. + if (_dismissingCellIndexPath) { + if ([_delegate respondsToSelector: + @selector(collectionView:didEndSwipeToDismissItemAtIndexPath:)]) { + [_delegate collectionView:_collectionView + didEndSwipeToDismissItemAtIndexPath:_dismissingCellIndexPath]; + } + } + + [UIView animateWithDuration:kMDCDismissalAnimationDuration + delay:0 + options:UIViewAnimationOptionCurveEaseOut + animations:^{ + _cellSnapshot.layer.transform = CATransform3DMakeAffineTransform(transform); + _cellSnapshot.alpha = 0; + } + completion:^(BOOL finished) { + [self restoreEditingItem]; + }]; +} + +- (void)restorePanningItemIfNecessaryWithMomentumX:(CGFloat)momentumX { + // If we never had a snapshot, or the snapshot never moved, then skip straight to cleanup. + if (_cellSnapshot == nil || CGAffineTransformIsIdentity(_cellSnapshot.transform)) { + [self restoreEditingItem]; + return; + } + + CAAnimationGroup *allAnimations = [CAAnimationGroup animation]; + allAnimations.duration = kMDCRestoreAnimationDuration; + + CATransform3D startTransform = CATransform3DMakeAffineTransform(_cellSnapshot.transform); + CATransform3D midTransform = CATransform3DTranslate(startTransform, momentumX, 0, 0); + CATransform3D endTransform = CATransform3DIdentity; + + CAKeyframeAnimation *transformAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform"]; + transformAnimation.values = @[ + [NSValue valueWithCATransform3D:startTransform], + [NSValue valueWithCATransform3D:midTransform], + [NSValue valueWithCATransform3D:endTransform] + ]; + transformAnimation.calculationMode = kCAAnimationCubicPaced; + + CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"]; + opacityAnimation.fromValue = @(_cellSnapshot.alpha); + opacityAnimation.toValue = @1; + + allAnimations.animations = @[ transformAnimation, opacityAnimation ]; + allAnimations.delegate = self; + allAnimations.fillMode = kCAFillModeBackwards; + _cellSnapshot.layer.transform = CATransform3DIdentity; + _cellSnapshot.alpha = 1; + [_cellSnapshot.layer addAnimation:allAnimations forKey:nil]; +} + +- (void)animationDidStop:(CAAnimation *)animation finished:(BOOL)didFinish { + [self cancelPanningItem]; +} + +- (void)cancelPanningItem { + // Notify delegate of panned section cancellation. + if (_dismissingSection != NSNotFound) { + if ([_delegate respondsToSelector:@selector(collectionView:didCancelSwipeToDismissSection:)]) { + [_delegate collectionView:_collectionView didCancelSwipeToDismissSection:_dismissingSection]; + } + } + + // Notify delegate of panned index path cancellation. + if (_dismissingCellIndexPath) { + if ([_delegate respondsToSelector: + @selector(collectionView:didCancelSwipeToDismissItemAtIndexPath:)]) { + [_delegate collectionView:_collectionView + didCancelSwipeToDismissItemAtIndexPath:_dismissingCellIndexPath]; + } + } + + [self restoreEditingItem]; +} + +- (void)restoreEditingItem { + // Remove snapshot and reset item. + [_cellSnapshot removeFromSuperview]; + _cellSnapshot = nil; + _dismissingSection = NSNotFound; + _dismissingCellIndexPath = nil; + _reorderingCellIndexPath = nil; + [_collectionView.collectionViewLayout invalidateLayout]; +} + +// The distance an item must be panned before it is dismissed. Currently half of the bounds width. +- (CGFloat)distanceThresholdForDismissal { + return _collectionView.bounds.size.width / 2; +} + +- (CGFloat)dismissalAlphaForTranslationX:(CGFloat)translationX { + translationX = (CGFloat)fabs(translationX) - kMDCDismissalDistanceBeforeFading; + CGFloat adjustedThreshold = + [self distanceThresholdForDismissal] - kMDCDismissalDistanceBeforeFading; + CGFloat dismissalPercentage = (CGFloat)MIN(1, fabs(translationX) / adjustedThreshold); + return kMDCDismissalMinimumAlpha + (1 - kMDCDismissalMinimumAlpha) * (1 - dismissalPercentage); +} + +@end diff --git a/components/Collections/src/private/MDCCollectionViewStyler.h b/components/Collections/src/private/MDCCollectionViewStyler.h new file mode 100644 index 00000000000..ece6adb9036 --- /dev/null +++ b/components/Collections/src/private/MDCCollectionViewStyler.h @@ -0,0 +1,37 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import "MDCCollectionViewStyling.h" + +/** + The MDCCollectionViewStyler class provides a default implementation for a UICollectionView to set + its style properties. + */ +@interface MDCCollectionViewStyler : NSObject + +- (nonnull instancetype)init NS_UNAVAILABLE; + +/** + Initializes and returns a newly allocated style manager object with the specified collection view. + + Designated initializer. + + @param collectionView The controller's collection view. + */ +- (nonnull instancetype)initWithCollectionView: + (nonnull UICollectionView *)collectionView NS_DESIGNATED_INITIALIZER; + +@end diff --git a/components/Collections/src/private/MDCCollectionViewStyler.m b/components/Collections/src/private/MDCCollectionViewStyler.m new file mode 100644 index 00000000000..0e42c353c10 --- /dev/null +++ b/components/Collections/src/private/MDCCollectionViewStyler.m @@ -0,0 +1,754 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#if !defined(__has_feature) || !__has_feature(objc_arc) +#error "This file requires ARC support." +#endif + +#import "MDCCollectionViewStyler.h" + +#import "MDCCollectionViewStylingDelegate.h" +#import "MaterialCollectionLayoutAttributes.h" + +#import + +#define RGBCOLOR(r, g, b) [UIColor colorWithRed:(r) / 255.0f green:(g) / 255.0f blue:(b) / 255.0f alpha:1] + +typedef NS_OPTIONS(NSUInteger, BackgroundCacheKey) { + BackgroundCacheKeyFlat = 0, + BackgroundCacheKeyTop = 1 << 0, + BackgroundCacheKeyBottom = 1 << 1, + BackgroundCacheKeyCard = 1 << 2, + BackgroundCacheKeyGrouped = 1 << 3, + BackgroundCacheKeyHighlighted = 1 << 4, + BackgroundCacheKeyMax = 1 << 5, +}; + +const CGFloat MDCCollectionViewCellStyleCardSectionInset = 8.0f; + +/** Cell content view insets for card-style cells */ +static const CGFloat kFourThirds = 4.0f / 3.0f; +static const UIEdgeInsets kCollectionViewCellContentInsetsRetina3x = { + kFourThirds, kFourThirds, kFourThirds, kFourThirds}; +static const UIEdgeInsets kCollectionViewCellContentInsetsRetina = {1.5, 1.5, 1.5, 1.5}; +static const UIEdgeInsets kCollectionViewCellContentInsets = {1, 2, 1, 2}; + +/** Default cell separator style settings */ +static const CGFloat kCollectionViewCellSeparatorDefaultHeightInPixels = 1.0f; + +/** Grid layout defaults */ +static const NSInteger kCollectionViewGridDefaultColumnCount = 2; +static const CGFloat kCollectionViewGridDefaultPadding = 4.0f; + +/** The drawn cell background */ +static const CGSize kCellImageSize = {44, 44}; +static const CGFloat kCollectionViewCellDefaultBorderWidth = 1.0f; +static const CGFloat kCollectionViewCellDefaultBorderRadius = 1.5f; +static inline UIColor *kCollectionViewCellDefaultBorderColor() { + return [UIColor colorWithWhite:0 alpha:0.05f]; +} + +/** Cell shadowing */ +static const CGFloat kCollectionViewCellDefaultShadowWidth = 1.0f; +static inline CGSize kCollectionViewCellDefaultShadowOffset() { + return CGSizeMake(0, 1); +} +static inline UIColor *kCollectionViewCellDefaultShadowColor() { + return [UIColor colorWithWhite:0 alpha:0.1f]; +} + +/** Animate cell on appearance settings */ +static const CGFloat kCollectionViewAnimatedAppearancePadding = 20.0f; +static const NSTimeInterval kCollectionViewAnimatedAppearanceDelay = 0.1; +static const NSTimeInterval kCollectionViewAnimatedAppearanceDuration = 0.3; + +/** Modifies only the right and bottom edges of a CGRect. */ +NS_INLINE CGRect RectContract(CGRect rect, CGFloat dx, CGFloat dy) { + return CGRectMake(rect.origin.x, rect.origin.y, rect.size.width - dx, rect.size.height - dy); +} + +/** Modifies only the top and left edges of a CGRect. */ +NS_INLINE CGRect RectShift(CGRect rect, CGFloat dx, CGFloat dy) { + return CGRectOffset(RectContract(rect, dx, dy), dx, dy); +} + +@interface MDCCollectionViewStyler () + +/** Convenience property defining the exact color that the collection view background should be. */ +@property(nonatomic, readonly) UIColor *collectionViewBackgroundColor; + +/** + A dictionary of NSPointerArray caches, keyed by UIColor, for cached cell background images + using that background color. Index into the NSPointerArray using the results of + backgroundCacheKeyForCardStyle:isGroupedStyle:isTop:isBottom:isHighlighted: + */ +@property(nonatomic, readonly) NSMutableDictionary *cellBackgroundCaches; + +/** An set of index paths for items that are inlaid. */ +@property(nonatomic, strong) NSMutableSet *inlaidIndexPathSet; + +@end + +@implementation MDCCollectionViewStyler { + UIColor *_collectionViewBackgroundColor; +} + +@synthesize collectionView = _collectionView; +@synthesize delegate = _delegate; +@synthesize shouldInvalidateLayout = _shouldInvalidateLayout; +@synthesize cellBackgroundColor = _cellBackgroundColor; +@synthesize cellLayoutType = _cellLayoutType; +@synthesize gridColumnCount = _gridColumnCount; +@synthesize gridPadding = _gridPadding; +@synthesize cellStyle = _cellStyle; +@synthesize separatorColor = _separatorColor; +@synthesize separatorInset = _separatorInset; +@synthesize separatorLineHeight = _separatorLineHeight; +@synthesize shouldHideSeparators = _shouldHideSeparators; +@synthesize allowsItemInlay = _allowsItemInlay; +@synthesize allowsMultipleItemInlays = _allowsMultipleItemInlays; +@synthesize shouldAnimateCellsOnAppearance = _shouldAnimateCellsOnAppearance; +@synthesize willAnimateCellsOnAppearance = _willAnimateCellsOnAppearance; +@synthesize animateCellsOnAppearancePadding = _animateCellsOnAppearancePadding; +@synthesize animateCellsOnAppearanceDuration = _animateCellsOnAppearanceDuration; + +- (instancetype)initWithCollectionView:(UICollectionView *)collectionView { + self = [super init]; + if (self) { + _collectionView = collectionView; + + // Cell default style properties. + _cellBackgroundColor = [UIColor whiteColor]; + _cellStyle = MDCCollectionViewCellStyleDefault; + _collectionView.backgroundColor = [UIColor whiteColor]; + _inlaidIndexPathSet = [NSMutableSet set]; + + // Cell separator defaults. + _separatorColor = RGBCOLOR(224, 224, 224); + _separatorInset = UIEdgeInsetsZero; + _separatorLineHeight = + kCollectionViewCellSeparatorDefaultHeightInPixels / [[UIScreen mainScreen] scale]; + _shouldHideSeparators = NO; + + // Grid defaults. + _cellLayoutType = MDCCollectionViewCellLayoutTypeList; + _gridColumnCount = kCollectionViewGridDefaultColumnCount; + _gridPadding = kCollectionViewGridDefaultPadding; + + // Animate cell on appearance settings. + _animateCellsOnAppearancePadding = kCollectionViewAnimatedAppearancePadding; + _animateCellsOnAppearanceDuration = kCollectionViewAnimatedAppearanceDuration; + + // Caching. + _cellBackgroundCaches = [NSMutableDictionary dictionary]; + } + return self; +} + +- (BOOL)isEqual:(id)object { + // If shouldInvalidateLayout property is NO, prevent a collection view layout caused when + // layout attributes check here if they are equal. + return (self == object) && ![self shouldInvalidateLayoutForStyleChange]; +} + +#pragma mark - Cell Appearance Animation + +- (void)setShouldAnimateCellsOnAppearance:(BOOL)shouldAnimateCellsOnAppearance { + _shouldAnimateCellsOnAppearance = shouldAnimateCellsOnAppearance; + _willAnimateCellsOnAppearance = shouldAnimateCellsOnAppearance; +} + +- (void)beginCellAppearanceAnimation { + if (_shouldAnimateCellsOnAppearance) { + _willAnimateCellsOnAppearance = NO; + [UIView animateWithDuration:_animateCellsOnAppearanceDuration + delay:kCollectionViewAnimatedAppearanceDelay + options:UIViewAnimationOptionCurveEaseInOut + animations:^{ + [self updateLayoutAnimated:YES]; + } + completion:^(BOOL finished) { + [self setShouldAnimateCellsOnAppearance:NO]; + }]; + } +} + +#pragma mark - Caching + +- (BackgroundCacheKey)backgroundCacheKeyForCardStyle:(BOOL)isCardStyle + isGroupedStyle:(BOOL)isGroupedStyle + isTop:(BOOL)isTop + isBottom:(BOOL)isBottom + isHighlighted:(BOOL)isHighlighted { + if (!isCardStyle && !isGroupedStyle) { + return BackgroundCacheKeyFlat; + } + BackgroundCacheKey options = isTop ? BackgroundCacheKeyTop : 0; + options |= isBottom ? BackgroundCacheKeyBottom : 0; + options |= isCardStyle ? BackgroundCacheKeyCard : 0; + options |= isGroupedStyle ? BackgroundCacheKeyGrouped : 0; + options |= isHighlighted ? BackgroundCacheKeyHighlighted : 0; + NSAssert(isCardStyle != isGroupedStyle, @"Cannot be both card and grouped style"); + return options; +} + +- (NSPointerArray *)cellBackgroundCache { + NSPointerArray *cache = [NSPointerArray strongObjectsPointerArray]; + cache.count = BackgroundCacheKeyMax; + return cache; +} + +#pragma mark - Separators + +- (void)setSeparatorColor:(UIColor *)separatorColor { + if (_separatorColor == separatorColor) { + return; + } + [self invalidateLayoutForStyleChange]; + _separatorColor = separatorColor; +} + +- (void)setSeparatorInset:(UIEdgeInsets)separatorInset { + if (UIEdgeInsetsEqualToEdgeInsets(_separatorInset, separatorInset)) { + return; + } + [self invalidateLayoutForStyleChange]; + _separatorInset = separatorInset; +} + +- (void)setSeparatorLineHeight:(CGFloat)separatorLineHeight { + if (_separatorLineHeight == separatorLineHeight) { + return; + } + [self invalidateLayoutForStyleChange]; + _separatorLineHeight = separatorLineHeight; +} + +- (void)setShouldHideSeparators:(BOOL)shouldHideSeparators { + if (_shouldHideSeparators == shouldHideSeparators) { + return; + } + [self invalidateLayoutForStyleChange]; + _shouldHideSeparators = shouldHideSeparators; +} + +- (void)setCellStyle:(MDCCollectionViewCellStyle)cellStyle { + if (_cellStyle == cellStyle) { + return; + } + [_cellBackgroundCaches removeAllObjects]; + [self invalidateLayoutForStyleChange]; + _cellStyle = cellStyle; +} + +#pragma mark - Public + +- (UIEdgeInsets)backgroundImageViewOutsetsForCellWithAttribute: + (MDCCollectionViewLayoutAttributes *)attr { + // Inset contentView to allow for shadowed borders in cards. + UIEdgeInsets insets = UIEdgeInsetsZero; + + MDCCollectionViewCellStyle cellStyle = [self cellStyleAtSectionIndex:attr.indexPath.section]; + BOOL isCardStyle = cellStyle == MDCCollectionViewCellStyleCard; + BOOL isGroupedStyle = cellStyle == MDCCollectionViewCellStyleGrouped; + BOOL isHighlighted = NO; + + MDCCollectionViewOrdinalPosition position = attr.sectionOrdinalPosition; + + if ([self drawShadowForCellWithIsCardStye:isCardStyle + isGroupStyle:isGroupedStyle + isHighlighted:isHighlighted]) { + CGFloat mainScreenScale = [[UIScreen mainScreen] scale]; + if (mainScreenScale > (CGFloat)2.1) { + insets = kCollectionViewCellContentInsetsRetina3x; + } else if (mainScreenScale > (CGFloat)1.1) { + insets = kCollectionViewCellContentInsetsRetina; + } else { + insets = kCollectionViewCellContentInsets; + } + + if (!isCardStyle) { + insets = UIEdgeInsetsMake(insets.top, 0, insets.bottom, 0); + } + + switch (position) { + case MDCCollectionViewOrdinalPositionVerticalTop: + insets = UIEdgeInsetsMake(insets.top, insets.left, 0, insets.right); + break; + case MDCCollectionViewOrdinalPositionVerticalBottom: + insets = UIEdgeInsetsMake(0, insets.left, insets.bottom, insets.right); + break; + case MDCCollectionViewOrdinalPositionVerticalCenter: + insets = UIEdgeInsetsMake(0, insets.left, 0, insets.right); + break; + default: + break; + } + } + + return insets; +} + +- (void)setCellStyle:(MDCCollectionViewCellStyle)cellStyle animated:(BOOL)animated { + _cellStyle = cellStyle; + [self updateLayoutAnimated:animated]; +} + +- (MDCCollectionViewCellStyle)cellStyleAtSectionIndex:(NSInteger)section { + MDCCollectionViewCellStyle cellStyle = self.cellStyle; + if (self.delegate && + [self.delegate respondsToSelector:@selector(collectionView:cellStyleForSection:)]) { + cellStyle = [self.delegate collectionView:_collectionView cellStyleForSection:section]; + } + return cellStyle; +} + +- (NSArray *)indexPathsForInlaidItems { + return [_inlaidIndexPathSet allObjects]; +} + +- (BOOL)isItemInlaidAtIndexPath:(NSIndexPath *)indexPath { + return [[_inlaidIndexPathSet allObjects] containsObject:indexPath]; +} + +- (void)applyInlayToItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated { + if (_allowsItemInlay) { + // If necessary, remove any previously inlaid items. + if (!_allowsMultipleItemInlays) { + for (NSIndexPath *inlaidIndexPath in [self indexPathsForInlaidItems]) { + [self removeInlayFromItemAtIndexPath:inlaidIndexPath animated:animated]; + } + } + + void (^completionBlock)(BOOL finished) = ^(BOOL finished) { + if ([self.delegate respondsToSelector: + @selector(collectionView:didApplyInlayToItemAtIndexPaths:)]) { + [self.delegate collectionView:_collectionView + didApplyInlayToItemAtIndexPaths:@[ indexPath ]]; + } + }; + + // Inlay this item. + [_inlaidIndexPathSet addObject:indexPath]; + [self updateLayoutAnimated:animated completion:completionBlock]; + } +} + +- (void)removeInlayFromItemAtIndexPath:(NSIndexPath *)indexPath animated:(BOOL)animated { + [_inlaidIndexPathSet removeObject:indexPath]; + + void (^completionBlock)(BOOL finished) = ^(BOOL finished) { + if ([self.delegate respondsToSelector: + @selector(collectionView:didRemoveInlayFromItemAtIndexPaths:)]) { + [self.delegate collectionView:_collectionView + didRemoveInlayFromItemAtIndexPaths:@[ indexPath ]]; + } + }; + + [self updateLayoutAnimated:animated completion:completionBlock]; +} + +- (void)applyInlayToAllItemsAnimated:(BOOL)animated { + if (_allowsItemInlay && _allowsMultipleItemInlays) { + // Store all index paths. + [_inlaidIndexPathSet removeAllObjects]; + NSInteger sections = [_collectionView numberOfSections]; + for (NSInteger section = 0; section < sections; section++) { + for (NSInteger item = 0; item < [_collectionView numberOfItemsInSection:section]; item++) { + [_inlaidIndexPathSet addObject:[NSIndexPath indexPathForItem:item inSection:section]]; + } + } + + void (^completionBlock)(BOOL finished) = ^(BOOL finished) { + if ([self.delegate respondsToSelector: + @selector(collectionView:didApplyInlayToItemAtIndexPaths:)]) { + [self.delegate collectionView:_collectionView + didApplyInlayToItemAtIndexPaths:[_inlaidIndexPathSet allObjects]]; + } + }; + + // Inlay all items. + [self updateLayoutAnimated:animated completion:completionBlock]; + } +} + +- (void)removeInlayFromAllItemsAnimated:(BOOL)animated { + NSArray *indexPaths = [_inlaidIndexPathSet allObjects]; + [_inlaidIndexPathSet removeAllObjects]; + + void (^completionBlock)(BOOL finished) = ^(BOOL finished) { + if ([self.delegate respondsToSelector: + @selector(collectionView:didRemoveInlayFromItemAtIndexPaths:)]) { + [self.delegate collectionView:_collectionView + didRemoveInlayFromItemAtIndexPaths:indexPaths]; + } + }; + + [self updateLayoutAnimated:animated completion:completionBlock]; +} + +- (void)resetIndexPathsForInlaidItems { + [_inlaidIndexPathSet removeAllObjects]; + [self applyInlayToAllItemsAnimated:NO]; +} + +- (void)updateLayoutAnimated:(BOOL)animated { + [self updateLayoutAnimated:animated completion:nil]; +} + +#pragma mark - Private + +- (void)updateLayoutAnimated:(BOOL)animated completion:(void (^)(BOOL finished))completion { + if (animated) { + // Invalidate current layout while allowing animation to new layout. + [_collectionView performBatchUpdates:nil + completion:^(BOOL finished) { + if (completion) { + completion(finished); + } + }]; + } else { + _shouldInvalidateLayout = YES; + + // Create new layout with existing layout properties. + NSData *data = + [NSKeyedArchiver archivedDataWithRootObject:_collectionView.collectionViewLayout]; + UICollectionViewFlowLayout *newLayout = [NSKeyedUnarchiver unarchiveObjectWithData:data]; + [_collectionView setCollectionViewLayout:newLayout + animated:animated + completion:^(BOOL finished) { + if (completion) { + completion(finished); + } + }]; + } +} + +- (void)invalidateLayoutForStyleChange { + _shouldInvalidateLayout = YES; +} + +- (BOOL)shouldInvalidateLayoutForStyleChange { + // Whether the collection view layout should be invalidated due to a style property that has + // changed value. + return _shouldInvalidateLayout; +} + +#pragma mark - Default Colors + +- (UIColor *)collectionViewBackgroundColor { + if (!_collectionViewBackgroundColor) { + _collectionViewBackgroundColor = RGBCOLOR(0xEE, 0xEE, 0xEE); + } + return _collectionViewBackgroundColor; +} + +#pragma mark - Cell Image Background + +- (BOOL)drawShadowForCellWithIsCardStye:(BOOL)isCardStyle + isGroupStyle:(BOOL)isGroupStyle + isHighlighted:(BOOL)isHighlighted { + return (isCardStyle || isGroupStyle) && + kCollectionViewCellDefaultShadowWidth > 0 && + !isHighlighted; +} + +- (UIImage *)backgroundImageForCellLayoutAttributes:(MDCCollectionViewLayoutAttributes *)attr { + BOOL isSectionHeader = + [attr.representedElementKind isEqualToString:UICollectionElementKindSectionHeader]; + BOOL isSectionFooter = + [attr.representedElementKind isEqualToString:UICollectionElementKindSectionFooter]; + BOOL isDecorationView = + attr.representedElementCategory == UICollectionElementCategoryDecorationView; + BOOL isTop = attr.sectionOrdinalPosition & MDCCollectionViewOrdinalPositionVerticalTop; + BOOL isBottom = attr.sectionOrdinalPosition & MDCCollectionViewOrdinalPositionVerticalBottom; + + MDCCollectionViewCellStyle cellStyle = [self cellStyleAtSectionIndex:attr.indexPath.section]; + BOOL isCardStyle = cellStyle == MDCCollectionViewCellStyleCard; + BOOL isGroupedStyle = cellStyle == MDCCollectionViewCellStyleGrouped; + BOOL isGridLayout = (_cellLayoutType == MDCCollectionViewCellLayoutTypeGrid); + if (!isCardStyle && !isGroupedStyle) { + // If not card or grouped style, revert |isBottom| to allow drawing separator at bottom. + isBottom = NO; + } else { + // Update background color for card/grouped styles. + _collectionView.backgroundColor = self.collectionViewBackgroundColor; + } + CGFloat borderRadius = (isCardStyle) ? kCollectionViewCellDefaultBorderRadius : 0.0f; + + // Allowance for grid decoration view. + if (isGridLayout) { + if (!isDecorationView && attr.shouldShowGridBackground) { + return nil; + } else { + isTop = isBottom = YES; + } + } + + // If no-background section header, return nil image. + BOOL hidesHeaderBackground = NO; + if ([_delegate respondsToSelector: + @selector(collectionView:shouldHideHeaderBackgroundForSection:)]) { + hidesHeaderBackground = [_delegate collectionView:_collectionView + shouldHideHeaderBackgroundForSection:attr.indexPath.section]; + } + if (hidesHeaderBackground && isSectionHeader) { + return nil; + } + + // If no-background section footer, return nil image. + BOOL hidesFooterBackground = NO; + if ([_delegate respondsToSelector: + @selector(collectionView:shouldHideFooterBackgroundForSection:)]) { + hidesFooterBackground = [_delegate collectionView:_collectionView + shouldHideFooterBackgroundForSection:attr.indexPath.section]; + } + if (hidesFooterBackground && isSectionFooter) { + return nil; + } + + // If no-background section item, return nil image. + BOOL hidesBackground = NO; + if ([_delegate respondsToSelector: + @selector(collectionView:shouldHideItemBackgroundAtIndexPath:)]) { + hidesBackground = [_delegate collectionView:_collectionView + shouldHideItemBackgroundAtIndexPath:attr.indexPath]; + } + if (hidesBackground && !(isDecorationView || isSectionFooter || isSectionHeader)) { + return nil; + } + + BOOL isHighlighted = NO; + + BackgroundCacheKey backgroundCacheKey = + [self backgroundCacheKeyForCardStyle:isCardStyle + isGroupedStyle:isGroupedStyle + isTop:isTop + isBottom:isBottom + isHighlighted:isHighlighted]; + + if (backgroundCacheKey > BackgroundCacheKeyMax) { + NSAssert(NO, @"Invalid style manager cell background cache key"); + return nil; + } + + // Get cell color. + UIColor *backgroundColor = _cellBackgroundColor; + if ([_delegate respondsToSelector:@selector(collectionView:cellBackgroundColorAtIndexPath:)]) { + backgroundColor = [_delegate collectionView:_collectionView + cellBackgroundColorAtIndexPath:attr.indexPath]; + } + + NSPointerArray *cellBackgroundCache = _cellBackgroundCaches[backgroundColor]; + if (!cellBackgroundCache) { + cellBackgroundCache = [self cellBackgroundCache]; + _cellBackgroundCaches[backgroundColor] = cellBackgroundCache; + } else if ([cellBackgroundCache pointerAtIndex:backgroundCacheKey]) { + return (__bridge UIImage *)[cellBackgroundCache pointerAtIndex:backgroundCacheKey]; + } + + CGRect imageRect = CGRectMake(0, 0, kCellImageSize.width, kCellImageSize.height); + UIGraphicsBeginImageContextWithOptions(imageRect.size, NO, 0); + + CGContextRef cx = UIGraphicsGetCurrentContext(); + + // Create a transparent background. + CGContextClearRect(cx, imageRect); + + // Inner background color + CGContextSetFillColorWithColor(cx, backgroundColor.CGColor); + + CGRect contentFrame = imageRect; + + // Draw the shadow. + if ([self drawShadowForCellWithIsCardStye:isCardStyle + isGroupStyle:isGroupedStyle + isHighlighted:isHighlighted]) { + if (isCardStyle) { + contentFrame = CGRectInset(imageRect, kCollectionViewCellDefaultShadowWidth, 0); + } + if (isTop) { + contentFrame = RectShift(contentFrame, 0, kCollectionViewCellDefaultShadowWidth); + } + if (isBottom) { + contentFrame = RectContract(contentFrame, 0, kCollectionViewCellDefaultShadowWidth); + } + + CGContextSaveGState(cx); + CGRect shadowFrame = contentFrame; + + // We want the shadow to clip to the top and bottom edges of the image so that when two cells + // are next to each other their shadows line up perfectly. + if (!isTop) { + shadowFrame = RectShift(shadowFrame, 0, -kCollectionViewCellDefaultShadowWidth); + } + if (!isBottom) { + shadowFrame = RectContract(shadowFrame, 0, -kCollectionViewCellDefaultShadowWidth); + } + + [self applyBackgroundPathToContext:cx + rect:shadowFrame + isTop:isTop + isBottom:isBottom + isCard:(isCardStyle || isGroupedStyle) + borderRadius:borderRadius]; + CGContextSetShadowWithColor(cx, + kCollectionViewCellDefaultShadowOffset(), + kCollectionViewCellDefaultShadowWidth, + kCollectionViewCellDefaultShadowColor().CGColor); + CGContextDrawPath(cx, kCGPathFill); + CGContextRestoreGState(cx); + } else { + // Draw a flat cell background. + CGContextSaveGState(cx); + [self applyBackgroundPathToContext:cx + rect:contentFrame + isTop:isTop + isBottom:isBottom + isCard:(isCardStyle || isGroupedStyle) + borderRadius:borderRadius]; + CGContextFillPath(cx); + CGContextRestoreGState(cx); + } + // Draw border paths for cells. We want the cell border to overlap the shadow and the content. + if ((isCardStyle || isGroupedStyle) && !isHighlighted) { + CGFloat minPixelOffset = [self minPixelOffset]; + CGRect borderFrame = CGRectInset(contentFrame, -minPixelOffset, -minPixelOffset); + CGContextSaveGState(cx); + CGContextSetLineWidth(cx, kCollectionViewCellDefaultBorderWidth); + CGContextSetStrokeColorWithColor(cx, kCollectionViewCellDefaultBorderColor().CGColor); + [self applyBorderPathToContext:cx + rect:borderFrame + isTop:isTop + isBottom:isBottom + isCard:isCardStyle + borderRadius:borderRadius]; + CGContextStrokePath(cx); + CGContextRestoreGState(cx); + } + + UIImage *image = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext(); + UIImage *resizableImage = [self resizableImage:image]; + [cellBackgroundCache replacePointerAtIndex:backgroundCacheKey + withPointer:(__bridge void *)(resizableImage)]; + return resizableImage; +} + +#pragma mark - Private Context Paths + +// We want to draw the borders and shadows on single retina-pixel boundaries if possible, but +// we need to avoid doing this on non-retina devices because it'll look blurry. +- (CGFloat)minPixelOffset { + return 1.0f / [[UIScreen mainScreen] scale]; +} + +- (UIImage *)resizableImage:(UIImage *)image { + // Returns a resizable version of this image with cap insets equal to center point. + CGFloat capWidth = (CGFloat)floor(image.size.width / 2); + CGFloat capHeight = (CGFloat)floor(image.size.height / 2); + UIEdgeInsets capInsets = UIEdgeInsetsMake(capHeight, capWidth, capHeight, capWidth); + return [image resizableImageWithCapInsets:capInsets]; +} + +- (void)applyBackgroundPathToContext:(CGContextRef)c + rect:(CGRect)rect + isTop:(BOOL)isTop + isBottom:(BOOL)isBottom + isCard:(BOOL)isCard + borderRadius:(CGFloat)borderRadius { + // Draw background paths for cell. + CGFloat minPixelOffset = (isCard) ? [self minPixelOffset] : 0.0f; + CGFloat minX = CGRectGetMinX(rect) + minPixelOffset; + CGFloat midX = CGRectGetMidX(rect) + minPixelOffset; + CGFloat maxX = CGRectGetMaxX(rect) - minPixelOffset; + CGFloat minY = CGRectGetMinY(rect) - minPixelOffset; + CGFloat midY = CGRectGetMidY(rect) - minPixelOffset; + CGFloat maxY = CGRectGetMaxY(rect) + minPixelOffset; + + CGContextBeginPath(c); + + CGContextMoveToPoint(c, minX, midY); + if (isTop && isCard) { + CGContextAddArcToPoint(c, minX, minY + 1, midX, minY + 1, borderRadius); + CGContextAddArcToPoint(c, maxX, minY + 1, maxX, midY, borderRadius); + } else { + CGContextAddLineToPoint(c, minX, minY); + CGContextAddLineToPoint(c, maxX, minY); + } + + CGContextAddLineToPoint(c, maxX, midY); + + if (isBottom & isCard) { + CGContextAddArcToPoint(c, maxX, maxY - 1, midX, maxY - 1, borderRadius); + CGContextAddArcToPoint(c, minX, maxY - 1, minX, midY, borderRadius); + } else { + CGContextAddLineToPoint(c, maxX, maxY); + CGContextAddLineToPoint(c, minX, maxY); + } + CGContextAddLineToPoint(c, minX, midY); + + CGContextClosePath(c); +} + +- (void)applyBorderPathToContext:(CGContextRef)c + rect:(CGRect)rect + isTop:(BOOL)isTop + isBottom:(BOOL)isBottom + isCard:(BOOL)isCard + borderRadius:(CGFloat)borderRadius { + // Draw border paths for cell. + CGFloat minPixelOffset = (isCard) ? [self minPixelOffset] : 0.0f; + CGFloat minX = CGRectGetMinX(rect) + minPixelOffset; + CGFloat midX = CGRectGetMidX(rect) + minPixelOffset; + CGFloat maxX = CGRectGetMaxX(rect) - minPixelOffset; + CGFloat minY = CGRectGetMinY(rect) - minPixelOffset; + CGFloat midY = CGRectGetMidY(rect) - minPixelOffset; + CGFloat maxY = CGRectGetMaxY(rect) + minPixelOffset; + + CGContextBeginPath(c); + + if (isTop && isBottom) { + CGContextMoveToPoint(c, minX, midY); + CGContextAddArcToPoint(c, minX, minY + 1, midX, minY + 1, borderRadius); + CGContextAddArcToPoint(c, maxX, minY + 1, maxX, midY, borderRadius); + CGContextAddLineToPoint(c, maxX, midY); + CGContextAddArcToPoint(c, maxX, maxY - 1, midX, maxY - 1, borderRadius); + CGContextAddArcToPoint(c, minX, maxY - 1, minX, midY, borderRadius); + CGContextAddLineToPoint(c, minX, midY); + } else if (isTop) { + CGContextMoveToPoint(c, minX, maxY); + CGContextAddLineToPoint(c, minX, midY); + CGContextAddArcToPoint(c, minX, minY + 1, midX, minY + 1, borderRadius); + CGContextAddArcToPoint(c, maxX, minY + 1, maxX, midY, borderRadius); + CGContextAddLineToPoint(c, maxX, maxY); + } else if (isBottom) { + CGContextMoveToPoint(c, maxX, minY); + CGContextAddLineToPoint(c, maxX, midY); + CGContextAddArcToPoint(c, maxX, maxY - 1, midX, maxY - 1, borderRadius); + CGContextAddArcToPoint(c, minX, maxY - 1, minX, midY, borderRadius); + CGContextAddLineToPoint(c, minX, minY); + } else { + CGContextMoveToPoint(c, minX, minY); + CGContextAddLineToPoint(c, minX, maxY); + CGContextMoveToPoint(c, maxX, minY); + CGContextAddLineToPoint(c, maxX, maxY); + } + + CGContextClosePath(c); +} + +@end diff --git a/components/Collections/src/private/MaterialCollectionsStrings.h b/components/Collections/src/private/MaterialCollectionsStrings.h new file mode 100644 index 00000000000..f91ad5c36f2 --- /dev/null +++ b/components/Collections/src/private/MaterialCollectionsStrings.h @@ -0,0 +1,20 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +typedef enum { + kStr_MaterialCollectionsInfoBarGestureHint = 0, + kStr_MaterialCollectionsDeleteButton = 1, +} MaterialCollectionsStringId; diff --git a/components/Collections/src/private/MaterialCollectionsStrings_table.h b/components/Collections/src/private/MaterialCollectionsStrings_table.h new file mode 100644 index 00000000000..0b8414be054 --- /dev/null +++ b/components/Collections/src/private/MaterialCollectionsStrings_table.h @@ -0,0 +1,27 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +// A table of string keys to look-up localized strings in the bundle. +// This table is to be indexed using the generated enum. + +static NSString *const kMaterialCollectionsStringTable[] = { + @"InfoBarGestureHint", // Swipe an item to delete + @"DeleteButton", // Delete +}; +#define kNumMaterialCollectionsStrings 2 +#define kMaterialCollectionsStringsOffset 0 +#define kMaterialCollectionsStringsEnd 10000 +static NSString *const kMaterialCollectionsStringsTableName = @"MaterialCollections"; diff --git a/components/FlexibleHeader/.jazzy.yaml b/components/FlexibleHeader/.jazzy.yaml index 750bd5e4036..be07c16d24c 100644 --- a/components/FlexibleHeader/.jazzy.yaml +++ b/components/FlexibleHeader/.jazzy.yaml @@ -1,4 +1,5 @@ -module_name: FlexibleHeader +# Auto-generated by scripts/generate_jazzy_yamls.sh. Used primarily by scripts/external/arc-jazzy-linter. +module: FlexibleHeader umbrella_header: src/MaterialFlexibleHeader.h objc: true sdk: iphonesimulator diff --git a/components/FlexibleHeader/README.md b/components/FlexibleHeader/README.md index 4c56914e527..1380d549eb9 100644 --- a/components/FlexibleHeader/README.md +++ b/components/FlexibleHeader/README.md @@ -1,7 +1,7 @@ --- title: "Flexible Header" layout: detail -section: documentation +section: components excerpt: "The Flexible Header is a container view whose height and vertical offset react to UIScrollViewDelegate events." --- # Flexible Header @@ -148,6 +148,19 @@ controller to a MDCFlexibleHeaderView instance. #### Swift ~~~ swift +let headerViewController = MDCFlexibleHeaderViewController() + +override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + + addChildViewController(headerViewController) +} + +required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + + addChildViewController(headerViewController) +} ~~~ @@ -171,6 +184,13 @@ ensure that the Flexible Header is in front of all other views. #### Swift ~~~ swift +override func viewDidLoad() { + super.viewDidLoad() + + headerViewController.view.frame = view.bounds + view.addSubview(headerViewController.view) + headerViewController.didMoveToParentViewController(self) +} ~~~ @@ -197,7 +217,7 @@ self.headerViewController.headerView.trackingScrollView = scrollView; #### Swift ~~~ swift -self.headerViewController.headerView.trackingScrollView = scrollView +headerViewController.headerView.trackingScrollView = scrollView ~~~ @@ -220,7 +240,7 @@ scrollView.delegate = self.headerViewController; #### Swift ~~~ swift -scrollView.delegate = self.headerViewController +scrollView.delegate = headerViewController ~~~ @@ -264,27 +284,29 @@ UIScrollView subclass. #### Swift ~~~ swift +// MARK: UIScrollViewDelegate + override func scrollViewDidScroll(scrollView: UIScrollView) { - if scrollView == self.headerViewController.headerView.trackingScrollView { - self.headerViewController.headerView.trackingScrollViewDidScroll() + if scrollView == headerViewController.headerView.trackingScrollView { + headerViewController.headerView.trackingScrollViewDidScroll() } } override func scrollViewDidEndDecelerating(scrollView: UIScrollView) { - if scrollView == self.headerViewController.headerView.trackingScrollView { - self.headerViewController.headerView.trackingScrollViewDidEndDecelerating() + if scrollView == headerViewController.headerView.trackingScrollView { + headerViewController.headerView.trackingScrollViewDidEndDecelerating() } } override func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) { - let headerView = self.headerViewController.headerView + let headerView = headerViewController.headerView if scrollView == headerView.trackingScrollView { headerView.trackingScrollViewDidEndDraggingWillDecelerate(decelerate) } } override func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { - let headerView = self.headerViewController.headerView + let headerView = headerViewController.headerView if scrollView == headerView.trackingScrollView { headerView.trackingScrollViewWillEndDraggingWithVelocity(velocity, targetContentOffset: targetContentOffset) } @@ -354,7 +376,7 @@ MDCFlexibleHeaderViewController instance's `layoutDelegate`. // Set yourself as the delegate. headerViewController.layoutDelegate = self; -#pragma mark - MDCFlexibleHeaderViewLayoutDelegate +#pragma - MDCFlexibleHeaderViewLayoutDelegate - (void)flexibleHeaderViewController:(MDCFlexibleHeaderViewController *)flexibleHeaderViewController flexibleHeaderViewFrameDidChange:(MDCFlexibleHeaderView *)flexibleHeaderView { @@ -365,6 +387,14 @@ headerViewController.layoutDelegate = self; #### Swift ~~~ swift +class MyViewController: UIViewController, MDCFlexibleHeaderViewLayoutDelegate { + + // MARK: MDCFlexibleHeaderViewLayoutDelegate + func flexibleHeaderViewController(flexibleHeaderViewController: MDCFlexibleHeaderViewController, + flexibleHeaderViewFrameDidChange flexibleHeaderView: MDCFlexibleHeaderView) { + // Called whenever the frame changes. + } +} ~~~ @@ -383,6 +413,7 @@ take the z-index into account: #### Swift ~~~ swift +view.insertSubview(myCustomView, belowSubview: headerViewController.headerView) ~~~ @@ -412,7 +443,7 @@ animating in/out in a reasonable manner. override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) - self.navigationController?.setNavigationBarHidden(true, animated: animated) + navigationController?.setNavigationBarHidden(true, animated: animated) } ~~~ @@ -434,7 +465,7 @@ Add the following to view controllers that don't have an app bar: override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) - self.navigationController?.setNavigationBarHidden(false, animated: animated) + navigationController?.setNavigationBarHidden(false, animated: animated) } ~~~ @@ -474,7 +505,7 @@ controller. #### Swift ~~~ swift override func childViewControllerForStatusBarStyle() -> UIViewController? { - return self.headerViewController + return headerViewController } ~~~ @@ -499,7 +530,7 @@ imageView.clipsToBounds = YES; #### Swift ~~~ swift -let headerView = self.headerViewController!.headerView +let headerView = headerViewController!.headerView let imageView = ... imageView.frame = headerView.bounds diff --git a/components/FlexibleHeader/examples/FlexibleHeaderConfiguratorExample.m b/components/FlexibleHeader/examples/FlexibleHeaderConfiguratorExample.m index 5a663fd2dc3..9f6ed2ef59e 100644 --- a/components/FlexibleHeader/examples/FlexibleHeaderConfiguratorExample.m +++ b/components/FlexibleHeader/examples/FlexibleHeaderConfiguratorExample.m @@ -20,171 +20,192 @@ #import "FlexibleHeaderConfiguratorSupplemental.h" -@interface FlexibleHeaderConfiguratorExample () - -@property(nonatomic) MDCFlexibleHeaderViewController *fhvc; - +@interface FlexibleHeaderConfiguratorExample () +@property(nonatomic) BOOL overrideStatusBarHidden; @end @implementation FlexibleHeaderConfiguratorExample -- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { - self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; - if (self) { - [self commonFlexibleHeaderConfiguratorExampleInit]; - } - return self; -} +// Invoked when the user has changed a control's value. +- (void)field:(FlexibleHeaderConfiguratorField)field didChangeValue:(NSNumber *)value { + MDCFlexibleHeaderView *headerView = self.fhvc.headerView; + switch (field) { + // Basic behavior + + case FlexibleHeaderConfiguratorFieldCanOverExtend: + headerView.canOverExtend = [value boolValue]; + break; + + case FlexibleHeaderConfiguratorFieldInFrontOfInfiniteContent: + headerView.inFrontOfInfiniteContent = [value boolValue]; + break; + + case FlexibleHeaderConfiguratorFieldHideStatusBar: { + self.overrideStatusBarHidden = [value boolValue]; + + BOOL statusBarCanBeVisible = !self.overrideStatusBarHidden; + headerView.statusBarHintCanOverlapHeader = statusBarCanBeVisible; + + [UIView animateWithDuration:0.4 + animations:^{ + [self setNeedsStatusBarAppearanceUpdate]; + }]; + break; + } + + // Shift behavior -- (instancetype)initWithCoder:(NSCoder *)aDecoder { - self = [super initWithCoder:aDecoder]; - if (self) { - [self commonFlexibleHeaderConfiguratorExampleInit]; + case FlexibleHeaderConfiguratorFieldShiftBehaviorEnabled: { + BOOL isOn = [value boolValue]; + if (!isOn) { + headerView.shiftBehavior = MDCFlexibleHeaderShiftBehaviorDisabled; + [self didChangeValueForField:FlexibleHeaderConfiguratorFieldShiftBehaviorEnabledWithStatusBar + animated:YES]; + } else { + headerView.shiftBehavior = MDCFlexibleHeaderShiftBehaviorEnabled; + } + break; + } + + case FlexibleHeaderConfiguratorFieldShiftBehaviorEnabledWithStatusBar: { + BOOL isOn = [value boolValue]; + if (!isOn) { + headerView.shiftBehavior = MDCFlexibleHeaderShiftBehaviorEnabled; + } else { + headerView.shiftBehavior = MDCFlexibleHeaderShiftBehaviorEnabledWithStatusBar; + [self didChangeValueForField:FlexibleHeaderConfiguratorFieldShiftBehaviorEnabled + animated:YES]; + } + break; + } + + case FlexibleHeaderConfiguratorFieldContentImportance: + headerView.headerContentImportance = ([value boolValue] + ? MDCFlexibleHeaderContentImportanceHigh + : MDCFlexibleHeaderContentImportanceDefault); + break; + + // Header height + + case FlexibleHeaderConfiguratorFieldMinimumHeight: + headerView.minimumHeight = [self heightDenormalized:[value floatValue]]; + break; + + case FlexibleHeaderConfiguratorFieldMaximumHeight: + headerView.maximumHeight = [self heightDenormalized:[value floatValue]]; + break; } - return self; } -- (void)commonFlexibleHeaderConfiguratorExampleInit { - _fhvc = [[MDCFlexibleHeaderViewController alloc] initWithNibName:nil bundle:nil]; - [self addChildViewController:_fhvc]; +#pragma mark - Typical Flexible Header implementations - self.title = @"Header Configuration"; +// Required for shiftBehavior == MDCFlexibleHeaderShiftBehaviorEnabledWithStatusBar. +- (BOOL)prefersStatusBarHidden { + return _overrideStatusBarHidden || self.fhvc.prefersStatusBarHidden; } -- (void)viewDidLoad { - [super viewDidLoad]; - self.view.backgroundColor = [UIColor whiteColor]; - - self.scrollView = [[UIScrollView alloc] init]; - [self.view addSubview:self.scrollView]; - - // If a tableView was being used instead of a scrollView, you would set the trackingScrollView - // to be that tableView and either set the MDCFlexibleHeaderViewController to be the - // UITableViewDelegate or forward the UIScrollViewDelegate methods to - // MDCFlexibleHeaderViewController from the UITableViewDelegate. - self.scrollView.delegate = self.fhvc; - self.fhvc.headerView.trackingScrollView = self.scrollView; - - self.fhvc.view.frame = self.view.bounds; - - [self.view addSubview:self.fhvc.view]; - - [self.fhvc didMoveToParentViewController:self]; - - UIColor *lightBlue500 = [UIColor colorWithRed:0.012 - green:0.663 - blue:0.957 - alpha:1]; - self.fhvc.headerView.backgroundColor = lightBlue500; - [self setupExampleViews:self.fhvc]; - - // Add Back button - UIToolbar *bar = [[UIToolbar alloc] initWithFrame:CGRectZero]; - bar.translatesAutoresizingMaskIntoConstraints = NO; - bar.barTintColor = lightBlue500; - bar.translucent = NO; - bar.clipsToBounds = YES; - - [self.fhvc.headerView addSubview:bar]; - - UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:@"Back" - style:UIBarButtonItemStyleDone - target:self - action:@selector(didTapButton:)]; - backButton.tintColor = [UIColor whiteColor]; - - // Add a title label - UIBarButtonItem *spacer = [[UIBarButtonItem alloc] - initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace - target:nil - action:nil]; - - UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; - titleLabel.text = @"Configurator"; - titleLabel.textColor = [UIColor whiteColor]; - titleLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleTitle2]; - [titleLabel sizeToFit]; - - UIBarButtonItem *titleItem = [[UIBarButtonItem alloc] initWithCustomView:titleLabel]; - - bar.items = @[ backButton, spacer, titleItem, spacer ]; - - NSDictionary *viewBindings = @{ @"bar" : bar }; - NSMutableArray<__kindof NSLayoutConstraint *> *arrayOfConstraints = [NSMutableArray array]; - [arrayOfConstraints addObjectsFromArray:[NSLayoutConstraint - constraintsWithVisualFormat:@"H:|-[bar]-|" - options:0 - metrics:nil - views:viewBindings]]; - [arrayOfConstraints addObjectsFromArray:[NSLayoutConstraint - constraintsWithVisualFormat:@"V:[bar]-8-|" - options:0 - metrics:nil - views:viewBindings]]; - - [self.view addConstraints:arrayOfConstraints]; +- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation { + return UIStatusBarAnimationSlide; } -- (void)viewWillAppear:(BOOL)animated { - [super viewWillAppear:animated]; +#pragma mark - UIScrollViewDelegate - // If the MDCFlexibleHeaderViewController's view is not going to replace a navigation bar, - // comment this line: - [self.navigationController setNavigationBarHidden:YES animated:animated]; +// Note that, unlike the Typical Use example, we are explicitly forwarding the UIScrollViewDelegate +// methods to the header view. This is because this example controller also needs to handle other +// UITableViewDelegate events. + +- (void)scrollViewDidScroll:(UIScrollView *)scrollView { + if (scrollView == self.fhvc.headerView.trackingScrollView) { + [self.fhvc.headerView trackingScrollViewDidScroll]; + } } -- (UIStatusBarStyle)preferredStatusBarStyle { - return UIStatusBarStyleLightContent; +- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { + if (scrollView == self.fhvc.headerView.trackingScrollView) { + [self.fhvc.headerView trackingScrollViewDidEndDecelerating]; + } } -// This method must be implemented for MDCFlexibleHeaderViewController's -// MDCFlexibleHeaderView to properly support MDCFlexibleHeaderShiftBehavior should you choose -// to customize it. -- (UIViewController *)childViewControllerForStatusBarHidden { - return self.fhvc; +- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { + MDCFlexibleHeaderView *headerView = self.fhvc.headerView; + if (scrollView == headerView.trackingScrollView) { + [headerView trackingScrollViewDidEndDraggingWillDecelerate:decelerate]; + } } -#pragma mark - Target Action - -- (void)sliderDidSlide:(UISlider *)sender { - if (sender == self.exampleView.minHeightSlider) { - self.fhvc.headerView.minimumHeight = sender.value; - self.exampleView.maxHeightSlider.value = MAX(self.exampleView.maxHeightSlider.value, - self.fhvc.headerView.minimumHeight); - } else if (sender == self.exampleView.maxHeightSlider) { - self.fhvc.headerView.maximumHeight = sender.value; - self.exampleView.minHeightSlider.value = MIN(self.exampleView.minHeightSlider.value, - self.fhvc.headerView.maximumHeight); +- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView + withVelocity:(CGPoint)velocity + targetContentOffset:(inout CGPoint *)targetContentOffset { + MDCFlexibleHeaderView *headerView = self.fhvc.headerView; + if (scrollView == headerView.trackingScrollView) { + [headerView trackingScrollViewWillEndDraggingWithVelocity:velocity + targetContentOffset:targetContentOffset]; } } -- (void)switchDidToggle:(UISwitch *)sender { - if (sender == self.exampleView.overExtendSwitch) { - self.fhvc.headerView.canOverExtend = sender.isOn; - } else if (sender == self.exampleView.shiftSwitch) { - if (!self.exampleView.shiftSwitch.isOn) { - self.exampleView.shiftStatusBarSwitch.on = NO; - self.fhvc.headerView.shiftBehavior = MDCFlexibleHeaderShiftBehaviorDisabled; - } else { - if (self.fhvc.headerView.shiftBehavior != MDCFlexibleHeaderShiftBehaviorEnabled || - self.fhvc.headerView.shiftBehavior != MDCFlexibleHeaderShiftBehaviorEnabledWithStatusBar) { - self.fhvc.headerView.shiftBehavior = MDCFlexibleHeaderShiftBehaviorEnabled; - } +#pragma mark - MDCFlexibleHeaderViewLayoutDelegate + +- (void)flexibleHeaderViewController:(nonnull MDCFlexibleHeaderViewController *)flexibleHeaderViewController + flexibleHeaderViewFrameDidChange:(nonnull MDCFlexibleHeaderView *)flexibleHeaderView { + CGFloat headerContentAlpha; + switch (flexibleHeaderView.scrollPhase) { + case MDCFlexibleHeaderScrollPhaseCollapsing: + case MDCFlexibleHeaderScrollPhaseOverExtending: + headerContentAlpha = 1; + break; + case MDCFlexibleHeaderScrollPhaseShifting: + headerContentAlpha = 1 - flexibleHeaderView.scrollPhasePercentage; + break; + } + for (UIView *subview in self.fhvc.headerView.subviews) { + subview.alpha = headerContentAlpha; + } +} + +#pragma mark - Field data manipulation + +static const CGFloat kHeightScalar = 300; + +- (CGFloat)normalizedHeight:(CGFloat)height { + return (height - self.minimumHeaderHeight) / (kHeightScalar - self.minimumHeaderHeight); +} + +- (CGFloat)heightDenormalized:(CGFloat)normalized { + return normalized * (kHeightScalar - self.minimumHeaderHeight) + self.minimumHeaderHeight; +} + +- (NSNumber *)valueForField:(FlexibleHeaderConfiguratorField)field { + switch (field) { + case FlexibleHeaderConfiguratorFieldCanOverExtend: + return @(self.fhvc.headerView.canOverExtend); + + case FlexibleHeaderConfiguratorFieldContentImportance: + return @((self.fhvc.headerView.headerContentImportance == MDCFlexibleHeaderContentImportanceHigh)); + + case FlexibleHeaderConfiguratorFieldHideStatusBar: + return @(self.overrideStatusBarHidden); + + case FlexibleHeaderConfiguratorFieldShiftBehaviorEnabled: { + MDCFlexibleHeaderShiftBehavior behavior = self.fhvc.headerView.shiftBehavior; + BOOL enabled = (behavior == MDCFlexibleHeaderShiftBehaviorEnabled || behavior == MDCFlexibleHeaderShiftBehaviorEnabledWithStatusBar); + return @(enabled); } - } else if (sender == self.exampleView.shiftStatusBarSwitch) { - if (sender.isOn) { - self.exampleView.shiftSwitch.on = YES; + + case FlexibleHeaderConfiguratorFieldShiftBehaviorEnabledWithStatusBar: { + MDCFlexibleHeaderShiftBehavior behavior = self.fhvc.headerView.shiftBehavior; + BOOL enabled = (behavior == MDCFlexibleHeaderShiftBehaviorEnabledWithStatusBar); + return @(enabled); } - self.fhvc.headerView.shiftBehavior = - sender.isOn ? MDCFlexibleHeaderShiftBehaviorEnabled : MDCFlexibleHeaderShiftBehaviorDisabled; - } else if (sender == self.exampleView.infiniteContentSwitch) { - self.fhvc.headerView.inFrontOfInfiniteContent = sender.isOn; - } -} + case FlexibleHeaderConfiguratorFieldInFrontOfInfiniteContent: + return @(self.fhvc.headerView.inFrontOfInfiniteContent); -- (void)didTapButton:(id)button { - [self.navigationController popViewControllerAnimated:YES]; + case FlexibleHeaderConfiguratorFieldMinimumHeight: + return @([self normalizedHeight:self.fhvc.headerView.minimumHeight]); + + case FlexibleHeaderConfiguratorFieldMaximumHeight: + return @([self normalizedHeight:self.fhvc.headerView.maximumHeight]); + } } @end diff --git a/components/FlexibleHeader/examples/FlexibleHeaderHorizontalPagingExample.m b/components/FlexibleHeader/examples/FlexibleHeaderHorizontalPagingExample.m new file mode 100644 index 00000000000..e53c37683bd --- /dev/null +++ b/components/FlexibleHeader/examples/FlexibleHeaderHorizontalPagingExample.m @@ -0,0 +1,170 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import "FlexibleHeaderHorizontalPagingSupplemental.h" + +#import "MaterialFlexibleHeader.h" + +static UIColor *HexColor(uint32_t hex) { + return [UIColor colorWithRed:(CGFloat)((uint8_t)(hex >> 16)) / (CGFloat)255 + green:(CGFloat)((uint8_t)(hex >> 8)) / (CGFloat)255 + blue:(CGFloat)((uint8_t)hex) / (CGFloat)255 + alpha:1]; +} + +static const NSUInteger kNumberOfPages = 10; + +@interface FlexibleHeaderHorizontalPagingViewController () +@end + +@implementation FlexibleHeaderHorizontalPagingViewController { + UIScrollView *_pagingScrollView; + NSArray *_pageScrollViews; +} + +- (void)viewDidLoad { + [super viewDidLoad]; + + _pagingScrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds]; + _pagingScrollView.pagingEnabled = YES; + _pagingScrollView.delegate = self; + _pagingScrollView.autoresizingMask = (UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight); + _pagingScrollView.scrollsToTop = NO; + + NSArray *pageColors = @[ HexColor(0x55C4F5), HexColor(0x8BC34A), HexColor(0xFFC107) ]; + + NSMutableArray *pageScrollViews = [NSMutableArray array]; + for (NSUInteger ix = 0; ix < kNumberOfPages; ++ix) { + UIScrollView *scrollView = [[UIScrollView alloc] init]; + scrollView.delegate = self.fhvc; + scrollView.backgroundColor = pageColors[ix % [pageColors count]]; + [_pagingScrollView addSubview:scrollView]; + + [pageScrollViews addObject:scrollView]; + } + _pageScrollViews = pageScrollViews; + + [self.view addSubview:_pagingScrollView]; + + self.fhvc.headerView.trackingScrollView = [_pageScrollViews firstObject]; + + [self typicalFlexibleHeaderViewDidLoad]; +} + +- (void)recalculatePageBounds { + CGRect frame = self.view.bounds; + for (NSUInteger ix = 0; ix < [_pageScrollViews count]; ++ix) { + UIScrollView *scrollView = _pageScrollViews[ix]; + + scrollView.frame = frame; + scrollView.contentSize = CGSizeMake(frame.size.width, frame.size.height * 10); + + frame.origin.x += frame.size.width; + } + + _pagingScrollView.contentSize = CGSizeMake(self.view.bounds.size.width * [_pageScrollViews count], + self.view.bounds.size.height); +} + +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + [self recalculatePageBounds]; + + [self.navigationController setNavigationBarHidden:YES animated:animated]; +} + +- (void)viewWillTransitionToSize:(CGSize)size + withTransitionCoordinator:(id)coordinator { + [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator]; + + [self recalculatePageBounds]; +} + +#pragma mark - UIScrollViewDelegate + +- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { + if (scrollView == _pagingScrollView) { + NSInteger pageIndex = (NSInteger)(scrollView.contentOffset.x / self.view.bounds.size.width); + for (NSInteger ix = MAX(0, pageIndex - 1); + ix <= MIN((NSInteger)_pageScrollViews.count - 1, pageIndex + 1); ++ix) { + if (ix != pageIndex) { + [self.fhvc.headerView trackingScrollWillChangeToScrollView:_pageScrollViews[ix]]; + } + } + } +} + +- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { + if (scrollView == _pagingScrollView) { + NSUInteger pageIndex = (NSUInteger)(scrollView.contentOffset.x / self.view.bounds.size.width); + self.fhvc.headerView.trackingScrollView = _pageScrollViews[pageIndex]; + [self.fhvc.headerView.trackingScrollView flashScrollIndicators]; + } +} + +#pragma mark - From the Typical Use example + +- (instancetype)init { + self = [super init]; + if (self) { + [self commonMDCFlexibleHeaderViewControllerInit]; + } + return self; +} + +- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + if (self) { + [self commonMDCFlexibleHeaderViewControllerInit]; + } + return self; +} + +- (instancetype)initWithCoder:(NSCoder *)aDecoder { + self = [super initWithCoder:aDecoder]; + if (self) { + [self commonMDCFlexibleHeaderViewControllerInit]; + } + return self; +} + +- (void)commonMDCFlexibleHeaderViewControllerInit { + self.fhvc = [[MDCFlexibleHeaderViewController alloc] initWithNibName:nil bundle:nil]; + [self addChildViewController:_fhvc]; +} + +- (void)typicalFlexibleHeaderViewDidLoad { + self.fhvc.view.frame = self.view.bounds; + [self.view addSubview:self.fhvc.view]; + [self.fhvc didMoveToParentViewController:self]; + + // Light blue 500 + self.fhvc.headerView.backgroundColor = [UIColor colorWithRed:0.012 + green:0.663 + blue:0.957 + alpha:1]; +} + +- (UIStatusBarStyle)preferredStatusBarStyle { + return UIStatusBarStyleLightContent; +} + +- (UIViewController *)childViewControllerForStatusBarHidden { + return self.fhvc; +} + +@end diff --git a/components/FlexibleHeader/examples/FlexibleHeaderTypicalUseExample.m b/components/FlexibleHeader/examples/FlexibleHeaderTypicalUseExample.m index b900bced49a..64e2d205159 100644 --- a/components/FlexibleHeader/examples/FlexibleHeaderTypicalUseExample.m +++ b/components/FlexibleHeader/examples/FlexibleHeaderTypicalUseExample.m @@ -1,5 +1,5 @@ /* - Copyright 2015-present Google Inc. All Rights Reserved. + Copyright 2016-present Google Inc. 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. @@ -28,6 +28,14 @@ @interface FlexibleHeaderTypicalUseViewController () @implementation FlexibleHeaderTypicalUseViewController +- (instancetype)init { + self = [super init]; + if (self) { + [self commonMDCFlexibleHeaderViewControllerInit]; + } + return self; +} + - (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; if (self) { @@ -52,7 +60,8 @@ - (void)commonMDCFlexibleHeaderViewControllerInit { - (void)viewDidLoad { [super viewDidLoad]; - self.scrollView = [[UIScrollView alloc] init]; + self.scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds]; + self.scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; [self.view addSubview:self.scrollView]; // If a tableView was being used instead of a scrollView, you would set the trackingScrollView @@ -63,9 +72,7 @@ - (void)viewDidLoad { self.fhvc.headerView.trackingScrollView = self.scrollView; self.fhvc.view.frame = self.view.bounds; - [self.view addSubview:self.fhvc.view]; - [self.fhvc didMoveToParentViewController:self]; // Light blue 500 diff --git a/components/FlexibleHeader/examples/resources/FlexibleHeaderTypicalUseInstructionsView.xib b/components/FlexibleHeader/examples/resources/FlexibleHeaderTypicalUseInstructionsView.xib new file mode 100644 index 00000000000..c024d153fe9 --- /dev/null +++ b/components/FlexibleHeader/examples/resources/FlexibleHeaderTypicalUseInstructionsView.xib @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/components/FlexibleHeader/examples/resources/ic_swap_vert.png b/components/FlexibleHeader/examples/resources/ic_swap_vert.png new file mode 100644 index 00000000000..90f8c4567e2 Binary files /dev/null and b/components/FlexibleHeader/examples/resources/ic_swap_vert.png differ diff --git a/components/FlexibleHeader/examples/resources/ic_swap_vert@2x.png b/components/FlexibleHeader/examples/resources/ic_swap_vert@2x.png new file mode 100644 index 00000000000..9b643bd3bcf Binary files /dev/null and b/components/FlexibleHeader/examples/resources/ic_swap_vert@2x.png differ diff --git a/components/FlexibleHeader/examples/resources/ic_swap_vert@3x.png b/components/FlexibleHeader/examples/resources/ic_swap_vert@3x.png new file mode 100644 index 00000000000..78e865dfae1 Binary files /dev/null and b/components/FlexibleHeader/examples/resources/ic_swap_vert@3x.png differ diff --git a/components/FlexibleHeader/examples/supplemental/FlexibleHeaderConfiguratorControlItem.h b/components/FlexibleHeader/examples/supplemental/FlexibleHeaderConfiguratorControlItem.h new file mode 100644 index 00000000000..33c2571498b --- /dev/null +++ b/components/FlexibleHeader/examples/supplemental/FlexibleHeaderConfiguratorControlItem.h @@ -0,0 +1,39 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +/** IMPORTANT: + This file contains supplemental code used to populate the examples with dummy data and/or + instructions. It is not necessary to import this file to implement any Material Design Components. + */ + +#import + +typedef enum : NSUInteger { + FlexibleHeaderConfiguratorControlTypeSwitch, + FlexibleHeaderConfiguratorControlTypeSlider +} FlexibleHeaderConfiguratorControlType; + +@interface FlexibleHeaderConfiguratorControlItem : NSObject + ++ (instancetype)itemWithTitle:(NSString *)title + controlType:(FlexibleHeaderConfiguratorControlType)controlType + field:(NSUInteger)field; + +@property(nonatomic, strong, readonly) NSString *title; +@property(nonatomic, readonly) FlexibleHeaderConfiguratorControlType controlType; +@property(nonatomic, readonly) NSUInteger field; + +@end diff --git a/components/FlexibleHeader/examples/supplemental/FlexibleHeaderConfiguratorControlItem.m b/components/FlexibleHeader/examples/supplemental/FlexibleHeaderConfiguratorControlItem.m new file mode 100644 index 00000000000..333393ece19 --- /dev/null +++ b/components/FlexibleHeader/examples/supplemental/FlexibleHeaderConfiguratorControlItem.m @@ -0,0 +1,36 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +/** IMPORTANT: + This file contains supplemental code used to populate the examples with dummy data and/or + instructions. It is not necessary to import this file to implement any Material Design Components. + */ + +#import "FlexibleHeaderConfiguratorControlItem.h" + +@implementation FlexibleHeaderConfiguratorControlItem + ++ (instancetype)itemWithTitle:(NSString *)title + controlType:(FlexibleHeaderConfiguratorControlType)controlType + field:(NSUInteger)field { + FlexibleHeaderConfiguratorControlItem *item = [[self alloc] init]; + item->_title = [title copy]; + item->_controlType = controlType; + item->_field = field; + return item; +} + +@end diff --git a/components/FlexibleHeader/examples/supplemental/FlexibleHeaderConfiguratorSupplemental.h b/components/FlexibleHeader/examples/supplemental/FlexibleHeaderConfiguratorSupplemental.h index 11e19897e70..7cd1b8df819 100644 --- a/components/FlexibleHeader/examples/supplemental/FlexibleHeaderConfiguratorSupplemental.h +++ b/components/FlexibleHeader/examples/supplemental/FlexibleHeaderConfiguratorSupplemental.h @@ -1,43 +1,58 @@ -/* IMPORTANT: +/* + Copyright 2016-present Google Inc. 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. + */ + +/** IMPORTANT: This file contains supplemental code used to populate the examples with dummy data and/or instructions. It is not necessary to import this file to implement any Material Design Components. */ #import -@class ExampleConfigurationsView; @class MDCFlexibleHeaderViewController; -@interface FlexibleHeaderConfiguratorExample : UIViewController +typedef enum : NSUInteger { + FlexibleHeaderConfiguratorFieldCanOverExtend, + FlexibleHeaderConfiguratorFieldInFrontOfInfiniteContent, + FlexibleHeaderConfiguratorFieldHideStatusBar, -@property(nonatomic) ExampleConfigurationsView *exampleView; -@property(nonatomic) UIScrollView *scrollView; + FlexibleHeaderConfiguratorFieldContentImportance, + FlexibleHeaderConfiguratorFieldShiftBehaviorEnabled, + FlexibleHeaderConfiguratorFieldShiftBehaviorEnabledWithStatusBar, -- (void)sliderDidSlide:(UISwitch *)sender; -- (void)switchDidToggle:(UISwitch *)sender; + FlexibleHeaderConfiguratorFieldMinimumHeight, + FlexibleHeaderConfiguratorFieldMaximumHeight, +} FlexibleHeaderConfiguratorField; -@end +@interface FlexibleHeaderConfiguratorExample : UITableViewController -@interface FlexibleHeaderConfiguratorExample (Supplemental) +- (NSNumber *)valueForField:(FlexibleHeaderConfiguratorField)field; +- (void)field:(FlexibleHeaderConfiguratorField)field didChangeValue:(NSNumber *)value; + +@property(nonatomic) MDCFlexibleHeaderViewController *fhvc; +@property(nonatomic) CGFloat minimumHeaderHeight; + +// Supplemental properties -- (void)setupExampleViews:(MDCFlexibleHeaderViewController *)fhvc; +@property(nonatomic, copy) NSArray *sections; +@property(nonatomic, copy) NSArray *sectionTitles; @end -@interface ExampleConfigurationsView : UIView - -@property(nonatomic) UISlider *minHeightSlider; -@property(nonatomic) UILabel *minHeightSliderLabel; -@property(nonatomic) UISlider *maxHeightSlider; -@property(nonatomic) UILabel *maxHeightSliderLabel; - -@property(nonatomic) UISwitch *overExtendSwitch; -@property(nonatomic) UILabel *overExtendSwitchLabel; -@property(nonatomic) UISwitch *shiftSwitch; -@property(nonatomic) UILabel *shiftSwitchLabel; -@property(nonatomic) UISwitch *shiftStatusBarSwitch; -@property(nonatomic) UILabel *shiftStatusBarSwitchLabel; -@property(nonatomic) UISwitch *infiniteContentSwitch; -@property(nonatomic) UILabel *infiniteContentSwitchLabel; +@interface FlexibleHeaderConfiguratorExample (Supplemental) + +- (void)didChangeValueForField:(FlexibleHeaderConfiguratorField)field animated:(BOOL)animated; @end diff --git a/components/FlexibleHeader/examples/supplemental/FlexibleHeaderConfiguratorSupplemental.m b/components/FlexibleHeader/examples/supplemental/FlexibleHeaderConfiguratorSupplemental.m index 74babc6e9e3..ebbdaf54ee7 100644 --- a/components/FlexibleHeader/examples/supplemental/FlexibleHeaderConfiguratorSupplemental.m +++ b/components/FlexibleHeader/examples/supplemental/FlexibleHeaderConfiguratorSupplemental.m @@ -1,12 +1,31 @@ -/* IMPORTANT: +/* + Copyright 2016-present Google Inc. 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. + */ + +/** IMPORTANT: This file contains supplemental code used to populate the examples with dummy data and/or instructions. It is not necessary to import this file to implement any Material Design Components. */ #import "FlexibleHeaderConfiguratorSupplemental.h" +#import "FlexibleHeaderConfiguratorControlItem.h" #import "MaterialFlexibleHeader.h" +static const UITableViewStyle kStyle = UITableViewStyleGrouped; + @implementation FlexibleHeaderConfiguratorExample (CatalogByConvention) + (NSArray *)catalogBreadcrumbs { @@ -21,292 +40,212 @@ - (BOOL)catalogShouldHideNavigation { @implementation FlexibleHeaderConfiguratorExample (Supplemental) -- (void)setupExampleViews:(MDCFlexibleHeaderViewController *)fhvc { - self.scrollView.translatesAutoresizingMaskIntoConstraints = NO; - - NSDictionary *viewBindings = @{ @"scrollView" : self.scrollView }; - NSMutableArray<__kindof NSLayoutConstraint *> *arrayOfConstraints = [NSMutableArray array]; - [arrayOfConstraints addObjectsFromArray:[NSLayoutConstraint - constraintsWithVisualFormat:@"H:|[scrollView]|" - options:0 - metrics:nil - views:viewBindings]]; - [arrayOfConstraints addObjectsFromArray:[NSLayoutConstraint - constraintsWithVisualFormat:@"V:|[scrollView]|" - options:0 - metrics:nil - views:viewBindings]]; - - [self.view addConstraints:arrayOfConstraints]; - - self.exampleView = [[ExampleConfigurationsView alloc] initWithFrame:CGRectZero]; - self.exampleView.translatesAutoresizingMaskIntoConstraints = NO; - [self.scrollView addSubview:self.exampleView]; - - [self.exampleView.overExtendSwitch addTarget:self - action:@selector(switchDidToggle:) - forControlEvents:UIControlEventValueChanged]; - [self.exampleView.shiftSwitch addTarget:self - action:@selector(switchDidToggle:) - forControlEvents:UIControlEventValueChanged]; - [self.exampleView.shiftStatusBarSwitch addTarget:self - action:@selector(switchDidToggle:) - forControlEvents:UIControlEventValueChanged]; - [self.exampleView.infiniteContentSwitch addTarget:self - action:@selector(switchDidToggle:) - forControlEvents:UIControlEventValueChanged]; - [self.exampleView.minHeightSlider addTarget:self - action:@selector(sliderDidSlide:) - forControlEvents:UIControlEventValueChanged]; - [self.exampleView.maxHeightSlider addTarget:self - action:@selector(sliderDidSlide:) - forControlEvents:UIControlEventValueChanged]; - - self.exampleView.minHeightSlider.minimumValue = fhvc.headerView.minimumHeight; - self.exampleView.minHeightSlider.maximumValue = 300; - self.exampleView.maxHeightSlider.minimumValue = fhvc.headerView.maximumHeight; - self.exampleView.maxHeightSlider.maximumValue = 300; - - NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:self.exampleView - attribute:NSLayoutAttributeHeight - relatedBy:NSLayoutRelationEqual - toItem:self.view - attribute:NSLayoutAttributeHeight - multiplier:1 - constant:0]; - - NSLayoutConstraint *centerX = [self.exampleView.centerXAnchor - constraintEqualToAnchor:self.view.centerXAnchor]; - NSLayoutConstraint *top = [self.exampleView.topAnchor - constraintEqualToAnchor:self.scrollView.topAnchor]; - NSLayoutConstraint *bottom = [self.exampleView.bottomAnchor - constraintEqualToAnchor:self.scrollView.bottomAnchor]; - NSLayoutConstraint *leading = [self.exampleView.leadingAnchor - constraintEqualToAnchor:self.scrollView.leadingAnchor]; - NSLayoutConstraint *trailing = [self.exampleView.trailingAnchor - constraintEqualToAnchor:self.scrollView.trailingAnchor]; - - [self.view addConstraints:@[ width, centerX, top, bottom, leading, trailing ]]; - - self.exampleView.overExtendSwitch.on = fhvc.headerView.canOverExtend; - self.exampleView.shiftSwitch.on = - fhvc.headerView.shiftBehavior != MDCFlexibleHeaderShiftBehaviorDisabled; - self.exampleView.shiftStatusBarSwitch.on = - fhvc.headerView.shiftBehavior == MDCFlexibleHeaderShiftBehaviorEnabledWithStatusBar; - self.exampleView.infiniteContentSwitch.on = fhvc.headerView.inFrontOfInfiniteContent; - - self.exampleView.minHeightSlider.value = fhvc.headerView.minimumHeight; - self.exampleView.maxHeightSlider.value = fhvc.headerView.maximumHeight; +- (instancetype)init { + return [self initWithStyle:kStyle]; } -@end - -@implementation ExampleConfigurationsView - -- (instancetype)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; +- (instancetype)initWithStyle:(UITableViewStyle)style { + self = [super initWithStyle:kStyle]; if (self) { - [self commonExampleConfigurationsViewInit]; + self.fhvc = [[MDCFlexibleHeaderViewController alloc] initWithNibName:nil bundle:nil]; + [self addChildViewController:self.fhvc]; + + self.title = @"Configurator"; } return self; } -- (instancetype)initWithCoder:(NSCoder *)aDecoder { - self = [super initWithCoder:aDecoder]; - if (self) { - [self commonExampleConfigurationsViewInit]; +- (void)viewWillAppear:(BOOL)animated { + [super viewWillAppear:animated]; + + [self.navigationController setNavigationBarHidden:YES animated:animated]; +} + +- (UIStatusBarStyle)preferredStatusBarStyle { + return UIStatusBarStyleLightContent; +} + +- (void)viewDidLoad { + self.minimumHeaderHeight = self.fhvc.headerView.minimumHeight; + + self.fhvc.headerView.trackingScrollView = self.tableView; + + self.fhvc.view.frame = self.view.bounds; + [self.view addSubview:self.fhvc.view]; + [self.fhvc didMoveToParentViewController:self]; + + UIColor *lightBlue500 = [UIColor colorWithRed:0.012 + green:0.663 + blue:0.957 + alpha:1]; + self.fhvc.headerView.backgroundColor = lightBlue500; + + UILabel *titleLabel = [[UILabel alloc] init]; + CGRect frame = self.fhvc.headerView.bounds; + frame.origin.y += 20; + frame.size.height -= 20; + titleLabel.frame = frame; + titleLabel.text = self.title; + titleLabel.textColor = [UIColor whiteColor]; + titleLabel.font = [UIFont systemFontOfSize:22]; + titleLabel.textAlignment = NSTextAlignmentCenter; + titleLabel.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin; + + [self.fhvc.headerView addSubview:titleLabel]; + + id (^switchItem)(NSString *, FlexibleHeaderConfiguratorField) = + ^(NSString *title, FlexibleHeaderConfiguratorField field) { + FlexibleHeaderConfiguratorControlType type = FlexibleHeaderConfiguratorControlTypeSwitch; + return [FlexibleHeaderConfiguratorControlItem itemWithTitle:title + controlType:type + field:field]; + }; + id (^sliderItem)(NSString *, FlexibleHeaderConfiguratorField) = + ^(NSString *title, FlexibleHeaderConfiguratorField field) { + FlexibleHeaderConfiguratorControlType type = FlexibleHeaderConfiguratorControlTypeSlider; + return [FlexibleHeaderConfiguratorControlItem itemWithTitle:title + controlType:type + field:field]; + }; + id (^filler)(void) = ^{ + return [NSNull null]; + }; + NSMutableArray *sections = [NSMutableArray array]; + NSMutableArray *sectionTitles = [NSMutableArray array]; + void (^createSection)(NSString *, NSArray *) = ^(NSString *title, NSArray *items) { + [sectionTitles addObject:title ?: @""]; + [sections addObject:items ?: @[]]; + }; + + createSection(@"Swipe right to go back", nil); + + createSection(@"Basic behavior", + @[ + switchItem(@"Can over-extend", + FlexibleHeaderConfiguratorFieldCanOverExtend), + switchItem(@"In front of infinite content", + FlexibleHeaderConfiguratorFieldInFrontOfInfiniteContent), + switchItem(@"Hide status bar", + FlexibleHeaderConfiguratorFieldHideStatusBar), + ]); + + createSection(@"Shift behavior", + @[ switchItem(@"Enabled", + FlexibleHeaderConfiguratorFieldShiftBehaviorEnabled), + switchItem(@"Enabled with status bar", + FlexibleHeaderConfiguratorFieldShiftBehaviorEnabledWithStatusBar), + switchItem(@"Header content is important", + FlexibleHeaderConfiguratorFieldContentImportance) ]); + + createSection(@"Header height", + @[ sliderItem(@"Minimum", + FlexibleHeaderConfiguratorFieldMinimumHeight), + sliderItem(@"Maximum", + FlexibleHeaderConfiguratorFieldMaximumHeight) ]); + + NSMutableArray *fillerItems = [NSMutableArray array]; + for (NSUInteger ix = 0; ix < 100; ++ix) { + [fillerItems addObject:filler()]; } - return self; + createSection(nil, fillerItems); + + self.sections = sections; + self.sectionTitles = sectionTitles; + + self.view.backgroundColor = [UIColor whiteColor]; } -- (void)commonExampleConfigurationsViewInit { - self.overExtendSwitch = [[UISwitch alloc] initWithFrame:CGRectZero]; - self.overExtendSwitch.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:self.overExtendSwitch]; - - UIFont *font = [UIFont preferredFontForTextStyle:UIFontTextStyleCaption1]; - NSDictionary *textAttributes = @{NSFontAttributeName : font}; - - self.overExtendSwitchLabel = [[UILabel alloc] initWithFrame:CGRectZero]; - self.overExtendSwitchLabel.translatesAutoresizingMaskIntoConstraints = NO; - NSAttributedString *overExtendText = - [[NSAttributedString alloc] initWithString:@"Can Over-Extend" - attributes:textAttributes]; - self.overExtendSwitchLabel.attributedText = overExtendText; - [self addSubview:self.overExtendSwitchLabel]; - - self.shiftSwitch = [[UISwitch alloc] initWithFrame:CGRectZero]; - self.shiftSwitch.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:self.shiftSwitch]; - - self.shiftSwitchLabel = [[UILabel alloc] initWithFrame:CGRectZero]; - self.shiftSwitchLabel.translatesAutoresizingMaskIntoConstraints = NO; - NSAttributedString *shiftSwitchText = - [[NSAttributedString alloc] initWithString:@"Hides when collapsed" - attributes:textAttributes]; - self.shiftSwitchLabel.attributedText = shiftSwitchText; - [self addSubview:self.shiftSwitchLabel]; - - self.shiftStatusBarSwitch = [[UISwitch alloc] initWithFrame:CGRectZero]; - self.shiftStatusBarSwitch.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:self.shiftStatusBarSwitch]; - - self.shiftStatusBarSwitchLabel = [[UILabel alloc] initWithFrame:CGRectZero]; - self.shiftStatusBarSwitchLabel.translatesAutoresizingMaskIntoConstraints = NO; - NSAttributedString *shiftStatusBarSwitchText = - [[NSAttributedString alloc] initWithString:@"Hides status bar when collapsed" - attributes:textAttributes]; - self.shiftStatusBarSwitchLabel.attributedText = shiftStatusBarSwitchText; - [self addSubview:self.shiftStatusBarSwitchLabel]; - - self.infiniteContentSwitch = [[UISwitch alloc] initWithFrame:CGRectZero]; - self.infiniteContentSwitch.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:self.infiniteContentSwitch]; - - self.infiniteContentSwitchLabel = [[UILabel alloc] initWithFrame:CGRectZero]; - self.infiniteContentSwitchLabel.translatesAutoresizingMaskIntoConstraints = NO; - NSAttributedString *infiniteContentSwitchText = - [[NSAttributedString alloc] initWithString:@"In front of infinite content" - attributes:textAttributes]; - self.infiniteContentSwitchLabel.attributedText = infiniteContentSwitchText; - [self addSubview:self.infiniteContentSwitchLabel]; - - self.minHeightSlider = [[UISlider alloc] initWithFrame:CGRectZero]; - self.minHeightSlider.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:self.minHeightSlider]; - - self.minHeightSliderLabel = [[UILabel alloc] initWithFrame:CGRectZero]; - self.minHeightSliderLabel.translatesAutoresizingMaskIntoConstraints = NO; - NSAttributedString *minHeightSliderText = - [[NSAttributedString alloc] initWithString:@"Minimum Height" - attributes:textAttributes]; - self.minHeightSliderLabel.attributedText = minHeightSliderText; - [self addSubview:self.minHeightSliderLabel]; - - self.maxHeightSlider = [[UISlider alloc] initWithFrame:CGRectZero]; - self.maxHeightSlider.translatesAutoresizingMaskIntoConstraints = NO; - [self addSubview:self.maxHeightSlider]; - - self.maxHeightSliderLabel = [[UILabel alloc] initWithFrame:CGRectZero]; - self.maxHeightSliderLabel.translatesAutoresizingMaskIntoConstraints = NO; - NSAttributedString *maxHeightSliderText = - [[NSAttributedString alloc] initWithString:@"Maximum Height" - attributes:textAttributes]; - self.maxHeightSliderLabel.attributedText = maxHeightSliderText; - [self addSubview:self.maxHeightSliderLabel]; - - NSDictionary *viewBindings = @{ @"overExtendSwitch" : self.overExtendSwitch, - @"overExtendSwitchLabel" : self.overExtendSwitchLabel, - @"shiftSwitch" : self.shiftSwitch, - @"shiftSwitchLabel" : self.shiftSwitchLabel, - @"shiftStatusBarSwitch" : self.shiftStatusBarSwitch, - @"shiftStatusBarSwitchLabel" : self.shiftStatusBarSwitchLabel, - @"infiniteContentSwitch" : self.infiniteContentSwitch, - @"infiniteContentSwitchLabel" : self.infiniteContentSwitchLabel, - @"minHeightSlider" : self.minHeightSlider, - @"minHeightSliderLabel" : self.minHeightSliderLabel, - @"maxHeightSlider" : self.maxHeightSlider, - @"maxHeightSliderLabel" : self.maxHeightSliderLabel }; - - NSMutableArray<__kindof NSLayoutConstraint *> *arrayOfConstraints = - [NSMutableArray array]; - // clang-format off - [arrayOfConstraints addObjectsFromArray: - [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-8-[overExtendSwitch]" - options:NSLayoutFormatAlignAllCenterX - metrics:nil - views:viewBindings]]; - - [arrayOfConstraints addObjectsFromArray:[NSLayoutConstraint - constraintsWithVisualFormat:@"H:|-8-[overExtendSwitchLabel]-[overExtendSwitch]-8-|" - options:NSLayoutFormatAlignAllCenterY - metrics:nil - views:viewBindings]]; - - [arrayOfConstraints addObjectsFromArray:[NSLayoutConstraint - constraintsWithVisualFormat:@"V:[overExtendSwitch]-8-[shiftSwitch]" - options:NSLayoutFormatAlignAllCenterX - metrics:nil - views:viewBindings]]; - - [arrayOfConstraints addObjectsFromArray:[NSLayoutConstraint - constraintsWithVisualFormat:@"H:|-8-[shiftSwitchLabel]-[shiftSwitch]-8-|" - options:NSLayoutFormatAlignAllCenterY - metrics:nil - views:viewBindings]]; - - [arrayOfConstraints addObjectsFromArray:[NSLayoutConstraint - constraintsWithVisualFormat:@"V:[shiftSwitch]-8-[shiftStatusBarSwitch]" - options:NSLayoutFormatAlignAllCenterX - metrics:nil - views:viewBindings]]; - - [arrayOfConstraints addObjectsFromArray:[NSLayoutConstraint - constraintsWithVisualFormat:@"H:|-8-[shiftStatusBarSwitchLabel]-[shiftStatusBarSwitch]-8-|" - options:NSLayoutFormatAlignAllCenterY - metrics:nil - views:viewBindings]]; - - [arrayOfConstraints addObjectsFromArray:[NSLayoutConstraint - constraintsWithVisualFormat:@"V:[shiftStatusBarSwitch]-8-[infiniteContentSwitch]" - options:NSLayoutFormatAlignAllCenterX - metrics:nil - views:viewBindings]]; - - [arrayOfConstraints addObjectsFromArray:[NSLayoutConstraint - constraintsWithVisualFormat:@"H:|-8-[infiniteContentSwitchLabel]-[infiniteContentSwitch]-8-|" - options:NSLayoutFormatAlignAllCenterY - metrics:nil - views:viewBindings]]; - - [arrayOfConstraints addObjectsFromArray:[NSLayoutConstraint - constraintsWithVisualFormat:@"V:[infiniteContentSwitchLabel]-16-[minHeightSliderLabel]" - options:0 - metrics:nil - views:viewBindings]]; - - [arrayOfConstraints addObjectsFromArray:[NSLayoutConstraint - constraintsWithVisualFormat:@"H:|-8-[minHeightSliderLabel]-8-|" - options:0 - metrics:nil - views:viewBindings]]; - - [arrayOfConstraints addObjectsFromArray:[NSLayoutConstraint - constraintsWithVisualFormat:@"V:[minHeightSliderLabel]-8-[minHeightSlider]" - options:0 - metrics:nil - views:viewBindings]]; - - [arrayOfConstraints addObjectsFromArray:[NSLayoutConstraint - constraintsWithVisualFormat:@"H:|-8-[minHeightSlider]-8-|" - options:0 - metrics:nil - views:viewBindings]]; - - [arrayOfConstraints addObjectsFromArray:[NSLayoutConstraint - constraintsWithVisualFormat:@"V:[minHeightSlider]-8-[maxHeightSliderLabel]" - options:0 - metrics:nil - views:viewBindings]]; - - [arrayOfConstraints addObjectsFromArray:[NSLayoutConstraint - constraintsWithVisualFormat:@"H:|-8-[maxHeightSliderLabel]-8-|" - options:0 - metrics:nil - views:viewBindings]]; - - [arrayOfConstraints addObjectsFromArray:[NSLayoutConstraint - constraintsWithVisualFormat:@"V:[maxHeightSliderLabel]-8-[maxHeightSlider]" - options:0 - metrics:nil - views:viewBindings]]; - - [arrayOfConstraints addObjectsFromArray:[NSLayoutConstraint - constraintsWithVisualFormat:@"H:|-8-[maxHeightSlider]-8-|" - options:0 - metrics:nil - views:viewBindings]]; - // clang-format on - [self addConstraints:arrayOfConstraints]; +- (UIControl *)controlForControlType:(FlexibleHeaderConfiguratorControlType)controlType { + switch (controlType) { + case FlexibleHeaderConfiguratorControlTypeSwitch: + return [[UISwitch alloc] init]; + + case FlexibleHeaderConfiguratorControlTypeSlider: + return [[UISlider alloc] init]; + } +} + +- (NSNumber *)valueForControl:(UIControl *)control { + if ([control isKindOfClass:[UISwitch class]]) { + return @([(UISwitch *)control isOn]); + } else if ([control isKindOfClass:[UISlider class]]) { + return @([(UISlider *)control value]); + } + return nil; +} + +- (void)setValue:(NSNumber *)value onControl:(UIControl *)control animated:(BOOL)animated { + if ([control isKindOfClass:[UISwitch class]]) { + [(UISwitch *)control setOn:[value boolValue] animated:animated]; + } else if ([control isKindOfClass:[UISlider class]]) { + [(UISlider *)control setValue:[value floatValue] animated:animated]; + } } + +- (void)didChangeValueForField:(FlexibleHeaderConfiguratorField)field animated:(BOOL)animated { + UIControl *control = [self.tableView viewWithTag:field]; + NSNumber *value = [self valueForField:field]; + [self setValue:value onControl:control animated:animated]; +} + +#pragma mark - UITableViewDataSource + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { + return [self.sections count]; +} + +- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { + return [self.sections[section] count]; +} + +- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { + return self.sectionTitles[section]; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView + cellForRowAtIndexPath:(NSIndexPath *)indexPath { + UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"]; + if (!cell) { + cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle + reuseIdentifier:@"cell"]; + } + id item = self.sections[indexPath.section][indexPath.row]; + if ([item isKindOfClass:[FlexibleHeaderConfiguratorControlItem class]]) { + FlexibleHeaderConfiguratorControlItem *fieldItem = (FlexibleHeaderConfiguratorControlItem *)item; + + cell.textLabel.font = [UIFont preferredFontForTextStyle:UIFontTextStyleCaption1]; + + cell.textLabel.text = fieldItem.title; + + UIControl *control = [self controlForControlType:fieldItem.controlType]; + control.tag = fieldItem.field; + [control addTarget:self + action:@selector(didChangeControl:) + forControlEvents:UIControlEventValueChanged]; + [self setValue:[self valueForField:fieldItem.field] onControl:control animated:NO]; + cell.accessoryView = control; + + } else { + cell.textLabel.text = nil; + cell.accessoryView = nil; + } + + return cell; +} + +- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath { + return NO; +} + +#pragma mark - User actions + +- (void)didChangeControl:(UIControl *)sender { + NSNumber *value = [self valueForControl:sender]; + [self field:(FlexibleHeaderConfiguratorField)sender.tag didChangeValue:value]; +} + +- (void)didTapBackButton:(id)button { + [self.navigationController popViewControllerAnimated:YES]; +} + @end diff --git a/components/FlexibleHeader/examples/supplemental/FlexibleHeaderHorizontalPagingSupplemental.h b/components/FlexibleHeader/examples/supplemental/FlexibleHeaderHorizontalPagingSupplemental.h new file mode 100644 index 00000000000..c8a418fb630 --- /dev/null +++ b/components/FlexibleHeader/examples/supplemental/FlexibleHeaderHorizontalPagingSupplemental.h @@ -0,0 +1,30 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +/** IMPORTANT: + This file contains supplemental code used to populate the demos with dummy data or instructions. + It is not necessary to import this file to implement any Material Design Components. + */ + +#import + +@class MDCFlexibleHeaderViewController; + +@interface FlexibleHeaderHorizontalPagingViewController : UIViewController + +@property(nonatomic, strong) MDCFlexibleHeaderViewController *fhvc; + +@end diff --git a/components/FlexibleHeader/examples/supplemental/FlexibleHeaderHorizontalPagingSupplemental.m b/components/FlexibleHeader/examples/supplemental/FlexibleHeaderHorizontalPagingSupplemental.m new file mode 100644 index 00000000000..2ffcd2485cc --- /dev/null +++ b/components/FlexibleHeader/examples/supplemental/FlexibleHeaderHorizontalPagingSupplemental.m @@ -0,0 +1,36 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +/** IMPORTANT: + This file contains supplemental code used to populate the demos with dummy data or instructions. + It is not necessary to import this file to implement any Material Design Components. + */ + +#import "FlexibleHeaderHorizontalPagingSupplemental.h" + +#import "MaterialFlexibleHeader.h" + +@implementation FlexibleHeaderHorizontalPagingViewController (CatalogByConvention) + ++ (NSArray *)catalogBreadcrumbs { + return @[ @"Flexible Header", @"Horizontal Paging" ]; +} + +- (BOOL)catalogShouldHideNavigation { + return YES; +} + +@end diff --git a/components/FlexibleHeader/examples/supplemental/FlexibleHeaderTypicalUseSupplemental.h b/components/FlexibleHeader/examples/supplemental/FlexibleHeaderTypicalUseSupplemental.h index f01a071e3c6..ff4ee71bb61 100644 --- a/components/FlexibleHeader/examples/supplemental/FlexibleHeaderTypicalUseSupplemental.h +++ b/components/FlexibleHeader/examples/supplemental/FlexibleHeaderTypicalUseSupplemental.h @@ -1,16 +1,34 @@ -/* IMPORTANT: +/* + Copyright 2016-present Google Inc. 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. + */ + +/** IMPORTANT: This file contains supplemental code used to populate the demos with dummy data or instructions. It is not necessary to import this file to implement any Material Design Components. */ #import -@class ExampleInstructionsViewFlexibleHeaderTypicalUse; +@class FlexibleHeaderTypicalUseInstructionsView; @interface FlexibleHeaderTypicalUseViewController : UIViewController -@property(nonatomic) ExampleInstructionsViewFlexibleHeaderTypicalUse *exampleView; -@property(nonatomic) UIScrollView *scrollView; +@property(nonatomic, strong, nullable) IBOutlet UIView *exampleView; +@property(nonatomic, strong, nullable) UIScrollView *scrollView; + +@property(nonatomic, strong, nullable) IBOutlet UIImageView *imageView; @end diff --git a/components/FlexibleHeader/examples/supplemental/FlexibleHeaderTypicalUseSupplemental.m b/components/FlexibleHeader/examples/supplemental/FlexibleHeaderTypicalUseSupplemental.m index 3d2521af09c..936894b437a 100644 --- a/components/FlexibleHeader/examples/supplemental/FlexibleHeaderTypicalUseSupplemental.m +++ b/components/FlexibleHeader/examples/supplemental/FlexibleHeaderTypicalUseSupplemental.m @@ -1,4 +1,20 @@ -/* IMPORTANT: +/* + Copyright 2016-present Google Inc. 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. + */ + +/** IMPORTANT: This file contains supplemental code used to populate the examples with dummy data and/or instructions. It is not necessary to import this file to implement any Material Design Components. */ @@ -7,16 +23,10 @@ #import "FlexibleHeaderTypicalUseSupplemental.h" -#pragma mark - FlexibleHeaderTypicalUseViewController - -@interface ExampleInstructionsViewFlexibleHeaderTypicalUse : UIView - -@end - @implementation FlexibleHeaderTypicalUseViewController (CatalogByConvention) + (NSArray *)catalogBreadcrumbs { - return @[ @"Flexible Header", @"Typical use" ]; + return @[ @"Flexible Header", @"Flexible Header" ]; } - (BOOL)catalogShouldHideNavigation { @@ -28,163 +38,30 @@ + (NSString *)catalogDescription { " UIScrollViewDelegate events."; } -- (BOOL)catalogIsPrimaryDemo { ++ (BOOL)catalogIsPrimaryDemo { return YES; } @end -@implementation FlexibleHeaderTypicalUseViewController (Rotation) - -- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation - duration:(NSTimeInterval)duration { - [self.exampleView setNeedsDisplay]; -} - -@end - @implementation FlexibleHeaderTypicalUseViewController (Supplemental) - (void)setupExampleViews { - self.scrollView.translatesAutoresizingMaskIntoConstraints = NO; - - NSDictionary *viewBindings = @{ @"scrollView" : self.scrollView }; - NSMutableArray<__kindof NSLayoutConstraint *> *arrayOfConstraints = [NSMutableArray array]; - [arrayOfConstraints addObjectsFromArray:[NSLayoutConstraint - constraintsWithVisualFormat:@"H:|[scrollView]|" - options:0 - metrics:nil - views:viewBindings]]; - [arrayOfConstraints addObjectsFromArray:[NSLayoutConstraint - constraintsWithVisualFormat:@"V:|[scrollView]|" - options:0 - metrics:nil - views:viewBindings]]; - - [self.view addConstraints:arrayOfConstraints]; - - self.exampleView = [[ExampleInstructionsViewFlexibleHeaderTypicalUse alloc] - initWithFrame:self.scrollView.bounds]; - [self.scrollView addSubview:self.exampleView]; - - self.exampleView.translatesAutoresizingMaskIntoConstraints = NO; - - NSLayoutConstraint *width = [NSLayoutConstraint constraintWithItem:self.exampleView - attribute:NSLayoutAttributeHeight - relatedBy:NSLayoutRelationEqual - toItem:self.view - attribute:NSLayoutAttributeHeight - multiplier:1 - constant:0]; - - NSLayoutConstraint *centerX = [self.exampleView.centerXAnchor - constraintEqualToAnchor:self.view.centerXAnchor]; - NSLayoutConstraint *top = [self.exampleView.topAnchor - constraintEqualToAnchor:self.scrollView.topAnchor]; - NSLayoutConstraint *bottom = [self.exampleView.bottomAnchor - constraintEqualToAnchor:self.scrollView.bottomAnchor]; - NSLayoutConstraint *leading = [self.exampleView.leadingAnchor - constraintEqualToAnchor:self.scrollView.leadingAnchor]; - NSLayoutConstraint *trailing = [self.exampleView.trailingAnchor - constraintEqualToAnchor:self.scrollView.trailingAnchor]; - - [self.view addConstraints:@[ width, centerX, top, bottom, leading, trailing ]]; -} - -@end - -@implementation ExampleInstructionsViewFlexibleHeaderTypicalUse - -- (void)drawRect:(CGRect)rect { - [[UIColor whiteColor] setFill]; - [[UIBezierPath bezierPathWithRect:rect] fill]; + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + [bundle loadNibNamed:@"FlexibleHeaderTypicalUseInstructionsView" owner:self options:nil]; - CGSize textSize = [self textSizeForRect:rect]; - CGRect rectForText = CGRectMake(rect.origin.x + rect.size.width / 2.f - textSize.width / 2.f, - rect.origin.y + rect.size.height / 2.f - textSize.height / 2.f, - textSize.width, textSize.height); - [[self instructionsString] drawInRect:rectForText]; - [self drawArrowWithFrame:CGRectMake(rect.size.width / 2.f - 12.f, - rect.size.height / 2.f - 58.f - 12.f, 24.f, 24.f)]; -} + self.imageView.image = [self.imageView.image + imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; + [self.scrollView addSubview:self.exampleView]; -- (CGSize)textSizeForRect:(CGRect)frame { - return [[self instructionsString] - boundingRectWithSize:frame.size - options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading - context:nil] - .size; + self.view.backgroundColor = [UIColor whiteColor]; } -- (NSAttributedString *)instructionsString { - NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init]; - [style setAlignment:NSTextAlignmentCenter]; - [style setLineBreakMode:NSLineBreakByWordWrapping]; - - NSDictionary *instructionAttributes1 = - @{NSFontAttributeName : [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline], - NSForegroundColorAttributeName : [UIColor colorWithRed:0.459 - green:0.459 - blue:0.459 - alpha:0.87f], - NSParagraphStyleAttributeName : style}; - NSDictionary *instructionAttributes2 = - @{NSFontAttributeName : [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline], - NSForegroundColorAttributeName : [UIColor colorWithRed:0.459 - green:0.459 - blue:0.459 - alpha:0.87f], - NSParagraphStyleAttributeName : style}; - - NSString *instructionText1 = @"PULL DOWN\n\nMDCFlexibleHeaderViewController\nallows the\ - blue area to stretch\nwhen scrolled down.\n\n\n\n\n\n"; - NSMutableAttributedString *instructionsAttributedString1 = [[NSMutableAttributedString alloc] - initWithString:instructionText1]; - [instructionsAttributedString1 setAttributes:instructionAttributes1 - range:NSMakeRange(0, 9)]; - [instructionsAttributedString1 setAttributes:instructionAttributes2 - range:NSMakeRange(9, instructionText1.length - 9)]; - - NSString *instructionText2 = @"PUSH UP\n\nIt also adds a shadow\nwhen scrolled up.\n\n\n\n\n\n\n"; - NSMutableAttributedString *instructionsAttributedString2 = [[NSMutableAttributedString alloc] - initWithString:instructionText2]; - [instructionsAttributedString2 setAttributes:instructionAttributes1 - range:NSMakeRange(0, 7)]; - [instructionsAttributedString2 setAttributes:instructionAttributes2 - range:NSMakeRange(7, instructionText2.length - 7)]; - - [instructionsAttributedString1 appendAttributedString:instructionsAttributedString2]; - - return instructionsAttributedString1; -} +- (void)viewDidLayoutSubviews { + [super viewDidLayoutSubviews]; -- (void)drawArrowWithFrame:(CGRect)frame { - UIBezierPath *bezierPath = [UIBezierPath bezierPath]; - [bezierPath moveToPoint:CGPointMake(CGRectGetMinX(frame) + 16, CGRectGetMinY(frame) + 17.01)]; - [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + 16, CGRectGetMinY(frame) + 10)]; - [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + 14, CGRectGetMinY(frame) + 10)]; - [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + 14, CGRectGetMinY(frame) + 17.01)]; - [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + 11, CGRectGetMinY(frame) + 17.01)]; - [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + 15, CGRectGetMinY(frame) + 21)]; - [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + 19, CGRectGetMinY(frame) + 17.01)]; - [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + 16, CGRectGetMinY(frame) + 17.01)]; - [bezierPath closePath]; - [bezierPath moveToPoint:CGPointMake(CGRectGetMinX(frame) + 9, CGRectGetMinY(frame) + 3)]; - [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + 5, CGRectGetMinY(frame) + 6.99)]; - [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + 8, CGRectGetMinY(frame) + 6.99)]; - [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + 8, CGRectGetMinY(frame) + 14)]; - [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + 10, CGRectGetMinY(frame) + 14)]; - [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + 10, CGRectGetMinY(frame) + 6.99)]; - [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + 13, CGRectGetMinY(frame) + 6.99)]; - [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + 9, CGRectGetMinY(frame) + 3)]; - [bezierPath closePath]; - bezierPath.miterLimit = 4; - - [[UIColor colorWithRed:0.459 - green:0.459 - blue:0.459 - alpha:0.87f] setFill]; - [bezierPath fill]; + self.exampleView.frame = self.view.bounds; + self.scrollView.contentSize = self.view.bounds.size; } @end diff --git a/components/FlexibleHeader/src/MDCFlexibleHeaderView.h b/components/FlexibleHeader/src/MDCFlexibleHeaderView.h index fd38221692b..ba1dc77778b 100644 --- a/components/FlexibleHeader/src/MDCFlexibleHeaderView.h +++ b/components/FlexibleHeader/src/MDCFlexibleHeaderView.h @@ -289,8 +289,21 @@ typedef NS_ENUM(NSInteger, MDCFlexibleHeaderScrollPhase) { #pragma mark Bounding Dimensions -@property(nonatomic) CGFloat minimumHeight; ///< The minimum height that this header can shrink to. -@property(nonatomic) CGFloat maximumHeight; ///< The maximum height that this header can expand to. +/** + The minimum height that this header can shrink to. + + If you change the value of this property and the maximumHeight of the receiver is below the new + minimumHeight, maximumHeight will be adjusted to match the new minimum value. + */ +@property(nonatomic) CGFloat minimumHeight; + +/** + The maximum height that this header can expand to. + + If you change the value of this property and the minimumHeight of the receiver is above the new + maximumHeight, minimumHeight will be adjusted to match the new maximumHeight. + */ +@property(nonatomic) CGFloat maximumHeight; #pragma mark Behaviors @@ -315,6 +328,21 @@ typedef NS_ENUM(NSInteger, MDCFlexibleHeaderScrollPhase) { */ @property(nonatomic) BOOL canOverExtend; +/** + A hint stating whether or not the operating system's status bar frame can ever overlap the header's + frame. + + This property is enabled by default with the expectation that the flexible header will primarily + be used in full-screen settings on the phone. + + Disabling this property informs the flexible header that it should not concern itself with the + status bar in any manner. shiftBehavior .EnabledWithStatusBar will be treated simply as .Enabled + in this case. + + Default: YES + */ +@property(nonatomic) BOOL statusBarHintCanOverlapHeader; + @property(nonatomic) float visibleShadowOpacity; ///< The visible shadow opacity. Default: 0.4 #pragma mark Scroll View Tracking diff --git a/components/FlexibleHeader/src/MDCFlexibleHeaderView.m b/components/FlexibleHeader/src/MDCFlexibleHeaderView.m index 61f68b58e22..1b8430b101e 100644 --- a/components/FlexibleHeader/src/MDCFlexibleHeaderView.m +++ b/components/FlexibleHeader/src/MDCFlexibleHeaderView.m @@ -148,13 +148,12 @@ @implementation MDCFlexibleHeaderView { @synthesize sharedWithManyScrollViews = _sharedWithManyScrollViews; @synthesize visibleShadowOpacity = _visibleShadowOpacity; -- (void)dealloc { #if DEBUG +- (void)dealloc { [_trackingScrollView.panGestureRecognizer removeTarget:self action:@selector(fhv_scrollViewDidPan:)]; -#endif - [self fhv_removeInsetsFromScrollView:_trackingScrollView]; } +#endif - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; @@ -173,6 +172,7 @@ - (instancetype)initWithFrame:(CGRect)frame { _trackedScrollViews = [NSMapTable mapTableWithKeyOptions:keyOptions valueOptions:valueOptions]; _headerContentImportance = MDCFlexibleHeaderContentImportanceDefault; + _statusBarHintCanOverlapHeader = YES; _minimumHeight = kFlexibleHeaderDefaultHeight; _maximumHeight = kFlexibleHeaderDefaultHeight; @@ -368,13 +368,32 @@ - (CGFloat)fhv_contentBottomEdge { return _trackingScrollView.contentSize.height; } +// Returns a value indicating how much the header is overlapping the tracking scroll view's content. +// > 0 overlapping the content +// = 0 attached to top of content +// < 0 the content is below the header +- (CGFloat)fhv_projectedHeaderBottomEdge { + CGFloat offsetWithoutInset = [self fhv_contentOffsetWithoutInjectedTopInset]; + CGRect projectedFrame = [self convertRect:self.bounds toView:self.trackingScrollView.superview]; + CGFloat frameBottomEdge = CGRectGetMaxY(projectedFrame); + return frameBottomEdge + offsetWithoutInset; +} + - (CGFloat)fhv_accumulatorMax { - return (self.hidesStatusBarWhenCollapsed ? _minimumHeight - : _minimumHeight - kExpectedStatusBarHeight); + BOOL shouldCollapseToStatusBar = [self fhv_shouldCollapseToStatusBar]; + return (shouldCollapseToStatusBar ? _minimumHeight - kExpectedStatusBarHeight : _minimumHeight); } #pragma mark Logical short forms +- (BOOL)fhv_shouldAllowShifting { + return self.hidesStatusBarWhenCollapsed && self.statusBarHintCanOverlapHeader; +} + +- (BOOL)fhv_shouldCollapseToStatusBar { + return !self.hidesStatusBarWhenCollapsed && self.statusBarHintCanOverlapHeader; +} + - (BOOL)fhv_canShiftOffscreen { return ((_shiftBehavior == MDCFlexibleHeaderShiftBehaviorEnabled || _shiftBehavior == MDCFlexibleHeaderShiftBehaviorEnabledWithStatusBar) && @@ -388,7 +407,8 @@ - (BOOL)fhv_isPartiallyShifted { // The flexible header is "in front of" the content. - (BOOL)fhv_isDetachedFromTopOfContent { - return [self fhv_contentOffsetWithoutInjectedTopInset] >= 0; + // Epsilon here is somewhat large in order to be visually-forgiving for sub-point situations. + return [self fhv_projectedHeaderBottomEdge] > (CGFloat)0.5; } - (BOOL)fhv_isOverExtendingBottom { @@ -410,7 +430,7 @@ - (void)fhv_recalculatePhase { _scrollPhase = MDCFlexibleHeaderScrollPhaseShifting; _scrollPhaseValue = frame.origin.y + _minimumHeight; CGFloat adjustedHeight = _minimumHeight; - if (!self.hidesStatusBarWhenCollapsed) { + if ([self fhv_shouldCollapseToStatusBar]) { adjustedHeight -= kExpectedStatusBarHeight; } if (adjustedHeight > 0) { @@ -493,6 +513,8 @@ - (void)fhv_shiftDisplayLinkDidFire:(CADisplayLink *)displayLink { _shiftOffscreenAccumulator += kAttachmentCoefficient * distanceToDestination * duration; _shiftOffscreenAccumulator = MAX(0, MIN([self fhv_accumulatorMax], _shiftOffscreenAccumulator)); + [_statusBarShifter setOffset:_shiftOffscreenAccumulator]; + // Have we reached our destination? if (fabs(destination - _shiftOffscreenAccumulator) <= kShiftEpsilon) { _shiftOffscreenAccumulator = destination; @@ -513,16 +535,9 @@ - (void)fhv_accumulatorDidChange { return; } - CGFloat frameBottomEdge; - CGRect frame = self.frame; - // Calculate the frame's bottom edge in visual relation to the tracking scroll view. - CGRect projectedFrame = [self convertRect:self.bounds toView:self.trackingScrollView.superview]; - frameBottomEdge = (float)CGRectGetMaxY(projectedFrame); - - CGFloat offsetWithoutInset = [self fhv_contentOffsetWithoutInjectedTopInset]; - frameBottomEdge = (float)(frameBottomEdge + offsetWithoutInset); + CGFloat frameBottomEdge = [self fhv_projectedHeaderBottomEdge]; frameBottomEdge = MAX(0, MIN(kShadowScaleLength, frameBottomEdge)); CGFloat boundedAccumulator = MIN([self fhv_accumulatorMax], _shiftOffscreenAccumulator); @@ -700,6 +715,8 @@ - (void)fhv_commitAccumulatorToFrame { [self fhv_accumulatorDidChange]; [self fhv_recalculatePhase]; + [_statusBarShifter setOffset:_shiftOffscreenAccumulator]; + [self.delegate flexibleHeaderViewFrameDidChange:self]; } @@ -870,6 +887,17 @@ - (BOOL)hidesStatusBarWhenCollapsed { !_trackingScrollView.pagingEnabled); } +- (void)setstatusBarHintCanOverlapHeader:(BOOL)statusBarHintCanOverlapHeader { + if (_statusBarHintCanOverlapHeader == statusBarHintCanOverlapHeader) { + return; + } + _statusBarHintCanOverlapHeader = statusBarHintCanOverlapHeader; + + _statusBarShifter.enabled = [self fhv_shouldAllowShifting]; + + [self fhv_startDisplayLink]; +} + - (void)setShiftBehavior:(MDCFlexibleHeaderShiftBehavior)shiftBehavior { if (_shiftBehavior == shiftBehavior) { return; @@ -878,7 +906,7 @@ - (void)setShiftBehavior:(MDCFlexibleHeaderShiftBehavior)shiftBehavior { shiftBehavior == MDCFlexibleHeaderShiftBehaviorDisabled); _shiftBehavior = shiftBehavior; - _statusBarShifter.enabled = self.hidesStatusBarWhenCollapsed; + _statusBarShifter.enabled = [self fhv_shouldAllowShifting]; if (needsShiftOnScreen) { _wantsToBeHidden = NO; @@ -966,7 +994,11 @@ - (void)setMinimumHeight:(CGFloat)minimumHeight { _minimumHeight = minimumHeight; - [self fhv_updateLayout]; + if (_minimumHeight > _maximumHeight) { + [self setMaximumHeight:_minimumHeight]; + } else { + [self fhv_updateLayout]; + } } - (void)setMaximumHeight:(CGFloat)maximumHeight { @@ -991,7 +1023,11 @@ - (void)setMaximumHeight:(CGFloat)maximumHeight { _trackingScrollView.contentOffset = originalOffset; } - [self fhv_updateLayout]; + if (_maximumHeight < _minimumHeight) { + [self setMinimumHeight:_maximumHeight]; + } else { + [self fhv_updateLayout]; + } } - (void)setInFrontOfInfiniteContent:(BOOL)inFrontOfInfiniteContent { diff --git a/components/FlexibleHeader/tests/unit/FlexibleHeaderView324Tests.swift b/components/FlexibleHeader/tests/unit/FlexibleHeaderView324Tests.swift new file mode 100644 index 00000000000..e61ac8482b1 --- /dev/null +++ b/components/FlexibleHeader/tests/unit/FlexibleHeaderView324Tests.swift @@ -0,0 +1,59 @@ +/* +Copyright 2016-present Google Inc. 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. +*/ + +import XCTest +import MaterialComponents + +// Tests confirming that setting the flexible header view's min or max height will bound the +// corresponding value. +// +// Based on issue https://github.com/google/material-components-ios/issues/324 +class FlexibleHeaderView324Tests: XCTestCase { + + var headerView: MDCFlexibleHeaderView! + + override func setUp() { + headerView = MDCFlexibleHeaderView() + } + + // Side effect cases + + func testSettingMinAboveMax() { + headerView.minimumHeight = headerView.maximumHeight + 10 + + XCTAssertEqual(headerView.maximumHeight, headerView.minimumHeight) + } + + func testSettingMaxBelowMin() { + headerView.maximumHeight = headerView.minimumHeight - 10 + + XCTAssertEqual(headerView.maximumHeight, headerView.minimumHeight) + } + + // No side effect cases + + func testSettingMinBelowMax() { + headerView.minimumHeight = headerView.maximumHeight - 10 + + XCTAssertGreaterThan(headerView.maximumHeight, headerView.minimumHeight) + } + + func testSettingMaxAboveMin() { + headerView.maximumHeight = headerView.minimumHeight + 10 + + XCTAssertGreaterThan(headerView.maximumHeight, headerView.minimumHeight) + } +} diff --git a/components/FontDiskLoader/.jazzy.yaml b/components/FontDiskLoader/.jazzy.yaml new file mode 100644 index 00000000000..6fa1baca600 --- /dev/null +++ b/components/FontDiskLoader/.jazzy.yaml @@ -0,0 +1,5 @@ +# Auto-generated by scripts/generate_jazzy_yamls.sh. Used primarily by scripts/external/arc-jazzy-linter. +module: FontDiskLoader +umbrella_header: src/MaterialFontDiskLoader.h +objc: true +sdk: iphonesimulator diff --git a/components/FontDiskLoader/README.md b/components/FontDiskLoader/README.md index 2df860329d1..d3eaf92d08b 100644 --- a/components/FontDiskLoader/README.md +++ b/components/FontDiskLoader/README.md @@ -1,7 +1,7 @@ --- title: "FontDiskLoader" layout: detail -section: documentation +section: components excerpt: "Registers a single custom font asset from disk." --- #FontDiskLoader diff --git a/components/FontDiskLoader/examples/FontDiskLoaderSimipleExample.m b/components/FontDiskLoader/examples/FontDiskLoaderSimipleExample.m index 588dd6970d5..7cf6e0b696c 100644 --- a/components/FontDiskLoader/examples/FontDiskLoaderSimipleExample.m +++ b/components/FontDiskLoader/examples/FontDiskLoaderSimipleExample.m @@ -27,7 +27,7 @@ @implementation FontDiskLoaderSimpleExample // TODO: Support other categorizational methods. + (NSArray *)catalogBreadcrumbs { - return @[ @"Roboto Font Loader", @"Font Disk Loader" ]; + return @[ @"Typography and Fonts", @"Font Disk Loader" ]; } - (void)viewDidLoad { diff --git a/components/HeaderStackView/.jazzy.yaml b/components/HeaderStackView/.jazzy.yaml index 1111b18f3ea..ed368b2b9e5 100644 --- a/components/HeaderStackView/.jazzy.yaml +++ b/components/HeaderStackView/.jazzy.yaml @@ -1,4 +1,5 @@ -module_name: HeaderStackView +# Auto-generated by scripts/generate_jazzy_yamls.sh. Used primarily by scripts/external/arc-jazzy-linter. +module: HeaderStackView umbrella_header: src/MaterialHeaderStackView.h objc: true sdk: iphonesimulator diff --git a/components/HeaderStackView/README.md b/components/HeaderStackView/README.md index 11c2cf428ed..3c843a0b603 100644 --- a/components/HeaderStackView/README.md +++ b/components/HeaderStackView/README.md @@ -1,7 +1,7 @@ --- title: "Header Stack View" layout: detail -section: documentation +section: components excerpt: "The Header Stack View component is a view that coordinates the layout of two vertically stacked bar views." --- # Header Stack View @@ -55,7 +55,9 @@ $ pod install ## Overview -This view's sole purpose is to facilitate the relative layout of two horizontal bars. +This view's sole purpose is to facilitate the relative layout of two horizontal bars. The bottom bar +will bottom align and be of fixed height. The top bar will stretch to fill the remaining space if +there is any. The top bar is typically a navigation bar. The bottom bar, when provided, is typically a tab bar. @@ -94,6 +96,7 @@ MDCHeaderStackView *headerStackView = [[MDCHeaderStackView alloc] init]; #### Swift ~~~ swift +let headerStackView = MDCHeaderStackView() ~~~ diff --git a/components/HeaderStackView/examples/HeaderStackViewTypicalUse.m b/components/HeaderStackView/examples/HeaderStackViewTypicalUse.m new file mode 100644 index 00000000000..5d12ed93031 --- /dev/null +++ b/components/HeaderStackView/examples/HeaderStackViewTypicalUse.m @@ -0,0 +1,45 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import + +#import "HeaderStackViewTypicalUseSupplemental.h" +#import "MaterialHeaderStackView.h" + +@interface HeaderStackViewTypicalUse () + +@end + +@implementation HeaderStackViewTypicalUse + +- (void)viewDidLoad { + [super viewDidLoad]; + + [self setupExampleViews]; + + self.stackView = [[MDCHeaderStackView alloc] init]; + self.stackView.autoresizingMask = UIViewAutoresizingFlexibleWidth; + self.stackView.topBar = self.topView; + self.stackView.bottomBar = self.navBar; + + CGRect frame = self.view.bounds; + self.stackView.frame = frame; + [self.stackView sizeToFit]; + + [self.view addSubview:self.stackView]; +} + +@end diff --git a/components/HeaderStackView/examples/HeaderStackViewTypicalUseViewController.m b/components/HeaderStackView/examples/HeaderStackViewTypicalUseViewController.m deleted file mode 100644 index cb7ce5a8f02..00000000000 --- a/components/HeaderStackView/examples/HeaderStackViewTypicalUseViewController.m +++ /dev/null @@ -1,91 +0,0 @@ -/* - Copyright 2015-present Google Inc. 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. - */ - -#import - -#import "MaterialHeaderStackView.h" - -@interface HeaderStackViewTypicalUseViewController : UIViewController -@end - -@implementation HeaderStackViewTypicalUseViewController { - BOOL _toggled; - MDCHeaderStackView *_stackView; -} - -// TODO: Support other categorizational methods. -+ (NSArray *)catalogBreadcrumbs { - return @[ @"Header Stack View", @"Header Stack View" ]; -} - -+ (NSString *)catalogDescription { - return @"The Header Stack View component is a view that coordinates the layout of two" - " vertically-stacked bar views."; -} - -- (BOOL)catalogIsPrimaryDemo { - return YES; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.view.backgroundColor = [UIColor whiteColor]; - - UIColor *blueColor = [UIColor colorWithRed:0.012 green:0.663 blue:0.957 alpha:0.2]; - _stackView = [[MDCHeaderStackView alloc] init]; - _stackView.backgroundColor = blueColor; - - UINavigationBar *topBar = [[UINavigationBar alloc] init]; - [topBar pushNavigationItem:self.navigationItem animated:NO]; - _stackView.topBar = topBar; - - UIToolbar *bottomBar = [[UIToolbar alloc] init]; - bottomBar.items = - @[ [[UIBarButtonItem alloc] initWithTitle:@"Toggle" - style:UIBarButtonItemStylePlain - target:self - action:@selector(didTapToggleButton:)] ]; - _stackView.bottomBar = bottomBar; - - CGRect frame = self.view.bounds; - frame.origin.y = 150; - _stackView.frame = frame; - [_stackView sizeToFit]; - - [self.view addSubview:_stackView]; -} - -#pragma mark User actions - -- (void)didTapToggleButton:(id)sender { - _toggled = !_toggled; - - [UIView animateWithDuration:0.4 - animations:^{ - CGRect frame = _stackView.frame; - if (_toggled) { - frame.size.height = 200; - } else { - frame.size = [_stackView sizeThatFits:_stackView.bounds.size]; - } - - _stackView.frame = frame; - [_stackView layoutIfNeeded]; - }]; -} - -@end diff --git a/components/HeaderStackView/examples/resources/header_stack_view_theme.png b/components/HeaderStackView/examples/resources/header_stack_view_theme.png new file mode 100644 index 00000000000..2aa056b38eb Binary files /dev/null and b/components/HeaderStackView/examples/resources/header_stack_view_theme.png differ diff --git a/components/HeaderStackView/examples/supplemental/HeaderStackViewTypicalUseSupplemental.h b/components/HeaderStackView/examples/supplemental/HeaderStackViewTypicalUseSupplemental.h new file mode 100644 index 00000000000..7dc4ca50ae5 --- /dev/null +++ b/components/HeaderStackView/examples/supplemental/HeaderStackViewTypicalUseSupplemental.h @@ -0,0 +1,41 @@ +/*Copyright 2016-present Google Inc. 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. +*/ +/* IMPORTANT: + This file contains supplemental code used to populate the examples with dummy data and/or + instructions. It is not necessary to import this file to implement any Material Design Components. + */ + +#import + +@class ExampleInstructionsViewHeaderStackViewTypicalUse; +@class HeaderStackViewTypicalUse; +@class MDCHeaderStackView; +@class MDCNavigationBar; + +@interface HeaderStackViewTypicalUse : UIViewController + +@property(nonatomic) BOOL toggled; +@property(nonatomic) ExampleInstructionsViewHeaderStackViewTypicalUse *_Nullable exampleView; +@property(nonatomic) MDCHeaderStackView *_Nullable stackView; +@property(nonatomic) MDCNavigationBar *_Nullable navBar; +@property(nonatomic) UIView *_Nullable topView; + +@end + +@interface HeaderStackViewTypicalUse (Supplemental) + +- (void)setupExampleViews; + +@end diff --git a/components/HeaderStackView/examples/supplemental/HeaderStackViewTypicalUseSupplemental.m b/components/HeaderStackView/examples/supplemental/HeaderStackViewTypicalUseSupplemental.m new file mode 100644 index 00000000000..564a743ffa4 --- /dev/null +++ b/components/HeaderStackView/examples/supplemental/HeaderStackViewTypicalUseSupplemental.m @@ -0,0 +1,215 @@ +/*Copyright 2016-present Google Inc. 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. + */ +/* IMPORTANT: + This file contains supplemental code used to populate the examples with dummy data and/or + instructions. It is not necessary to import this file to implement any Material Design Components. + */ + +#import + +#import "HeaderStackViewTypicalUseSupplemental.h" + +#import "MaterialHeaderStackView.h" +#import "MaterialNavigationBar.h" + +@interface ExampleInstructionsViewHeaderStackViewTypicalUse : UIView + +@end + +@implementation HeaderStackViewTypicalUse (Supplemental) + +- (void)setupExampleViews { + self.exampleView = + [[ExampleInstructionsViewHeaderStackViewTypicalUse alloc] initWithFrame:self.view.bounds]; + self.exampleView.autoresizingMask = + UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin; + + [self.view addSubview:self.exampleView]; + + self.view.backgroundColor = [UIColor whiteColor]; + + self.topView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 100)]; + self.topView.autoresizingMask = + UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + + UIImageView *imageView = [[UIImageView alloc] initWithImage:[self headerBackgroundImage]]; + imageView.frame = self.topView.bounds; + imageView.contentMode = UIViewContentModeScaleAspectFill; + imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; + + self.topView.clipsToBounds = YES; + [self.topView addSubview:imageView]; + + self.navBar = [[MDCNavigationBar alloc] initWithFrame:CGRectZero]; + self.navBar.tintColor = [UIColor whiteColor]; + self.navBar.title = @"Header Stack View"; + + // Light blue 500 + [self.navBar setBackgroundColor:[UIColor colorWithRed:0.012 green:0.663 blue:0.957 alpha:1]]; + + UIBarButtonItem *moreButton = + [[UIBarButtonItem alloc] initWithTitle:@"More" + style:UIBarButtonItemStylePlain + target:self + action:@selector(didTapToggleButton:)]; + + // Set the title text attributes before assigning to buttonBar.items + // because of https://github.com/google/material-components-ios/issues/277 + [moreButton setTitleTextAttributes:[self itemTitleTextAttributes] forState:UIControlStateNormal]; + self.navBar.rightBarButtonItems = @[ moreButton ]; +} + +- (NSDictionary *)itemTitleTextAttributes { + UIColor *textColor = [UIColor whiteColor]; + return @{NSForegroundColorAttributeName : textColor}; +} + +- (void)didTapToggleButton:(id)sender { + self.toggled = !self.toggled; + + [UIView animateWithDuration:0.4 + animations:^{ + CGRect frame = self.stackView.frame; + if (self.toggled) { + frame.size.height = 200; + } else { + frame.size = [self.stackView sizeThatFits:self.stackView.bounds.size]; + } + self.stackView.frame = frame; + [self.stackView layoutIfNeeded]; + }]; +} + +- (UIImage *)headerBackgroundImage { + NSBundle *bundle = [NSBundle bundleForClass:[self class]]; + NSString *imagePath = [bundle pathForResource:@"header_stack_view_theme" ofType:@"png"]; + return [UIImage imageWithContentsOfFile:imagePath]; +} + +- (BOOL)prefersStatusBarHidden { + return YES; +} + +@end + +@implementation HeaderStackViewTypicalUse (CatalogByConvention) + ++ (NSArray *)catalogBreadcrumbs { + return @[ @"Header Stack View", @"Header Stack View" ]; +} + +- (BOOL)catalogShouldHideNavigation { + return YES; +} + ++ (NSString *)catalogDescription { + return @"The Header Stack View component is a view that coordinates the layout of two" + " vertically-stacked bar views."; +} + ++ (BOOL)catalogIsPrimaryDemo { + return YES; +} + +@end + +@implementation HeaderStackViewTypicalUse (Rotation) + +- (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation + duration:(NSTimeInterval)duration { +} + +@end + +@implementation ExampleInstructionsViewHeaderStackViewTypicalUse + +- (void)drawRect:(CGRect)rect { + [[UIColor whiteColor] setFill]; + [[UIBezierPath bezierPathWithRect:rect] fill]; + + CGSize textSize = [self textSizeForRect:rect]; + CGRect rectForText = CGRectMake(rect.origin.x + rect.size.width / 2.f - textSize.width / 2.f, + rect.origin.y + rect.size.height / 2.f - textSize.height / 2.f, + textSize.width, textSize.height); + [[self instructionsString] drawInRect:rectForText]; + [self drawArrowWithFrame:CGRectMake(rect.size.width / 2.f - 12.f, + rect.size.height / 2.f - 58.f - 12.f, 24.f, 24.f)]; +} + +- (CGSize)textSizeForRect:(CGRect)frame { + return [[self instructionsString] + boundingRectWithSize:frame.size + options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading + context:nil] + .size; +} + +- (NSAttributedString *)instructionsString { + NSMutableParagraphStyle *style = [[NSMutableParagraphStyle alloc] init]; + [style setAlignment:NSTextAlignmentCenter]; + [style setLineBreakMode:NSLineBreakByWordWrapping]; + + NSDictionary *instructionAttributes1 = + @{NSFontAttributeName : [UIFont preferredFontForTextStyle:UIFontTextStyleHeadline], + NSForegroundColorAttributeName : [UIColor colorWithRed:0.459 + green:0.459 + blue:0.459 + alpha:0.87f], + NSParagraphStyleAttributeName : style}; + + NSDictionary *instructionAttributes2 = + @{NSFontAttributeName : [UIFont preferredFontForTextStyle:UIFontTextStyleSubheadline], + NSForegroundColorAttributeName : [UIColor colorWithRed:0.459 + green:0.459 + blue:0.459 + alpha:0.87f], + NSParagraphStyleAttributeName : style}; + + NSString *instructionText = @"SWIPE RIGHT\n\n\n\nto go back\n\n\n\n\n\n"; + NSMutableAttributedString *instructionsAttributedString = [[NSMutableAttributedString alloc] + initWithString:instructionText]; + [instructionsAttributedString setAttributes:instructionAttributes1 + range:NSMakeRange(0, 11)]; + [instructionsAttributedString setAttributes:instructionAttributes2 + range:NSMakeRange(11, instructionText.length - 11)]; + + return instructionsAttributedString; +} + +- (void)drawArrowWithFrame:(CGRect)frame { + UIBezierPath *bezierPath = [UIBezierPath bezierPath]; + [bezierPath moveToPoint:CGPointMake(CGRectGetMinX(frame) + 12, CGRectGetMinY(frame) + 4)]; + [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + 10.59, + CGRectGetMinY(frame) + 5.41)]; + [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + 16.17, CGRectGetMinY(frame) + 11)]; + [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + 4, CGRectGetMinY(frame) + 11)]; + [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + 4, CGRectGetMinY(frame) + 13)]; + [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + 16.17, CGRectGetMinY(frame) + 13)]; + [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + 10.59, + CGRectGetMinY(frame) + 18.59)]; + [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + 12, CGRectGetMinY(frame) + 20)]; + [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + 20, CGRectGetMinY(frame) + 12)]; + [bezierPath addLineToPoint:CGPointMake(CGRectGetMinX(frame) + 12, CGRectGetMinY(frame) + 4)]; + [bezierPath closePath]; + bezierPath.miterLimit = 4; + + [[UIColor colorWithRed:0.459 + green:0.459 + blue:0.459 + alpha:0.87f] setFill]; + [bezierPath fill]; +} + +@end diff --git a/components/Ink/.jazzy.yaml b/components/Ink/.jazzy.yaml index 5481ddfdf2c..ac8dbd76f17 100644 --- a/components/Ink/.jazzy.yaml +++ b/components/Ink/.jazzy.yaml @@ -1,4 +1,5 @@ -module_name: Ink +# Auto-generated by scripts/generate_jazzy_yamls.sh. Used primarily by scripts/external/arc-jazzy-linter. +module: Ink umbrella_header: src/MaterialInk.h objc: true sdk: iphonesimulator diff --git a/components/Ink/README.md b/components/Ink/README.md index d2e720be7e9..1d8d16bc561 100644 --- a/components/Ink/README.md +++ b/components/Ink/README.md @@ -1,7 +1,7 @@ --- title: "Ink" layout: detail -section: documentation +section: components excerpt: "The Ink component provides a radial action in the form of a visual ripple of ink expanding outward from the user's touch." --- # Ink @@ -71,6 +71,7 @@ Before using Ink, you'll need to import it: ~~~ swift import MaterialComponents ~~~ + The Ink component exposes two interfaces that you can use to add material-like @@ -88,6 +89,7 @@ The simplest method of using ink in your views is to use a `MDCInkTouchController`: + #### Objective-C ~~~ objc UIButton *myButton = [UIButton buttonWithType:UIButtonTypeSystem]; @@ -96,6 +98,15 @@ MDCInkTouchController *inkTouchController = [[MDCInkTouchController alloc] initWithView:myButton]; [inkTouchController addInkView]; ~~~ + +#### Swift +~~~ swift +let myButton = UIButton(type: .System) +myButton.setTitle("Tap Me", forState: .Normal) +let inkTouchController = MDCInkTouchController(view: myButton) +inkTouchController?.addInkView() +~~~ + @@ -107,6 +118,7 @@ touches, the following code uses the delegate's `inkTouchController:shouldProcessInkTouchesAtTouchLocation:` method: + #### Objective-C ~~~ objc @interface MyDelegate @@ -132,6 +144,28 @@ inkTouchController.delegate = myDelegate; ~~~ +#### Swift +~~~ swift +class MyDelegate: NSObject, MDCInkTouchControllerDelegate { + + func inkTouchController(inkTouchController: MDCInkTouchController, + shouldProcessInkTouchesAtTouchLocation location: CGPoint) -> Bool { + // Determine if we want to display the ink + return true + } + +} + +... + +let myButton = UIButton(type: .System) +let myDelegate = MyDelegate() +let inkTouchController = MDCInkTouchController(view: myButton) +inkTouchController?.delegate = myDelegate +inkTouchController?.addInkView() + +~~~ + ### MDCInkView @@ -140,6 +174,7 @@ Alternatively, you can use MCDInkView directly to display ink ripples using your own touch processing: + #### Objective-C ~~~ objc MyCustomView *myCustomView = [[MyCustomView alloc] initWithFrame:CGRectZero]; @@ -147,7 +182,17 @@ MDCInkView *inkView = [[MDCInkView alloc] init]; inkView.inkColor = [UIColor redColor]; [myCustomView addSubview:inkView]; ... -[inkView spreadFromPoint:CGPointMake(100, 100) completion:NULL]; +[inkView spreadInkFromPoint:CGPointMake(100, 100) completion:NULL]; +~~~ + +#### Swift +~~~ swift +let myCustomView = MyCustomView(frame: CGRectZero) +let inkView = MDCInkView() +inkView.inkColor = UIColor.redColor() +myCustomView.addSubview(inkView) +... +myCustomView.spreadInk(CGPoint(), completion:nil) ~~~ diff --git a/components/Ink/examples/InkTypicalUse.m b/components/Ink/examples/InkTypicalUse.m new file mode 100644 index 00000000000..60f9f34fc69 --- /dev/null +++ b/components/Ink/examples/InkTypicalUse.m @@ -0,0 +1,80 @@ +/* + Copyright 2015-present Google Inc. 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. + */ + +#import + +#import "MaterialInk.h" + +#import "InkTypicalUseSupplemental.h" + +@interface InkTypicalUseViewController () + +@property(nonatomic, strong) NSMutableArray *inkTouchControllers; // MDCInkTouchControllers. + +@end + +@implementation InkTypicalUseViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + + CGFloat spacing = 16; + CGRect customFrame = CGRectMake(0, 0, 200, 200); + CGRect unboundedFrame = CGRectMake(spacing / 2, + spacing / 2, + customFrame.size.width - spacing, + customFrame.size.height - spacing); + + // ExampleShapes is a custom UIView with several subviews of various shapes. + self.boundedShapes = [[ExampleShapes alloc] initWithFrame:customFrame]; + self.unboundedShape = [[UIView alloc] initWithFrame:unboundedFrame]; + + [self setupExampleViews]; + + _inkTouchControllers = [[NSMutableArray alloc] init]; + + for (UIView *view in self.boundedShapes.subviews) { + MDCInkTouchController *inkTouchController = [[MDCInkTouchController alloc] initWithView:view]; + inkTouchController.delegate = self; + [inkTouchController addInkView]; + [_inkTouchControllers addObject:inkTouchController]; + } + [self.view addSubview:self.boundedShapes]; + + MDCInkTouchController *inkTouchController = + [[MDCInkTouchController alloc] initWithView:self.unboundedShape]; + inkTouchController.delegate = self; + [inkTouchController addInkView]; + + UIColor *blueColor = [UIColor colorWithRed:0.012 green:0.663 blue:0.957 alpha:0.2]; + inkTouchController.defaultInkView.inkColor = blueColor; + inkTouchController.defaultInkView.inkStyle = MDCInkStyleUnbounded; + [_inkTouchControllers addObject:inkTouchController]; + [self.view addSubview:self.unboundedShape]; +} + +#pragma mark - Private + +- (void)inkTouchController:(MDCInkTouchController *)inkTouchController + didProcessInkView:(MDCInkView *)inkView + atTouchLocation:(CGPoint)location { + NSLog(@"InkTouchController %p did process ink view: %p at touch location: %@", + inkTouchController, + inkView, + NSStringFromCGPoint(location)); +} + +@end diff --git a/components/Ink/examples/InkTypicalUseExample.m b/components/Ink/examples/InkTypicalUseExample.m deleted file mode 100644 index 1a7f5efc700..00000000000 --- a/components/Ink/examples/InkTypicalUseExample.m +++ /dev/null @@ -1,243 +0,0 @@ -/* - Copyright 2015-present Google Inc. 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. - */ - -#import - -#import "MaterialInk.h" - -// A set of UILabels in an variety of shapes to tap on. -// -// Something like: -// -// .----.----.----.----. .----. -// . . . . -// . . . . -// . . . . -// . . . . -// . . . . -// . . . . -// . . . . -// ----.----.----.---- ---- -// -// .----.----.----.----. .----. -// . . . . -// . . . . -// ----.----.----.---- ---- -@interface ExampleShapes : UIView -@property(nonatomic) CGFloat padding; -@property(nonatomic) CGFloat ratio; -@property(nonatomic, strong) UIColor *shapeColor; -@property(nonatomic, copy) NSString *title; -@end - -@interface InkTypicalUseViewController : UIViewController -@end - -@interface InkTypicalUseViewController () -@property(nonatomic) CGFloat shapeDimension; -@property(nonatomic) CGFloat spacing; -@property(nonatomic, strong) ExampleShapes *boundedShapes; -@property(nonatomic, strong) NSMutableArray *inkTouchControllers; // MDCInkTouchControllers. -@property(nonatomic, strong) UIView *unboundedShape; -@end - -@implementation InkTypicalUseViewController - -// TODO: Support other categorizational methods. -+ (NSArray *)catalogBreadcrumbs { - return @[ @"Ink", @"Ink" ]; -} - -+ (NSString *)catalogDescription { - return @"The Ink component provides a radial action in the form of a visual ripple of ink" - " expanding outward from the user's touch."; -} - -- (BOOL)catalogIsPrimaryDemo { - return YES; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - self.view.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1]; - _inkTouchControllers = [[NSMutableArray alloc] init]; - - self.spacing = 16; - self.shapeDimension = 200; - CGRect customFrame = CGRectMake(0, 0, self.shapeDimension, self.shapeDimension); - self.boundedShapes = [[ExampleShapes alloc] initWithFrame:customFrame]; - self.boundedShapes.title = @"Bounded"; - self.boundedShapes.autoresizingMask = - UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | - UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin; - self.boundedShapes.shapeColor = [UIColor whiteColor]; - for (UIView *view in self.boundedShapes.subviews) { - MDCInkTouchController *inkTouchController = [[MDCInkTouchController alloc] initWithView:view]; - inkTouchController.delegate = self; - [inkTouchController addInkView]; - [_inkTouchControllers addObject:inkTouchController]; - } - [self.view addSubview:self.boundedShapes]; - - CGRect unboundedFrame = CGRectMake(self.spacing / 2, - self.spacing / 2, - customFrame.size.width - self.spacing, - customFrame.size.height - self.spacing); - self.unboundedShape = [[UIView alloc] initWithFrame:unboundedFrame]; - self.unboundedShape.autoresizingMask = - UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | - UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin; - self.unboundedShape.backgroundColor = [UIColor whiteColor]; - MDCInkTouchController *inkTouchController = - [[MDCInkTouchController alloc] initWithView:self.unboundedShape]; - inkTouchController.delegate = self; - [inkTouchController addInkView]; - UIColor *blueColor = [UIColor colorWithRed:0.012 green:0.663 blue:0.957 alpha:0.2]; - inkTouchController.defaultInkView.inkColor = blueColor; - inkTouchController.defaultInkView.inkStyle = MDCInkStyleUnbounded; - [_inkTouchControllers addObject:inkTouchController]; - [self.view addSubview:self.unboundedShape]; - - UILabel *unboundedTitleLabel = [[UILabel alloc] initWithFrame:self.unboundedShape.bounds]; - unboundedTitleLabel.text = @"Unbounded"; - unboundedTitleLabel.textAlignment = NSTextAlignmentCenter; - unboundedTitleLabel.textColor = [UIColor grayColor]; - [self.unboundedShape addSubview:unboundedTitleLabel]; -} - -- (void)viewWillLayoutSubviews { - if (self.view.frame.size.height > self.view.frame.size.width) { - self.boundedShapes.center = - CGPointMake(self.view.center.x, self.view.center.y - self.shapeDimension); - self.unboundedShape.center = CGPointMake(self.view.center.x, - self.view.center.y + self.spacing * 2); - } else { - self.boundedShapes.center = - CGPointMake(self.view.center.x - self.shapeDimension / 2 - self.spacing * 2, - self.view.center.y / 2 + self.spacing * 2); - self.unboundedShape.center = - CGPointMake(self.view.center.x + self.shapeDimension / 2 + self.spacing * 2, - self.view.center.y / 2 + self.spacing * 2); - } -} - -#pragma mark - Private - -- (void)inkTouchController:(MDCInkTouchController *)inkTouchController - didProcessInkView:(MDCInkView *)inkView - atTouchLocation:(CGPoint)location { - NSLog(@"InkTouchController %p did process ink view: %p at touch location: %@", - inkTouchController, - inkView, - NSStringFromCGPoint(location)); -} - -@end - -@interface ExampleShapes () -@property(nonatomic, strong) UILabel *titleLabel; -@end - -@implementation ExampleShapes - -- (id)initWithFrame:(CGRect)frame { - self = [super initWithFrame:frame]; - if (self) { - _padding = 8; - _ratio = 4; - - for (int i = 0; i < 4; ++i) { - UIView *view = [[UIView alloc] initWithFrame:CGRectZero]; - [self addSubview:view]; - - if (i == 0) { - _titleLabel = [[UILabel alloc] initWithFrame:CGRectZero]; - [view addSubview:_titleLabel]; - } - } - } - return self; -} - -- (void)setShapeColor:(UIColor *)shapeColor { - if ([_shapeColor isEqual:shapeColor]) { - return; - } - - for (UIView *view in self.subviews) { - view.backgroundColor = shapeColor; - } -} - -- (NSString *)title { - return _titleLabel.text; -} - -- (void)setTitle:(NSString *)title { - _titleLabel.text = title; -} - -- (void)layoutSubviews { - CGFloat totalLength = MIN(CGRectGetWidth(self.bounds), CGRectGetHeight(self.bounds)); - - // There are two shapes in each direction, so three margins total. - CGFloat available = totalLength - 3 * self.padding; - if (available <= 0) { - return; - } - - // Construct the shape with the right size but at (0,0) and transform later. - CGFloat shortLength = available / (1 + self.ratio); - CGFloat longLength = self.ratio * shortLength; - - CGRect bigSquareFrame = CGRectMake(0, 0, longLength, longLength); - - CGRect vertFrame = CGRectMake(CGRectGetMaxX(bigSquareFrame) + self.padding, - 0, - shortLength, - longLength); - - CGRect horzFrame = CGRectMake(0, - CGRectGetMaxY(bigSquareFrame) + self.padding, - longLength, - shortLength); - - CGRect smallSquareFrame = CGRectMake(CGRectGetMaxX(bigSquareFrame) + self.padding, - CGRectGetMaxY(bigSquareFrame) + self.padding, - shortLength, - shortLength); - - // Offset the frames so they have the correct leading padding. - CGRect frames[] = {bigSquareFrame, vertFrame, horzFrame, smallSquareFrame}; - for (int i = 0; i < 4; ++i) { - frames[i] = CGRectOffset(frames[i], self.padding, self.padding); - } - - NSArray *subviews = self.subviews; - NSAssert(subviews.count == 4, @""); - for (int i = 0; i < 4; ++i) { - UIView *view = subviews[i]; - view.frame = frames[i]; - if (i == 0) { - _titleLabel.frame = CGRectMake(0, 0, view.bounds.size.width, view.bounds.size.height); - _titleLabel.textAlignment = NSTextAlignmentCenter; - _titleLabel.textColor = [UIColor grayColor]; - } - } -} - -@end diff --git a/components/Ink/examples/supplemental/InkTypicalUseSupplemental.h b/components/Ink/examples/supplemental/InkTypicalUseSupplemental.h new file mode 100644 index 00000000000..f8acf8d3e00 --- /dev/null +++ b/components/Ink/examples/supplemental/InkTypicalUseSupplemental.h @@ -0,0 +1,25 @@ +/* IMPORTANT: + This file contains supplemental code used to populate the demos with dummy data or instructions. + It is not necessary to import this file to implement any Material Design Components. + */ + +#import + +@interface ExampleShapes : UIView + +@end + +@class InkTypicalUseViewController; + +@interface InkTypicalUseViewController : UIViewController + +@property(nonatomic, strong) ExampleShapes *boundedShapes; +@property(nonatomic, strong) UIView *unboundedShape; + +@end + +@interface InkTypicalUseViewController (Supplemental) + +- (void)setupExampleViews; + +@end diff --git a/components/Ink/examples/supplemental/InkTypicalUseSupplemental.m b/components/Ink/examples/supplemental/InkTypicalUseSupplemental.m new file mode 100644 index 00000000000..3820e08b6b3 --- /dev/null +++ b/components/Ink/examples/supplemental/InkTypicalUseSupplemental.m @@ -0,0 +1,138 @@ +/* IMPORTANT: + This file contains supplemental code used to populate the examples with dummy data and/or + instructions. It is not necessary to import this file to implement any Material Design Components. + */ + +#import + +#import "InkTypicalUseSupplemental.h" + +#import "MaterialTypography.h" + +// A set of UILabels in an variety of shapes to tap on. + +@interface ExampleShapes () +@end + +@implementation ExampleShapes + +- (id)initWithFrame:(CGRect)frame { + self = [super initWithFrame:frame]; + if (self) { + CGFloat padding = 8; + CGFloat bigViewFrameHeight = 130; + CGRect bigViewFrame = CGRectMake(padding, + padding, + frame.size.width - 2 * padding, + bigViewFrameHeight); + UIView *bigView = [[UIView alloc] initWithFrame:bigViewFrame]; + bigView.backgroundColor = [UIColor whiteColor]; + [self addSubview:bigView]; + + CGFloat buttonViewDim = 50; + CGFloat pseudoButtonViewHeight = 40; + CGFloat fabPadding = 6; + CGRect pseudoButtonViewFrame = + CGRectMake(padding, + padding + bigViewFrameHeight + fabPadding + padding, + frame.size.width - 2 * padding - buttonViewDim - fabPadding * 3, + pseudoButtonViewHeight); + UIView *pseudoButtonView = [[UIView alloc] initWithFrame:pseudoButtonViewFrame]; + pseudoButtonView.backgroundColor = [UIColor whiteColor]; + pseudoButtonView.layer.cornerRadius = 5; + pseudoButtonView.clipsToBounds = YES; + [self addSubview:pseudoButtonView]; + + CGFloat pseudoFABViewFrameLeft = + padding + frame.size.width - 2 * padding - buttonViewDim + padding - fabPadding * 2; + CGRect pseudoFABViewFrame = CGRectMake(pseudoFABViewFrameLeft, + padding + bigViewFrameHeight + padding, + buttonViewDim + fabPadding, + buttonViewDim + fabPadding); + UIView *pseudoFABView = [[UIView alloc] initWithFrame:pseudoFABViewFrame]; + pseudoFABView.backgroundColor = [UIColor whiteColor]; + pseudoFABView.layer.cornerRadius = 28; + pseudoFABView.clipsToBounds = YES; + [self addSubview:pseudoFABView]; + + self.autoresizingMask = + UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | + UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin; + } + return self; +} + +@end + +#pragma mark - InkTypicalUseViewController + +@implementation InkTypicalUseViewController (CatalogByConvention) + ++ (NSArray *)catalogBreadcrumbs { + return @[ @"Ink", @"Ink" ]; +} + ++ (NSString *)catalogDescription { + return @"The Ink component provides a radial action in the form of a visual ripple of ink" + " expanding outward from the user's touch."; +} + ++ (BOOL)catalogIsPrimaryDemo { + return YES; +} + +@end + +@implementation InkTypicalUseViewController (Supplemental) + +- (void)setupExampleViews { + self.view.backgroundColor = [UIColor colorWithWhite:0.95 alpha:1]; + + CGRect boundedTitleLabelFrame = CGRectMake(0, + self.boundedShapes.frame.size.height, + self.boundedShapes.frame.size.width, + 24); + UILabel *boundedTitleLabel = [[UILabel alloc] initWithFrame:boundedTitleLabelFrame]; + boundedTitleLabel.text = @"Bounded"; + boundedTitleLabel.textAlignment = NSTextAlignmentCenter; + boundedTitleLabel.font = [MDCTypography captionFont]; + boundedTitleLabel.alpha = [MDCTypography captionFontOpacity]; + [self.boundedShapes addSubview:boundedTitleLabel]; + + self.unboundedShape.autoresizingMask = + UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | + UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin; + self.unboundedShape.backgroundColor = [UIColor whiteColor]; + + CGRect unboundedTitleLabelFrame = CGRectMake(0, + self.unboundedShape.frame.size.height, + self.unboundedShape.frame.size.width, + 36); + UILabel *unboundedTitleLabel = [[UILabel alloc] initWithFrame:unboundedTitleLabelFrame]; + unboundedTitleLabel.text = @"Unbounded"; + unboundedTitleLabel.textAlignment = NSTextAlignmentCenter; + unboundedTitleLabel.font = [MDCTypography captionFont]; + unboundedTitleLabel.alpha = [MDCTypography captionFontOpacity]; + [self.unboundedShape addSubview:unboundedTitleLabel]; +} + +- (void)viewWillLayoutSubviews { + CGFloat offset = 8; + CGFloat shapeDimension = 200; + CGFloat spacing = 16; + if (self.view.frame.size.height > self.view.frame.size.width) { + self.boundedShapes.center = + CGPointMake(self.view.center.x, self.view.center.y - shapeDimension - offset); + self.unboundedShape.center = CGPointMake(self.view.center.x, + self.view.center.y + spacing * 2 + offset); + } else { + self.boundedShapes.center = + CGPointMake(self.view.center.x - shapeDimension / 2 - spacing * 2, + self.view.center.y / 2 + spacing * 2); + self.unboundedShape.center = + CGPointMake(self.view.center.x + shapeDimension / 2 + spacing * 2, + self.view.center.y / 2 + spacing * 2); + } +} + +@end diff --git a/components/NavigationBar/.jazzy.yaml b/components/NavigationBar/.jazzy.yaml index 281a3602d63..6b4740527d6 100644 --- a/components/NavigationBar/.jazzy.yaml +++ b/components/NavigationBar/.jazzy.yaml @@ -1,4 +1,5 @@ -module_name: NavigationBar +# Auto-generated by scripts/generate_jazzy_yamls.sh. Used primarily by scripts/external/arc-jazzy-linter. +module: NavigationBar umbrella_header: src/MaterialNavigationBar.h objc: true sdk: iphonesimulator diff --git a/components/NavigationBar/README.md b/components/NavigationBar/README.md index e0e7cd1a76c..ce0daf1f956 100644 --- a/components/NavigationBar/README.md +++ b/components/NavigationBar/README.md @@ -1,7 +1,7 @@ --- title: "Navigation Bar" layout: detail -section: documentation +section: components excerpt: "The Navigation Bar component is a view composed of a left and right Button Bar and either a title label or a custom title view." --- # Navigation Bar @@ -54,7 +54,11 @@ $ pod install ## Overview -Navigation Bar is designed to be a drop-in replacement for UINavigationBar. +Navigation Bar is a drop-in replacement for UINavigationBar with a few notable exceptions: + +- No navigationItem stack. Instances of MDCNavigationBar must be explicitly provided with a back + button. TODO(featherless): Explain how to create a back button with Navigation Bar once + https://github.com/google/material-components-ios/issues/340 lands. The MDCNavigationBar class is a composition of two [Button Bars](../ButtonBar) and a title label or title view. The left and right Button Bars are provided with the navigation item's corresponding bar diff --git a/components/NavigationBar/examples/NavigationBarTypicalUseExample.m b/components/NavigationBar/examples/NavigationBarTypicalUseExample.m index 80927aed941..70cf67b66b3 100644 --- a/components/NavigationBar/examples/NavigationBarTypicalUseExample.m +++ b/components/NavigationBar/examples/NavigationBarTypicalUseExample.m @@ -1,5 +1,5 @@ /* - Copyright 2015-present Google Inc. All Rights Reserved. + Copyright 2016-present Google Inc. 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. diff --git a/components/NavigationBar/examples/NavigationBarTypicalUseExample.swift b/components/NavigationBar/examples/NavigationBarTypicalUseExample.swift new file mode 100644 index 00000000000..154d736eeb3 --- /dev/null +++ b/components/NavigationBar/examples/NavigationBarTypicalUseExample.swift @@ -0,0 +1,59 @@ +/* +Copyright 2016-present Google Inc. 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. +*/ + +import UIKit +import MaterialComponents.MaterialNavigationBar + +public class NavigationBarTypicalUseSwiftExample: NavigationBarTypicalUseExample { + + override public func viewDidLoad() { + + super.viewDidLoad() + view.backgroundColor = .whiteColor() + + title = "Navigation Bar (Swift)" + + navBar = MDCNavigationBar() + navBar!.observeNavigationItem(navigationItem) + navBar!.tintColor = .whiteColor() + + // Light blue 500 + navBar!.backgroundColor = UIColor.init(red: 0.012, green: 0.663, blue: 0.957, alpha: 1) + + view.addSubview(navBar!) + + navBar!.translatesAutoresizingMaskIntoConstraints = false + + let viewBindings = ["navBar" : navBar!] + + var constraints = NSLayoutConstraint.constraintsWithVisualFormat("H:|[navBar]|", + options: [], metrics: nil, views: viewBindings) + constraints += NSLayoutConstraint.constraintsWithVisualFormat("V:|[navBar]", + options: [], metrics: nil, views: viewBindings) + + view.addConstraints(constraints) + self.setupExampleViews() + } + + override public func viewWillAppear(animated: Bool) { + super.viewWillAppear(animated) + navigationController?.setNavigationBarHidden(true, animated: animated) + } + + override public func prefersStatusBarHidden() -> Bool { + return true + } +} diff --git a/components/NavigationBar/examples/supplemental/NavigationBarTypicalUseExampleSupplemental.h b/components/NavigationBar/examples/supplemental/NavigationBarTypicalUseExampleSupplemental.h index b8d9ed03fc4..d55c7af8590 100644 --- a/components/NavigationBar/examples/supplemental/NavigationBarTypicalUseExampleSupplemental.h +++ b/components/NavigationBar/examples/supplemental/NavigationBarTypicalUseExampleSupplemental.h @@ -1,3 +1,17 @@ +/*Copyright 2016-present Google Inc. 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. +*/ /* IMPORTANT: This file contains supplemental code used to populate the examples with dummy data and/or instructions. It is not necessary to import this file to implement any Material Design Components. @@ -10,8 +24,8 @@ @interface NavigationBarTypicalUseExample : UIViewController -@property(nonatomic) ExampleInstructionsViewNavigationBarTypicalUseExample *exampleView; -@property(nonatomic) MDCNavigationBar *navBar; +@property(nonatomic) ExampleInstructionsViewNavigationBarTypicalUseExample *_Nullable exampleView; +@property(nonatomic) MDCNavigationBar *_Nullable navBar; @end diff --git a/components/NavigationBar/examples/supplemental/NavigationBarTypicalUseExampleSupplemental.m b/components/NavigationBar/examples/supplemental/NavigationBarTypicalUseExampleSupplemental.m index 9917ee32966..743cc2f8df1 100644 --- a/components/NavigationBar/examples/supplemental/NavigationBarTypicalUseExampleSupplemental.m +++ b/components/NavigationBar/examples/supplemental/NavigationBarTypicalUseExampleSupplemental.m @@ -1,3 +1,17 @@ +/*Copyright 2016-present Google Inc. 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. + */ /* IMPORTANT: This file contains supplemental code used to populate the examples with dummy data and/or instructions. It is not necessary to import this file to implement any Material Design Components. @@ -46,7 +60,7 @@ - (void)setupExampleViews { @implementation NavigationBarTypicalUseExample (CatalogByConvention) + (NSArray *)catalogBreadcrumbs { - return @[ @"Navigation Bar", @"Typical use" ]; + return @[ @"Navigation Bar", @"Navigation Bar" ]; } - (BOOL)catalogShouldHideNavigation { @@ -58,7 +72,7 @@ + (NSString *)catalogDescription { " either a title label or a custom title view."; } -- (BOOL)catalogIsPrimaryDemo { ++ (BOOL)catalogIsPrimaryDemo { return YES; } diff --git a/components/NavigationBar/examples/supplemental/NavigationBarTypicalUseExampleSupplemental.swift b/components/NavigationBar/examples/supplemental/NavigationBarTypicalUseExampleSupplemental.swift new file mode 100644 index 00000000000..3c0e760d2d8 --- /dev/null +++ b/components/NavigationBar/examples/supplemental/NavigationBarTypicalUseExampleSupplemental.swift @@ -0,0 +1,58 @@ +/* +Copyright 2016-present Google Inc. 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. +*/ + +/* IMPORTANT: +This file contains supplemental code used to populate the examples with dummy data and/or +instructions. It is not necessary to import this file to implement any Material Design Components. +*/ + +import Foundation +import MaterialComponents + +extension NavigationBarTypicalUseSwiftExample { + + // (CatalogByConvention) + + class func catalogBreadcrumbs() -> [String] { + return [ "Navigation Bar", "Navigation Bar (Swift)" ] + } + + class func catalogDescription() -> String { + return "The Navigation Bar component is a view composed of a left and right Button Bar and" + " either a title label or a custom title view." + } + + class func catalogIsPrimaryDemo() -> Bool { + return false + } + + func catalogShouldHideNavigation() -> Bool { + return true + } + + override public func setupExampleViews() { + /// Both self.viewDidLoad() and super.viewDidLoad() will add NavigationBars to the hierarchy. + /// We only want to keep one. + + for subview in view.subviews { + if let navBarSubview = subview as? MDCNavigationBar where navBarSubview != self.navBar { + navBarSubview.removeFromSuperview() + } + } + super.setupExampleViews() + } + +} diff --git a/components/NavigationBar/src/MDCNavigationBar.m b/components/NavigationBar/src/MDCNavigationBar.m index 56a7ab852a5..03b987e6940 100644 --- a/components/NavigationBar/src/MDCNavigationBar.m +++ b/components/NavigationBar/src/MDCNavigationBar.m @@ -235,7 +235,7 @@ - (CGSize)sizeThatFits:(CGSize)size { - (CGSize)intrinsicContentSize { const BOOL isPad = [[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPad; CGFloat height = (isPad ? kNavigationBarPadDefaultHeight : kNavigationBarDefaultHeight); - return CGSizeMake(self.superview.superview.bounds.size.width, height); + return CGSizeMake(UIViewNoIntrinsicMetric, height); } #pragma mark - Private diff --git a/components/PageControl/.jazzy.yaml b/components/PageControl/.jazzy.yaml index fb0f2614321..4ac3c38b82c 100644 --- a/components/PageControl/.jazzy.yaml +++ b/components/PageControl/.jazzy.yaml @@ -1,4 +1,5 @@ -module_name: PageControl +# Auto-generated by scripts/generate_jazzy_yamls.sh. Used primarily by scripts/external/arc-jazzy-linter. +module: PageControl umbrella_header: src/MaterialPageControl.h objc: true sdk: iphonesimulator diff --git a/components/PageControl/README.md b/components/PageControl/README.md index 84e555336ed..f0b4d6de006 100644 --- a/components/PageControl/README.md +++ b/components/PageControl/README.md @@ -1,7 +1,7 @@ --- title: "Page Control" layout: detail -section: documentation +section: components excerpt: "Page Control is a drop-in Material Design replacement for UIPageControl that implements Material Design animation and layout." --- # Page Control @@ -156,8 +156,38 @@ notified of page changes. } ~~~ - +#### Swift + +~~~ swift +class PageControlSwiftExampleViewController: UIViewController, UIScrollViewDelegate { + + let pageControl = MDCPageControl() + let scrollView = UIScrollView() + let pages = NSMutableArray() + + override func viewDidLoad() { + super.viewDidLoad() + scrollView.delegate = self + view.addSubview(scrollView) + + pageControl.numberOfPages = pageColors.count + + let pageControlSize = pageControl.sizeThatFits(view.bounds.size) + pageControl.frame = CGRectMake(0, view.bounds.height - pageControlSize.height, view.bounds.width, pageControlSize.height); + pageControl.addTarget(self, action: "didChangePage:", forControlEvents: .ValueChanged) + pageControl.autoresizingMask = [.FlexibleTopMargin, .FlexibleWidth]; + view.addSubview(pageControl) + } + + func didChangePage(sender: MDCPageControl){ + var offset = scrollView.contentOffset + offset.x = CGFloat(sender.currentPage) * scrollView.bounds.size.width; + scrollView.setContentOffset(offset, animated: true) + } + +~~~ + ### Step 2: Forwarding the required scroll view delegate methods @@ -184,4 +214,20 @@ scrolling movement of the designated scroll view. } ~~~ +#### Swift + +~~~ swift +func scrollViewDidScroll(scrollView: UIScrollView) { + pageControl.scrollViewDidScroll(scrollView) +} + +func scrollViewDidEndDecelerating(scrollView: UIScrollView) { + pageControl.scrollViewDidEndDecelerating(scrollView) +} + +func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) { + pageControl.scrollViewDidEndScrollingAnimation(scrollView) +} +~~~ + diff --git a/components/PageControl/examples/PageControlTypicalUseExample.m b/components/PageControl/examples/PageControlTypicalUseExample.m index 5683586dcd4..ea3d7a708bd 100644 --- a/components/PageControl/examples/PageControlTypicalUseExample.m +++ b/components/PageControl/examples/PageControlTypicalUseExample.m @@ -39,7 +39,7 @@ + (NSString *)catalogDescription { " experience influenced by Material Design."; } -- (BOOL)catalogIsPrimaryDemo { ++ (BOOL)catalogIsPrimaryDemo { return YES; } diff --git a/components/PageControl/examples/PageControlTypicalUseExample.swift b/components/PageControl/examples/PageControlTypicalUseExample.swift new file mode 100644 index 00000000000..1a20da3b503 --- /dev/null +++ b/components/PageControl/examples/PageControlTypicalUseExample.swift @@ -0,0 +1,123 @@ +/* +Copyright 2016-present Google Inc. 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. +*/ + +import Foundation + +import MaterialComponents + +class PageControlSwiftExampleViewController: UIViewController, UIScrollViewDelegate { + + let pageControl = MDCPageControl() + let scrollView = UIScrollView() + let pages = NSMutableArray() + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = UIColor.whiteColor() + + let pageColors = [ + self.dynamicType.ColorFromRGB(0x55C4f5), + self.dynamicType.ColorFromRGB(0x35B7F3), + self.dynamicType.ColorFromRGB(0x1EAAF1), + self.dynamicType.ColorFromRGB(0x55C4f5), + self.dynamicType.ColorFromRGB(0x35B7F3), + self.dynamicType.ColorFromRGB(0x1EAAF1), + ]; + + scrollView.frame = self.view.bounds + scrollView.autoresizingMask = [.FlexibleWidth, .FlexibleHeight] + scrollView.delegate = self + scrollView.pagingEnabled = true + scrollView.contentSize = CGSizeMake(view.bounds.width * CGFloat(pageColors.count), view.bounds.height) + scrollView.showsHorizontalScrollIndicator = false; + view.addSubview(scrollView) + + // Add pages to scrollView. + for (var i:NSInteger = 0; i < pageColors.count; i++) { + let pageFrame:CGRect = CGRectOffset(self.view.bounds, CGFloat(i) * view.bounds.width, 0); + let page = UILabel.init(frame:pageFrame) + page.text = String(format: "Page %zd", i + 1) + page.font = page.font.fontWithSize(50) + page.textColor = UIColor.init(white: 0, alpha: 0.8) + page.backgroundColor = pageColors[i]; + page.textAlignment = NSTextAlignment.Center; + page.autoresizingMask = [.FlexibleTopMargin, .FlexibleBottomMargin] + scrollView.addSubview(page) + pages.addObject(page) + } + + pageControl.numberOfPages = pageColors.count + + let pageControlSize = pageControl.sizeThatFits(view.bounds.size) + pageControl.frame = CGRectMake(0, view.bounds.height - pageControlSize.height, view.bounds.width, pageControlSize.height); + pageControl.addTarget(self, action: "didChangePage:", forControlEvents: .ValueChanged) + pageControl.autoresizingMask = [.FlexibleTopMargin, .FlexibleWidth]; + view.addSubview(pageControl) + } + + // MARK: - Frame changes + + override func viewWillLayoutSubviews() { + super.viewWillLayoutSubviews() + let pageBeforeFrameChange = pageControl.currentPage; + for (var i = 0; i < pages.count; i++) { + let page:UILabel = pages.objectAtIndex(i) as! UILabel + page.frame = CGRectOffset(view.bounds, CGFloat(i) * view.bounds.width, 0); + } + scrollView.contentSize = CGSizeMake(view.bounds.width * CGFloat(pages.count), view.bounds.height); + var offset = scrollView.contentOffset; + offset.x = CGFloat(pageBeforeFrameChange) * view.bounds.width; + // This non-anmiated change of offset ensures we keep the same page + scrollView.contentOffset = offset; + } + + // MARK: - UIScrollViewDelegate + + func scrollViewDidScroll(scrollView: UIScrollView) { + pageControl.scrollViewDidScroll(scrollView) + } + + func scrollViewDidEndDecelerating(scrollView: UIScrollView) { + pageControl.scrollViewDidEndDecelerating(scrollView) + } + + func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) { + pageControl.scrollViewDidEndScrollingAnimation(scrollView) + } + + // MARK: - User events + + func didChangePage(sender: MDCPageControl){ + var offset = scrollView.contentOffset + offset.x = CGFloat(sender.currentPage) * scrollView.bounds.size.width; + scrollView.setContentOffset(offset, animated: true) + } + + // MARK: CatalogByConvention + + class func catalogBreadcrumbs() -> [String] { + return [ "Page Control", "Swift example"] + } + + // Creates a UIColor from a 24-bit RGB color encoded as an integer. + // Pass in hex color values like so: ColorFromRGB(0x1EAAF1). + class func ColorFromRGB(rgbValue: UInt32) -> UIColor { + return UIColor.init(red: ((CGFloat)((rgbValue & 0xFF0000) >> 16)) / 255, + green: ((CGFloat)((rgbValue & 0x00FF00) >> 8)) / 255, + blue: ((CGFloat)((rgbValue & 0x0000FF) >> 0)) / 255, + alpha: 1) + } +} diff --git a/components/PageControl/src/MDCPageControl.h b/components/PageControl/src/MDCPageControl.h index 3ba4d608126..4459bbdf849 100644 --- a/components/PageControl/src/MDCPageControl.h +++ b/components/PageControl/src/MDCPageControl.h @@ -23,8 +23,22 @@ material design specifications for animation and layout. The UIControlEventValueChanged control event is sent when the user changes the current page. + + ### UIScrollViewDelegate + + In order for the Page Control to respond correctly to scroll events set the scrollView.delegate to + your pageControl: + + scrollView.delegate = pageControl; + + or forward the UIScrollViewDelegate methods: + + @c scrollViewDidScroll: + @c scrollViewDidEndDecelerating: + @c scrollViewDidEndScrollingAnimation: + */ -@interface MDCPageControl : UIControl +@interface MDCPageControl : UIControl #pragma mark Managing the page diff --git a/components/PageControl/src/MDCPageControl.m b/components/PageControl/src/MDCPageControl.m index 472bffd79d7..3f1b36dda34 100644 --- a/components/PageControl/src/MDCPageControl.m +++ b/components/PageControl/src/MDCPageControl.m @@ -53,9 +53,6 @@ static inline CGFloat normalizeValue(CGFloat value, CGFloat minRange, CGFloat ma return (diff > 0) ? ((value - minRange) / diff) : 0; } -@interface MDCPageControl () -@end - @implementation MDCPageControl { UIView *_containerView; NSMutableArray *_indicators; @@ -69,34 +66,45 @@ @implementation MDCPageControl { - (id)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { - CGFloat radius = kMDCPageControlIndicatorRadius; - CGFloat topEdge = (CGFloat)(floor(CGRectGetHeight(self.bounds) - (radius * 2)) / 2); - CGRect containerFrame = CGRectMake(0, topEdge, CGRectGetWidth(self.bounds), radius * 2); - _containerView = [[UIView alloc] initWithFrame:containerFrame]; - - _trackLayer = [[MDCPageControlTrackLayer alloc] initWithRadius:radius]; - [_containerView.layer addSublayer:_trackLayer]; - _containerView.autoresizingMask = - UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | - UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin; - [self addSubview:_containerView]; - - // Defaults. - _currentPage = 0; - _currentPageIndicatorTintColor = - [UIColor colorWithWhite:kMDCPageControlCurrentPageIndicatorWhiteColor - alpha:1]; - _pageIndicatorTintColor = - [UIColor colorWithWhite:kMDCPageControlPageIndicatorWhiteColor - alpha:1]; + [self commonMDCPageControlInit]; + } + return self; +} + +- (id)initWithCoder:(NSCoder *)aDecoder { + self = [super initWithCoder:aDecoder]; + if (self) { + [self commonMDCPageControlInit]; } + return self; +} + +- (void)commonMDCPageControlInit { + CGFloat radius = kMDCPageControlIndicatorRadius; + CGFloat topEdge = (CGFloat)(floor(CGRectGetHeight(self.bounds) - (radius * 2)) / 2); + CGRect containerFrame = CGRectMake(0, topEdge, CGRectGetWidth(self.bounds), radius * 2); + _containerView = [[UIView alloc] initWithFrame:containerFrame]; + + _trackLayer = [[MDCPageControlTrackLayer alloc] initWithRadius:radius]; + [_containerView.layer addSublayer:_trackLayer]; + _containerView.autoresizingMask = + UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | + UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin; + [self addSubview:_containerView]; + + // Defaults. + _currentPage = 0; + _currentPageIndicatorTintColor = + [UIColor colorWithWhite:kMDCPageControlCurrentPageIndicatorWhiteColor + alpha:1]; + _pageIndicatorTintColor = + [UIColor colorWithWhite:kMDCPageControlPageIndicatorWhiteColor + alpha:1]; UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapGesture:)]; [self addGestureRecognizer:tapGestureRecognizer]; - - return self; } - (void)layoutSubviews { @@ -217,7 +225,8 @@ - (void)setCurrentPageIndicatorTintColor:(UIColor *)currentPageIndicatorTintColo - (NSInteger)scrolledPageNumber:(UIScrollView *)scrollView { // Returns paged index of scrollView. - return lround(scrollView.contentOffset.x / scrollView.frame.size.width); + NSInteger unboundedPageNumber = lround(scrollView.contentOffset.x / scrollView.frame.size.width); + return MAX(0, MIN(_numberOfPages - 1, unboundedPageNumber)); } - (CGFloat)scrolledPercentage:(UIScrollView *)scrollView { diff --git a/components/PageControl/tests/unit/PageControlExampleTests.m b/components/PageControl/tests/unit/PageControlExampleTests.m index 7e981d86605..e20a2540d1f 100644 --- a/components/PageControl/tests/unit/PageControlExampleTests.m +++ b/components/PageControl/tests/unit/PageControlExampleTests.m @@ -98,4 +98,37 @@ - (void)testSizeThatFits { XCTAssertFalse(CGRectEqualToRect(CGRectIntegral(pageControl.frame), nativePageControl.frame)); } +- (void)testScrollOffsetOutOfBoundsOfNumberOfPages { + // Given + CGRect frame = CGRectMake(0, 0, 100, 100); + MDCPageControl *pageControl = [[MDCPageControl alloc] init]; + pageControl.numberOfPages = 3; + UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:frame]; + scrollView.delegate = pageControl; + + // When + [scrollView setContentOffset:CGPointMake(frame.size.width * pageControl.numberOfPages, 0) + animated:YES]; + + // Then + XCTAssertEqual(pageControl.currentPage, pageControl.numberOfPages - 1); +} + +- (void)testCurrentPageGetsUpdatedWhenOffsetIsChanged { + // Given + CGRect frame = CGRectMake(0, 0, 100, 100); + MDCPageControl *pageControl = [[MDCPageControl alloc] init]; + pageControl.numberOfPages = 3; + UIScrollView *scrollView = [[UIScrollView alloc] initWithFrame:frame]; + scrollView.delegate = pageControl; + NSUInteger page = 2; + + // When + [scrollView setContentOffset:CGPointMake(frame.size.width * page, 0) + animated:YES]; + + // Then + XCTAssertEqual(pageControl.currentPage, page); +} + @end diff --git a/components/README.md b/components/README.md index 48a70f1fd8c..1d3f25bc142 100644 --- a/components/README.md +++ b/components/README.md @@ -1,7 +1,7 @@ --- title: "Material Components Documentation" layout: landing -section: documentation +section: components --- # Component Documentation diff --git a/components/RobotoFontLoader/.jazzy.yaml b/components/RobotoFontLoader/.jazzy.yaml index 6fcb7d31d93..647121122ef 100644 --- a/components/RobotoFontLoader/.jazzy.yaml +++ b/components/RobotoFontLoader/.jazzy.yaml @@ -1,4 +1,5 @@ -module_name: RobotoFontLoader +# Auto-generated by scripts/generate_jazzy_yamls.sh. Used primarily by scripts/external/arc-jazzy-linter. +module: RobotoFontLoader umbrella_header: src/MaterialRobotoFontLoader.h objc: true sdk: iphonesimulator diff --git a/components/RobotoFontLoader/README.md b/components/RobotoFontLoader/README.md index 4f9276c2a59..895304b0af4 100644 --- a/components/RobotoFontLoader/README.md +++ b/components/RobotoFontLoader/README.md @@ -1,7 +1,7 @@ --- title: "TODO: Roboto Font Loader" layout: detail -section: documentation +section: components excerpt: "The Roboto Font Loader lazy loads the Robot font." --- # Roboto Font Loader diff --git a/components/RobotoFontLoader/examples/RobotoFontLoaderSimpleExampleViewController.m b/components/RobotoFontLoader/examples/RobotoFontLoaderSimpleExampleViewController.m index b0f42abb436..50d8d649957 100644 --- a/components/RobotoFontLoader/examples/RobotoFontLoaderSimpleExampleViewController.m +++ b/components/RobotoFontLoader/examples/RobotoFontLoaderSimpleExampleViewController.m @@ -40,7 +40,7 @@ - (void)viewDidLoad { } + (NSArray *)catalogBreadcrumbs { - return @[ @"Roboto Font Loader", @"Roboto Font Loader" ]; + return @[ @"Typography and Fonts", @"Roboto Font Loader" ]; } @end diff --git a/components/RobotoFontLoader/examples/RobotoVsSystemExampleViewController.m b/components/RobotoFontLoader/examples/RobotoVsSystemExampleViewController.m index a0f9c5cf711..43610ac5e49 100644 --- a/components/RobotoFontLoader/examples/RobotoVsSystemExampleViewController.m +++ b/components/RobotoFontLoader/examples/RobotoVsSystemExampleViewController.m @@ -34,19 +34,11 @@ - (void)viewDidLoad { } + (NSArray *)catalogBreadcrumbs { - return @[ @"Roboto Font Loader", @"Roboto Font Loader" ]; + return @[ @"Typography and Fonts", @"Roboto Font Loader" ]; } + (NSString *)catalogStoryboardName { return @"RobotoVsSystem"; } -+ (NSString *)catalogDescription { - return @"The Roboto Font Loader lazy loads the Roboto font."; -} - -- (BOOL)catalogIsPrimaryDemo { - return YES; -} - @end diff --git a/components/ScrollViewDelegateMultiplexer/.jazzy.yaml b/components/ScrollViewDelegateMultiplexer/.jazzy.yaml deleted file mode 100644 index f73e43fea00..00000000000 --- a/components/ScrollViewDelegateMultiplexer/.jazzy.yaml +++ /dev/null @@ -1,4 +0,0 @@ -module_name: ScrollViewDelegateMultiplexer -umbrella_header: src/MaterialScrollViewDelegateMultiplexer.h -objc: true -sdk: iphonesimulator diff --git a/components/ScrollViewDelegateMultiplexer/README.md b/components/ScrollViewDelegateMultiplexer/README.md deleted file mode 100644 index 55cea8b5093..00000000000 --- a/components/ScrollViewDelegateMultiplexer/README.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -title: "ScrollViewDelegateMultiplexer" -layout: detail -section: documentation -excerpt: "ScrollViewDelegateMultiplexer acts as a proxy object for UIScrollViewDelegate events and forwards all received events to an ordered list of registered observers." ---- -# ScrollViewDelegateMultiplexer - -This class acts as a proxy object for UIScrollViewDelegate events and forwards all received -events to an ordered list of registered observers. - -> Notice: This component is now available at -> https://github.com/google/GOSScrollViewDelegateMultiplexer and will be removed in a subsequent MDC -> release. - -## Requirements - -- Xcode 7.0 or higher. -- iOS SDK version 7.0 or higher. - -## Usage - -```objectivec -#import "MaterialScrollViewDelegateMultiplexer.h" - -_multiplexer = [[MDCScrollViewDelegateMultiplexer alloc] init]; -myScrollView.delegate = _multiplexer; -[_multiplexer addObservingDelegate:myControl]; -[_multiplexer addObservingDelegate:anotherControl]; -``` diff --git a/components/ScrollViewDelegateMultiplexer/examples/SVDMTypicalUseExample.m b/components/ScrollViewDelegateMultiplexer/examples/SVDMTypicalUseExample.m deleted file mode 100644 index 1912138cb36..00000000000 --- a/components/ScrollViewDelegateMultiplexer/examples/SVDMTypicalUseExample.m +++ /dev/null @@ -1,147 +0,0 @@ -/* - Copyright 2015-present Google Inc. 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. - */ - -#import - -#import "MaterialScrollViewDelegateMultiplexer.h" -#import "ObservingPageControl.h" - -@interface SVDMTypicalUseViewController : UIViewController -@end - -#define RGBCOLOR(r, g, b) [UIColor colorWithRed:(r) / 255.0f green:(g) / 255.0f blue:(b) / 255.0f alpha:1] -#define HEXCOLOR(hex) RGBCOLOR((((hex) >> 16) & 0xFF), (((hex) >> 8) & 0xFF), ((hex)&0xFF)) - -@implementation SVDMTypicalUseViewController { - UIScrollView *_scrollView; - UIPageControl *_pageControl; - NSArray *_pages; - - MDCScrollViewDelegateMultiplexer *_multiplexer; -} - -+ (NSArray *)catalogBreadcrumbs { - return @[ @"Scroll View Delegate Multiplexer", @"Scroll View Delegate Multiplexer" ]; -} - -- (void)viewDidLoad { - [super viewDidLoad]; - - CGFloat boundsWidth = CGRectGetWidth(self.view.bounds); - CGFloat boundsHeight = CGRectGetHeight(self.view.bounds); - - NSArray *pageColors = @[ HEXCOLOR(0x81D4FA), HEXCOLOR(0x80CBC4), HEXCOLOR(0xFFCC80) ]; - - // Scroll view configuration - - _scrollView = [[UIScrollView alloc] initWithFrame:self.view.bounds]; - _scrollView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight; - _scrollView.pagingEnabled = YES; - _scrollView.contentSize = CGSizeMake(boundsWidth * pageColors.count, boundsHeight); - _scrollView.minimumZoomScale = 0.5; - _scrollView.maximumZoomScale = 6.0; - _scrollView.showsHorizontalScrollIndicator = NO; - - NSMutableArray *pages = [NSMutableArray array]; - - // Add pages to scrollView. - for (NSInteger i = 0; i < pageColors.count; i++) { - CGRect pageFrame = CGRectOffset(self.view.bounds, i * boundsWidth, 0); - UILabel *page = [[UILabel alloc] initWithFrame:pageFrame]; - page.text = [NSString stringWithFormat:@"Page %zd", i + 1]; - page.font = [UIFont systemFontOfSize:50]; - page.textColor = [UIColor colorWithWhite:0 alpha:0.8]; - page.textAlignment = NSTextAlignmentCenter; - page.backgroundColor = pageColors[i]; - page.autoresizingMask = - UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin; - [_scrollView addSubview:page]; - [pages addObject:page]; - } - _pages = [pages copy]; - - // Page control configuration - - ObservingPageControl *pageControl = [[ObservingPageControl alloc] init]; - pageControl.numberOfPages = pageColors.count; - - pageControl.pageIndicatorTintColor = [UIColor colorWithWhite:0 alpha:0.2]; - pageControl.currentPageIndicatorTintColor = [UIColor colorWithWhite:0 alpha:0.8]; - - CGSize pageControlSize = [pageControl sizeThatFits:self.view.bounds.size]; - // We want the page control to span the bottom of the screen. - pageControlSize.width = self.view.bounds.size.width; - pageControl.frame = CGRectMake(0, - self.view.bounds.size.height - pageControlSize.height, - self.view.bounds.size.width, - pageControlSize.height); - [pageControl addTarget:self - action:@selector(didChangePage:) - forControlEvents:UIControlEventValueChanged]; - pageControl.defersCurrentPageDisplay = YES; - _pageControl = pageControl; - _pageControl.autoresizingMask = - UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth; - - // Add subviews - - [self.view addSubview:_scrollView]; - [self.view addSubview:pageControl]; - - // Create scrollView delegate multiplexer and register observers - - _multiplexer = [[MDCScrollViewDelegateMultiplexer alloc] init]; - _scrollView.delegate = _multiplexer; - [_multiplexer addObservingDelegate:self]; - [_multiplexer addObservingDelegate:pageControl]; -} - -#pragma mark - Frame changes - -- (void)viewWillLayoutSubviews { - [super viewWillLayoutSubviews]; - NSInteger pageBeforeFrameChange = _pageControl.currentPage; - NSInteger pageCount = _pages.count; - CGFloat boundsWidth = CGRectGetWidth(self.view.bounds); - CGFloat boundsHeight = CGRectGetHeight(self.view.bounds); - for (NSInteger i = 0; i < pageCount; i++) { - UILabel *page = [_pages objectAtIndex:i]; - page.frame = CGRectOffset(self.view.bounds, i * boundsWidth, 0); - } - _scrollView.contentSize = CGSizeMake(boundsWidth * pageCount, boundsHeight); - CGPoint offset = _scrollView.contentOffset; - offset.x = pageBeforeFrameChange * boundsWidth; - // This non-anmiated change of offset ensures we keep the same page - [_scrollView setContentOffset:offset animated:NO]; -} - -#pragma mark - UIScrollViewDelegate - -- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { - NSLog(@"%@", NSStringFromSelector(_cmd)); - - [_pageControl updateCurrentPageDisplay]; -} - -#pragma mark - User events - -- (void)didChangePage:(UIPageControl *)sender { - CGPoint offset = _scrollView.contentOffset; - offset.x = sender.currentPage * _scrollView.bounds.size.width; - [_scrollView setContentOffset:offset animated:YES]; -} - -@end diff --git a/components/ScrollViewDelegateMultiplexer/examples/SVDMultiplexer.playground/Contents.swift b/components/ScrollViewDelegateMultiplexer/examples/SVDMultiplexer.playground/Contents.swift deleted file mode 100644 index 195bb547fa9..00000000000 --- a/components/ScrollViewDelegateMultiplexer/examples/SVDMultiplexer.playground/Contents.swift +++ /dev/null @@ -1,115 +0,0 @@ -/*: -# Scroll view delegate multiplexer playground - -This playground explores the use of a UIScrollViewDelegate multiplexer in order to react to UIScrollView events. - -Requirements: **Xcode 7.1 or higher**. - -We've created a framework called MaterialScrollViewDelegateMultiplexer in which we've placed the relevant code. - -If the Playground can't find the framework then you need to initiate a build of the ScrollViewDelegateMultiplexerExample project. Also make sure that you're viewing the playground via Examples.xcworkspace. -*/ -import MaterialScrollViewDelegateMultiplexer - -//: We'll use the XCPlayground framework to show a live view in the Assistant editor. Simply click the Assistant editor icon to view the live view. -import XCPlayground - -/*: -## Create a multiplexer - -The multiplexer object is meant to be assigned as the delegate of a UIScrollView. The delegate property won't hold onto a reference of the multiplexer, so we need to store an instance of the multiplexer elsewhere. - -For the purposes of this Playground we'll store the multiplexer in the global scope. In practice you'd store this multiplexer as a property on your UIViewController. -*/ -let multiplexer = MDCScrollViewDelegateMultiplexer() - -/*: -Let's create a hypothetical object that is interested in reacting to UIScrollViewDelegate events. When it receives a `scrollViewDidScroll:` event it will execute a provided block. -*/ -class ScrollViewListener : NSObject { - init(didScrollCallback: UIScrollView -> Void) { self.didScrollCallback = didScrollCallback } - let didScrollCallback: UIScrollView -> Void -} - -extension ScrollViewListener : UIScrollViewDelegate { - func scrollViewDidScroll(scrollView: UIScrollView) { - self.didScrollCallback(scrollView) - } -} - -/*: -## Register listeners - -We'll create two listeners objects and add both of them to our multiplexer. When the Playground executes you'll be able to verify that each block was invoked while the scroll view scrolls. -*/ -let listener1 = ScrollViewListener { scrollView in - scrollView.contentOffset.y -//: Without the following return statement, Swift will treat the line above as an implicit return statement and we won't be able to see the Playground graph. - return -} - -let listener2 = ScrollViewListener { scrollView in - scrollView.contentOffset.y - return -} - -//: We can now register each listener instance to the multiplexer. Our multiplexer is now ready to be assigned as the delegate of a UIScrollView. -multiplexer.addObservingDelegate(listener1) -multiplexer.addObservingDelegate(listener2) - -/*: -## The view controller - -We'll use the Playground live view feature to show a subclass of UIViewController we've created for this playground. -*/ -class PlaygroundViewController : UIViewController { - var scrollView: UIScrollView? -} - -//: Assigning our view controller as the live view places will cause our view controller's view to show up in the Assistant editor. -XCPlaygroundPage.currentPage.liveView = PlaygroundViewController() - -extension PlaygroundViewController { - - override func viewDidLoad() { - super.viewDidLoad() - self.view.backgroundColor = UIColor.whiteColor() - - let width = self.view.bounds.size.width - - scrollView = UIScrollView(frame: self.view.bounds) - -//: We can now assign the multiplexer as the scroll view's delegate. - scrollView!.delegate = multiplexer - -//: The rest of this logic is straightforward UIKit view creation and manipulation. - scrollView!.contentSize = CGSize(width: width, height: self.view.bounds.height * 100) - self.view.addSubview(scrollView!) - - for _ in 0..<15 { - let square = UIView(frame: CGRect( - x: drand48() * Double(width - 30), - y: drand48() * 1000, - width: 30, - height: 30) - ) - square.backgroundColor = UIColor.redColor().colorWithAlphaComponent(0.5) - - scrollView!.addSubview(square) - } - } - - override func viewDidAppear(animated: Bool) { - super.viewDidAppear(animated) - - scrollView?.setContentOffset(CGPoint(x: 0, y: 400), animated: true) - } -} - -//: # Playground logic - -extension ScrollViewListener { - func scrollViewDidEndScrollingAnimation(scrollView: UIScrollView) { - XCPlaygroundPage.currentPage.finishExecution() - } -} diff --git a/components/ScrollViewDelegateMultiplexer/examples/SVDMultiplexer.playground/contents.xcplayground b/components/ScrollViewDelegateMultiplexer/examples/SVDMultiplexer.playground/contents.xcplayground deleted file mode 100644 index 73b0de178c2..00000000000 --- a/components/ScrollViewDelegateMultiplexer/examples/SVDMultiplexer.playground/contents.xcplayground +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/components/ScrollViewDelegateMultiplexer/examples/SVDMultiplexer.playground/timeline.xctimeline b/components/ScrollViewDelegateMultiplexer/examples/SVDMultiplexer.playground/timeline.xctimeline deleted file mode 100644 index 9ec1a58f8fb..00000000000 --- a/components/ScrollViewDelegateMultiplexer/examples/SVDMultiplexer.playground/timeline.xctimeline +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/components/ScrollViewDelegateMultiplexer/src/MDCScrollViewDelegateMultiplexer.h b/components/ScrollViewDelegateMultiplexer/src/MDCScrollViewDelegateMultiplexer.h deleted file mode 100644 index 111ce5d802c..00000000000 --- a/components/ScrollViewDelegateMultiplexer/src/MDCScrollViewDelegateMultiplexer.h +++ /dev/null @@ -1,111 +0,0 @@ -/* - Copyright 2015-present Google Inc. 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. - */ - -#import - -@protocol MDCScrollViewDelegateCombining; - -/** - MDCScrollViewDelegateMultiplexer acts as a proxy object for UIScrollViewDelegate events and - forwards all received events to an ordered list of registered observers. - - When a UIScrollViewDelegate method invocation is received by the multiplexer, the multiplexer - forwards the invocation to each observer in order of registration. - - If the scroll view method signature has a return value, after all delegate method invocations, - the return value will be provided by the first observing delegate that responded. If no - observers implement the method, a default return value will be used. - - However, if a combiner is set and the receiving class conforms to the - MDCScrollViewDelegateCombining protocol, then the receiving class can designate which result - value to return via the use of the optional protocol methods. - - Example implementation: - - _multiplexer = [[MDCScrollViewDelegateMultiplexer alloc] init]; - myScrollView.delegate = _multiplexer; - [_multiplexer addObservingDelegate:myControl]; - [_multiplexer addObservingDelegate:anotherControl]; - */ -// clang-format off -__deprecated_msg("This component is now available at https://github.com/google/GOSScrollViewDelegateMultiplexer.") -@interface MDCScrollViewDelegateMultiplexer : NSObject - -/** - Adds an observing delegate to the end of the array of delegates. - - @param delegate The observing delegate to be added. - */ -- (void)addObservingDelegate : (nonnull id)delegate; - -/** - Removes an observing delegate from the array of delegates. - - @param delegate The observing delegate to be removed. - */ -- (void)removeObservingDelegate:(nonnull id)delegate; - -/** - An optional delegate through which the MDCScrollViewDelegateMultiplexer may provide a - single UIScrollViewDelegate protocol return value from its array of observing delegates. - */ -@property(nonatomic, weak, null_resettable) id combiner; - -@end - -/** - The MDCScrollViewDelegateCombining protocol defines an internal mechanism through which - MDCScrollViewDelegateMultiplexer provides an array of responding observing delegates - and their respective return values. - - Since it is possible that multiple delegates may respond to UIScrollViewDelegate methods that - provide return values, this protocol allows the receiver to select the specific value to return - from an array of those responding result values. - */ -__deprecated_msg("This component is now available at https://github.com/google/GOSScrollViewDelegateMultiplexer.") -@protocol MDCScrollViewDelegateCombining @optional - -/** - Allows the receiver to return the preferred UIView result from observer delegates that have - responded to the UIScrollViewDelegate -viewForZoomingInScrollView method. - - @param multiplexer The scrollView delegate multiplexer. - @param results A pointer array of UIView instances returned by responding observer delegates. - NSPointerArray here to allow nil results from -viewForZoomingInScrollView. - @param respondingObservers An array of observing delegates that responded. - - @return The preferred UIView result for this method. - */ -- (nullable UIView *)scrollViewDelegateMultiplexer:(nonnull MDCScrollViewDelegateMultiplexer *)multiplexer - viewForZoomingWithResults:(nonnull NSPointerArray *)results - fromRespondingObservers:(nonnull NSArray *)respondingObservers; - -/** - Allows the receiver to return the preferred BOOL result from observer delegates that have - responded to the UIScrollViewDelegate -scrollViewShouldScrollToTop method. - - @param multiplexer The scrollView delegate multiplexer. - @param results An array of NSNumber instances returned by responding observer delegates. - @param respondingObservers An array of observing delegates that responded. - - @return The preferred BOOL result for this method. - */ -- (BOOL)scrollViewDelegateMultiplexer:(nonnull MDCScrollViewDelegateMultiplexer *)multiplexer - shouldScrollToTopWithResults:(nonnull NSArray *)results - fromRespondingObservers:(nonnull NSArray *)respondingObservers; - -@end - // clang-format on diff --git a/components/ScrollViewDelegateMultiplexer/src/MDCScrollViewDelegateMultiplexer.m b/components/ScrollViewDelegateMultiplexer/src/MDCScrollViewDelegateMultiplexer.m deleted file mode 100644 index 4bf751ef5b4..00000000000 --- a/components/ScrollViewDelegateMultiplexer/src/MDCScrollViewDelegateMultiplexer.m +++ /dev/null @@ -1,243 +0,0 @@ -/* - Copyright 2015-present Google Inc. 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. - */ - -#import "MDCScrollViewDelegateMultiplexer.h" - -#import - -@implementation MDCScrollViewDelegateMultiplexer { - NSPointerArray *_observingDelegates; -} - -- (instancetype)init { - self = [super init]; - if (self) { - NSPointerFunctionsOptions options = - (NSPointerFunctionsWeakMemory | NSPointerFunctionsObjectPointerPersonality); - _observingDelegates = [NSPointerArray pointerArrayWithOptions:options]; - } - return self; -} - -- (void)addObservingDelegate:(id)delegate { - [_observingDelegates addPointer:(__bridge void *)(delegate)]; -} - -- (void)removeObservingDelegate:(id)delegate { - for (NSUInteger i = 0; i < _observingDelegates.count; i++) { - if ([_observingDelegates pointerAtIndex:i] == (__bridge void *)(delegate)) { - [_observingDelegates removePointerAtIndex:i]; - } - } -} - -- (void)setCombiner:(id)combiner { - _combiner = combiner; -} - -#pragma mark - NSObject - -- (BOOL)shouldForwardSelector:(SEL)selector { - // Check optional methods. - struct objc_method_description description = - protocol_getMethodDescription(@protocol(UIScrollViewDelegate), selector, NO, YES); - return (description.name != NULL && description.types != NULL); -} - -- (BOOL)respondsToSelector:(SEL)aSelector { - if ([super respondsToSelector:aSelector]) { - return YES; - } else if ([self shouldForwardSelector:aSelector]) { - for (id delegate in _observingDelegates) { - if ([delegate respondsToSelector:aSelector]) { - return YES; - } - } - } - return NO; -} - -#pragma mark - UIScrollViewDelegate Forwarding - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView { - for (id delegate in _observingDelegates) { - if ([delegate respondsToSelector:_cmd]) { - [delegate scrollViewDidScroll:scrollView]; - } - } -} - -- (void)scrollViewDidZoom:(UIScrollView *)scrollView { - for (id delegate in _observingDelegates) { - if ([delegate respondsToSelector:_cmd]) { - [delegate scrollViewDidZoom:scrollView]; - } - } -} - -- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { - for (id delegate in _observingDelegates) { - if ([delegate respondsToSelector:_cmd]) { - [delegate scrollViewWillBeginDragging:scrollView]; - } - } -} - -- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView - withVelocity:(CGPoint)velocity - targetContentOffset:(inout CGPoint *)targetContentOffset { - for (id delegate in _observingDelegates) { - if ([delegate respondsToSelector:_cmd]) { - [delegate scrollViewWillEndDragging:scrollView - withVelocity:velocity - targetContentOffset:targetContentOffset]; - } - } -} - -- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { - for (id delegate in _observingDelegates) { - if ([delegate respondsToSelector:_cmd]) { - [delegate scrollViewDidEndDragging:scrollView willDecelerate:decelerate]; - } - } -} - -- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView { - for (id delegate in _observingDelegates) { - if ([delegate respondsToSelector:_cmd]) { - [delegate scrollViewWillBeginDecelerating:scrollView]; - } - } -} - -- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView { - for (id delegate in _observingDelegates) { - if ([delegate respondsToSelector:_cmd]) { - [delegate scrollViewDidEndDecelerating:scrollView]; - } - } -} - -- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView { - for (id delegate in _observingDelegates) { - if ([delegate respondsToSelector:_cmd]) { - [delegate scrollViewDidEndScrollingAnimation:scrollView]; - } - } -} - -- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView { - // NSPointerArray here to allow nil results from -viewForZoomingInScrollView. - NSPointerArray *results = - [NSPointerArray pointerArrayWithOptions:(NSPointerFunctionsStrongMemory | - NSPointerFunctionsObjectPointerPersonality)]; - NSMutableArray *respondingObservers = [NSMutableArray array]; - - // Execute this method for every responding observer. - for (id delegate in _observingDelegates) { - if ([delegate respondsToSelector:_cmd]) { - UIView *result = [delegate viewForZoomingInScrollView:scrollView]; - [results addPointer:(__bridge void *)(result)]; - [respondingObservers addObject:delegate]; - } - } - - if ([_combiner respondsToSelector:@selector(scrollViewDelegateMultiplexer: - viewForZoomingWithResults: - fromRespondingObservers:)]) { - return [_combiner scrollViewDelegateMultiplexer:(id)self - viewForZoomingWithResults:results - fromRespondingObservers:respondingObservers]; - } else if (results.count > 0) { -#if DEBUG - NSHashTable *hash = [NSHashTable weakObjectsHashTable]; - for (UIView *view in results) { - [hash addObject:view ? view : [NSNull null]]; - } - NSAssert(hash.count == 1, - @"-viewForZoomingInScrollView returns different results from multiple observers." - " Use the combiner protocol MDCScrollViewDelegateCombining to select the appropriate" - " observer return value for this method."); -#endif - - return [results pointerAtIndex:0]; - } - - return nil; -} - -- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView - withView:(UIView *)view { - for (id delegate in _observingDelegates) { - if ([delegate respondsToSelector:_cmd]) { - [delegate scrollViewWillBeginZooming:scrollView withView:view]; - } - } -} - -- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView - withView:(UIView *)view - atScale:(CGFloat)scale { - for (id delegate in _observingDelegates) { - if ([delegate respondsToSelector:_cmd]) { - [delegate scrollViewDidEndZooming:scrollView withView:view atScale:scale]; - } - } -} - -- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView { - NSMutableArray *results = [NSMutableArray array]; - NSMutableArray *respondingObservers = [NSMutableArray array]; - - // Execute this method for every responding observer. - for (id delegate in _observingDelegates) { - if ([delegate respondsToSelector:_cmd]) { - [results addObject:@([delegate scrollViewShouldScrollToTop:scrollView])]; - [respondingObservers addObject:delegate]; - } - } - - if ([_combiner respondsToSelector:@selector(scrollViewDelegateMultiplexer: - shouldScrollToTopWithResults: - fromRespondingObservers:)]) { - return [_combiner scrollViewDelegateMultiplexer:(id)self - shouldScrollToTopWithResults:results - fromRespondingObservers:respondingObservers]; - } else if (results.count > 0) { -#if DEBUG - NSSet *set = [NSSet setWithArray:results]; - NSAssert(set.count == 1, - @"-scrollViewShouldScrollToTop returns different results from multiple observers." - " Use the combiner protocol MDCScrollViewDelegateCombining to select the appropriate" - " observer return value for this method."); -#endif - - return [results[0] boolValue]; - } - - return YES; -} - -- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView { - for (id delegate in _observingDelegates) { - if ([delegate respondsToSelector:_cmd]) { - [delegate scrollViewDidScrollToTop:scrollView]; - } - } -} - -@end diff --git a/components/ScrollViewDelegateMultiplexer/tests/unit/ScrollViewDelegateMultiplexerExampleTests.m b/components/ScrollViewDelegateMultiplexer/tests/unit/ScrollViewDelegateMultiplexerExampleTests.m deleted file mode 100644 index 334b4c1d223..00000000000 --- a/components/ScrollViewDelegateMultiplexer/tests/unit/ScrollViewDelegateMultiplexerExampleTests.m +++ /dev/null @@ -1,136 +0,0 @@ -/* - Copyright 2015-present Google Inc. 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. - */ - -#import - -#import "MaterialScrollViewDelegateMultiplexer.h" - -static NSString *const kScrollViewDidScroll = @"scrollViewDidScroll"; - -#pragma mark - Simple observer object - -/** Simple object that conforms to UIScrollViewDelegate protocol. */ -@interface ScrollObservingObject : UIView -- (instancetype)initWithExpectation:(XCTestExpectation *)expectation; -@end - -@implementation ScrollObservingObject { - XCTestExpectation *_observerExpectation; -} - -- (instancetype)initWithExpectation:(XCTestExpectation *)expectation { - self = [self initWithFrame:CGRectZero]; - if (self) { - _observerExpectation = expectation; - } - return self; -} - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView { - [_observerExpectation fulfill]; -} - -@end - -@interface ScrollViewDelegateMultiplexerExampleTests : XCTestCase -@end - -@implementation ScrollViewDelegateMultiplexerExampleTests { - UIScrollView *_scrollView; - ScrollObservingObject *_observingObject; - MDCScrollViewDelegateMultiplexer *_multiplexer; - XCTestExpectation *_expectation; - XCTestExpectation *_observerExpectation; -} - -- (void)setUp { - [super setUp]; - _scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 0, 100, 10)]; - _scrollView.contentSize = CGSizeMake(200, 10); -} - -- (void)tearDown { - [super tearDown]; -} - -#pragma mark - Tests - -- (void)testWithoutMultiplexer { - _expectation = [self expectationWithDescription:kScrollViewDidScroll]; - - _scrollView.delegate = self; - - // Instigate event. - [_scrollView setContentOffset:CGPointMake(50, 0) animated:YES]; - - [self waitForExpectationsWithTimeout:0 - handler:^(NSError *error) { - XCTAssertEqual(error, nil); - }]; -} - -- (void)testMuliplexerSingleDelegate { - _expectation = [self expectationWithDescription:kScrollViewDidScroll]; - - // Create scrollView delegate multiplexer. - _multiplexer = [[MDCScrollViewDelegateMultiplexer alloc] init]; - [_multiplexer addObservingDelegate:self]; - _scrollView.delegate = _multiplexer; - - // Instigate event. - [_scrollView setContentOffset:CGPointMake(50, 0) animated:YES]; - - [self waitForExpectationsWithTimeout:0 - handler:^(NSError *error) { - XCTAssertEqual(error, nil); - }]; -} - -- (void)testMuliplexerMultipleDelegate { - _expectation = [self expectationWithDescription:kScrollViewDidScroll]; - _observerExpectation = [self expectationWithDescription:kScrollViewDidScroll]; - - // Create simple object - _observingObject = [[ScrollObservingObject alloc] initWithExpectation:_observerExpectation]; - - // Create scrollView delegate multiplexer. - _multiplexer = [[MDCScrollViewDelegateMultiplexer alloc] init]; - [_multiplexer addObservingDelegate:self]; - [_multiplexer addObservingDelegate:_observingObject]; - _scrollView.delegate = _multiplexer; - - // Instigate event. - [_scrollView setContentOffset:CGPointMake(50, 0) animated:YES]; - - [self waitForExpectationsWithTimeout:0 - handler:^(NSError *error) { - XCTAssertEqual(error, nil); - - [self waitForExpectationsWithTimeout:0 - handler:^(NSError *error2) { - XCTAssertEqual(error2, nil); - }]; - - }]; -} - -#pragma mark - UIScrollViewDelegate - -- (void)scrollViewDidScroll:(UIScrollView *)scrollView { - [_expectation fulfill]; -} - -@end diff --git a/components/ShadowElevations/.jazzy.yaml b/components/ShadowElevations/.jazzy.yaml index f691f1a1ca9..ccc0f2925e0 100644 --- a/components/ShadowElevations/.jazzy.yaml +++ b/components/ShadowElevations/.jazzy.yaml @@ -1,4 +1,5 @@ -module_name: ShadowElevations +# Auto-generated by scripts/generate_jazzy_yamls.sh. Used primarily by scripts/external/arc-jazzy-linter. +module: ShadowElevations umbrella_header: src/MaterialShadowElevations.h objc: true sdk: iphonesimulator diff --git a/components/ShadowElevations/README.md b/components/ShadowElevations/README.md index 67b708ee5ff..431f2dd3883 100644 --- a/components/ShadowElevations/README.md +++ b/components/ShadowElevations/README.md @@ -1,7 +1,7 @@ --- title: "Shadow Elevations" layout: detail -section: documentation +section: components excerpt: "The Shadow Elevations component provides the most commonly-used Material Design elevations." --- # Shadow Elevations @@ -56,7 +56,6 @@ Before using Shadow Elevations, you'll need to import it: #### Objective-C - ~~~ objc #import "MaterialShadowElevations.h" ~~~ @@ -65,9 +64,32 @@ Before using Shadow Elevations, you'll need to import it: ~~~ swift import MaterialComponents ~~~ + +#### Objective-C +~~~ objc +@interface ShadowedView: UIView + +@end + +@implementation ShadowedView + ++ (Class)layerClass { + return [MDCShadowLayer class]; +} + +- (MDCShadowLayer *)shadowLayer { + return (MDCShadowLayer *)self.layer; +} + +- (void)setDefaultElevation { + self.shadowLayer.elevation = MDCShadowElevationCardResting; +} + +@end +~~~ #### Swift ~~~ swift @@ -81,7 +103,7 @@ class ShadowedView: UIView { return self.layer as! MDCShadowLayer } - func setElevation(points: CGFloat) { + func setDefaultElevation() { self.shadowLayer.elevation = MDCShadowElevationCardResting } diff --git a/components/ShadowElevations/examples/ShadowElevationsTypicalUseExample.m b/components/ShadowElevations/examples/ShadowElevationsTypicalUseExample.m index 6a62a934af5..ca30bd318db 100644 --- a/components/ShadowElevations/examples/ShadowElevationsTypicalUseExample.m +++ b/components/ShadowElevations/examples/ShadowElevationsTypicalUseExample.m @@ -130,7 +130,7 @@ @implementation ShadowElevationsTypicalUseViewController - (void)viewDidLoad { [super viewDidLoad]; self.view.backgroundColor = [UIColor whiteColor]; - self.title = @"Shadows (Points)"; + self.title = @"Shadow Elevations"; _shadowsView = [[ShadowElevationsPointsView alloc] initWithFrame:self.view.bounds]; _shadowsView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; @@ -140,15 +140,7 @@ - (void)viewDidLoad { #pragma mark catalog by convention + (NSArray *)catalogBreadcrumbs { - return @[ @"Shadow Elevations", @"Shadow Elevations" ]; -} - -+ (NSString *)catalogDescription { - return @"This component provides the most commonly-used Material Design elevations."; -} - -- (BOOL)catalogIsPrimaryDemo { - return YES; + return @[ @"Shadow", @"Shadow Elevations" ]; } @end diff --git a/components/ShadowLayer/.jazzy.yaml b/components/ShadowLayer/.jazzy.yaml index 72154fae6a1..d96d0b65b13 100644 --- a/components/ShadowLayer/.jazzy.yaml +++ b/components/ShadowLayer/.jazzy.yaml @@ -1,4 +1,5 @@ -module_name: ShadowLayer +# Auto-generated by scripts/generate_jazzy_yamls.sh. Used primarily by scripts/external/arc-jazzy-linter. +module: ShadowLayer umbrella_header: src/MaterialShadowLayer.h objc: true sdk: iphonesimulator diff --git a/components/ShadowLayer/README.md b/components/ShadowLayer/README.md index 92a346e4339..7de7c1ab317 100644 --- a/components/ShadowLayer/README.md +++ b/components/ShadowLayer/README.md @@ -1,7 +1,7 @@ --- title: "Shadow Layer" layout: detail -section: documentation +section: components excerpt: "The Shadow Layer component implements the Material Design specifications for elevation and shadows." --- @@ -92,9 +92,11 @@ Before using Shadow Layer, you'll need to import it: ~~~ #### Swift + ~~~ swift import MaterialComponents ~~~ + @@ -116,12 +118,25 @@ Example of a custom button based on UIButton with Material Design shadows: @end ~~~ + +### Swift +~~~ swift +class ShadowButton: UIButton { + + override class func layerClass() -> AnyClass { + return MDCShadowLayer.self + } + +} +~~~ + Add the custom button to view: + ### Objective C ~~~ objc ShadowButton *button = [ShadowButton buttonWithType:UIButtonTypeSystem]; @@ -131,13 +146,47 @@ button.frame = CGRectMake(100, 100, 200, 50); [self.view addSubview:button]; ~~~ + +### Swift +~~~ swift +let button: ShadowButton = ShadowButton.init(type: UIButtonType.System) +button.frame = CGRect(x: 100, y: 100, width: 200, height: 50) +button.setTitle("Button", forState: UIControlState.Normal) +(button.layer as! MDCShadowLayer).setElevation(6.0) +self.addSubview(button) + +~~~ + Creating a custom UIView with a shadow: -#### Swift + +### Objective C +~~~ objc +@interface ShadowedView : UIView +@end + +@implementation ShadowedView + ++ (Class)layerClass { + return [MDCShadowLayer class]; +} + +- (MDCShadowLayer)shadowLayer { + return (MDCShadowLayer *)self.layer; +} + +- (void)setElevation:(CGFloat)points { + [(MDCShadowLayer *)self.layer setElevation:points]; +} + +@end +~~~ + +### Swift ~~~ swift class ShadowedView: UIView { @@ -154,8 +203,8 @@ class ShadowedView: UIView { } } - ~~~ + @@ -163,6 +212,7 @@ To improve performance, consider rasterizing MDCShadowLayer when the view using animating or changing size. + ### Objective C ~~~ objc @@ -170,6 +220,15 @@ self.layer.shouldRasterize = YES; self.layer.rasterizationScale = [UIScreen mainScreen].scale; ~~~ + +### Swift +~~~ swift + +self.layer.shouldRasterize = true; +self.layer.rasterizationScale = UIScreen.mainScreen().scale + +~~~ + Disable rasterization before animating MDCShadowLayer. diff --git a/components/ShadowLayer/examples/ShadowDragSquareExampleViewController.swift b/components/ShadowLayer/examples/ShadowDragSquareExampleViewController.swift index 9d5d59c4a0e..99754de202e 100644 --- a/components/ShadowLayer/examples/ShadowDragSquareExampleViewController.swift +++ b/components/ShadowLayer/examples/ShadowDragSquareExampleViewController.swift @@ -72,7 +72,7 @@ class ShadowDragSquareExampleViewController: UIViewController { // MARK: catalog by convention class func catalogBreadcrumbs() -> Array { - return [ "Shadow Layer", "Shadow Layer"] + return [ "Shadow", "Shadow Layer"] } class func catalogStoryboardName() -> String { @@ -83,7 +83,7 @@ class ShadowDragSquareExampleViewController: UIViewController { return "Shadow Layer implements the Material Design specifications for elevation and shadows." } - func catalogIsPrimaryDemo() -> Bool { + class func catalogIsPrimaryDemo() -> Bool { return true } diff --git a/components/Slider/.jazzy.yaml b/components/Slider/.jazzy.yaml index c3e13d04646..4d9c582b966 100644 --- a/components/Slider/.jazzy.yaml +++ b/components/Slider/.jazzy.yaml @@ -1,4 +1,5 @@ -module_name: Slider +# Auto-generated by scripts/generate_jazzy_yamls.sh. Used primarily by scripts/external/arc-jazzy-linter. +module: Slider umbrella_header: src/MaterialSlider.h objc: true sdk: iphonesimulator diff --git a/components/Slider/README.md b/components/Slider/README.md index 48cd02c449f..3018add499a 100644 --- a/components/Slider/README.md +++ b/components/Slider/README.md @@ -1,7 +1,7 @@ --- title: "Slider" layout: detail -section: documentation +section: components excerpt: "The Slider component provides a Material Design control for selecting a value from a continuous range or discrete set of values." --- # Slider @@ -72,29 +72,42 @@ import MaterialComponents ~~~ +### Standard usage + +MDCSlider can be be used like a standard `UIControl`. + +#### Objective C -### Objective C ~~~ objc - (void)viewDidLoad { -... - - MDCSlider *slider = [[MDCSlider alloc] initWithFrame:CGRectMake(0, 0, 100, 27)]; + MDCSlider *slider = [[MDCSlider alloc] initWithFrame:CGRectMake(50, 50, 100, 27)]; [slider addTarget:self action:@selector(didChangeSliderValue:) forControlEvents:UIControlEventValueChanged]; [self.view addSubview:slider]; - slider.center = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds) - 2 * slider.frame.size.height); +} -... +- (void)didChangeSliderValue:(MDCSlider *)slider { + NSLog(@"did change %@ value: %f", NSStringFromClass([slider class]), slider.value); } +~~~ -- (void)didChangeSliderValue:(id)sender { - MDCSlider *slider = sender; - NSLog(@"did change %@ value: %f", NSStringFromClass([sender class]), slider.value); +#### Swift + +~~~ swift +override func viewDidLoad() { + let slider = MDCSlider(frame: CGRectMake(50, 50, 100, 27)) + slider.addTarget(self, + action: Selector("didChangeSliderValue:"), + forControlEvents: .ValueChanged) + view.addSubview(slider) } +func didChangeSliderValue(senderSlider:MDCSlider) { + NSLog("Did change slider value to: %@", senderSlider.value) +} ~~~ diff --git a/components/Slider/examples/supplemental/SliderTypicalUseSupplemental.m b/components/Slider/examples/supplemental/SliderTypicalUseSupplemental.m index 2e856b59e71..49fd4420372 100644 --- a/components/Slider/examples/supplemental/SliderTypicalUseSupplemental.m +++ b/components/Slider/examples/supplemental/SliderTypicalUseSupplemental.m @@ -5,8 +5,9 @@ #import -#import "SliderTypicalUseSupplemental.h" +#import "MaterialSlider.h" #import "MaterialTypography.h" +#import "SliderTypicalUseSupplemental.h" #pragma mark - SliderTypicalUseViewController @@ -21,7 +22,7 @@ + (NSString *)catalogDescription { " continuous range or discrete set of values."; } -- (BOOL)catalogIsPrimaryDemo { ++ (BOOL)catalogIsPrimaryDemo { return YES; } @@ -30,33 +31,33 @@ - (BOOL)catalogIsPrimaryDemo { @implementation SliderTypicalUseViewController (Supplemental) - (void)setupExampleViews { - UILabel *raisedButtonLabel = [[UILabel alloc] init]; - raisedButtonLabel.text = @"Slider"; - raisedButtonLabel.font = [MDCTypography captionFont]; - raisedButtonLabel.alpha = [MDCTypography captionFontOpacity]; - [raisedButtonLabel sizeToFit]; - raisedButtonLabel.translatesAutoresizingMaskIntoConstraints = NO; - [self.view addSubview:raisedButtonLabel]; - - UILabel *disabledSliderButtonLabel = [[UILabel alloc] init]; - disabledSliderButtonLabel.text = @"Slider Disabled"; - disabledSliderButtonLabel.font = [MDCTypography captionFont]; - disabledSliderButtonLabel.alpha = [MDCTypography captionFontOpacity]; - [disabledSliderButtonLabel sizeToFit]; - disabledSliderButtonLabel.translatesAutoresizingMaskIntoConstraints = NO; - [self.view addSubview:disabledSliderButtonLabel]; + UILabel *sliderLabel = [[UILabel alloc] init]; + sliderLabel.text = @"Slider"; + sliderLabel.font = [MDCTypography captionFont]; + sliderLabel.alpha = [MDCTypography captionFontOpacity]; + [sliderLabel sizeToFit]; + sliderLabel.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:sliderLabel]; + + UILabel *disabledSliderLabel = [[UILabel alloc] init]; + disabledSliderLabel.text = @"Slider Disabled"; + disabledSliderLabel.font = [MDCTypography captionFont]; + disabledSliderLabel.alpha = [MDCTypography captionFontOpacity]; + [disabledSliderLabel sizeToFit]; + disabledSliderLabel.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:disabledSliderLabel]; NSDictionary *views = @{ @"slider" : self.slider, - @"label" : raisedButtonLabel, + @"label" : sliderLabel, @"disabledSlider" : self.disabledSlider, - @"disabledLabel" : disabledSliderButtonLabel + @"disabledSliderLabel" : disabledSliderLabel }; NSDictionary *metrics = @{ @"smallVMargin" : @24.0, @"largeVMargin" : @56.0, @"smallHMargin" : @24.0, - @"buttonHeight" : @(self.slider.bounds.size.height) }; + @"sliderHeight" : @(self.slider.bounds.size.height) }; // Vertical column of sliders NSString *sliderLayoutConstraints = @@ -64,7 +65,7 @@ - (void)setupExampleViews { // Vertical column of labels NSString *labelLayoutConstraints = - @"V:[label(buttonHeight)]-smallVMargin-[disabledLabel(buttonHeight)]"; + @"V:[label(sliderHeight)]-smallVMargin-[disabledSliderLabel(sliderHeight)]"; // Horizontal alignment between the two columns NSString *columnConstraints = @"[label(100)]-smallHMargin-[slider]"; @@ -79,7 +80,7 @@ - (void)setupExampleViews { multiplier:1.f constant:12.f]]; - // Center view vertically on the flat button (it's the middlemost) + // Center view vertically [self.view addConstraint: [NSLayoutConstraint constraintWithItem:self.disabledSlider attribute:NSLayoutAttributeBottom @@ -108,7 +109,7 @@ - (void)setupExampleViews { [NSLayoutConstraint constraintWithItem:self.slider attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual - toItem:raisedButtonLabel + toItem:sliderLabel attribute:NSLayoutAttributeCenterY multiplier:1.f constant:0.f]]; diff --git a/components/Slider/tests/unit/SliderTests.m b/components/Slider/tests/unit/SliderTests.m index ddb3de5f153..4872e4fe6d9 100644 --- a/components/Slider/tests/unit/SliderTests.m +++ b/components/Slider/tests/unit/SliderTests.m @@ -19,7 +19,7 @@ #import "MaterialSlider.h" static const NSUInteger kNumberOfRepeats = 20; -static const CGFloat kEpsilonAccuracy = 0.0001f; +static const CGFloat kEpsilonAccuracy = 0.001f; // Blue 500 from http://www.google.com/design/spec/style/color.html#color-color-palette . static const uint32_t MDCBlueColor = 0x2196F3; diff --git a/components/SpritedAnimationView/.jazzy.yaml b/components/SpritedAnimationView/.jazzy.yaml index f61882e660e..88788009c3a 100644 --- a/components/SpritedAnimationView/.jazzy.yaml +++ b/components/SpritedAnimationView/.jazzy.yaml @@ -1,4 +1,5 @@ -module_name: SpritedAnimationView +# Auto-generated by scripts/generate_jazzy_yamls.sh. Used primarily by scripts/external/arc-jazzy-linter. +module: SpritedAnimationView umbrella_header: src/MaterialSpritedAnimationView.h objc: true sdk: iphonesimulator diff --git a/components/SpritedAnimationView/README.md b/components/SpritedAnimationView/README.md index ba4ab20d17c..f930fdc9fa8 100644 --- a/components/SpritedAnimationView/README.md +++ b/components/SpritedAnimationView/README.md @@ -1,7 +1,7 @@ --- title: "Sprited Animation View" layout: detail -section: documentation +section: components excerpt: "The Sprited Animation View component provides an alternative to animating an array of images with an UIImageView." --- # SpritedAnimationView diff --git a/components/SpritedAnimationView/examples/SpritedAnimationViewTypicalUseViewController.m b/components/SpritedAnimationView/examples/SpritedAnimationViewTypicalUseViewController.m index a7f13a5af49..697733f534a 100644 --- a/components/SpritedAnimationView/examples/SpritedAnimationViewTypicalUseViewController.m +++ b/components/SpritedAnimationView/examples/SpritedAnimationViewTypicalUseViewController.m @@ -36,7 +36,7 @@ + (NSString *)catalogDescription { " UIImageView."; } -- (BOOL)catalogIsPrimaryDemo { ++ (BOOL)catalogIsPrimaryDemo { return YES; } diff --git a/components/Switch/.jazzy.yaml b/components/Switch/.jazzy.yaml index b5627b69b82..fd438042c65 100644 --- a/components/Switch/.jazzy.yaml +++ b/components/Switch/.jazzy.yaml @@ -1,4 +1,5 @@ -module_name: Switch +# Auto-generated by scripts/generate_jazzy_yamls.sh. Used primarily by scripts/external/arc-jazzy-linter. +module: Switch umbrella_header: src/MaterialSwitch.h objc: true sdk: iphonesimulator diff --git a/components/Switch/README.md b/components/Switch/README.md index b0ba197d836..88de65a1d4e 100644 --- a/components/Switch/README.md +++ b/components/Switch/README.md @@ -1,7 +1,7 @@ --- title: "Switch" layout: detail -section: documentation +section: components excerpt: "The Switch component provides an Material Design on/off switch control with an interface similar to UISwitch." --- @@ -75,6 +75,7 @@ import MaterialComponents ~~~ +### Setup ### Objective C @@ -95,5 +96,22 @@ import MaterialComponents ... } +~~~ +#### Swift +~~~ swift + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = UIColor.whiteColor() + + switchComponent.on = true + switchComponent.addTarget(self, action: Selector("didChangeSwitchValue:"), forControlEvents: UIControlEvents.ValueChanged) + view.addSubview(switchComponent) + switchComponent.center = CGPointMake(50, 50); + } + + func didChangeSwitchValue(senderSwitch:MDCSwitch) { + NSLog("did change value: %@", senderSwitch.on); + } + ~~~ diff --git a/components/Switch/examples/SwitchSimpleExampleViewController.m b/components/Switch/examples/SwitchSimpleExampleViewController.m index f2199e0e261..374aaf6e8bc 100644 --- a/components/Switch/examples/SwitchSimpleExampleViewController.m +++ b/components/Switch/examples/SwitchSimpleExampleViewController.m @@ -68,16 +68,7 @@ - (void)didChangeSliderValue:(id)sender { #pragma mark catalg by convention + (NSArray *)catalogBreadcrumbs { - return @[ @"Switch", @"Switch" ]; -} - -+ (NSString *)catalogDescription { - return @"Switch provides an Material Design on/off switch control with an interface similar to" - " UISwitch."; -} - -- (BOOL)catalogIsPrimaryDemo { - return YES; + return @[ @"Switch", @"MDCSwitch and UISwitch Compared" ]; } @end diff --git a/components/Switch/examples/SwitchSwiftExampleViewController.swift b/components/Switch/examples/SwitchSwiftExampleViewController.swift new file mode 100644 index 00000000000..e3b8fc8f258 --- /dev/null +++ b/components/Switch/examples/SwitchSwiftExampleViewController.swift @@ -0,0 +1,39 @@ +/* + Copyright 2015-present Google Inc. 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. + */ + +import Foundation + +import MaterialComponents + +class SwitchSwiftExampleViewController : UIViewController { + + let switchComponent = MDCSwitch() + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = UIColor.whiteColor() + + switchComponent.on = true + switchComponent.addTarget(self, action: Selector("didChangeSwitchValue:"), forControlEvents: UIControlEvents.ValueChanged) + view.addSubview(switchComponent) + switchComponent.center = CGPointMake(CGRectGetMidX(view.bounds), CGRectGetMidY(view.bounds)); + switchComponent.autoresizingMask = [.FlexibleBottomMargin, .FlexibleTopMargin, .FlexibleLeftMargin, .FlexibleRightMargin] + } + + func didChangeSwitchValue(senderSwitch:MDCSwitch) { + NSLog("Did change switch value to: %@.", senderSwitch.on); + } +} diff --git a/components/Switch/examples/SwitchTypicalUse.m b/components/Switch/examples/SwitchTypicalUse.m new file mode 100644 index 00000000000..edf9d07b887 --- /dev/null +++ b/components/Switch/examples/SwitchTypicalUse.m @@ -0,0 +1,68 @@ +/* + Copyright 2015-present Google Inc. 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. + */ + +#import + +#import "MaterialSwitch.h" + +#import "SwitchTypicalUseSupplemental.h" + +@implementation SwitchTypicalUseViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.backgroundColor = [UIColor whiteColor]; + + // Switch + + self.switchComponent = [[MDCSwitch alloc] init]; + [self.switchComponent setOn:YES]; + [self.switchComponent addTarget:self + action:@selector(didChangeSwitchValue:) + forControlEvents:UIControlEventValueChanged]; + [self.view addSubview:self.switchComponent]; + + // Switch custom color + + self.colorSwitchComponent = [[MDCSwitch alloc] init]; + self.colorSwitchComponent.onTintColor = [UIColor colorWithRed:0 green:0.47f blue:0.9f alpha:1]; + [self.colorSwitchComponent setOn:YES]; + [self.colorSwitchComponent addTarget:self + action:@selector(didChangeSwitchValue:) + forControlEvents:UIControlEventValueChanged]; + [self.view addSubview:self.colorSwitchComponent]; + + // Switch disabled + + self.disabledSwitchComponent = [[MDCSwitch alloc] init]; + [self.disabledSwitchComponent addTarget:self + action:@selector(didChangeSwitchValue:) + forControlEvents:UIControlEventValueChanged]; + self.disabledSwitchComponent.enabled = NO; + [self.view addSubview:self.disabledSwitchComponent]; + + self.switchComponent.translatesAutoresizingMaskIntoConstraints = NO; + self.colorSwitchComponent.translatesAutoresizingMaskIntoConstraints = NO; + self.disabledSwitchComponent.translatesAutoresizingMaskIntoConstraints = NO; + + [self setupExampleViews]; +} + +- (void)didChangeSwitchValue:(MDCSwitch *)sender { + NSLog(@"did change %@ value: %d", NSStringFromClass([sender class]), sender.isOn); +} + +@end diff --git a/components/Switch/examples/supplemental/SwitchSwiftExampleViewControllerSupplemental.swift b/components/Switch/examples/supplemental/SwitchSwiftExampleViewControllerSupplemental.swift new file mode 100644 index 00000000000..023df401eac --- /dev/null +++ b/components/Switch/examples/supplemental/SwitchSwiftExampleViewControllerSupplemental.swift @@ -0,0 +1,17 @@ +/* IMPORTANT: + This file contains supplemental code used to populate the examples with dummy data and/or + instructions. It is not necessary to import this file to implement any Material Design Components. + */ + + +import Foundation +import MaterialComponents + +extension SwitchSwiftExampleViewController { + + // (CatalogByConvention) + + class func catalogBreadcrumbs() -> [String] { + return [ "Switch", "Swift example"] + } +} diff --git a/components/Switch/examples/supplemental/SwitchTypicalUseSupplemental.h b/components/Switch/examples/supplemental/SwitchTypicalUseSupplemental.h new file mode 100644 index 00000000000..25d6681a12d --- /dev/null +++ b/components/Switch/examples/supplemental/SwitchTypicalUseSupplemental.h @@ -0,0 +1,23 @@ +/* IMPORTANT: + This file contains supplemental code used to populate the demos with dummy data or instructions. + It is not necessary to import this file to implement any Material Design Components. + */ + +#import + +@class MDCSwitch; +@class SwitchTypicalUseViewController; + +@interface SwitchTypicalUseViewController : UIViewController + +@property(nonatomic) MDCSwitch *switchComponent; +@property(nonatomic) MDCSwitch *colorSwitchComponent; +@property(nonatomic) MDCSwitch *disabledSwitchComponent; + +@end + +@interface SwitchTypicalUseViewController (Supplemental) + +- (void)setupExampleViews; + +@end diff --git a/components/Switch/examples/supplemental/SwitchTypicalUseSupplemental.m b/components/Switch/examples/supplemental/SwitchTypicalUseSupplemental.m new file mode 100644 index 00000000000..1a66b8b5ad7 --- /dev/null +++ b/components/Switch/examples/supplemental/SwitchTypicalUseSupplemental.m @@ -0,0 +1,135 @@ +/* IMPORTANT: + This file contains supplemental code used to populate the examples with dummy data and/or + instructions. It is not necessary to import this file to implement any Material Design Components. + */ + +#import + +#import "MaterialSwitch.h" +#import "MaterialTypography.h" +#import "SwitchTypicalUseSupplemental.h" + +#pragma mark - SwitchTypicalUseViewController + +@implementation SwitchTypicalUseViewController (CatalogByConvention) + ++ (NSArray *)catalogBreadcrumbs { + return @[ @"Switch", @"Switch" ]; +} + ++ (NSString *)catalogDescription { + return @"The MDCSlider object is a Material Design control used to select a value from a" + " continuous range or discrete set of values."; +} + ++ (BOOL)catalogIsPrimaryDemo { + return YES; +} + +@end + +@implementation SwitchTypicalUseViewController (Supplemental) + +- (void)setupExampleViews { + UILabel *switchLabel = [[UILabel alloc] init]; + switchLabel.text = @"Switch"; + switchLabel.font = [MDCTypography captionFont]; + switchLabel.alpha = [MDCTypography captionFontOpacity]; + [switchLabel sizeToFit]; + switchLabel.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:switchLabel]; + + UILabel *colorSwitchButtonLabel = [[UILabel alloc] init]; + colorSwitchButtonLabel.text = @"Custom Color"; + colorSwitchButtonLabel.font = [MDCTypography captionFont]; + colorSwitchButtonLabel.alpha = [MDCTypography captionFontOpacity]; + [colorSwitchButtonLabel sizeToFit]; + colorSwitchButtonLabel.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:colorSwitchButtonLabel]; + + UILabel *disabledSwitchButtonLabel = [[UILabel alloc] init]; + disabledSwitchButtonLabel.text = @"Disabled"; + disabledSwitchButtonLabel.font = [MDCTypography captionFont]; + disabledSwitchButtonLabel.alpha = [MDCTypography captionFontOpacity]; + [disabledSwitchButtonLabel sizeToFit]; + disabledSwitchButtonLabel.translatesAutoresizingMaskIntoConstraints = NO; + [self.view addSubview:disabledSwitchButtonLabel]; + + NSDictionary *views = @{ + @"slider" : self.switchComponent, + @"label" : switchLabel, + @"colorSlider" : self.colorSwitchComponent, + @"colorLabel" : colorSwitchButtonLabel, + @"disabledSlider" : self.disabledSwitchComponent, + @"disabledLabel" : disabledSwitchButtonLabel + }; + + NSDictionary *metrics = @{ @"smallVMargin" : @24.0, + @"smallHMargin" : @24.0, + @"buttonHeight" : @(self.switchComponent.bounds.size.height) }; + + // Vertical column of switches + NSString *sliderLayoutConstraints = + @"V:[slider]-smallVMargin-[colorSlider]-smallVMargin-[disabledSlider]"; + + // Vertical column of labels + NSString *labelLayoutConstraints = + @"V:[label(buttonHeight)]-smallVMargin-[colorLabel(buttonHeight)]-smallVMargin-" + "[disabledLabel(buttonHeight)]"; + + // Horizontal alignment between the two columns + NSString *columnConstraints = @"[label(100)]-smallHMargin-[slider]"; + + // Center view horizontally on the left edge of one of the switches + [self.view addConstraint: + [NSLayoutConstraint constraintWithItem:self.colorSwitchComponent + attribute:NSLayoutAttributeLeft + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeCenterX + multiplier:1.f + constant:40.f]]; + + // Center view vertically + [self.view addConstraint: + [NSLayoutConstraint constraintWithItem:self.colorSwitchComponent + attribute:NSLayoutAttributeBottom + relatedBy:NSLayoutRelationEqual + toItem:self.view + attribute:NSLayoutAttributeCenterY + multiplier:1.f + constant:0]]; + + // Center switches in their column + [self.view addConstraints: + [NSLayoutConstraint constraintsWithVisualFormat:sliderLayoutConstraints + options:NSLayoutFormatAlignAllCenterX + metrics:metrics + views:views]]; + + // Left align labels in their column + [self.view addConstraints: + [NSLayoutConstraint constraintsWithVisualFormat:labelLayoutConstraints + options:NSLayoutFormatAlignAllLeft + metrics:metrics + views:views]]; + + // Vertically align first element in label column to first element in switch column + [self.view addConstraint: + [NSLayoutConstraint constraintWithItem:self.switchComponent + attribute:NSLayoutAttributeCenterY + relatedBy:NSLayoutRelationEqual + toItem:switchLabel + attribute:NSLayoutAttributeCenterY + multiplier:1.f + constant:0.f]]; + + // Position label column left of slider column, wide enough to accommodate label text + [self.view addConstraints: + [NSLayoutConstraint constraintsWithVisualFormat:columnConstraints + options:0 + metrics:metrics + views:views]]; +} + +@end diff --git a/components/Switch/src/MDCSwitch.m b/components/Switch/src/MDCSwitch.m index e4de2786592..5e3ee62a275 100644 --- a/components/Switch/src/MDCSwitch.m +++ b/components/Switch/src/MDCSwitch.m @@ -295,7 +295,7 @@ + (NSString *)a11yHintString { static const CGFloat kMDCSwitchLightThemeTrackDisabledAlpha = 0.12f; + (UIColor *)defaultOnTintColor { - return [UIColor greenColor]; + return [UIColor colorWithRed:0.32f green:0.87f blue:0 alpha:1]; } + (UIColor *)defaultOffThumbColor { diff --git a/components/Typography/.jazzy.yaml b/components/Typography/.jazzy.yaml index 653647b1ef0..1e13afea8cd 100644 --- a/components/Typography/.jazzy.yaml +++ b/components/Typography/.jazzy.yaml @@ -1,4 +1,5 @@ -module_name: Typography +# Auto-generated by scripts/generate_jazzy_yamls.sh. Used primarily by scripts/external/arc-jazzy-linter. +module: Typography umbrella_header: src/MaterialTypography.h objc: true sdk: iphonesimulator diff --git a/components/Typography/README.md b/components/Typography/README.md index 333f503cd90..657cf51c549 100644 --- a/components/Typography/README.md +++ b/components/Typography/README.md @@ -1,7 +1,7 @@ --- title: "Typography" layout: detail -section: documentation +section: components excerpt: "The Typography component provides methods for displaying text using the type sizes and opacities from the Material Design specifications." --- # Typography diff --git a/components/Typography/examples/TypographyExamplesViewController.swift b/components/Typography/examples/TypographyExamplesViewController.swift deleted file mode 100644 index 32c95ae518f..00000000000 --- a/components/Typography/examples/TypographyExamplesViewController.swift +++ /dev/null @@ -1,218 +0,0 @@ -/* -Copyright 2016-present Google Inc. 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. -*/ - -import MaterialComponents - -class TypographyExampleViewCell: UICollectionViewCell { - - var cellSize = CGSizeZero - let textView = UITextView() - let label = UILabel() - let offset = CGFloat(-5) - - override init(frame: CGRect) { - super.init(frame: frame) - self.backgroundColor = UIColor.whiteColor() - textView.text = "Example Text" - textView.editable = false - textView.scrollEnabled = false - - textView.contentInset = UIEdgeInsetsMake(offset, offset, offset, offset); - self.addSubview(textView) - - label.text = "Font Style Name" - label.alpha = MDCTypography.captionFontOpacity() - label.font = MDCTypography.captionFont() - label.frame = CGRectMake(0, - frame.size.height - (label.font.pointSize + 2), frame.size.width, label.font.pointSize + 2) - label.autoresizingMask = [.FlexibleTopMargin, .FlexibleWidth] - self.addSubview(label) - } - - required init(coder: NSCoder) { - super.init(coder: coder)! - } - - func populateCell(text : String, font : UIFont, opacity: CGFloat, name: String) { - textView.text = text - textView.font = font - textView.alpha = opacity - label.text = name - - let fixedWidth = self.frame.width - textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.max)) - let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.max)) - var newFrame = textView.frame - newFrame.size = CGSize(width: max(newSize.width, fixedWidth), height: newSize.height) - textView.frame = newFrame; - cellSize = newFrame.size - } - - static func cellSize(width: CGFloat, text : String, font : UIFont) -> CGSize { - let textView = UITextView() - textView.text = text - textView.font = font - - let fixedWidth = width - textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.max)) - let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.max)) - var newFrame = textView.frame - let height = newSize.height + MDCTypography.captionFont().pointSize - 8 - newFrame.size = CGSize(width: max(newSize.width, fixedWidth), height: height) - textView.frame = newFrame; - return newFrame.size - } - -} - -class TypographyExamplesViewController: UICollectionViewController { - - let strings = [ - "Material Design Components", - "A quick brown fox jumped over the lazy dog.", - "ABCDEFGHIJKLMNOPQRSTUVWXYZ", - "abcdefghijklmnopqrstuvwxyz", - "1234567890", - "!@#$%^&*()-=_+[]\\;',./<>?:\"" - ] - - let fonts = [ - - // Common UI fonts. - MDCTypography.headlineFont(), - MDCTypography.titleFont(), - MDCTypography.subheadFont(), - MDCTypography.body2Font(), - MDCTypography.body1Font(), - MDCTypography.captionFont(), - MDCTypography.buttonFont(), - - // Display fonts (extra large fonts) - MDCTypography.display1Font(), - MDCTypography.display2Font(), - MDCTypography.display3Font(), - MDCTypography.display4Font() - ] - - let fontOpacities = [ - - // Common UI fonts. - MDCTypography.headlineFontOpacity(), - MDCTypography.titleFontOpacity(), - MDCTypography.subheadFontOpacity(), - MDCTypography.body2FontOpacity(), - MDCTypography.body1FontOpacity(), - MDCTypography.captionFontOpacity(), - MDCTypography.buttonFontOpacity(), - - // Display fonts (extra large fonts) - MDCTypography.display1FontOpacity(), - MDCTypography.display2FontOpacity(), - MDCTypography.display3FontOpacity(), - MDCTypography.display4FontOpacity() - ] - - let fontStyleNames = [ - - // Common UI fonts. - "Headline Font", - "Title Font", - "Subhead Font", - "Body 2 Font", - "Body 1 Font", - "Caption Font", - "Button Font", - - // Display fonts (extra large fonts) - "Display 1 Font", - "Display 2 Font", - "Display 3 Font", - "Display 4 Font" - ] - - override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { - let flowLayout = UICollectionViewFlowLayout() - flowLayout.sectionInset = UIEdgeInsetsMake(10, 0, 10, 0) - super.init(collectionViewLayout: flowLayout) - self.collectionView?.registerClass(TypographyExampleViewCell.self, - forCellWithReuseIdentifier: "TypographyExampleViewCell") - self.collectionView?.backgroundColor = UIColor.whiteColor() - } - - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int { - return strings.count - } - - override func collectionView(collectionView: UICollectionView, - numberOfItemsInSection section: Int) -> Int { - return fonts.count - } - - override func collectionView(collectionView: UICollectionView, - cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell { - let cell = collectionView.dequeueReusableCellWithReuseIdentifier("TypographyExampleViewCell", - forIndexPath: indexPath) as! TypographyExampleViewCell - - let itemNum:NSInteger = indexPath.row; - let sectionNum:NSInteger = indexPath.section; - - var text = strings[sectionNum] - let font = fonts[itemNum] - let opacity = fontOpacities[itemNum] - let name = fontStyleNames[itemNum] - if (font.pointSize > 100 && text == strings[0]) { - text = "MDC" - } - cell.populateCell(text, font: font, opacity: opacity, name: name) - - return cell - } - - func collectionView(collectionView: UICollectionView, - layout collectionViewLayout: UICollectionViewLayout, - sizeForItemAtIndexPath indexPath: NSIndexPath) -> CGSize { - - let itemNum:NSInteger = indexPath.row; - let sectionNum:NSInteger = indexPath.section; - - var text = strings[sectionNum] - let font = fonts[itemNum] - if (font.pointSize > 100 && text == strings[0]) { - text = "MDC" - } - let size = - TypographyExampleViewCell.cellSize(self.view.frame.width - 20, text: text, font: font) - return size - } - - class func catalogBreadcrumbs() -> [String] { - return ["Typography", "Typography"] - } - - class func catalogDescription() -> String { - return "The Typography component provides methods for displaying text using the type sizes and" - + " opacities from the Material Design specifications." - } - - func catalogIsPrimaryDemo() -> Bool { - return true - } - -} diff --git a/components/Typography/examples/TypographyFontListExample.swift b/components/Typography/examples/TypographyFontListExample.swift new file mode 100644 index 00000000000..2cb86750922 --- /dev/null +++ b/components/Typography/examples/TypographyFontListExample.swift @@ -0,0 +1,160 @@ +/* +Copyright 2016-present Google Inc. 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. +*/ + +import MaterialComponents + +// This example is primarily a visual example of the various Typography fonts. The code below is a +// standard UIKit table view configuration using Auto Layout for height calculation. + +class TypographyFontListExampleViewController: UITableViewController { + + override func viewDidLoad() { + super.viewDidLoad() + + self.tableView.separatorStyle = .None + + self.tableView.rowHeight = UITableViewAutomaticDimension + self.tableView.estimatedRowHeight = 50 + } + + override func numberOfSectionsInTableView(tableView: UITableView) -> Int { + return strings.count + } + + override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return fonts.count + } + + override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + var cell = tableView.dequeueReusableCellWithIdentifier("cell") + if cell == nil { + cell = UITableViewCell(style: .Subtitle, reuseIdentifier: "cell") + } + cell!.textLabel!.text = strings[indexPath.section] + cell!.textLabel!.font = fonts[indexPath.row] + cell!.textLabel!.alpha = fontOpacities[indexPath.row] + cell!.textLabel!.numberOfLines = 0 + cell!.textLabel!.lineBreakMode = .ByWordWrapping + + if cell!.textLabel!.font.pointSize > 100 && indexPath.section == 0 { + cell!.textLabel!.text = "MDC" + } + + cell!.detailTextLabel!.text = fontStyleNames[indexPath.row] + cell!.detailTextLabel!.font = MDCTypography.captionFont() + cell!.detailTextLabel!.alpha = MDCTypography.captionFontOpacity() + cell!.selectionStyle = .None + + return cell! + } + + convenience init() { + self.init(style: .Plain) + } + + override init(style: UITableViewStyle) { + super.init(style: style) + + self.title = "Font list" + } + + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + let strings = [ + "Material Design Components", + "A quick brown fox jumped over the lazy dog.", + "ABCDEFGHIJKLMNOPQRSTUVWXYZ", + "abcdefghijklmnopqrstuvwxyz", + "1234567890", + "!@#$%^&*()-=_+[]\\;',./<>?:\"" + ] + + let fonts = [ + + // Common UI fonts. + MDCTypography.headlineFont(), + MDCTypography.titleFont(), + MDCTypography.subheadFont(), + MDCTypography.body2Font(), + MDCTypography.body1Font(), + MDCTypography.captionFont(), + MDCTypography.buttonFont(), + + // Display fonts (extra large fonts) + MDCTypography.display1Font(), + MDCTypography.display2Font(), + MDCTypography.display3Font(), + MDCTypography.display4Font() + ] + + let fontOpacities = [ + + // Common UI fonts. + MDCTypography.headlineFontOpacity(), + MDCTypography.titleFontOpacity(), + MDCTypography.subheadFontOpacity(), + MDCTypography.body2FontOpacity(), + MDCTypography.body1FontOpacity(), + MDCTypography.captionFontOpacity(), + MDCTypography.buttonFontOpacity(), + + // Display fonts (extra large fonts) + MDCTypography.display1FontOpacity(), + MDCTypography.display2FontOpacity(), + MDCTypography.display3FontOpacity(), + MDCTypography.display4FontOpacity() + ] + + let fontStyleNames = [ + + // Common UI fonts. + "Headline Font", + "Title Font", + "Subhead Font", + "Body 2 Font", + "Body 1 Font", + "Caption Font", + "Button Font", + + // Display fonts (extra large fonts) + "Display 1 Font", + "Display 2 Font", + "Display 3 Font", + "Display 4 Font" + ] +} + +// MARK: Catalog by convention +extension TypographyFontListExampleViewController { + class func catalogBreadcrumbs() -> [String] { + return ["Typography and Fonts", "Typography"] + } + + class func catalogDescription() -> String { + return "The Typography component provides methods for displaying text using the type sizes and" + + " opacities from the Material Design specifications." + } + + class func catalogIsPrimaryDemo() -> Bool { + return true + } +} diff --git a/components/Typography/examples/TypographySimpleExampleViewController.m b/components/Typography/examples/TypographySimpleExampleViewController.m index 16cf75353b4..d4bb30abe6a 100644 --- a/components/Typography/examples/TypographySimpleExampleViewController.m +++ b/components/Typography/examples/TypographySimpleExampleViewController.m @@ -30,12 +30,15 @@ - (void)viewDidLoad { label.alpha = [MDCTypography titleFontOpacity]; [label sizeToFit]; + label.center = CGPointMake(CGRectGetMidX(self.view.bounds), CGRectGetMidY(self.view.bounds)); + label.autoresizingMask = (UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin); + [self.view addSubview:label]; } + (NSArray *)catalogBreadcrumbs { - return @[ @"Typography", @"Read Me Demo" ]; + return @[ @"Typography and Fonts", @"Read Me Demo" ]; } @end diff --git a/components/Typography/examples/TypographySystemFontLoaderExampleViewController.m b/components/Typography/examples/TypographySystemFontLoaderExampleViewController.m index a013e2903d4..9834d10128e 100644 --- a/components/Typography/examples/TypographySystemFontLoaderExampleViewController.m +++ b/components/Typography/examples/TypographySystemFontLoaderExampleViewController.m @@ -58,7 +58,7 @@ - (IBAction)didChangeSwitchValue:(id)sender { } + (NSArray *)catalogBreadcrumbs { - return @[ @"Typography", @"Set Font Loader" ]; + return @[ @"Typography and Fonts", @"Set Font Loader" ]; } + (NSString *)catalogStoryboardName { diff --git a/components/private/Icons/icons/ic_check/src/MaterialIcons+ic_check.h b/components/private/Icons/icons/ic_check/src/MaterialIcons+ic_check.h new file mode 100644 index 00000000000..1091fdd77a3 --- /dev/null +++ b/components/private/Icons/icons/ic_check/src/MaterialIcons+ic_check.h @@ -0,0 +1,27 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import "MaterialIcons.h" + +// This file was automatically generated by running scripts/sync_icons.sh +// Do not modify directly. + +@interface MDCIcons (ic_check) + +/** Returns the path for the ic_check image contained in MaterialIcons_ic_check.bundle. */ ++ (nonnull NSString *)pathFor_ic_check; + +@end diff --git a/components/private/Icons/icons/ic_check/src/MaterialIcons+ic_check.m b/components/private/Icons/icons/ic_check/src/MaterialIcons+ic_check.m new file mode 100644 index 00000000000..f218c6bab06 --- /dev/null +++ b/components/private/Icons/icons/ic_check/src/MaterialIcons+ic_check.m @@ -0,0 +1,33 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +// This file was automatically generated by running scripts/sync_icons.sh +// Do not modify directly. + +#import "MaterialIcons+ic_check.h" + +#import "MDCIcons+BundleLoader.h" + +static NSString *const kBundleName = @"MaterialIcons_ic_check"; +static NSString *const kIconName = @"ic_check"; + +@implementation MDCIcons (ic_check) + ++ (nonnull NSString *)pathFor_ic_check { + return [self pathForIconName:kIconName withBundleName:kBundleName]; +} + +@end diff --git a/components/private/Icons/icons/ic_check/src/MaterialIcons_ic_check.bundle/ic_check.xcassets/ic_check.imageset/Contents.json b/components/private/Icons/icons/ic_check/src/MaterialIcons_ic_check.bundle/ic_check.xcassets/ic_check.imageset/Contents.json new file mode 100644 index 00000000000..44f0095d9ac --- /dev/null +++ b/components/private/Icons/icons/ic_check/src/MaterialIcons_ic_check.bundle/ic_check.xcassets/ic_check.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images": [ + { + "filename": "ic_check.png", + "idiom": "universal", + "scale": "1x" + }, + { + "filename": "ic_check@2x.png", + "idiom": "universal", + "scale": "2x" + }, + { + "filename": "ic_check@3x.png", + "idiom": "universal", + "scale": "3x" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/components/private/Icons/icons/ic_check/src/MaterialIcons_ic_check.bundle/ic_check.xcassets/ic_check.imageset/ic_check.png b/components/private/Icons/icons/ic_check/src/MaterialIcons_ic_check.bundle/ic_check.xcassets/ic_check.imageset/ic_check.png new file mode 100644 index 00000000000..1c14c9c4459 Binary files /dev/null and b/components/private/Icons/icons/ic_check/src/MaterialIcons_ic_check.bundle/ic_check.xcassets/ic_check.imageset/ic_check.png differ diff --git a/components/private/Icons/icons/ic_check/src/MaterialIcons_ic_check.bundle/ic_check.xcassets/ic_check.imageset/ic_check@2x.png b/components/private/Icons/icons/ic_check/src/MaterialIcons_ic_check.bundle/ic_check.xcassets/ic_check.imageset/ic_check@2x.png new file mode 100644 index 00000000000..64a4944f753 Binary files /dev/null and b/components/private/Icons/icons/ic_check/src/MaterialIcons_ic_check.bundle/ic_check.xcassets/ic_check.imageset/ic_check@2x.png differ diff --git a/components/private/Icons/icons/ic_check/src/MaterialIcons_ic_check.bundle/ic_check.xcassets/ic_check.imageset/ic_check@3x.png b/components/private/Icons/icons/ic_check/src/MaterialIcons_ic_check.bundle/ic_check.xcassets/ic_check.imageset/ic_check@3x.png new file mode 100644 index 00000000000..b26a2c05e3f Binary files /dev/null and b/components/private/Icons/icons/ic_check/src/MaterialIcons_ic_check.bundle/ic_check.xcassets/ic_check.imageset/ic_check@3x.png differ diff --git a/components/private/Icons/icons/ic_check_circle/src/MaterialIcons+ic_check_circle.h b/components/private/Icons/icons/ic_check_circle/src/MaterialIcons+ic_check_circle.h new file mode 100644 index 00000000000..729f8f35152 --- /dev/null +++ b/components/private/Icons/icons/ic_check_circle/src/MaterialIcons+ic_check_circle.h @@ -0,0 +1,27 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import "MaterialIcons.h" + +// This file was automatically generated by running scripts/sync_icons.sh +// Do not modify directly. + +@interface MDCIcons (ic_check_circle) + +/** Returns the path for the ic_check_circle image contained in MaterialIcons_ic_check_circle.bundle. */ ++ (nonnull NSString *)pathFor_ic_check_circle; + +@end diff --git a/components/private/Icons/icons/ic_check_circle/src/MaterialIcons+ic_check_circle.m b/components/private/Icons/icons/ic_check_circle/src/MaterialIcons+ic_check_circle.m new file mode 100644 index 00000000000..153fb97050e --- /dev/null +++ b/components/private/Icons/icons/ic_check_circle/src/MaterialIcons+ic_check_circle.m @@ -0,0 +1,33 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +// This file was automatically generated by running scripts/sync_icons.sh +// Do not modify directly. + +#import "MaterialIcons+ic_check_circle.h" + +#import "MDCIcons+BundleLoader.h" + +static NSString *const kBundleName = @"MaterialIcons_ic_check_circle"; +static NSString *const kIconName = @"ic_check_circle"; + +@implementation MDCIcons (ic_check_circle) + ++ (nonnull NSString *)pathFor_ic_check_circle { + return [self pathForIconName:kIconName withBundleName:kBundleName]; +} + +@end diff --git a/components/private/Icons/icons/ic_check_circle/src/MaterialIcons_ic_check_circle.bundle/ic_check_circle.xcassets/ic_check_circle.imageset/Contents.json b/components/private/Icons/icons/ic_check_circle/src/MaterialIcons_ic_check_circle.bundle/ic_check_circle.xcassets/ic_check_circle.imageset/Contents.json new file mode 100644 index 00000000000..de6673864e8 --- /dev/null +++ b/components/private/Icons/icons/ic_check_circle/src/MaterialIcons_ic_check_circle.bundle/ic_check_circle.xcassets/ic_check_circle.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images": [ + { + "filename": "ic_check_circle.png", + "idiom": "universal", + "scale": "1x" + }, + { + "filename": "ic_check_circle@2x.png", + "idiom": "universal", + "scale": "2x" + }, + { + "filename": "ic_check_circle@3x.png", + "idiom": "universal", + "scale": "3x" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/components/private/Icons/icons/ic_check_circle/src/MaterialIcons_ic_check_circle.bundle/ic_check_circle.xcassets/ic_check_circle.imageset/ic_check_circle.png b/components/private/Icons/icons/ic_check_circle/src/MaterialIcons_ic_check_circle.bundle/ic_check_circle.xcassets/ic_check_circle.imageset/ic_check_circle.png new file mode 100644 index 00000000000..a2caa18db2b Binary files /dev/null and b/components/private/Icons/icons/ic_check_circle/src/MaterialIcons_ic_check_circle.bundle/ic_check_circle.xcassets/ic_check_circle.imageset/ic_check_circle.png differ diff --git a/components/private/Icons/icons/ic_check_circle/src/MaterialIcons_ic_check_circle.bundle/ic_check_circle.xcassets/ic_check_circle.imageset/ic_check_circle@2x.png b/components/private/Icons/icons/ic_check_circle/src/MaterialIcons_ic_check_circle.bundle/ic_check_circle.xcassets/ic_check_circle.imageset/ic_check_circle@2x.png new file mode 100644 index 00000000000..86bf38e98fd Binary files /dev/null and b/components/private/Icons/icons/ic_check_circle/src/MaterialIcons_ic_check_circle.bundle/ic_check_circle.xcassets/ic_check_circle.imageset/ic_check_circle@2x.png differ diff --git a/components/private/Icons/icons/ic_check_circle/src/MaterialIcons_ic_check_circle.bundle/ic_check_circle.xcassets/ic_check_circle.imageset/ic_check_circle@3x.png b/components/private/Icons/icons/ic_check_circle/src/MaterialIcons_ic_check_circle.bundle/ic_check_circle.xcassets/ic_check_circle.imageset/ic_check_circle@3x.png new file mode 100644 index 00000000000..1c2ddcd5f34 Binary files /dev/null and b/components/private/Icons/icons/ic_check_circle/src/MaterialIcons_ic_check_circle.bundle/ic_check_circle.xcassets/ic_check_circle.imageset/ic_check_circle@3x.png differ diff --git a/components/private/Icons/icons/ic_chevron_right/src/MaterialIcons+ic_chevron_right.h b/components/private/Icons/icons/ic_chevron_right/src/MaterialIcons+ic_chevron_right.h new file mode 100644 index 00000000000..6678ffea3d4 --- /dev/null +++ b/components/private/Icons/icons/ic_chevron_right/src/MaterialIcons+ic_chevron_right.h @@ -0,0 +1,27 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import "MaterialIcons.h" + +// This file was automatically generated by running scripts/sync_icons.sh +// Do not modify directly. + +@interface MDCIcons (ic_chevron_right) + +/** Returns the path for the ic_chevron_right image contained in MaterialIcons_ic_chevron_right.bundle. */ ++ (nonnull NSString *)pathFor_ic_chevron_right; + +@end diff --git a/components/private/Icons/icons/ic_chevron_right/src/MaterialIcons+ic_chevron_right.m b/components/private/Icons/icons/ic_chevron_right/src/MaterialIcons+ic_chevron_right.m new file mode 100644 index 00000000000..dff76203da6 --- /dev/null +++ b/components/private/Icons/icons/ic_chevron_right/src/MaterialIcons+ic_chevron_right.m @@ -0,0 +1,33 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +// This file was automatically generated by running scripts/sync_icons.sh +// Do not modify directly. + +#import "MaterialIcons+ic_chevron_right.h" + +#import "MDCIcons+BundleLoader.h" + +static NSString *const kBundleName = @"MaterialIcons_ic_chevron_right"; +static NSString *const kIconName = @"ic_chevron_right"; + +@implementation MDCIcons (ic_chevron_right) + ++ (nonnull NSString *)pathFor_ic_chevron_right { + return [self pathForIconName:kIconName withBundleName:kBundleName]; +} + +@end diff --git a/components/private/Icons/icons/ic_chevron_right/src/MaterialIcons_ic_chevron_right.bundle/ic_chevron_right.xcassets/ic_chevron_right.imageset/Contents.json b/components/private/Icons/icons/ic_chevron_right/src/MaterialIcons_ic_chevron_right.bundle/ic_chevron_right.xcassets/ic_chevron_right.imageset/Contents.json new file mode 100644 index 00000000000..24a339f84de --- /dev/null +++ b/components/private/Icons/icons/ic_chevron_right/src/MaterialIcons_ic_chevron_right.bundle/ic_chevron_right.xcassets/ic_chevron_right.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images": [ + { + "filename": "ic_chevron_right.png", + "idiom": "universal", + "scale": "1x" + }, + { + "filename": "ic_chevron_right@2x.png", + "idiom": "universal", + "scale": "2x" + }, + { + "filename": "ic_chevron_right@3x.png", + "idiom": "universal", + "scale": "3x" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/components/private/Icons/icons/ic_chevron_right/src/MaterialIcons_ic_chevron_right.bundle/ic_chevron_right.xcassets/ic_chevron_right.imageset/ic_chevron_right.png b/components/private/Icons/icons/ic_chevron_right/src/MaterialIcons_ic_chevron_right.bundle/ic_chevron_right.xcassets/ic_chevron_right.imageset/ic_chevron_right.png new file mode 100644 index 00000000000..c11a2a5e2d3 Binary files /dev/null and b/components/private/Icons/icons/ic_chevron_right/src/MaterialIcons_ic_chevron_right.bundle/ic_chevron_right.xcassets/ic_chevron_right.imageset/ic_chevron_right.png differ diff --git a/components/private/Icons/icons/ic_chevron_right/src/MaterialIcons_ic_chevron_right.bundle/ic_chevron_right.xcassets/ic_chevron_right.imageset/ic_chevron_right@2x.png b/components/private/Icons/icons/ic_chevron_right/src/MaterialIcons_ic_chevron_right.bundle/ic_chevron_right.xcassets/ic_chevron_right.imageset/ic_chevron_right@2x.png new file mode 100644 index 00000000000..23338b8b5b2 Binary files /dev/null and b/components/private/Icons/icons/ic_chevron_right/src/MaterialIcons_ic_chevron_right.bundle/ic_chevron_right.xcassets/ic_chevron_right.imageset/ic_chevron_right@2x.png differ diff --git a/components/private/Icons/icons/ic_chevron_right/src/MaterialIcons_ic_chevron_right.bundle/ic_chevron_right.xcassets/ic_chevron_right.imageset/ic_chevron_right@3x.png b/components/private/Icons/icons/ic_chevron_right/src/MaterialIcons_ic_chevron_right.bundle/ic_chevron_right.xcassets/ic_chevron_right.imageset/ic_chevron_right@3x.png new file mode 100644 index 00000000000..f97c51b8ed7 Binary files /dev/null and b/components/private/Icons/icons/ic_chevron_right/src/MaterialIcons_ic_chevron_right.bundle/ic_chevron_right.xcassets/ic_chevron_right.imageset/ic_chevron_right@3x.png differ diff --git a/components/private/Icons/icons/ic_info/src/MaterialIcons+ic_info.h b/components/private/Icons/icons/ic_info/src/MaterialIcons+ic_info.h new file mode 100644 index 00000000000..9a9157565bd --- /dev/null +++ b/components/private/Icons/icons/ic_info/src/MaterialIcons+ic_info.h @@ -0,0 +1,27 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import "MaterialIcons.h" + +// This file was automatically generated by running scripts/sync_icons.sh +// Do not modify directly. + +@interface MDCIcons (ic_info) + +/** Returns the path for the ic_info image contained in MaterialIcons_ic_info.bundle. */ ++ (nonnull NSString *)pathFor_ic_info; + +@end diff --git a/components/private/Icons/icons/ic_info/src/MaterialIcons+ic_info.m b/components/private/Icons/icons/ic_info/src/MaterialIcons+ic_info.m new file mode 100644 index 00000000000..3712e75fceb --- /dev/null +++ b/components/private/Icons/icons/ic_info/src/MaterialIcons+ic_info.m @@ -0,0 +1,33 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +// This file was automatically generated by running scripts/sync_icons.sh +// Do not modify directly. + +#import "MaterialIcons+ic_info.h" + +#import "MDCIcons+BundleLoader.h" + +static NSString *const kBundleName = @"MaterialIcons_ic_info"; +static NSString *const kIconName = @"ic_info"; + +@implementation MDCIcons (ic_info) + ++ (nonnull NSString *)pathFor_ic_info { + return [self pathForIconName:kIconName withBundleName:kBundleName]; +} + +@end diff --git a/components/private/Icons/icons/ic_info/src/MaterialIcons_ic_info.bundle/ic_info.xcassets/ic_info.imageset/Contents.json b/components/private/Icons/icons/ic_info/src/MaterialIcons_ic_info.bundle/ic_info.xcassets/ic_info.imageset/Contents.json new file mode 100644 index 00000000000..710d76aafc4 --- /dev/null +++ b/components/private/Icons/icons/ic_info/src/MaterialIcons_ic_info.bundle/ic_info.xcassets/ic_info.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images": [ + { + "filename": "ic_info.png", + "idiom": "universal", + "scale": "1x" + }, + { + "filename": "ic_info@2x.png", + "idiom": "universal", + "scale": "2x" + }, + { + "filename": "ic_info@3x.png", + "idiom": "universal", + "scale": "3x" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/components/private/Icons/icons/ic_info/src/MaterialIcons_ic_info.bundle/ic_info.xcassets/ic_info.imageset/ic_info.png b/components/private/Icons/icons/ic_info/src/MaterialIcons_ic_info.bundle/ic_info.xcassets/ic_info.imageset/ic_info.png new file mode 100644 index 00000000000..5ef3dc0809e Binary files /dev/null and b/components/private/Icons/icons/ic_info/src/MaterialIcons_ic_info.bundle/ic_info.xcassets/ic_info.imageset/ic_info.png differ diff --git a/components/private/Icons/icons/ic_info/src/MaterialIcons_ic_info.bundle/ic_info.xcassets/ic_info.imageset/ic_info@2x.png b/components/private/Icons/icons/ic_info/src/MaterialIcons_ic_info.bundle/ic_info.xcassets/ic_info.imageset/ic_info@2x.png new file mode 100644 index 00000000000..46ed12a89bd Binary files /dev/null and b/components/private/Icons/icons/ic_info/src/MaterialIcons_ic_info.bundle/ic_info.xcassets/ic_info.imageset/ic_info@2x.png differ diff --git a/components/private/Icons/icons/ic_info/src/MaterialIcons_ic_info.bundle/ic_info.xcassets/ic_info.imageset/ic_info@3x.png b/components/private/Icons/icons/ic_info/src/MaterialIcons_ic_info.bundle/ic_info.xcassets/ic_info.imageset/ic_info@3x.png new file mode 100644 index 00000000000..a81eeb9ee7e Binary files /dev/null and b/components/private/Icons/icons/ic_info/src/MaterialIcons_ic_info.bundle/ic_info.xcassets/ic_info.imageset/ic_info@3x.png differ diff --git a/components/private/Icons/icons/ic_radio_button_unchecked/src/MaterialIcons+ic_radio_button_unchecked.h b/components/private/Icons/icons/ic_radio_button_unchecked/src/MaterialIcons+ic_radio_button_unchecked.h new file mode 100644 index 00000000000..efd5bc51521 --- /dev/null +++ b/components/private/Icons/icons/ic_radio_button_unchecked/src/MaterialIcons+ic_radio_button_unchecked.h @@ -0,0 +1,27 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import "MaterialIcons.h" + +// This file was automatically generated by running scripts/sync_icons.sh +// Do not modify directly. + +@interface MDCIcons (ic_radio_button_unchecked) + +/** Returns the path for the ic_radio_button_unchecked image contained in MaterialIcons_ic_radio_button_unchecked.bundle. */ ++ (nonnull NSString *)pathFor_ic_radio_button_unchecked; + +@end diff --git a/components/private/Icons/icons/ic_radio_button_unchecked/src/MaterialIcons+ic_radio_button_unchecked.m b/components/private/Icons/icons/ic_radio_button_unchecked/src/MaterialIcons+ic_radio_button_unchecked.m new file mode 100644 index 00000000000..57cadcbc4a0 --- /dev/null +++ b/components/private/Icons/icons/ic_radio_button_unchecked/src/MaterialIcons+ic_radio_button_unchecked.m @@ -0,0 +1,33 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +// This file was automatically generated by running scripts/sync_icons.sh +// Do not modify directly. + +#import "MaterialIcons+ic_radio_button_unchecked.h" + +#import "MDCIcons+BundleLoader.h" + +static NSString *const kBundleName = @"MaterialIcons_ic_radio_button_unchecked"; +static NSString *const kIconName = @"ic_radio_button_unchecked"; + +@implementation MDCIcons (ic_radio_button_unchecked) + ++ (nonnull NSString *)pathFor_ic_radio_button_unchecked { + return [self pathForIconName:kIconName withBundleName:kBundleName]; +} + +@end diff --git a/components/private/Icons/icons/ic_radio_button_unchecked/src/MaterialIcons_ic_radio_button_unchecked.bundle/ic_radio_button_unchecked.xcassets/ic_radio_button_unchecked.imageset/Contents.json b/components/private/Icons/icons/ic_radio_button_unchecked/src/MaterialIcons_ic_radio_button_unchecked.bundle/ic_radio_button_unchecked.xcassets/ic_radio_button_unchecked.imageset/Contents.json new file mode 100644 index 00000000000..1da06359b98 --- /dev/null +++ b/components/private/Icons/icons/ic_radio_button_unchecked/src/MaterialIcons_ic_radio_button_unchecked.bundle/ic_radio_button_unchecked.xcassets/ic_radio_button_unchecked.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images": [ + { + "filename": "ic_radio_button_unchecked.png", + "idiom": "universal", + "scale": "1x" + }, + { + "filename": "ic_radio_button_unchecked@2x.png", + "idiom": "universal", + "scale": "2x" + }, + { + "filename": "ic_radio_button_unchecked@3x.png", + "idiom": "universal", + "scale": "3x" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/components/private/Icons/icons/ic_radio_button_unchecked/src/MaterialIcons_ic_radio_button_unchecked.bundle/ic_radio_button_unchecked.xcassets/ic_radio_button_unchecked.imageset/ic_radio_button_unchecked.png b/components/private/Icons/icons/ic_radio_button_unchecked/src/MaterialIcons_ic_radio_button_unchecked.bundle/ic_radio_button_unchecked.xcassets/ic_radio_button_unchecked.imageset/ic_radio_button_unchecked.png new file mode 100644 index 00000000000..4c0688f03a1 Binary files /dev/null and b/components/private/Icons/icons/ic_radio_button_unchecked/src/MaterialIcons_ic_radio_button_unchecked.bundle/ic_radio_button_unchecked.xcassets/ic_radio_button_unchecked.imageset/ic_radio_button_unchecked.png differ diff --git a/components/private/Icons/icons/ic_radio_button_unchecked/src/MaterialIcons_ic_radio_button_unchecked.bundle/ic_radio_button_unchecked.xcassets/ic_radio_button_unchecked.imageset/ic_radio_button_unchecked@2x.png b/components/private/Icons/icons/ic_radio_button_unchecked/src/MaterialIcons_ic_radio_button_unchecked.bundle/ic_radio_button_unchecked.xcassets/ic_radio_button_unchecked.imageset/ic_radio_button_unchecked@2x.png new file mode 100644 index 00000000000..daa867f1375 Binary files /dev/null and b/components/private/Icons/icons/ic_radio_button_unchecked/src/MaterialIcons_ic_radio_button_unchecked.bundle/ic_radio_button_unchecked.xcassets/ic_radio_button_unchecked.imageset/ic_radio_button_unchecked@2x.png differ diff --git a/components/private/Icons/icons/ic_radio_button_unchecked/src/MaterialIcons_ic_radio_button_unchecked.bundle/ic_radio_button_unchecked.xcassets/ic_radio_button_unchecked.imageset/ic_radio_button_unchecked@3x.png b/components/private/Icons/icons/ic_radio_button_unchecked/src/MaterialIcons_ic_radio_button_unchecked.bundle/ic_radio_button_unchecked.xcassets/ic_radio_button_unchecked.imageset/ic_radio_button_unchecked@3x.png new file mode 100644 index 00000000000..d8c86365f9d Binary files /dev/null and b/components/private/Icons/icons/ic_radio_button_unchecked/src/MaterialIcons_ic_radio_button_unchecked.bundle/ic_radio_button_unchecked.xcassets/ic_radio_button_unchecked.imageset/ic_radio_button_unchecked@3x.png differ diff --git a/components/private/Icons/icons/ic_reorder/src/MaterialIcons+ic_reorder.h b/components/private/Icons/icons/ic_reorder/src/MaterialIcons+ic_reorder.h new file mode 100644 index 00000000000..1e28784f16c --- /dev/null +++ b/components/private/Icons/icons/ic_reorder/src/MaterialIcons+ic_reorder.h @@ -0,0 +1,27 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +#import "MaterialIcons.h" + +// This file was automatically generated by running scripts/sync_icons.sh +// Do not modify directly. + +@interface MDCIcons (ic_reorder) + +/** Returns the path for the ic_reorder image contained in MaterialIcons_ic_reorder.bundle. */ ++ (nonnull NSString *)pathFor_ic_reorder; + +@end diff --git a/components/private/Icons/icons/ic_reorder/src/MaterialIcons+ic_reorder.m b/components/private/Icons/icons/ic_reorder/src/MaterialIcons+ic_reorder.m new file mode 100644 index 00000000000..99aa1a7fedd --- /dev/null +++ b/components/private/Icons/icons/ic_reorder/src/MaterialIcons+ic_reorder.m @@ -0,0 +1,33 @@ +/* + Copyright 2016-present Google Inc. 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. + */ + +// This file was automatically generated by running scripts/sync_icons.sh +// Do not modify directly. + +#import "MaterialIcons+ic_reorder.h" + +#import "MDCIcons+BundleLoader.h" + +static NSString *const kBundleName = @"MaterialIcons_ic_reorder"; +static NSString *const kIconName = @"ic_reorder"; + +@implementation MDCIcons (ic_reorder) + ++ (nonnull NSString *)pathFor_ic_reorder { + return [self pathForIconName:kIconName withBundleName:kBundleName]; +} + +@end diff --git a/components/private/Icons/icons/ic_reorder/src/MaterialIcons_ic_reorder.bundle/ic_reorder.xcassets/ic_reorder.imageset/Contents.json b/components/private/Icons/icons/ic_reorder/src/MaterialIcons_ic_reorder.bundle/ic_reorder.xcassets/ic_reorder.imageset/Contents.json new file mode 100644 index 00000000000..215eccf96fb --- /dev/null +++ b/components/private/Icons/icons/ic_reorder/src/MaterialIcons_ic_reorder.bundle/ic_reorder.xcassets/ic_reorder.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images": [ + { + "filename": "ic_reorder.png", + "idiom": "universal", + "scale": "1x" + }, + { + "filename": "ic_reorder@2x.png", + "idiom": "universal", + "scale": "2x" + }, + { + "filename": "ic_reorder@3x.png", + "idiom": "universal", + "scale": "3x" + } + ], + "info": { + "author": "xcode", + "version": 1 + } +} diff --git a/components/private/Icons/icons/ic_reorder/src/MaterialIcons_ic_reorder.bundle/ic_reorder.xcassets/ic_reorder.imageset/ic_reorder.png b/components/private/Icons/icons/ic_reorder/src/MaterialIcons_ic_reorder.bundle/ic_reorder.xcassets/ic_reorder.imageset/ic_reorder.png new file mode 100644 index 00000000000..d18997cd483 Binary files /dev/null and b/components/private/Icons/icons/ic_reorder/src/MaterialIcons_ic_reorder.bundle/ic_reorder.xcassets/ic_reorder.imageset/ic_reorder.png differ diff --git a/components/private/Icons/icons/ic_reorder/src/MaterialIcons_ic_reorder.bundle/ic_reorder.xcassets/ic_reorder.imageset/ic_reorder@2x.png b/components/private/Icons/icons/ic_reorder/src/MaterialIcons_ic_reorder.bundle/ic_reorder.xcassets/ic_reorder.imageset/ic_reorder@2x.png new file mode 100644 index 00000000000..0b080a18715 Binary files /dev/null and b/components/private/Icons/icons/ic_reorder/src/MaterialIcons_ic_reorder.bundle/ic_reorder.xcassets/ic_reorder.imageset/ic_reorder@2x.png differ diff --git a/components/private/Icons/icons/ic_reorder/src/MaterialIcons_ic_reorder.bundle/ic_reorder.xcassets/ic_reorder.imageset/ic_reorder@3x.png b/components/private/Icons/icons/ic_reorder/src/MaterialIcons_ic_reorder.bundle/ic_reorder.xcassets/ic_reorder.imageset/ic_reorder@3x.png new file mode 100644 index 00000000000..0a66529bfea Binary files /dev/null and b/components/private/Icons/icons/ic_reorder/src/MaterialIcons_ic_reorder.bundle/ic_reorder.xcassets/ic_reorder.imageset/ic_reorder@3x.png differ diff --git a/community/README.md b/contributing/README.md similarity index 99% rename from community/README.md rename to contributing/README.md index 6ef69d70cca..773aa82cecb 100644 --- a/community/README.md +++ b/contributing/README.md @@ -1,11 +1,11 @@ --- -title: "Community" +title: "Contributing" layout: landing -section: community +section: contributing --- -# Community +# Contributing Material Components for iOS is intended to be a full open source project that accepts contributions from community members. We can work together to optimize Material Design on iOS. diff --git a/community/contributor_guides/checklist.md b/contributing/contributor_guides/checklist.md similarity index 100% rename from community/contributor_guides/checklist.md rename to contributing/contributor_guides/checklist.md diff --git a/community/contributor_guides/hotfixing.md b/contributing/contributor_guides/hotfixing.md similarity index 100% rename from community/contributor_guides/hotfixing.md rename to contributing/contributor_guides/hotfixing.md diff --git a/community/contributor_guides/releasing.md b/contributing/contributor_guides/releasing.md similarity index 86% rename from community/contributor_guides/releasing.md rename to contributing/contributor_guides/releasing.md index a882a47e555..2f759a87faf 100644 --- a/community/contributor_guides/releasing.md +++ b/contributing/contributor_guides/releasing.md @@ -43,6 +43,12 @@ To test the branch locally you can run: Verify that the unit tests do not fail. +Build and run the catalog and demo applications: + + scripts/build_all_pod_projects + +Identify why any failures occurred and resolve them before continuing. + ### Push the release branch early and often Push `release-candidate` to GitHub as you make necessary changes. This allows other people and @@ -184,11 +190,14 @@ To see all changes that are part of this release, run: ### Classify the release type -Based on the information at your disposal you should now be able to identify the release number. +Based on the information at your disposal you should now be able to identify the release type. +Use the `next` script to generate the next version number: -Bump the release by running `bump` with the next version number: + scripts/release/next {major|minor|patch} - scripts/release/bump $(scripts/release/next {major|minor|patch}) +Bump the release by running `bump` with the release's version number: + + scripts/release/bump Also rename CHANGELOG.md's "release-candidate" section with the name of this release. @@ -196,13 +205,31 @@ Also rename CHANGELOG.md's "release-candidate" section with the name of this rel Commit the results to your branch. - git commit -am "Bumped version number to $(scripts/release/next {major|minor|patch})." + git commit -am "Bumped version number to $(pod ipc spec MaterialComponents.podspec | grep '"version"' | cut -d'"' -f4)." git push origin release-candidate -## Merge the release branch +## Send the release out for review + +Sent the release-candidate branch out for review: + + git fetch + git checkout release-candidate + arc diff origin/master --message-file scripts/release/release_checklist.txt + +Check off each item in the diff's checklist before merging the release candidate branch. + +## Do not land the release candidate + +Before you can merge the release branch into either develop or master you **must** get the release +go-ahead from the following clients: + +- Google: must verify that the release branch passes all internal tests. If you are a Googler, see + the internal "mirroring" document for further instructions. + +## Merge the release candidate branch -Once the release has been fully tested and clients have had a reasonable opportunity to test it, you -may merge the release into the `develop` and `master` branches. +Once the release has passed all tests by clients, you may merge the release into the `develop` and +`master` branches. scripts/release/merge diff --git a/community/contributor_guides/writing_readmes.md b/contributing/contributor_guides/writing_readmes.md similarity index 99% rename from community/contributor_guides/writing_readmes.md rename to contributing/contributor_guides/writing_readmes.md index 242aca752ad..aa23a31a901 100644 --- a/community/contributor_guides/writing_readmes.md +++ b/contributing/contributor_guides/writing_readmes.md @@ -11,7 +11,7 @@ fill out have been marked with `TODO` statements. --- title: "TODO: ComponentName" layout: detail - section: documentation + section: components excerpt: "TODO: Single sentence description of the component." --- # TODO: ComponentName diff --git a/demos/Pesto/Pesto/Info.plist b/demos/Pesto/Pesto/Info.plist index 95f195bad59..862232fff95 100644 --- a/demos/Pesto/Pesto/Info.plist +++ b/demos/Pesto/Pesto/Info.plist @@ -30,6 +30,8 @@ UIRequiresFullScreen + UIStatusBarStyle + UIStatusBarStyleLightContent UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/demos/Pesto/Pesto/PestoAppDelegate.m b/demos/Pesto/Pesto/PestoAppDelegate.m index 87ec7775d2d..c12ff5a5716 100644 --- a/demos/Pesto/Pesto/PestoAppDelegate.m +++ b/demos/Pesto/Pesto/PestoAppDelegate.m @@ -25,14 +25,6 @@ @implementation PestoAppDelegate - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { -// setStatusBarHidden:withAnimation: was deprecated in iOS 9. -// Silence the related warning. -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wdeprecated-declarations" - [[UIApplication sharedApplication] setStatusBarHidden:YES - withAnimation:NO]; -#pragma clang diagnostic pop - PestoFlexibleHeaderContainerViewController *flexHeadContainerVC = [[PestoFlexibleHeaderContainerViewController alloc] init]; self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]]; diff --git a/demos/Pesto/Pesto/PestoCollectionViewController.m b/demos/Pesto/Pesto/PestoCollectionViewController.m index 6b2f6827cb5..625bdc5f863 100644 --- a/demos/Pesto/Pesto/PestoCollectionViewController.m +++ b/demos/Pesto/Pesto/PestoCollectionViewController.m @@ -46,7 +46,6 @@ - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout { [self.collectionView registerClass:[PestoCardCollectionViewCell class] forCellWithReuseIdentifier:NSStringFromClass([PestoCardCollectionViewCell class])]; _pestoData = [[PestoData alloc] init]; - [self setNeedsStatusBarAppearanceUpdate]; } return self; } @@ -57,7 +56,7 @@ - (void)setFlexHeaderContainerVC:(MDCFlexibleHeaderContainerViewController *)fle headerView.trackingScrollView = self.collectionView; headerView.maximumHeight = kPestoCollectionViewControllerDefaultHeaderHeight; headerView.minimumHeight = kPestoCollectionViewControllerSmallHeaderHeight; - [headerView.contentView addSubview:[self pestoHeaderView]]; + [headerView addSubview:[self pestoHeaderView]]; // Use a custom shadow under the flexible header. MDCShadowLayer *shadowLayer = [MDCShadowLayer layer]; @@ -74,10 +73,6 @@ - (NSInteger)collectionView:(UICollectionView *)view return (NSInteger)[self.pestoData.imageFileNames count]; } -- (BOOL)prefersStatusBarHidden { - return YES; -} - - (void)willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)toInterfaceOrientation duration:(NSTimeInterval)duration { [self.collectionView.collectionViewLayout invalidateLayout]; diff --git a/demos/Pesto/Pesto/PestoFlexibleHeaderContainerViewController.m b/demos/Pesto/Pesto/PestoFlexibleHeaderContainerViewController.m index bffdc6a8fb7..b5cbc584b77 100644 --- a/demos/Pesto/Pesto/PestoFlexibleHeaderContainerViewController.m +++ b/demos/Pesto/Pesto/PestoFlexibleHeaderContainerViewController.m @@ -57,7 +57,6 @@ - (instancetype)init { _collectionViewController = collectionVC; _collectionViewController.flexHeaderContainerVC = self; _collectionViewController.delegate = self; - [self setNeedsStatusBarAppearanceUpdate]; } return self; } @@ -93,10 +92,6 @@ - (void)viewDidLoad { [self.view insertSubview:self.zoomableView belowSubview:self.animatedMenuArrow]; } -- (BOOL)prefersStatusBarHidden { - return YES; -} - - (void)showMenu { self.sideView.hidden = NO; [self.sideView showSideView]; diff --git a/demos/Pesto/Pesto/PestoSettingsViewController.m b/demos/Pesto/Pesto/PestoSettingsViewController.m index 96dcfd1b7b7..7dae7ba72e8 100644 --- a/demos/Pesto/Pesto/PestoSettingsViewController.m +++ b/demos/Pesto/Pesto/PestoSettingsViewController.m @@ -20,8 +20,6 @@ #import "MaterialSwitch.h" #import "MaterialTypography.h" -static CGFloat kPestoSettingsTableViewOffsetTop = 0.f; - static NSString *const kPestoSettingsTableViewCellReuseIdentifier = @"PestoSettingsTableViewCell"; static NSString *const kPestoSettingsTableViewHeaderViewReuseIdentifier = @"PestoSettingsTableViewHeaderView"; @@ -150,9 +148,9 @@ - (void)viewDidLoad { CGRect settingsTableViewFrame = CGRectMake(0, - kPestoSettingsTableViewOffsetTop, + 0, self.view.bounds.size.width, - self.view.bounds.size.height - kPestoSettingsTableViewOffsetTop); + self.view.bounds.size.height); self.settingsTableView = [[UITableView alloc] initWithFrame:settingsTableViewFrame style:UITableViewStylePlain]; self.settingsTableView.autoresizingMask = UIViewAutoresizingFlexibleWidth; @@ -178,10 +176,6 @@ - (void)viewDidLoad { self.appBar.headerViewController.view.backgroundColor = teal; self.appBar.headerViewController.headerView.trackingScrollView = self.settingsTableView; self.appBar.headerViewController.headerView.tintColor = [UIColor whiteColor]; - - // This app has a forced-hidden status bar. The headerView needs to compensate. - self.appBar.headerViewController.headerView.maximumHeight -= 20; - self.appBar.headerViewController.headerView.minimumHeight -= 20; } + (UIColor *)tableViewSeparatorColor { diff --git a/demos/Pesto/Podfile.lock b/demos/Pesto/Podfile.lock index 242a9b2c87e..91e20388c24 100644 --- a/demos/Pesto/Podfile.lock +++ b/demos/Pesto/Podfile.lock @@ -1,24 +1,26 @@ PODS: - - MaterialComponents (4.0.1): - - MaterialComponents/AppBar (= 4.0.1) - - MaterialComponents/ButtonBar (= 4.0.1) - - MaterialComponents/Buttons (= 4.0.1) - - MaterialComponents/FlexibleHeader (= 4.0.1) - - MaterialComponents/FontDiskLoader (= 4.0.1) - - MaterialComponents/HeaderStackView (= 4.0.1) - - MaterialComponents/Ink (= 4.0.1) - - MaterialComponents/NavigationBar (= 4.0.1) - - MaterialComponents/PageControl (= 4.0.1) - - MaterialComponents/private (= 4.0.1) - - MaterialComponents/RobotoFontLoader (= 4.0.1) - - MaterialComponents/ScrollViewDelegateMultiplexer (= 4.0.1) - - MaterialComponents/ShadowElevations (= 4.0.1) - - MaterialComponents/ShadowLayer (= 4.0.1) - - MaterialComponents/Slider (= 4.0.1) - - MaterialComponents/SpritedAnimationView (= 4.0.1) - - MaterialComponents/Switch (= 4.0.1) - - MaterialComponents/Typography (= 4.0.1) - - MaterialComponents/AppBar (4.0.1): + - MaterialComponents (5.0.0): + - MaterialComponents/AppBar (= 5.0.0) + - MaterialComponents/ButtonBar (= 5.0.0) + - MaterialComponents/Buttons (= 5.0.0) + - MaterialComponents/CollectionCells (= 5.0.0) + - MaterialComponents/CollectionLayoutAttributes (= 5.0.0) + - MaterialComponents/Collections (= 5.0.0) + - MaterialComponents/FlexibleHeader (= 5.0.0) + - MaterialComponents/FontDiskLoader (= 5.0.0) + - MaterialComponents/HeaderStackView (= 5.0.0) + - MaterialComponents/Ink (= 5.0.0) + - MaterialComponents/NavigationBar (= 5.0.0) + - MaterialComponents/PageControl (= 5.0.0) + - MaterialComponents/private (= 5.0.0) + - MaterialComponents/RobotoFontLoader (= 5.0.0) + - MaterialComponents/ShadowElevations (= 5.0.0) + - MaterialComponents/ShadowLayer (= 5.0.0) + - MaterialComponents/Slider (= 5.0.0) + - MaterialComponents/SpritedAnimationView (= 5.0.0) + - MaterialComponents/Switch (= 5.0.0) + - MaterialComponents/Typography (= 5.0.0) + - MaterialComponents/AppBar (5.0.0): - MaterialComponents/FlexibleHeader - MaterialComponents/HeaderStackView - MaterialComponents/NavigationBar @@ -26,49 +28,84 @@ PODS: - MaterialComponents/ShadowElevations - MaterialComponents/ShadowLayer - MaterialComponents/Typography - - MaterialComponents/ButtonBar (4.0.1): + - MaterialComponents/ButtonBar (5.0.0): - MaterialComponents/Buttons - - MaterialComponents/Buttons (4.0.1): + - MaterialComponents/Buttons (5.0.0): - MaterialComponents/Ink - MaterialComponents/ShadowElevations - MaterialComponents/ShadowLayer - MaterialComponents/Typography - - MaterialComponents/FlexibleHeader (4.0.1) - - MaterialComponents/FontDiskLoader (4.0.1) - - MaterialComponents/HeaderStackView (4.0.1) - - MaterialComponents/Ink (4.0.1) - - MaterialComponents/NavigationBar (4.0.1): + - MaterialComponents/CollectionCells (5.0.0): + - MaterialComponents/CollectionLayoutAttributes + - MaterialComponents/Ink + - MaterialComponents/private/Icons/ic_check + - MaterialComponents/private/Icons/ic_check_circle + - MaterialComponents/private/Icons/ic_chevron_right + - MaterialComponents/private/Icons/ic_info + - MaterialComponents/private/Icons/ic_radio_button_unchecked + - MaterialComponents/private/Icons/ic_reorder + - MaterialComponents/Typography + - MaterialComponents/CollectionLayoutAttributes (5.0.0) + - MaterialComponents/Collections (5.0.0): + - MaterialComponents/CollectionCells + - MaterialComponents/CollectionLayoutAttributes + - MaterialComponents/Ink + - MaterialComponents/ShadowElevations + - MaterialComponents/ShadowLayer + - MaterialComponents/Typography + - MaterialComponents/FlexibleHeader (5.0.0) + - MaterialComponents/FontDiskLoader (5.0.0) + - MaterialComponents/HeaderStackView (5.0.0) + - MaterialComponents/Ink (5.0.0) + - MaterialComponents/NavigationBar (5.0.0): - MaterialComponents/ButtonBar - MaterialComponents/Typography - - MaterialComponents/PageControl (4.0.1) - - MaterialComponents/private (4.0.1): - - MaterialComponents/private/Color (= 4.0.1) - - MaterialComponents/private/Icons (= 4.0.1) - - MaterialComponents/private/ThumbTrack (= 4.0.1) - - MaterialComponents/private/Color (4.0.1) - - MaterialComponents/private/Icons (4.0.1): - - MaterialComponents/private/Icons/Base (= 4.0.1) - - MaterialComponents/private/Icons/ic_arrow_back (= 4.0.1) - - MaterialComponents/private/Icons/Base (4.0.1) - - MaterialComponents/private/Icons/ic_arrow_back (4.0.1): + - MaterialComponents/PageControl (5.0.0) + - MaterialComponents/private (5.0.0): + - MaterialComponents/private/Color (= 5.0.0) + - MaterialComponents/private/Icons (= 5.0.0) + - MaterialComponents/private/ThumbTrack (= 5.0.0) + - MaterialComponents/private/Color (5.0.0) + - MaterialComponents/private/Icons (5.0.0): + - MaterialComponents/private/Icons/Base (= 5.0.0) + - MaterialComponents/private/Icons/ic_arrow_back (= 5.0.0) + - MaterialComponents/private/Icons/ic_check (= 5.0.0) + - MaterialComponents/private/Icons/ic_check_circle (= 5.0.0) + - MaterialComponents/private/Icons/ic_chevron_right (= 5.0.0) + - MaterialComponents/private/Icons/ic_info (= 5.0.0) + - MaterialComponents/private/Icons/ic_radio_button_unchecked (= 5.0.0) + - MaterialComponents/private/Icons/ic_reorder (= 5.0.0) + - MaterialComponents/private/Icons/Base (5.0.0) + - MaterialComponents/private/Icons/ic_arrow_back (5.0.0): + - MaterialComponents/private/Icons/Base + - MaterialComponents/private/Icons/ic_check (5.0.0): + - MaterialComponents/private/Icons/Base + - MaterialComponents/private/Icons/ic_check_circle (5.0.0): + - MaterialComponents/private/Icons/Base + - MaterialComponents/private/Icons/ic_chevron_right (5.0.0): + - MaterialComponents/private/Icons/Base + - MaterialComponents/private/Icons/ic_info (5.0.0): + - MaterialComponents/private/Icons/Base + - MaterialComponents/private/Icons/ic_radio_button_unchecked (5.0.0): + - MaterialComponents/private/Icons/Base + - MaterialComponents/private/Icons/ic_reorder (5.0.0): - MaterialComponents/private/Icons/Base - - MaterialComponents/private/ThumbTrack (4.0.1): + - MaterialComponents/private/ThumbTrack (5.0.0): - MaterialComponents/Ink - MaterialComponents/private/Color - MaterialComponents/ShadowElevations - MaterialComponents/ShadowLayer - - MaterialComponents/RobotoFontLoader (4.0.1): + - MaterialComponents/RobotoFontLoader (5.0.0): - MaterialComponents/FontDiskLoader - MaterialComponents/Typography - - MaterialComponents/ScrollViewDelegateMultiplexer (4.0.1) - - MaterialComponents/ShadowElevations (4.0.1) - - MaterialComponents/ShadowLayer (4.0.1) - - MaterialComponents/Slider (4.0.1): + - MaterialComponents/ShadowElevations (5.0.0) + - MaterialComponents/ShadowLayer (5.0.0) + - MaterialComponents/Slider (5.0.0): - MaterialComponents/private/ThumbTrack - - MaterialComponents/SpritedAnimationView (4.0.1) - - MaterialComponents/Switch (4.0.1): + - MaterialComponents/SpritedAnimationView (5.0.0) + - MaterialComponents/Switch (5.0.0): - MaterialComponents/private/ThumbTrack - - MaterialComponents/Typography (4.0.1) + - MaterialComponents/Typography (5.0.0) DEPENDENCIES: - MaterialComponents (from `../../`) @@ -78,6 +115,6 @@ EXTERNAL SOURCES: :path: ../../ SPEC CHECKSUMS: - MaterialComponents: 0f57dfb792eee175d9c287e6b5778cbeb60fcf0b + MaterialComponents: 5de833a54f1cd86c5826c605d8fb00db0d5c4626 COCOAPODS: 0.39.0 diff --git a/demos/Shrine/Podfile.lock b/demos/Shrine/Podfile.lock index 242a9b2c87e..91e20388c24 100644 --- a/demos/Shrine/Podfile.lock +++ b/demos/Shrine/Podfile.lock @@ -1,24 +1,26 @@ PODS: - - MaterialComponents (4.0.1): - - MaterialComponents/AppBar (= 4.0.1) - - MaterialComponents/ButtonBar (= 4.0.1) - - MaterialComponents/Buttons (= 4.0.1) - - MaterialComponents/FlexibleHeader (= 4.0.1) - - MaterialComponents/FontDiskLoader (= 4.0.1) - - MaterialComponents/HeaderStackView (= 4.0.1) - - MaterialComponents/Ink (= 4.0.1) - - MaterialComponents/NavigationBar (= 4.0.1) - - MaterialComponents/PageControl (= 4.0.1) - - MaterialComponents/private (= 4.0.1) - - MaterialComponents/RobotoFontLoader (= 4.0.1) - - MaterialComponents/ScrollViewDelegateMultiplexer (= 4.0.1) - - MaterialComponents/ShadowElevations (= 4.0.1) - - MaterialComponents/ShadowLayer (= 4.0.1) - - MaterialComponents/Slider (= 4.0.1) - - MaterialComponents/SpritedAnimationView (= 4.0.1) - - MaterialComponents/Switch (= 4.0.1) - - MaterialComponents/Typography (= 4.0.1) - - MaterialComponents/AppBar (4.0.1): + - MaterialComponents (5.0.0): + - MaterialComponents/AppBar (= 5.0.0) + - MaterialComponents/ButtonBar (= 5.0.0) + - MaterialComponents/Buttons (= 5.0.0) + - MaterialComponents/CollectionCells (= 5.0.0) + - MaterialComponents/CollectionLayoutAttributes (= 5.0.0) + - MaterialComponents/Collections (= 5.0.0) + - MaterialComponents/FlexibleHeader (= 5.0.0) + - MaterialComponents/FontDiskLoader (= 5.0.0) + - MaterialComponents/HeaderStackView (= 5.0.0) + - MaterialComponents/Ink (= 5.0.0) + - MaterialComponents/NavigationBar (= 5.0.0) + - MaterialComponents/PageControl (= 5.0.0) + - MaterialComponents/private (= 5.0.0) + - MaterialComponents/RobotoFontLoader (= 5.0.0) + - MaterialComponents/ShadowElevations (= 5.0.0) + - MaterialComponents/ShadowLayer (= 5.0.0) + - MaterialComponents/Slider (= 5.0.0) + - MaterialComponents/SpritedAnimationView (= 5.0.0) + - MaterialComponents/Switch (= 5.0.0) + - MaterialComponents/Typography (= 5.0.0) + - MaterialComponents/AppBar (5.0.0): - MaterialComponents/FlexibleHeader - MaterialComponents/HeaderStackView - MaterialComponents/NavigationBar @@ -26,49 +28,84 @@ PODS: - MaterialComponents/ShadowElevations - MaterialComponents/ShadowLayer - MaterialComponents/Typography - - MaterialComponents/ButtonBar (4.0.1): + - MaterialComponents/ButtonBar (5.0.0): - MaterialComponents/Buttons - - MaterialComponents/Buttons (4.0.1): + - MaterialComponents/Buttons (5.0.0): - MaterialComponents/Ink - MaterialComponents/ShadowElevations - MaterialComponents/ShadowLayer - MaterialComponents/Typography - - MaterialComponents/FlexibleHeader (4.0.1) - - MaterialComponents/FontDiskLoader (4.0.1) - - MaterialComponents/HeaderStackView (4.0.1) - - MaterialComponents/Ink (4.0.1) - - MaterialComponents/NavigationBar (4.0.1): + - MaterialComponents/CollectionCells (5.0.0): + - MaterialComponents/CollectionLayoutAttributes + - MaterialComponents/Ink + - MaterialComponents/private/Icons/ic_check + - MaterialComponents/private/Icons/ic_check_circle + - MaterialComponents/private/Icons/ic_chevron_right + - MaterialComponents/private/Icons/ic_info + - MaterialComponents/private/Icons/ic_radio_button_unchecked + - MaterialComponents/private/Icons/ic_reorder + - MaterialComponents/Typography + - MaterialComponents/CollectionLayoutAttributes (5.0.0) + - MaterialComponents/Collections (5.0.0): + - MaterialComponents/CollectionCells + - MaterialComponents/CollectionLayoutAttributes + - MaterialComponents/Ink + - MaterialComponents/ShadowElevations + - MaterialComponents/ShadowLayer + - MaterialComponents/Typography + - MaterialComponents/FlexibleHeader (5.0.0) + - MaterialComponents/FontDiskLoader (5.0.0) + - MaterialComponents/HeaderStackView (5.0.0) + - MaterialComponents/Ink (5.0.0) + - MaterialComponents/NavigationBar (5.0.0): - MaterialComponents/ButtonBar - MaterialComponents/Typography - - MaterialComponents/PageControl (4.0.1) - - MaterialComponents/private (4.0.1): - - MaterialComponents/private/Color (= 4.0.1) - - MaterialComponents/private/Icons (= 4.0.1) - - MaterialComponents/private/ThumbTrack (= 4.0.1) - - MaterialComponents/private/Color (4.0.1) - - MaterialComponents/private/Icons (4.0.1): - - MaterialComponents/private/Icons/Base (= 4.0.1) - - MaterialComponents/private/Icons/ic_arrow_back (= 4.0.1) - - MaterialComponents/private/Icons/Base (4.0.1) - - MaterialComponents/private/Icons/ic_arrow_back (4.0.1): + - MaterialComponents/PageControl (5.0.0) + - MaterialComponents/private (5.0.0): + - MaterialComponents/private/Color (= 5.0.0) + - MaterialComponents/private/Icons (= 5.0.0) + - MaterialComponents/private/ThumbTrack (= 5.0.0) + - MaterialComponents/private/Color (5.0.0) + - MaterialComponents/private/Icons (5.0.0): + - MaterialComponents/private/Icons/Base (= 5.0.0) + - MaterialComponents/private/Icons/ic_arrow_back (= 5.0.0) + - MaterialComponents/private/Icons/ic_check (= 5.0.0) + - MaterialComponents/private/Icons/ic_check_circle (= 5.0.0) + - MaterialComponents/private/Icons/ic_chevron_right (= 5.0.0) + - MaterialComponents/private/Icons/ic_info (= 5.0.0) + - MaterialComponents/private/Icons/ic_radio_button_unchecked (= 5.0.0) + - MaterialComponents/private/Icons/ic_reorder (= 5.0.0) + - MaterialComponents/private/Icons/Base (5.0.0) + - MaterialComponents/private/Icons/ic_arrow_back (5.0.0): + - MaterialComponents/private/Icons/Base + - MaterialComponents/private/Icons/ic_check (5.0.0): + - MaterialComponents/private/Icons/Base + - MaterialComponents/private/Icons/ic_check_circle (5.0.0): + - MaterialComponents/private/Icons/Base + - MaterialComponents/private/Icons/ic_chevron_right (5.0.0): + - MaterialComponents/private/Icons/Base + - MaterialComponents/private/Icons/ic_info (5.0.0): + - MaterialComponents/private/Icons/Base + - MaterialComponents/private/Icons/ic_radio_button_unchecked (5.0.0): + - MaterialComponents/private/Icons/Base + - MaterialComponents/private/Icons/ic_reorder (5.0.0): - MaterialComponents/private/Icons/Base - - MaterialComponents/private/ThumbTrack (4.0.1): + - MaterialComponents/private/ThumbTrack (5.0.0): - MaterialComponents/Ink - MaterialComponents/private/Color - MaterialComponents/ShadowElevations - MaterialComponents/ShadowLayer - - MaterialComponents/RobotoFontLoader (4.0.1): + - MaterialComponents/RobotoFontLoader (5.0.0): - MaterialComponents/FontDiskLoader - MaterialComponents/Typography - - MaterialComponents/ScrollViewDelegateMultiplexer (4.0.1) - - MaterialComponents/ShadowElevations (4.0.1) - - MaterialComponents/ShadowLayer (4.0.1) - - MaterialComponents/Slider (4.0.1): + - MaterialComponents/ShadowElevations (5.0.0) + - MaterialComponents/ShadowLayer (5.0.0) + - MaterialComponents/Slider (5.0.0): - MaterialComponents/private/ThumbTrack - - MaterialComponents/SpritedAnimationView (4.0.1) - - MaterialComponents/Switch (4.0.1): + - MaterialComponents/SpritedAnimationView (5.0.0) + - MaterialComponents/Switch (5.0.0): - MaterialComponents/private/ThumbTrack - - MaterialComponents/Typography (4.0.1) + - MaterialComponents/Typography (5.0.0) DEPENDENCIES: - MaterialComponents (from `../../`) @@ -78,6 +115,6 @@ EXTERNAL SOURCES: :path: ../../ SPEC CHECKSUMS: - MaterialComponents: 0f57dfb792eee175d9c287e6b5778cbeb60fcf0b + MaterialComponents: 5de833a54f1cd86c5826c605d8fb00db0d5c4626 COCOAPODS: 0.39.0 diff --git a/howto/README.md b/howto/README.md index 0b9a4b26443..5b45e024df8 100644 --- a/howto/README.md +++ b/howto/README.md @@ -1,716 +1,18 @@ --- -title: "Material Components Development Guide" +title: "How to use Material Components" layout: landing section: howto --- -# Material Components Development Guide +# How to use Material Components -Material Components for iOS is a set of components that help iOS app developers build Material Design apps. These are the same components Google uses to build apps like Google Maps, Calendar, Chrome and many more. +Material Components iOS should be immediately useable out of the box with +Apple's standard development tool chain. -Individually, the components bring Material Design principles to common UI elements and behaviors, but tailored for iOS. Our team has taken care to design the APIs to feel natural on iOS. +- [Tutorial](/howto/tutorial/) + -Our goal is to make implementing Material Design as easy as possible. The components are easy to assemble and be used piecemeal. - -This tutorial will take you through building an example app called Abstractor and show some of the neat features and benefits of using our components to build your app. In order to get through this tutorial, Swift and iOS development knowledge is required. - - -- - - - -## Getting Started - -### Tutorial Setup - -Material Components for iOS can be integrated like any other shared code library on iOS. The preferred method of integration is through CocoaPods. - -To help get started quickly, `git clone` this skeleton new project which the rest of the tutorial will use. - -~~~ bash -git clone https://github.com/google/material-components-ios-example/ -~~~ - -This project is similar to a new project created using Xcode's new project template except with a small number of changes: - -1. Removes the Main.storyboard and references to it in favor of programmatically creating the UI. -2. Adds a bridging header (BridgingHeader.h) and the Xcode configuration for it. -3. Adds a simple String class extension for creating sample text. -4. Adds two icons (search and add) from [Material Icons](https://github.io/google/material-icons) -5. Creates a new MainViewController.swift. - -#### CocoaPods - -The first step is to add Material Components through CocoaPods. The [Material Components quickstart](https://materialcomponents.org/) has detailed instructions, but in short, create a Podfile in the root of the example with the following contents: - -~~~ ruby -target 'Abstractor' do - pod 'MaterialComponents' -end -~~~ - -Run `pod install` in that directory and open up `Abstractor.xcworkspace`. - -#### Bridging for Swift - -Material Components is written in Objective-C and is completely usable from Swift. In order to make the classes visible to Swift, the headers need to be added to the `BridgingHeaders.h`. Open up `BridgingHeaders.h` and add the following lines. - - -~~~ objc -#import "MaterialAppBar.h" -#import "MaterialButtons.h" -#import "MaterialCollections.h" -#import "MaterialFlexibleHeader.h" -~~~ - -#### Building and running the app - -The Abstractor project should be now set up and ready to run. Building and running the project should show you a fairly boring app with a yellow background with no contents. That is our skeleton project the rest of the tutorial will use. - -**TODO: Insert image of the app.** - - -- - - - -## Material Headers - -Headers exist in nearly all apps we see to provide framing and navigation. The header and scrolling behavior is well defined in the [Material Design Guidelines](https://www.google.com/design/spec/TODO) but it is tricky to get right. - -Material Components for iOS provides both a higher level and lower level implementation that allow developers to easily customize the right behavior for the view controller they are building. Both implementations provide a responsive header that can expand and contract in response to scrolling behaviors to maximize the content area or show high level information to the user. - -The [App Bar](https://materialcomponents.org/components/appbar/) is the first way to implement a response header. It uses the familiar UINavigationItem properties of a UIViewController to derive the contents of the header view. - -The [Flexible Header](https://materialcomponents.org/components/flexible-header/) is the second way to implement the responsive header. This component is what the App Bar is built on and is perfect if the developer would like fine grained control over it's contents and behavior. For example, the flexible header can contain a fully custom view that would respond to size changes as the user scrolled. - -If you are used to UINavigationController's UINavigationBar, the fundamental different in design is that your UIViewController has the actual header bar in it's view hierarchy. This contrasts with UINavigationBar being part of UINavigationController's view hierarchy. The different view hierarchy allows gives developers more flexibility when animating between view controllers or customizing unique behaviors when the size of the header changes. - -- - - - -## Starting with App Bar - -In the Abstractor project, there is a view controller already created called MainViewController. To start, we will add a scroll view to the MainViewController since the AppBar works best with a scroll view. - -Modify the MainViewController.swift by adding a UIScrollView. Make the following changes to the MainViewController: - - - -#### Swift - -~~~ swift -class MainViewController : UIViewController { - var scrollView: UIScrollView? - - override func viewDidLoad() { - super.viewDidLoad() - - // Create and initialize a blank scroll view. - scrollView = UIScrollView(frame: view.bounds) - scrollView!.contentSize = CGSize(width: view.bounds.size.width, height: 1000) - scrollView!.backgroundColor = UIColor.whiteColor() - view.addSubview(scrollView!) - } -} -~~~ - - -This snippet is basic UIKit code to create a scroll view, setting the size of the scroll view to be at least 1000 points tall so it will scroll further off screen. - -To actually add the App Bar, we need to give the view controller a protocol to conform to, and override the initializers: - - - -#### Swift - -~~~ swift -class MainViewController : UIViewController, MDCAppBarParenting { - var scrollView: UIScrollView? - - // -- start MDCAppBarParenting - var headerStackView: MDCHeaderStackView? - var navigationBar: MDCNavigationBar? - var headerViewController: MDCFlexibleHeaderViewController? - - override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { - super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) - MDCAppBarPrepareParent(self) - } - - required init?(coder aDecoder: NSCoder) { - super.init(coder: aDecoder) - MDCAppBarPrepareParent(self) - } - // -- end MDCAppBarParenting - - override func viewDidLoad() { - super.viewDidLoad() - - // Create and initialize a blank scroll view. - scrollView = UIScrollView(frame: view.bounds) - scrollView!.contentSize = CGSize(width: view.bounds.size.width, height: 1000) - scrollView!.backgroundColor = UIColor.whiteColor() - view.addSubview(scrollView!) - - // -- start MDCAppBarParenting - MDCAppBarAddViews(self) - // -- end MDCAppBarParenting - } -} - -~~~ - - - -At this point, the app will add a grey header bar at the top of the view, but the scroll view will live under the header bar. You can observe this by scrolling the the view and seeing the scroll indicator go below the grey header bar. - -**TODO: Add image of the grey header** - -The MDCFlexibleHeaderViewController that was now exposed as a property of our view controller doesn't know about the scroll view and therefore it cannot adjust any scroll view insets the scroll view needs to render below the bar. To rectify this, simply tell the headerView in the MDCFlexibleHeaderViewController about the scroll view in viewDidLoad(): - - - -#### Swift - -~~~ swift -class MainViewController : UIViewController, MDCAppBarParenting { - // ... - override func viewDidLoad() { - // ... - - // Connect scroll view with the header view controller. - headerViewController?.headerView.trackingScrollView = contentScrollView - headerViewController?.headerView.behavior = .EnabledWithStatusBar - scrollView.delegate = headerViewController - - } -~~~ - - - -Now the scroll view correctly aligns to the bottom of the header bar. Notice that `headerView` had a property called `behavior` which is set to EnabledWithStatusBar. This behavior controls how the header view reacts to scrolling. When `Enabled`, the header will collapse to maximize the content area. Developers can choose whether the status bar should also be hidden. - -**TODO: Add animation of the grey header collapsing.** - -The status bar is not hiding yet, and the reason is by default UIViewController does not hide the status bar. In order for `headerViewController` to assume control of the status bar, override the method `childViewControllerForStatusBarHidden` to use the headerViewController as the childViewController (see the FlexibleHeader component documentation for more details): - - - -#### Swift - -~~~ swift -class MainViewController : UIViewController, MDCAppBarParenting { - // ... - override func childViewControllerForStatusBarHidden() -> UIViewController { - return headerViewController! - } -} -~~~ - - - -**TODO: Add animation of the status bar correctly collapsing.** - -To complete the integration, let's set a proper color and some items on to the header bar. - -To set the color of the header, we can directly manipulate the headerView in viewDidLoad: - - - -#### Swift - -~~~ swift -class MainViewController : UIViewController, MDCAppBarParenting { - // ... - override func viewDidLoad() { - // ... - - // Set color using UIColor extension in UIColorAbstractor.swift - headerViewController!.headerView.backgroundColor = UIColor.materialOrange700() - headerViewController!.headerView.tintColor = UIColor.whiteColor().colorWithAlphaComponent(0.87) - } - - // Set the status bar to white. - override func preferredStatusBarStyle() -> UIStatusBarStyle { - return .LightContent - } -} -~~~ - - - -And finally put some buttons in to the header bar. - - - -#### Swift - -~~~ swift -class MainViewController : UIViewController, MDCAppBarParenting { - override func viewDidLoad() { - // ... - - // Set up UINavigationItems - navigationItem.title = "Abstractor".blackout() - navigationItem.rightBarButtonItem = UIBarButtonItem( - image: UIImage(named: "ic_search")?.imageWithRenderingMode(.AlwaysTemplate), - style: .Plain, - target: self, - action: #selector(MainViewController.search(_:))) - - // Last step in viewDidLoad - MDCAppBarAddViews(self) - } - - // Implement the callback method for the search button. - func search(target: AnyObject) { - } -} -~~~ - - - -And there you have a responsive header that reacts to the scroll view and collapses to maximize -the content. - -- - - - -## Flexible Header - -One advantage of the App Bar component is it's compatibility with UINavigationItem. If developers would like to customize the actual contents inside the header, they need to look at the powerful Flexible Header component. - -Observant developers would already have noticed that App Bar uses Flexible Header to create the behavior. Imagine instead that we would like to lock the title to the bottom of the header but keep search button attached to the top. - - - -### Create a custom header view - -The first thing to do is to create a custom view that will be placed inside the FlexibleHeader. This can be in conjunction with the App Bar or completed without. Notice in the previous steps, another property we added is the `MDCNavigationBar` that provides the logic to layout the single line button bar. - - - -#### Swift - -~~~ swift -class CustomHeaderView : UIView { - var titleLabel = UILabel() - var iconView = UIImageView() - - override init(frame: CGRect) { - super.init(frame: frame) - - let icon = UIImage(named: "ic_search")!.imageWithRenderingMode(.AlwaysTemplate) - iconView.image = icon - iconView.tintColor = UIColor.whiteColor().colorWithAlphaComponent(0.7) - titleLabel.textColor = UIColor.whiteColor().colorWithAlphaComponent(0.7) - - self.addSubview(iconView) - self.addSubview(titleLabel) - } - - required init?(coder: NSCoder) { - super.init(coder: coder) - // not implementated - } - - override func layoutSubviews() { - super.layoutSubviews() - // Some fancying arithmetic : TODO replace with autolayout - titleLabel.frame = CGRect(x: 16, y: self.bounds.size.height - 40, width: 128, height: 24) - iconView.frame = CGRect(x: self.bounds.size.width - 24 - 16, y: 20 + 16, width: 24, height: 24) - } -} -~~~ - - - -This header view creates a similar view as the one in the App Bar, for simplicity, but the layoutSubviews -logic locks the titleLabel to the bottom of the header while the iconView stays locks to the top right. - -### Adding Flexible Header - -Integrating the Flexible Header is about the same amount of work as App Bar: - - - -#### Swift - -~~~ swift -class MainViewController : UIViewController, MDCFlexibleHeaderViewLayoutDelegate { - var headerViewController: MDCFlexibleHeaderViewController - var customHeaderView = CustomHeaderView() - - //... -} -~~~ - - - -Override the initialize to add the MDCFlexibleHeaderViewController to the view controller -as a childViewController replacing the previous `MDCAppBArPrepareParent`: - - - -#### Swift - -~~~ swift -override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { - headerViewController = MDCFlexibleHeaderViewController() - super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) - self.addChildViewController(headerViewController) - } - - required init?(coder aDecoder: NSCoder) { - headerViewController = MDCFlexibleHeaderViewController() - super.init(coder: aDecoder) - self.addChildViewController(headerViewController) - } -~~~ - - - -Initialize the customHeaderView and connect the headerViewController's headerView to -the UIViewController's hierarchy. This replaces `MDCAppBarAddViews` was doing -except this is not creating any observing of the UINavigationItem. - - - -#### Swift - -~~~ swift -override func viewDidLoad() { - super.viewDidLoad() - - // ... - - // Remove setting up UINavigationItem and MDCAppBarAddViews(self). - - // Initialize the customHeaderView - var headerViewInitialFrame = headerViewController.headerView.contentView!.bounds - headerViewInitialFrame.size.height = 56 + 20 - customHeaderView.frame = headerViewInitialFrame - headerViewController.headerView.contentView?.addSubview(customHeaderView) - - // Connect headerViewController to this viewController's view hierarchy - view.addSubview(headerViewController!.headerView) - headerViewController.didMoveToParentViewController(self) - headerViewController.layoutDelegate = self - -} -~~~ - - - -One final thing that is added is to add this viewController as the layoutDelegate. This allows -us to listen for events when the header is resized. And we can implement a very simple way to -update the header view contents when the layout is changed. - - - -#### Swift - -~~~ swift -// MDCFlexibleHeaderViewLayoutDelegate - func flexibleHeaderViewController(flexibleHeaderViewController: MDCFlexibleHeaderViewController, - flexibleHeaderViewFrameDidChange headerView: MDCFlexibleHeaderView) { - customHeaderView.frame = headerView.contentView!.bounds - } -~~~ - - - - - - -- - - - -## Material Collection Views - -In previous examples, we used a scroll view rather than any content. In Material Components, there -is a sophisticated collection view addition that we've added which implements many of the Material -design styled layout and transitions. - -In order for styling to be done in a compartmentalized way, MDCCollectionViewController has an -abstraction called MDCCollectionViewModel. The model is an implementation of the -UICollectionViewDataSource. The model implements storage for a model objects that contain the -data for rendering the cells. - - -Apps can use the MDCCollectionViewController without the model API and still get the same styling, -but there is more plumbing that needs to be implemented. In some cases, working without the model -API is required if more fine grained control is required. - -The following steps will use the MDCCollectionViewModel to build up a simple collection view - -### Using the MDCCollectionViewController - -MDCCollectionViewController is a subclass of the UICollectionViewController and can be used in place -of a UIViewController base class. Using this is the easiest way to get started with Material collection views. - - - -#### Swift - -~~~ swift -class MainViewController : MDCCollectionViewController, MDCAppBarParenting { - -} -~~~ - - - -The collection view will replace the scroll view that was in the App Bar example earlier. In place of -the scroll view, a model is initialized: - - - -#### Swift - -~~~ swift -override func viewDidLoad() { - super.viewDidLoad() - - // Remove initialization of the scrollView. - - // Initialize the collection view. - let thisCollectionView = collectionView as UICollectionView! - thisCollectionView.mdc_styleController.cellStyle = .Grouped - thisCollectionView.frame = view.bounds - thisCollectionView.delegate = self - view.addSubview(collectionView!) - - // Setup the collection view as the scroll view for the App Bar header to track. - headerViewController?.headerView.trackingScrollView = thisCollectionView - - // Setup the model. - model = MDCCollectionViewModel(delegate: self) - model.setHeader(MDCCellModel.objectWithHeader("Inbox".blackout()), forSection: 0) - // Add 100 rows. - for 0..100 { - model.addItem(MDCCellModel.objectWithTitle("Hello there".blackout()), toSection: 0) - } - - // ... - MDCAppBarAddViews(self) -} -~~~ - - - -Unlike with the simple example with the App Bar above, the UICollectionViewController is the -delegate for the UICollectionView, we cannot just do a simple delegate assignment like we did -with the App Bar to forward scroll view events. - -Instead, we need to manually forward four additional UIScrollViewDelegate methods to -the MDCFlexibleHeaderView to preserve our collapsing header functionality. - - - -#### Swift - -~~~ swift - - override func childViewControllerForStatusBarHidden() -> UIViewController { - return headerViewController! - } - - // UIScrollViewDelegate - override func scrollViewDidScroll(scrollView: UIScrollView) { - headerViewController?.headerView.trackingScrollViewDidScroll() - } - - override func scrollViewDidEndDecelerating(scrollView: UIScrollView) { - headerViewController?.headerView.trackingScrollViewDidEndDecelerating() - } - - override func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) { - headerViewController?.headerView.trackingScrollViewDidEndDraggingWillDecelerate(decelerate) - } - - override func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { - headerViewController?.headerView.trackingScrollViewWillEndDraggingWithVelocity(velocity, targetContentOffset: targetContentOffset) - } -~~~ - - - -If these are omitted, the header will continue to work, but there will not be any collapsing and expanding behavior. - -If you prefer to use not use the model, see the Material Collection View component documentation for -more detail. This component also handles editing and moving of rows, which is also covered in the component documentation. - -### Handling taps on a row - -The final step is to handle taps on a row. It is very similar to the normal UICollectionViewDelegate -way of doing things - - - -#### Swift - -~~~ swift -override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { - super.collectionView(collectionView, didSelectItemAtIndexPath: indexPath) - let vc = ViewControllerWithCollections(nibName: nil, bundle: nil) - self.navigationController?.pushViewController(vc, animated: true) -} -~~~ - - - -### Bonus: Custom cells - -[TODO] - - -- - - - -## Material Buttons - -Material Components has several styled buttons depending on where they are placed. For Floating Action Buttons (spec), -the Material Buttons component has a class called MDCShapedButton that allows for creating a simple -rounded button that contains an icon. - -To add this to the Abstract app, the button should be initialized at viewDidLoad and then -added to the view controller's root view so it stays floated in the corner. - - - -#### Swift - -~~~ swift -override func viewDidLoad() { - - button = MDCFloatingButton(shape: .Default) - button!.sizeToFit() - var buttonFrame = button!.frame - buttonFrame.origin.x = view.bounds.size.width - buttonFrame.size.width - 24 - buttonFrame.origin.y = view.bounds.size.height - buttonFrame.size.height - 24 - button!.setBackgroundColor(UIColor(red: 1.0, green: 0.562, blue: 0, alpha:1.0), forState: .Normal) - button!.frame = buttonFrame - button!.setImage(UIImage(named: "ic_add")?.imageWithRenderingMode(.AlwaysTemplate), forState: .Normal) - button!.tintColor = UIColor.whiteColor() - button!.alpha = 0 - button!.addTarget(self, - action: #selector(ViewControllerWithCollections.add(_:)), - forControlEvents: .TouchUpInside) -} - -override func viewDidAppear { - super.viewDidAppear() - weak var weakButton = button - UIView.animateWithDuration(0.2, animations: { - weakButton!.alpha = 1 - }) -} - -~~~ - - - -When the floating action button is tapped on, the `add:` selector is called and it will add a -row to the collection view. Collection views can animate any changes using MDCCollectionViewModel.performBatchOperations - - - -#### Swift - -~~~ swift -func add(target: AnyObject) { - weak var weakModel = model - model.performBatchUpdates({ - weakModel?.insertItem( - MDCCellModel.objectWithTitle("I got added".blackout(), - subtitle: "More text".blackout()), - atIndexPath: NSIndexPath(forRow: 0, inSection: 0)) - }, - withCollectionView: collectionView, - completion: nil) -} -~~~ - - - - -- - - - -## Bonus : Proper view controller transitions - -One odd artifact with this app is the floating action button animates with the rest of the view -controller. The intention of the design is it floats on top of all the views, but for convenience -we've added it to the view controller. - -Rather, when a view controller transition happens, the floating action button should be removed -from the view hierarchy and animated in when the view controller appears. - -Fading in to the view controller: - - - -#### Swift - -~~~ swift - override func viewDidAppear(animated: Bool) { - weak var weakButton = button - UIView.animateWithDuration(0.2, animations: { - weakButton!.alpha = 1 - }) - } -~~~ - - - -Fading out when the view controller changes, replace the `collectionView:didSelectItemAtIndexPath` -to pushViewController with an animation to FAB. - - - -#### Swift - -~~~ swift -override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { - super.collectionView(collectionView, didSelectItemAtIndexPath: indexPath) - let vc = ViewControllerWithCollections(nibName: nil, bundle: nil) - - weak var weakButton = button - weak var weakSelf = self - UIView.animateWithDuration(0.2, animations: { - weakButton!.alpha = 0 - }) { (completed) in - weakSelf?.navigationController?.pushViewController(vc, animated: true) - } -} - -~~~ - - - -- - - - -## Next steps - -This tutorial has taken you through implementing a basic Material Design style app with some of our -components. There are a lot more components that are not covered which are covered in our component -documentation. - -Also see our examples and catalog apps that are in the project to show how to use some of the more -advanced features. - - -- [Read the Component Documentation](/components/) - - -- [Stack Overflow "material-components-ios"](http://stackoverflow.com/questions/tagged/material-components-ios) - +- [Build environment](/howto/build-env/) + + - -- - - - -## Sample Code - -- [**Pesto** - A simple recipe app, incorporating a flexible header, floating action button, and collections. - ](https://github.com/google/material-components-ios/tree/master/demos/Pesto) - - -- [**Shrine** - A demo shopping app, incorporating a flexible header, custom typography, and collections. - ](https://github.com/google/material-components-ios/tree/master/demos/Shrine) - - diff --git a/howto/build-env/README.md b/howto/build-env/README.md new file mode 100644 index 00000000000..ec12f15ff79 --- /dev/null +++ b/howto/build-env/README.md @@ -0,0 +1,49 @@ +--- +title: "Build environment" +layout: landing +section: howto +--- + +# Build environment + +Material Components iOS builds with the standard open-source iOS toolchain: +Xcode and CocoaPods. However, there are certain settings that you can use to +maximize compatibility with our source. + +- - - + +## Xcode warning settings + +Deprecation warnings are an important part of how we communicate upcoming +changes to the library; they are enabled by default in typical Xcode and +CocoaPods projects. + +If your project doesn't already specify these warnings, include the following +flags in your build: + + -Wdeprecated + -Wdeprecated-declarations + +If you treat warnings as errors (`-Werror` or "Treat warnings as errors" in +Xcode), then you should exclude deprecation warnings from being treated as +errors to allow the normal deprecation process to work: + + -Wno-error=deprecated + -Wno-error=deprecated-declarations + +## Supported versions + +### Xcode + +The core team uses **Xcode 7.2.1**, which corresponds to Swift 2.1. Once Swift +has stablized we will track particular Swift versions independently of Xcode +versions. + +### iOS + +All components are expected to support **iOS 7.0 and above**. + +### Ruby + +The core team uses **Ruby 2.0.0**. Newer versions of ruby have subtle modifications that affect our +`Podfile.lock` output. diff --git a/howto/tutorial/README.md b/howto/tutorial/README.md new file mode 100644 index 00000000000..0b9a4b26443 --- /dev/null +++ b/howto/tutorial/README.md @@ -0,0 +1,716 @@ +--- +title: "Material Components Development Guide" +layout: landing +section: howto +--- + +# Material Components Development Guide + +Material Components for iOS is a set of components that help iOS app developers build Material Design apps. These are the same components Google uses to build apps like Google Maps, Calendar, Chrome and many more. + +Individually, the components bring Material Design principles to common UI elements and behaviors, but tailored for iOS. Our team has taken care to design the APIs to feel natural on iOS. + +Our goal is to make implementing Material Design as easy as possible. The components are easy to assemble and be used piecemeal. + +This tutorial will take you through building an example app called Abstractor and show some of the neat features and benefits of using our components to build your app. In order to get through this tutorial, Swift and iOS development knowledge is required. + + +- - - + +## Getting Started + +### Tutorial Setup + +Material Components for iOS can be integrated like any other shared code library on iOS. The preferred method of integration is through CocoaPods. + +To help get started quickly, `git clone` this skeleton new project which the rest of the tutorial will use. + +~~~ bash +git clone https://github.com/google/material-components-ios-example/ +~~~ + +This project is similar to a new project created using Xcode's new project template except with a small number of changes: + +1. Removes the Main.storyboard and references to it in favor of programmatically creating the UI. +2. Adds a bridging header (BridgingHeader.h) and the Xcode configuration for it. +3. Adds a simple String class extension for creating sample text. +4. Adds two icons (search and add) from [Material Icons](https://github.io/google/material-icons) +5. Creates a new MainViewController.swift. + +#### CocoaPods + +The first step is to add Material Components through CocoaPods. The [Material Components quickstart](https://materialcomponents.org/) has detailed instructions, but in short, create a Podfile in the root of the example with the following contents: + +~~~ ruby +target 'Abstractor' do + pod 'MaterialComponents' +end +~~~ + +Run `pod install` in that directory and open up `Abstractor.xcworkspace`. + +#### Bridging for Swift + +Material Components is written in Objective-C and is completely usable from Swift. In order to make the classes visible to Swift, the headers need to be added to the `BridgingHeaders.h`. Open up `BridgingHeaders.h` and add the following lines. + + +~~~ objc +#import "MaterialAppBar.h" +#import "MaterialButtons.h" +#import "MaterialCollections.h" +#import "MaterialFlexibleHeader.h" +~~~ + +#### Building and running the app + +The Abstractor project should be now set up and ready to run. Building and running the project should show you a fairly boring app with a yellow background with no contents. That is our skeleton project the rest of the tutorial will use. + +**TODO: Insert image of the app.** + + +- - - + +## Material Headers + +Headers exist in nearly all apps we see to provide framing and navigation. The header and scrolling behavior is well defined in the [Material Design Guidelines](https://www.google.com/design/spec/TODO) but it is tricky to get right. + +Material Components for iOS provides both a higher level and lower level implementation that allow developers to easily customize the right behavior for the view controller they are building. Both implementations provide a responsive header that can expand and contract in response to scrolling behaviors to maximize the content area or show high level information to the user. + +The [App Bar](https://materialcomponents.org/components/appbar/) is the first way to implement a response header. It uses the familiar UINavigationItem properties of a UIViewController to derive the contents of the header view. + +The [Flexible Header](https://materialcomponents.org/components/flexible-header/) is the second way to implement the responsive header. This component is what the App Bar is built on and is perfect if the developer would like fine grained control over it's contents and behavior. For example, the flexible header can contain a fully custom view that would respond to size changes as the user scrolled. + +If you are used to UINavigationController's UINavigationBar, the fundamental different in design is that your UIViewController has the actual header bar in it's view hierarchy. This contrasts with UINavigationBar being part of UINavigationController's view hierarchy. The different view hierarchy allows gives developers more flexibility when animating between view controllers or customizing unique behaviors when the size of the header changes. + +- - - + +## Starting with App Bar + +In the Abstractor project, there is a view controller already created called MainViewController. To start, we will add a scroll view to the MainViewController since the AppBar works best with a scroll view. + +Modify the MainViewController.swift by adding a UIScrollView. Make the following changes to the MainViewController: + + + +#### Swift + +~~~ swift +class MainViewController : UIViewController { + var scrollView: UIScrollView? + + override func viewDidLoad() { + super.viewDidLoad() + + // Create and initialize a blank scroll view. + scrollView = UIScrollView(frame: view.bounds) + scrollView!.contentSize = CGSize(width: view.bounds.size.width, height: 1000) + scrollView!.backgroundColor = UIColor.whiteColor() + view.addSubview(scrollView!) + } +} +~~~ + + +This snippet is basic UIKit code to create a scroll view, setting the size of the scroll view to be at least 1000 points tall so it will scroll further off screen. + +To actually add the App Bar, we need to give the view controller a protocol to conform to, and override the initializers: + + + +#### Swift + +~~~ swift +class MainViewController : UIViewController, MDCAppBarParenting { + var scrollView: UIScrollView? + + // -- start MDCAppBarParenting + var headerStackView: MDCHeaderStackView? + var navigationBar: MDCNavigationBar? + var headerViewController: MDCFlexibleHeaderViewController? + + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + MDCAppBarPrepareParent(self) + } + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + MDCAppBarPrepareParent(self) + } + // -- end MDCAppBarParenting + + override func viewDidLoad() { + super.viewDidLoad() + + // Create and initialize a blank scroll view. + scrollView = UIScrollView(frame: view.bounds) + scrollView!.contentSize = CGSize(width: view.bounds.size.width, height: 1000) + scrollView!.backgroundColor = UIColor.whiteColor() + view.addSubview(scrollView!) + + // -- start MDCAppBarParenting + MDCAppBarAddViews(self) + // -- end MDCAppBarParenting + } +} + +~~~ + + + +At this point, the app will add a grey header bar at the top of the view, but the scroll view will live under the header bar. You can observe this by scrolling the the view and seeing the scroll indicator go below the grey header bar. + +**TODO: Add image of the grey header** + +The MDCFlexibleHeaderViewController that was now exposed as a property of our view controller doesn't know about the scroll view and therefore it cannot adjust any scroll view insets the scroll view needs to render below the bar. To rectify this, simply tell the headerView in the MDCFlexibleHeaderViewController about the scroll view in viewDidLoad(): + + + +#### Swift + +~~~ swift +class MainViewController : UIViewController, MDCAppBarParenting { + // ... + override func viewDidLoad() { + // ... + + // Connect scroll view with the header view controller. + headerViewController?.headerView.trackingScrollView = contentScrollView + headerViewController?.headerView.behavior = .EnabledWithStatusBar + scrollView.delegate = headerViewController + + } +~~~ + + + +Now the scroll view correctly aligns to the bottom of the header bar. Notice that `headerView` had a property called `behavior` which is set to EnabledWithStatusBar. This behavior controls how the header view reacts to scrolling. When `Enabled`, the header will collapse to maximize the content area. Developers can choose whether the status bar should also be hidden. + +**TODO: Add animation of the grey header collapsing.** + +The status bar is not hiding yet, and the reason is by default UIViewController does not hide the status bar. In order for `headerViewController` to assume control of the status bar, override the method `childViewControllerForStatusBarHidden` to use the headerViewController as the childViewController (see the FlexibleHeader component documentation for more details): + + + +#### Swift + +~~~ swift +class MainViewController : UIViewController, MDCAppBarParenting { + // ... + override func childViewControllerForStatusBarHidden() -> UIViewController { + return headerViewController! + } +} +~~~ + + + +**TODO: Add animation of the status bar correctly collapsing.** + +To complete the integration, let's set a proper color and some items on to the header bar. + +To set the color of the header, we can directly manipulate the headerView in viewDidLoad: + + + +#### Swift + +~~~ swift +class MainViewController : UIViewController, MDCAppBarParenting { + // ... + override func viewDidLoad() { + // ... + + // Set color using UIColor extension in UIColorAbstractor.swift + headerViewController!.headerView.backgroundColor = UIColor.materialOrange700() + headerViewController!.headerView.tintColor = UIColor.whiteColor().colorWithAlphaComponent(0.87) + } + + // Set the status bar to white. + override func preferredStatusBarStyle() -> UIStatusBarStyle { + return .LightContent + } +} +~~~ + + + +And finally put some buttons in to the header bar. + + + +#### Swift + +~~~ swift +class MainViewController : UIViewController, MDCAppBarParenting { + override func viewDidLoad() { + // ... + + // Set up UINavigationItems + navigationItem.title = "Abstractor".blackout() + navigationItem.rightBarButtonItem = UIBarButtonItem( + image: UIImage(named: "ic_search")?.imageWithRenderingMode(.AlwaysTemplate), + style: .Plain, + target: self, + action: #selector(MainViewController.search(_:))) + + // Last step in viewDidLoad + MDCAppBarAddViews(self) + } + + // Implement the callback method for the search button. + func search(target: AnyObject) { + } +} +~~~ + + + +And there you have a responsive header that reacts to the scroll view and collapses to maximize +the content. + +- - - + +## Flexible Header + +One advantage of the App Bar component is it's compatibility with UINavigationItem. If developers would like to customize the actual contents inside the header, they need to look at the powerful Flexible Header component. + +Observant developers would already have noticed that App Bar uses Flexible Header to create the behavior. Imagine instead that we would like to lock the title to the bottom of the header but keep search button attached to the top. + + + +### Create a custom header view + +The first thing to do is to create a custom view that will be placed inside the FlexibleHeader. This can be in conjunction with the App Bar or completed without. Notice in the previous steps, another property we added is the `MDCNavigationBar` that provides the logic to layout the single line button bar. + + + +#### Swift + +~~~ swift +class CustomHeaderView : UIView { + var titleLabel = UILabel() + var iconView = UIImageView() + + override init(frame: CGRect) { + super.init(frame: frame) + + let icon = UIImage(named: "ic_search")!.imageWithRenderingMode(.AlwaysTemplate) + iconView.image = icon + iconView.tintColor = UIColor.whiteColor().colorWithAlphaComponent(0.7) + titleLabel.textColor = UIColor.whiteColor().colorWithAlphaComponent(0.7) + + self.addSubview(iconView) + self.addSubview(titleLabel) + } + + required init?(coder: NSCoder) { + super.init(coder: coder) + // not implementated + } + + override func layoutSubviews() { + super.layoutSubviews() + // Some fancying arithmetic : TODO replace with autolayout + titleLabel.frame = CGRect(x: 16, y: self.bounds.size.height - 40, width: 128, height: 24) + iconView.frame = CGRect(x: self.bounds.size.width - 24 - 16, y: 20 + 16, width: 24, height: 24) + } +} +~~~ + + + +This header view creates a similar view as the one in the App Bar, for simplicity, but the layoutSubviews +logic locks the titleLabel to the bottom of the header while the iconView stays locks to the top right. + +### Adding Flexible Header + +Integrating the Flexible Header is about the same amount of work as App Bar: + + + +#### Swift + +~~~ swift +class MainViewController : UIViewController, MDCFlexibleHeaderViewLayoutDelegate { + var headerViewController: MDCFlexibleHeaderViewController + var customHeaderView = CustomHeaderView() + + //... +} +~~~ + + + +Override the initialize to add the MDCFlexibleHeaderViewController to the view controller +as a childViewController replacing the previous `MDCAppBArPrepareParent`: + + + +#### Swift + +~~~ swift +override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { + headerViewController = MDCFlexibleHeaderViewController() + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + self.addChildViewController(headerViewController) + } + + required init?(coder aDecoder: NSCoder) { + headerViewController = MDCFlexibleHeaderViewController() + super.init(coder: aDecoder) + self.addChildViewController(headerViewController) + } +~~~ + + + +Initialize the customHeaderView and connect the headerViewController's headerView to +the UIViewController's hierarchy. This replaces `MDCAppBarAddViews` was doing +except this is not creating any observing of the UINavigationItem. + + + +#### Swift + +~~~ swift +override func viewDidLoad() { + super.viewDidLoad() + + // ... + + // Remove setting up UINavigationItem and MDCAppBarAddViews(self). + + // Initialize the customHeaderView + var headerViewInitialFrame = headerViewController.headerView.contentView!.bounds + headerViewInitialFrame.size.height = 56 + 20 + customHeaderView.frame = headerViewInitialFrame + headerViewController.headerView.contentView?.addSubview(customHeaderView) + + // Connect headerViewController to this viewController's view hierarchy + view.addSubview(headerViewController!.headerView) + headerViewController.didMoveToParentViewController(self) + headerViewController.layoutDelegate = self + +} +~~~ + + + +One final thing that is added is to add this viewController as the layoutDelegate. This allows +us to listen for events when the header is resized. And we can implement a very simple way to +update the header view contents when the layout is changed. + + + +#### Swift + +~~~ swift +// MDCFlexibleHeaderViewLayoutDelegate + func flexibleHeaderViewController(flexibleHeaderViewController: MDCFlexibleHeaderViewController, + flexibleHeaderViewFrameDidChange headerView: MDCFlexibleHeaderView) { + customHeaderView.frame = headerView.contentView!.bounds + } +~~~ + + + + + + +- - - + +## Material Collection Views + +In previous examples, we used a scroll view rather than any content. In Material Components, there +is a sophisticated collection view addition that we've added which implements many of the Material +design styled layout and transitions. + +In order for styling to be done in a compartmentalized way, MDCCollectionViewController has an +abstraction called MDCCollectionViewModel. The model is an implementation of the +UICollectionViewDataSource. The model implements storage for a model objects that contain the +data for rendering the cells. + + +Apps can use the MDCCollectionViewController without the model API and still get the same styling, +but there is more plumbing that needs to be implemented. In some cases, working without the model +API is required if more fine grained control is required. + +The following steps will use the MDCCollectionViewModel to build up a simple collection view + +### Using the MDCCollectionViewController + +MDCCollectionViewController is a subclass of the UICollectionViewController and can be used in place +of a UIViewController base class. Using this is the easiest way to get started with Material collection views. + + + +#### Swift + +~~~ swift +class MainViewController : MDCCollectionViewController, MDCAppBarParenting { + +} +~~~ + + + +The collection view will replace the scroll view that was in the App Bar example earlier. In place of +the scroll view, a model is initialized: + + + +#### Swift + +~~~ swift +override func viewDidLoad() { + super.viewDidLoad() + + // Remove initialization of the scrollView. + + // Initialize the collection view. + let thisCollectionView = collectionView as UICollectionView! + thisCollectionView.mdc_styleController.cellStyle = .Grouped + thisCollectionView.frame = view.bounds + thisCollectionView.delegate = self + view.addSubview(collectionView!) + + // Setup the collection view as the scroll view for the App Bar header to track. + headerViewController?.headerView.trackingScrollView = thisCollectionView + + // Setup the model. + model = MDCCollectionViewModel(delegate: self) + model.setHeader(MDCCellModel.objectWithHeader("Inbox".blackout()), forSection: 0) + // Add 100 rows. + for 0..100 { + model.addItem(MDCCellModel.objectWithTitle("Hello there".blackout()), toSection: 0) + } + + // ... + MDCAppBarAddViews(self) +} +~~~ + + + +Unlike with the simple example with the App Bar above, the UICollectionViewController is the +delegate for the UICollectionView, we cannot just do a simple delegate assignment like we did +with the App Bar to forward scroll view events. + +Instead, we need to manually forward four additional UIScrollViewDelegate methods to +the MDCFlexibleHeaderView to preserve our collapsing header functionality. + + + +#### Swift + +~~~ swift + + override func childViewControllerForStatusBarHidden() -> UIViewController { + return headerViewController! + } + + // UIScrollViewDelegate + override func scrollViewDidScroll(scrollView: UIScrollView) { + headerViewController?.headerView.trackingScrollViewDidScroll() + } + + override func scrollViewDidEndDecelerating(scrollView: UIScrollView) { + headerViewController?.headerView.trackingScrollViewDidEndDecelerating() + } + + override func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) { + headerViewController?.headerView.trackingScrollViewDidEndDraggingWillDecelerate(decelerate) + } + + override func scrollViewWillEndDragging(scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer) { + headerViewController?.headerView.trackingScrollViewWillEndDraggingWithVelocity(velocity, targetContentOffset: targetContentOffset) + } +~~~ + + + +If these are omitted, the header will continue to work, but there will not be any collapsing and expanding behavior. + +If you prefer to use not use the model, see the Material Collection View component documentation for +more detail. This component also handles editing and moving of rows, which is also covered in the component documentation. + +### Handling taps on a row + +The final step is to handle taps on a row. It is very similar to the normal UICollectionViewDelegate +way of doing things + + + +#### Swift + +~~~ swift +override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { + super.collectionView(collectionView, didSelectItemAtIndexPath: indexPath) + let vc = ViewControllerWithCollections(nibName: nil, bundle: nil) + self.navigationController?.pushViewController(vc, animated: true) +} +~~~ + + + +### Bonus: Custom cells + +[TODO] + + +- - - + +## Material Buttons + +Material Components has several styled buttons depending on where they are placed. For Floating Action Buttons (spec), +the Material Buttons component has a class called MDCShapedButton that allows for creating a simple +rounded button that contains an icon. + +To add this to the Abstract app, the button should be initialized at viewDidLoad and then +added to the view controller's root view so it stays floated in the corner. + + + +#### Swift + +~~~ swift +override func viewDidLoad() { + + button = MDCFloatingButton(shape: .Default) + button!.sizeToFit() + var buttonFrame = button!.frame + buttonFrame.origin.x = view.bounds.size.width - buttonFrame.size.width - 24 + buttonFrame.origin.y = view.bounds.size.height - buttonFrame.size.height - 24 + button!.setBackgroundColor(UIColor(red: 1.0, green: 0.562, blue: 0, alpha:1.0), forState: .Normal) + button!.frame = buttonFrame + button!.setImage(UIImage(named: "ic_add")?.imageWithRenderingMode(.AlwaysTemplate), forState: .Normal) + button!.tintColor = UIColor.whiteColor() + button!.alpha = 0 + button!.addTarget(self, + action: #selector(ViewControllerWithCollections.add(_:)), + forControlEvents: .TouchUpInside) +} + +override func viewDidAppear { + super.viewDidAppear() + weak var weakButton = button + UIView.animateWithDuration(0.2, animations: { + weakButton!.alpha = 1 + }) +} + +~~~ + + + +When the floating action button is tapped on, the `add:` selector is called and it will add a +row to the collection view. Collection views can animate any changes using MDCCollectionViewModel.performBatchOperations + + + +#### Swift + +~~~ swift +func add(target: AnyObject) { + weak var weakModel = model + model.performBatchUpdates({ + weakModel?.insertItem( + MDCCellModel.objectWithTitle("I got added".blackout(), + subtitle: "More text".blackout()), + atIndexPath: NSIndexPath(forRow: 0, inSection: 0)) + }, + withCollectionView: collectionView, + completion: nil) +} +~~~ + + + + +- - - + +## Bonus : Proper view controller transitions + +One odd artifact with this app is the floating action button animates with the rest of the view +controller. The intention of the design is it floats on top of all the views, but for convenience +we've added it to the view controller. + +Rather, when a view controller transition happens, the floating action button should be removed +from the view hierarchy and animated in when the view controller appears. + +Fading in to the view controller: + + + +#### Swift + +~~~ swift + override func viewDidAppear(animated: Bool) { + weak var weakButton = button + UIView.animateWithDuration(0.2, animations: { + weakButton!.alpha = 1 + }) + } +~~~ + + + +Fading out when the view controller changes, replace the `collectionView:didSelectItemAtIndexPath` +to pushViewController with an animation to FAB. + + + +#### Swift + +~~~ swift +override func collectionView(collectionView: UICollectionView, didSelectItemAtIndexPath indexPath: NSIndexPath) { + super.collectionView(collectionView, didSelectItemAtIndexPath: indexPath) + let vc = ViewControllerWithCollections(nibName: nil, bundle: nil) + + weak var weakButton = button + weak var weakSelf = self + UIView.animateWithDuration(0.2, animations: { + weakButton!.alpha = 0 + }) { (completed) in + weakSelf?.navigationController?.pushViewController(vc, animated: true) + } +} + +~~~ + + + +- - - + +## Next steps + +This tutorial has taken you through implementing a basic Material Design style app with some of our +components. There are a lot more components that are not covered which are covered in our component +documentation. + +Also see our examples and catalog apps that are in the project to show how to use some of the more +advanced features. + + +- [Read the Component Documentation](/components/) + + +- [Stack Overflow "material-components-ios"](http://stackoverflow.com/questions/tagged/material-components-ios) + + + +- - - + +## Sample Code + +- [**Pesto** + A simple recipe app, incorporating a flexible header, floating action button, and collections. + ](https://github.com/google/material-components-ios/tree/master/demos/Pesto) + + +- [**Shrine** + A demo shopping app, incorporating a flexible header, custom typography, and collections. + ](https://github.com/google/material-components-ios/tree/master/demos/Shrine) + + diff --git a/scripts/build-site.sh b/scripts/build-site.sh index bf773c1f885..85302602f79 100755 --- a/scripts/build-site.sh +++ b/scripts/build-site.sh @@ -63,7 +63,11 @@ fi # Checking pre-requsits for folders # Getting the link for github repository -GITHUB_REMOTE=`git remote -v | grep "fetch" | sed -e 's/origin.//' | sed -e 's/.(fetch)//' | grep -v "\t"` +if [ `git remote -v | grep "https://" | wc -l` -gt 0 ]; then + GITHUB_REMOTE=https://github.com/google/material-components-ios.git +else + GITHUB_REMOTE=git@github.com:google/material-components-ios.git +fi SITE_SOURCE_BRANCH="site-source" SITE_SOURCE_FOLDER=$SITE_SOURCE_BRANCH diff --git a/scripts/build_all_pod_projects b/scripts/build_all_pod_projects new file mode 100755 index 00000000000..6a5b7470f33 --- /dev/null +++ b/scripts/build_all_pod_projects @@ -0,0 +1,33 @@ +#!/bin/bash +# +# Copyright 2016-present Google Inc. 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. + +find . -name 'Podfile' | while read path; do + project_dir=$(dirname $path) + + echo "Entering $project_dir..." + + pushd $project_dir >> /dev/null + + pod install || { echo "Failed to run pod install."; exit 1; } + + workspace=$(find . -d 1 -name '*.xcworkspace') + scheme=$(xcodebuild build -workspace $workspace -list | grep "Information about workspace" | cut -d'"' -f2) + xcodebuild clean -workspace $workspace -scheme $scheme + xcodebuild build -workspace $workspace -scheme $scheme \ + || { echo "Failed to build $workspace."; exit 1; } + + popd >> /dev/null +done diff --git a/scripts/generate_jazzy_yamls.sh b/scripts/generate_jazzy_yamls.sh index 8ff97e8741b..017ea985b63 100755 --- a/scripts/generate_jazzy_yamls.sh +++ b/scripts/generate_jazzy_yamls.sh @@ -21,7 +21,8 @@ for d in components/*/README.md; do component=$(basename $folder) cat > "$folder/.jazzy.yaml" < [ + "components/private/Icons/icons/ic_check/src/MaterialIcons_ic_check.bundle/**/*.png", + "components/private/Icons/icons/ic_check/src/MaterialIcons_ic_check.bundle/*.xcassets" + ] + } + ss.dependency "#{Pathname.new(ss.name).dirname}/Base" + end + + iss.subspec "ic_check_circle" do |ss| + ss.public_header_files = "components/private/Icons/icons/ic_check_circle/src/*.h" + ss.source_files = "components/private/Icons/icons/ic_check_circle/src/*.{h,m}" + ss.header_mappings_dir = "components/private/Icons/icons/ic_check_circle/src/*" + ss.resource_bundles = { + "MaterialIcons_ic_check_circle" => [ + "components/private/Icons/icons/ic_check_circle/src/MaterialIcons_ic_check_circle.bundle/**/*.png", + "components/private/Icons/icons/ic_check_circle/src/MaterialIcons_ic_check_circle.bundle/*.xcassets" + ] + } + ss.dependency "#{Pathname.new(ss.name).dirname}/Base" + end + + iss.subspec "ic_chevron_right" do |ss| + ss.public_header_files = "components/private/Icons/icons/ic_chevron_right/src/*.h" + ss.source_files = "components/private/Icons/icons/ic_chevron_right/src/*.{h,m}" + ss.header_mappings_dir = "components/private/Icons/icons/ic_chevron_right/src/*" + ss.resource_bundles = { + "MaterialIcons_ic_chevron_right" => [ + "components/private/Icons/icons/ic_chevron_right/src/MaterialIcons_ic_chevron_right.bundle/**/*.png", + "components/private/Icons/icons/ic_chevron_right/src/MaterialIcons_ic_chevron_right.bundle/*.xcassets" + ] + } + ss.dependency "#{Pathname.new(ss.name).dirname}/Base" + end + + iss.subspec "ic_info" do |ss| + ss.public_header_files = "components/private/Icons/icons/ic_info/src/*.h" + ss.source_files = "components/private/Icons/icons/ic_info/src/*.{h,m}" + ss.header_mappings_dir = "components/private/Icons/icons/ic_info/src/*" + ss.resource_bundles = { + "MaterialIcons_ic_info" => [ + "components/private/Icons/icons/ic_info/src/MaterialIcons_ic_info.bundle/**/*.png", + "components/private/Icons/icons/ic_info/src/MaterialIcons_ic_info.bundle/*.xcassets" + ] + } + ss.dependency "#{Pathname.new(ss.name).dirname}/Base" + end + + iss.subspec "ic_radio_button_unchecked" do |ss| + ss.public_header_files = "components/private/Icons/icons/ic_radio_button_unchecked/src/*.h" + ss.source_files = "components/private/Icons/icons/ic_radio_button_unchecked/src/*.{h,m}" + ss.header_mappings_dir = "components/private/Icons/icons/ic_radio_button_unchecked/src/*" + ss.resource_bundles = { + "MaterialIcons_ic_radio_button_unchecked" => [ + "components/private/Icons/icons/ic_radio_button_unchecked/src/MaterialIcons_ic_radio_button_unchecked.bundle/**/*.png", + "components/private/Icons/icons/ic_radio_button_unchecked/src/MaterialIcons_ic_radio_button_unchecked.bundle/*.xcassets" + ] + } + ss.dependency "#{Pathname.new(ss.name).dirname}/Base" + end + + iss.subspec "ic_reorder" do |ss| + ss.public_header_files = "components/private/Icons/icons/ic_reorder/src/*.h" + ss.source_files = "components/private/Icons/icons/ic_reorder/src/*.{h,m}" + ss.header_mappings_dir = "components/private/Icons/icons/ic_reorder/src/*" + ss.resource_bundles = { + "MaterialIcons_ic_reorder" => [ + "components/private/Icons/icons/ic_reorder/src/MaterialIcons_ic_reorder.bundle/**/*.png", + "components/private/Icons/icons/ic_reorder/src/MaterialIcons_ic_reorder.bundle/*.xcassets" + ] + } + ss.dependency "#{Pathname.new(ss.name).dirname}/Base" + end end end diff --git a/scripts/release/cut b/scripts/release/cut index 8e58bd6b1a6..70d7bcf1a79 100755 --- a/scripts/release/cut +++ b/scripts/release/cut @@ -26,8 +26,8 @@ else fi # Prepare the CHANGELOG for updates. -if ! grep "## release-candidate" CHANGELOG.md >> /dev/null; then - echo -e "## release-candidate\n" | cat - CHANGELOG.md > /tmp/out && mv /tmp/out CHANGELOG.md +if ! grep "# release-candidate" CHANGELOG.md >> /dev/null; then + echo -e "# release-candidate TODO: Replace me with version number. \n" | cat - CHANGELOG.md > /tmp/out && mv /tmp/out CHANGELOG.md fi RELEASE_SHA=$(git merge-base --fork-point release-candidate origin/develop) diff --git a/scripts/release/release_checklist.txt b/scripts/release/release_checklist.txt new file mode 100644 index 00000000000..77175dffa71 --- /dev/null +++ b/scripts/release/release_checklist.txt @@ -0,0 +1,12 @@ +Release candidate. + +Checklist: + +- [ ] Ran `arc unit --everything`. +- [ ] Ran `scripts/build_all_pod_projects`. +- [ ] Ran `scripts/release/api_diff` and pasted the results into CHANGELOG.md. +- [ ] Ran `scripts/release/changes` and pasted the results into CHANGELOG.md. +- [ ] Visually inspected the API diff to ensure it accurately reflects the release's changes. +- [ ] Ran `scripts/release/diff components/*/src/` and visually inspected the changes. +- [ ] Ran `scripts/release/bump` with the new version number. +- [ ] Updated CHANGELOG.md's latest section header to match the release's version number. diff --git a/usage/build_configuration.md b/usage/build_configuration.md deleted file mode 100644 index c5904ba2386..00000000000 --- a/usage/build_configuration.md +++ /dev/null @@ -1,19 +0,0 @@ -# Build configuration - -These are recommendations for configuring build settings in a project using material-components-ios. - -## Deprecation warnings - -material-components-ios uses deprecation warnings to communicate when an API will eventually be -removed from the code base. - -Deprecation warnings are enabled by default in typical Xcode projects and CocoaPods Pods projects. -If you need to explicitly enable these warnings in your project: - - -Wdeprecated - -Wdeprecated-declarations - -We encourage projects treating warnings as errors to disable deprecation errors: - - -Wno-error=deprecated - -Wno-error=deprecated-declarations