├── .gitignore ├── cover.jpg ├── chapter2 ├── 2.1.png ├── 2.2.png ├── 2.3.png ├── 2.4.png ├── 2.5.png ├── 2.6.png ├── 2.7.png ├── 2.8.png ├── 2.9.png ├── 2.10.png ├── 2.11.png ├── 2.12.png ├── summary.md ├── the-Backing-Image.md ├── masktobounds.md ├── contentscenter.md ├── contentscale.md ├── custom-drawing.md └── contentsrect.md ├── chapter4 ├── 4.1.png ├── 4.2.png ├── 4.3.png ├── 4.4.png ├── 4.5.png ├── 4.6.png ├── 4.7.png ├── 4.8.png ├── 4.9.png ├── 4.10.png ├── 4.11.png ├── 4.12.png ├── 4.13.png ├── 4.14.png ├── 4.15.png ├── 4.16.png ├── 4.17.png ├── 4.18.png ├── 4.19.png ├── 4.20.png ├── 4.21.png ├── summary.md ├── visual-effcts.md ├── ayer-borders.md ├── the-shadowpath-property.md ├── rounded-corners.md ├── layer-masking.md ├── shadow-clipping.md ├── group-opacity.md ├── scaling-filters.md └── drop-shadows.md ├── chapter6 ├── 6.1.png ├── 6.2.png ├── 6.3.png ├── 6.4.png ├── 6.5.png ├── 6.6.png ├── 6.7.png ├── 6.8.png ├── 6.9.png ├── 6.10.png ├── 6.11.png ├── 6.12.png ├── 6.13.png ├── 6.14.png ├── 6.15.png ├── 6.16.png ├── 6.17.png ├── leading-and-kerning.md ├── specialized-layers.md ├── summarry.md ├── rounded-corners.md ├── creating-a-cgpath.md ├── caemitterlayer.md ├── avplayerlayer.md ├── cagradientLayer.md ├── rich-text.md ├── a-uilabel-replacement.md ├── cashapelayer.md ├── cascrollLayer.md ├── careplicatorLayer.md ├── catransformlayer.md └── caeagllayer.md ├── chapter1 ├── 1.1.jpeg ├── 1.2.jpeg ├── 1.3.jpeg ├── 1.4.jpeg ├── 1.5.jpeg ├── summary.md ├── caLayer.md ├── the-layer-tree.md ├── layer-capabilities.md ├── parallel-hierarchies.md ├── layers-and-trees.md └── working-with-layers.md ├── chapter13 ├── 13.1.png ├── 13.2.png ├── 13.3.png ├── 13.4.png ├── summary.md ├── efficient-drawing.md ├── software-drawing-vector-graphics.md ├── drawsasynchronously.md └── catiledLayer.md ├── chapter15 ├── 15.1.png ├── layer-performance.md ├── summary.md ├── blending-and-overdraw.md ├── inexplicit-drawing-text.md ├── offscreen-rendering.md └── dirty-rectangles.md ├── chapter3 ├── 3.1.jpeg ├── 3.10.jpeg ├── 3.2.jpeg ├── 3.3.jpeg ├── 3.4.jpeg ├── 3.5.jpeg ├── 3.6.jpeg ├── 3.7.jpeg ├── 3.8.jpeg ├── 3.9.jpeg ├── layer-geometry.md ├── summary.md ├── flipped-geometry.md ├── automatic-layout.md ├── layout.md ├── the-z-axis.md ├── coordinate-systens.md ├── hit-testing.md └── anchor.md ├── chapter5 ├── 5.1.jpeg ├── 5.10.jpeg ├── 5.11.jpeg ├── 5.12.jpeg ├── 5.13.jpeg ├── 5.14.jpeg ├── 5.15.jpeg ├── 5.16.jpeg ├── 5.17.jpeg ├── 5.18.jpeg ├── 5.19.jpeg ├── 5.2.jpeg ├── 5.20.jpeg ├── 5.21.jpeg ├── 5.22.jpeg ├── 5.23.jpeg ├── 5.3.jpeg ├── 5.4.jpeg ├── 5.5.jpeg ├── 5.6.jpeg ├── 5.7.jpeg ├── 5.8.jpeg ├── 5.9.jpeg ├── transforms.md ├── summary.md ├── the-vanishing-point.md ├── backfaces.md ├── touch-events.md ├── the-shear-transform.md ├── perspective-projection.md ├── combining-transforms.md ├── creating-a-cgaffinetransform.md ├── the-sublayertransfrom-property.md ├── layering-flattening.md ├── affine-fransforms.md └── light-and-shadow.md ├── chapter7 ├── 7.1.jpeg ├── 7.2.jpeg ├── 7.3.jpeg ├── 7.4.jpeg ├── implicit-animations.md ├── summary.md ├── completion-blocks.md ├── presentation-versus-model.md ├── transactions.md └── layer-actions.md ├── chapter8 ├── 8.1.jpeg ├── 8.2.jpeg ├── 8.3.jpeg ├── 8.4.jpeg ├── 8.5.jpeg ├── 8.6.jpeg ├── summary.md ├── explicit-animations.md ├── animation-groups.md └── changes-custom-transitions.md ├── chapter9 ├── 9.1.jpeg ├── 9.2.jpeg ├── 9.3.jpeg ├── summary.md ├── layer-time.md ├── hierarchical-time.md └── manual-animation.md ├── chapter10 ├── 10.1.jpeg ├── 10.2.jpeg ├── 10.3.jpeg ├── 10.4.jpeg ├── 10.5.jpeg ├── 10.6.jpeg ├── easing.md └── summary.md ├── chapter11 ├── 11.1.jpeg ├── 11.2.jpeg └── timer-based-animation.md ├── chapter12 ├── 12.1.jpeg ├── 12.10.jpeg ├── 12.11.jpeg ├── 12.12.jpeg ├── 12.13.jpeg ├── 12.2.jpeg ├── 12.3.jpeg ├── 12.4.jpeg ├── 12.5.jpeg ├── 12.6.jpeg ├── 12.7.jpeg ├── 12.8.jpeg ├── 12.9.jpeg ├── summary.md ├── tuning-for-speed.md ├── measure-dont-guess.md └── cpu-versus-gpu.md ├── chapter14 ├── 14.1.jpeg ├── 14.2.jpeg ├── 14.3.jpeg ├── 14.4.jpeg ├── 14.5.jpeg ├── summary.md ├── image-ioi.md └── caching.md ├── README.md ├── SUMMARY.md └── 目录.md /.gitignore: -------------------------------------------------------------------------------- 1 | /_book/ -------------------------------------------------------------------------------- /cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/cover.jpg -------------------------------------------------------------------------------- /chapter2/2.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter2/2.1.png -------------------------------------------------------------------------------- /chapter2/2.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter2/2.2.png -------------------------------------------------------------------------------- /chapter2/2.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter2/2.3.png -------------------------------------------------------------------------------- /chapter2/2.4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter2/2.4.png -------------------------------------------------------------------------------- /chapter2/2.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter2/2.5.png -------------------------------------------------------------------------------- /chapter2/2.6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter2/2.6.png -------------------------------------------------------------------------------- /chapter2/2.7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter2/2.7.png -------------------------------------------------------------------------------- /chapter2/2.8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter2/2.8.png -------------------------------------------------------------------------------- /chapter2/2.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter2/2.9.png -------------------------------------------------------------------------------- /chapter4/4.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter4/4.1.png -------------------------------------------------------------------------------- /chapter4/4.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter4/4.2.png -------------------------------------------------------------------------------- /chapter4/4.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter4/4.3.png -------------------------------------------------------------------------------- /chapter4/4.4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter4/4.4.png -------------------------------------------------------------------------------- /chapter4/4.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter4/4.5.png -------------------------------------------------------------------------------- /chapter4/4.6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter4/4.6.png -------------------------------------------------------------------------------- /chapter4/4.7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter4/4.7.png -------------------------------------------------------------------------------- /chapter4/4.8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter4/4.8.png -------------------------------------------------------------------------------- /chapter4/4.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter4/4.9.png -------------------------------------------------------------------------------- /chapter6/6.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter6/6.1.png -------------------------------------------------------------------------------- /chapter6/6.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter6/6.2.png -------------------------------------------------------------------------------- /chapter6/6.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter6/6.3.png -------------------------------------------------------------------------------- /chapter6/6.4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter6/6.4.png -------------------------------------------------------------------------------- /chapter6/6.5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter6/6.5.png -------------------------------------------------------------------------------- /chapter6/6.6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter6/6.6.png -------------------------------------------------------------------------------- /chapter6/6.7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter6/6.7.png -------------------------------------------------------------------------------- /chapter6/6.8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter6/6.8.png -------------------------------------------------------------------------------- /chapter6/6.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter6/6.9.png -------------------------------------------------------------------------------- /chapter1/1.1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter1/1.1.jpeg -------------------------------------------------------------------------------- /chapter1/1.2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter1/1.2.jpeg -------------------------------------------------------------------------------- /chapter1/1.3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter1/1.3.jpeg -------------------------------------------------------------------------------- /chapter1/1.4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter1/1.4.jpeg -------------------------------------------------------------------------------- /chapter1/1.5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter1/1.5.jpeg -------------------------------------------------------------------------------- /chapter13/13.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter13/13.1.png -------------------------------------------------------------------------------- /chapter13/13.2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter13/13.2.png -------------------------------------------------------------------------------- /chapter13/13.3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter13/13.3.png -------------------------------------------------------------------------------- /chapter13/13.4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter13/13.4.png -------------------------------------------------------------------------------- /chapter15/15.1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter15/15.1.png -------------------------------------------------------------------------------- /chapter2/2.10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter2/2.10.png -------------------------------------------------------------------------------- /chapter2/2.11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter2/2.11.png -------------------------------------------------------------------------------- /chapter2/2.12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter2/2.12.png -------------------------------------------------------------------------------- /chapter3/3.1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter3/3.1.jpeg -------------------------------------------------------------------------------- /chapter3/3.10.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter3/3.10.jpeg -------------------------------------------------------------------------------- /chapter3/3.2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter3/3.2.jpeg -------------------------------------------------------------------------------- /chapter3/3.3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter3/3.3.jpeg -------------------------------------------------------------------------------- /chapter3/3.4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter3/3.4.jpeg -------------------------------------------------------------------------------- /chapter3/3.5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter3/3.5.jpeg -------------------------------------------------------------------------------- /chapter3/3.6.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter3/3.6.jpeg -------------------------------------------------------------------------------- /chapter3/3.7.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter3/3.7.jpeg -------------------------------------------------------------------------------- /chapter3/3.8.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter3/3.8.jpeg -------------------------------------------------------------------------------- /chapter3/3.9.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter3/3.9.jpeg -------------------------------------------------------------------------------- /chapter4/4.10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter4/4.10.png -------------------------------------------------------------------------------- /chapter4/4.11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter4/4.11.png -------------------------------------------------------------------------------- /chapter4/4.12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter4/4.12.png -------------------------------------------------------------------------------- /chapter4/4.13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter4/4.13.png -------------------------------------------------------------------------------- /chapter4/4.14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter4/4.14.png -------------------------------------------------------------------------------- /chapter4/4.15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter4/4.15.png -------------------------------------------------------------------------------- /chapter4/4.16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter4/4.16.png -------------------------------------------------------------------------------- /chapter4/4.17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter4/4.17.png -------------------------------------------------------------------------------- /chapter4/4.18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter4/4.18.png -------------------------------------------------------------------------------- /chapter4/4.19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter4/4.19.png -------------------------------------------------------------------------------- /chapter4/4.20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter4/4.20.png -------------------------------------------------------------------------------- /chapter4/4.21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter4/4.21.png -------------------------------------------------------------------------------- /chapter5/5.1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter5/5.1.jpeg -------------------------------------------------------------------------------- /chapter5/5.10.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter5/5.10.jpeg -------------------------------------------------------------------------------- /chapter5/5.11.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter5/5.11.jpeg -------------------------------------------------------------------------------- /chapter5/5.12.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter5/5.12.jpeg -------------------------------------------------------------------------------- /chapter5/5.13.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter5/5.13.jpeg -------------------------------------------------------------------------------- /chapter5/5.14.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter5/5.14.jpeg -------------------------------------------------------------------------------- /chapter5/5.15.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter5/5.15.jpeg -------------------------------------------------------------------------------- /chapter5/5.16.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter5/5.16.jpeg -------------------------------------------------------------------------------- /chapter5/5.17.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter5/5.17.jpeg -------------------------------------------------------------------------------- /chapter5/5.18.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter5/5.18.jpeg -------------------------------------------------------------------------------- /chapter5/5.19.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter5/5.19.jpeg -------------------------------------------------------------------------------- /chapter5/5.2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter5/5.2.jpeg -------------------------------------------------------------------------------- /chapter5/5.20.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter5/5.20.jpeg -------------------------------------------------------------------------------- /chapter5/5.21.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter5/5.21.jpeg -------------------------------------------------------------------------------- /chapter5/5.22.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter5/5.22.jpeg -------------------------------------------------------------------------------- /chapter5/5.23.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter5/5.23.jpeg -------------------------------------------------------------------------------- /chapter5/5.3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter5/5.3.jpeg -------------------------------------------------------------------------------- /chapter5/5.4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter5/5.4.jpeg -------------------------------------------------------------------------------- /chapter5/5.5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter5/5.5.jpeg -------------------------------------------------------------------------------- /chapter5/5.6.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter5/5.6.jpeg -------------------------------------------------------------------------------- /chapter5/5.7.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter5/5.7.jpeg -------------------------------------------------------------------------------- /chapter5/5.8.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter5/5.8.jpeg -------------------------------------------------------------------------------- /chapter5/5.9.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter5/5.9.jpeg -------------------------------------------------------------------------------- /chapter6/6.10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter6/6.10.png -------------------------------------------------------------------------------- /chapter6/6.11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter6/6.11.png -------------------------------------------------------------------------------- /chapter6/6.12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter6/6.12.png -------------------------------------------------------------------------------- /chapter6/6.13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter6/6.13.png -------------------------------------------------------------------------------- /chapter6/6.14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter6/6.14.png -------------------------------------------------------------------------------- /chapter6/6.15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter6/6.15.png -------------------------------------------------------------------------------- /chapter6/6.16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter6/6.16.png -------------------------------------------------------------------------------- /chapter6/6.17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter6/6.17.png -------------------------------------------------------------------------------- /chapter7/7.1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter7/7.1.jpeg -------------------------------------------------------------------------------- /chapter7/7.2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter7/7.2.jpeg -------------------------------------------------------------------------------- /chapter7/7.3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter7/7.3.jpeg -------------------------------------------------------------------------------- /chapter7/7.4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter7/7.4.jpeg -------------------------------------------------------------------------------- /chapter8/8.1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter8/8.1.jpeg -------------------------------------------------------------------------------- /chapter8/8.2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter8/8.2.jpeg -------------------------------------------------------------------------------- /chapter8/8.3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter8/8.3.jpeg -------------------------------------------------------------------------------- /chapter8/8.4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter8/8.4.jpeg -------------------------------------------------------------------------------- /chapter8/8.5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter8/8.5.jpeg -------------------------------------------------------------------------------- /chapter8/8.6.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter8/8.6.jpeg -------------------------------------------------------------------------------- /chapter9/9.1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter9/9.1.jpeg -------------------------------------------------------------------------------- /chapter9/9.2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter9/9.2.jpeg -------------------------------------------------------------------------------- /chapter9/9.3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter9/9.3.jpeg -------------------------------------------------------------------------------- /chapter10/10.1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter10/10.1.jpeg -------------------------------------------------------------------------------- /chapter10/10.2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter10/10.2.jpeg -------------------------------------------------------------------------------- /chapter10/10.3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter10/10.3.jpeg -------------------------------------------------------------------------------- /chapter10/10.4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter10/10.4.jpeg -------------------------------------------------------------------------------- /chapter10/10.5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter10/10.5.jpeg -------------------------------------------------------------------------------- /chapter10/10.6.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter10/10.6.jpeg -------------------------------------------------------------------------------- /chapter11/11.1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter11/11.1.jpeg -------------------------------------------------------------------------------- /chapter11/11.2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter11/11.2.jpeg -------------------------------------------------------------------------------- /chapter12/12.1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter12/12.1.jpeg -------------------------------------------------------------------------------- /chapter12/12.10.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter12/12.10.jpeg -------------------------------------------------------------------------------- /chapter12/12.11.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter12/12.11.jpeg -------------------------------------------------------------------------------- /chapter12/12.12.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter12/12.12.jpeg -------------------------------------------------------------------------------- /chapter12/12.13.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter12/12.13.jpeg -------------------------------------------------------------------------------- /chapter12/12.2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter12/12.2.jpeg -------------------------------------------------------------------------------- /chapter12/12.3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter12/12.3.jpeg -------------------------------------------------------------------------------- /chapter12/12.4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter12/12.4.jpeg -------------------------------------------------------------------------------- /chapter12/12.5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter12/12.5.jpeg -------------------------------------------------------------------------------- /chapter12/12.6.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter12/12.6.jpeg -------------------------------------------------------------------------------- /chapter12/12.7.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter12/12.7.jpeg -------------------------------------------------------------------------------- /chapter12/12.8.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter12/12.8.jpeg -------------------------------------------------------------------------------- /chapter12/12.9.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter12/12.9.jpeg -------------------------------------------------------------------------------- /chapter14/14.1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter14/14.1.jpeg -------------------------------------------------------------------------------- /chapter14/14.2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter14/14.2.jpeg -------------------------------------------------------------------------------- /chapter14/14.3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter14/14.3.jpeg -------------------------------------------------------------------------------- /chapter14/14.4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter14/14.4.jpeg -------------------------------------------------------------------------------- /chapter14/14.5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZsIsMe/ios_core_animation_advanced_techniques/HEAD/chapter14/14.5.jpeg -------------------------------------------------------------------------------- /chapter9/summary.md: -------------------------------------------------------------------------------- 1 | ## 总结 2 | 3 | 在这一章,我们了解了`CAMediaTiming`协议,以及Core Animation用来操作时间控制动画的机制。在下一章,我们将要接触`缓冲`,另一个用来使动画更加真实的操作时间的技术。 4 | -------------------------------------------------------------------------------- /chapter13/summary.md: -------------------------------------------------------------------------------- 1 | # 总结 2 | 3 | 4 | 本章我们主要围绕用Core Graphics软件绘制讨论了一些性能挑战,然后探索了一些改进方法:比如提高绘制性能或者减少需要绘制的数量。 5 | 6 | 第14章,『图像IO』,我们将讨论图片的载入性能。 -------------------------------------------------------------------------------- /chapter4/summary.md: -------------------------------------------------------------------------------- 1 | # 总结 2 | 3 | 4 |     这一章介绍了一些可以通过代码应用到图层上的视觉效果,比如圆角,阴影和蒙板。我们也了解了拉伸过滤器和组透明。 5 | 6 | 在第五章,『变换』中,我们将会研究图层变化和3D转换 -------------------------------------------------------------------------------- /chapter14/summary.md: -------------------------------------------------------------------------------- 1 | ## 总结 2 | 3 |     在这章中,我们研究了和图片加载解压相关的性能问题,并延展了一系列解决方案。 4 | 5 |     在第15章“图层性能”中,我们将讨论和图层渲染和组合相关的性能问题。 -------------------------------------------------------------------------------- /chapter14/image-ioi.md: -------------------------------------------------------------------------------- 1 | # 图像IO 2 | 3 | >*潜伏期值得思考* - 凯文 帕萨特 4 | 5 |     在第13章“高效绘图”中,我们研究了和Core Graphics绘图相关的性能问题,以及如何修复。和绘图性能相关紧密相关的是图像性能。在这一章中,我们将研究如何优化从闪存驱动器或者网络中加载和显示图片。 -------------------------------------------------------------------------------- /chapter15/layer-performance.md: -------------------------------------------------------------------------------- 1 | #图层性能 2 | 3 | >要更快性能,也要做对正确的事情。 4 | >——Stephen R. Covey 5 | 6 |     在第14章『图像IO』讨论如何高效地载入和显示图像,通过视图来避免可能引起动画帧率下降的性能问题。在最后一章,我们将着重图层树本身,以发掘最好的性能。 7 | -------------------------------------------------------------------------------- /chapter12/summary.md: -------------------------------------------------------------------------------- 1 | # 总结 2 | 3 |     在这章中,我们学习了Core Animation是如何渲染,以及我们可能出现的瓶颈所在。你同样学习了如何使用Instruments来检测和修复性能问题。 4 | 5 |     在下三章中,我们将对每个普通程序的性能陷阱进行详细讨论,然后学习如何修复。 -------------------------------------------------------------------------------- /chapter3/layer-geometry.md: -------------------------------------------------------------------------------- 1 | # 图层几何学 2 | 3 | >*不熟悉几何学的人就不要来这里了* --柏拉图学院入口的签名 4 | 5 | 在第二章里面,我们介绍了图层背后的图片,和一些控制图层坐标和旋转的属性。在这一章中,我们将要看一看图层内部是如何根据父图层和兄弟图层来控制位置和尺寸的。另外我们也会涉及如何管理图层的几何结构,以及它是如何被自动调整和自动布局影响的。 6 | -------------------------------------------------------------------------------- /chapter8/summary.md: -------------------------------------------------------------------------------- 1 | # 总结 2 | 3 | 4 | 这一章中,我们涉及了属性动画(你可以对单独的图层属性动画有更加具体的控制),动画组(把多个属性动画组合成一个独立单元)以及过度(影响整个图层,可以用来对图层的任何内容做任何类型的动画,包括子图层的添加和移除)。 5 | 6 | 在第九章中,我们继续学习`CAMediaTiming`协议,来看一看Core Animation是怎样处理逝去的时间。 7 | -------------------------------------------------------------------------------- /chapter9/layer-time.md: -------------------------------------------------------------------------------- 1 | # 图层时间 2 | 3 | >*时间和空间最大的区别在于,时间不能被复用* -- 弗斯特梅里克 4 | 5 | 在上面两章中,我们探讨了可以用`CAAnimation`和它的子类实现的多种图层动画。动画的发生是需要持续一段时间的,所以*计时*对整个概念来说至关重要。在这一章中,我们来看看`CAMediaTiming`,看看Core Animation是如何跟踪时间的。 6 | -------------------------------------------------------------------------------- /chapter7/implicit-animations.md: -------------------------------------------------------------------------------- 1 | # 隐式动画 2 | 3 | 4 | >*按照我的意思去做,而不是我说的。* -- 埃德娜,辛普森 5 | 6 | 我们在第一部分讨论了Core Animation除了动画之外可以做到的任何事情。但是动画是Core Animation库一个非常显著的特性。这一章我们来看看它是怎么做到的。具体来说,我们先来讨论框架自动完成的*隐式动画*(除非你明确禁用了这个功能)。 7 | -------------------------------------------------------------------------------- /chapter10/easing.md: -------------------------------------------------------------------------------- 1 | # 缓冲 2 | 3 | >生活和艺术一样,最美的永远是曲线。 -- 爱德华布尔沃 - 利顿 4 | 5 | 在第九章“图层时间”中,我们讨论了动画时间和`CAMediaTiming`协议。现在我们来看一下另一个和时间相关的机制--所谓的*缓冲*。Core Animation使用缓冲来使动画移动更平滑更自然,而不是看起来的那种机械和人工,在这一章我们将要研究如何对你的动画控制和自定义缓冲曲线。 6 | -------------------------------------------------------------------------------- /chapter5/transforms.md: -------------------------------------------------------------------------------- 1 | # 变换 2 | 3 | 4 | >*很不幸,没人能告诉你母体是什么,你只能自己体会* -- 骇客帝国 5 | 6 | 在第四章“可视效果”中,我们研究了一些增强图层和它的内容显示效果的一些技术,在这一章中,我们将要研究可以用来对图层旋转,摆放或者扭曲的`CGAffineTransform`,以及可以将扁平物体转换成三维空间对象的`CATransform3D`(而不是仅仅对圆角矩形添加下沉阴影)。 -------------------------------------------------------------------------------- /chapter6/leading-and-kerning.md: -------------------------------------------------------------------------------- 1 | # 行距和字距 2 | 3 | 4 | 有必要提一下的是,由于绘制的实现机制不同(Core Text和WebKit),用`CATextLayer`渲染和用`UILabel`渲染出的文本行距和字距也不是不尽相同的。 5 | 6 | 二者的差异程度(由使用的字体和字符决定)总的来说挺小,但是如果你想正确的显示普通便签和`CATextLayer`就一定要记住这一点。 7 | 8 | -------------------------------------------------------------------------------- /chapter8/explicit-animations.md: -------------------------------------------------------------------------------- 1 | # 显式动画 2 | 3 | 4 | >如果想让事情变得顺利,只有靠自己 -- 夏尔·纪尧姆 5 | 6 | 上一章介绍了隐式动画的概念。隐式动画是在iOS平台创建动态用户界面的一种直接方式,也是UIKit动画机制的基础,不过它并不能涵盖所有的动画类型。在这一章中,我们将要研究一下*显式动画*,它能够对一些属性做指定的自定义动画,或者创建非线性动画,比如沿着任意一条曲线移动。 7 | -------------------------------------------------------------------------------- /chapter2/summary.md: -------------------------------------------------------------------------------- 1 | # 总结 2 | 3 | 4 |     本章介绍了寄宿图和一些相关的属性。你学到了如何显示和放置图片, 使用拼合技术来显示, 以及用CALayerDelegate和Core Graphics来绘制图层内容。 5 | 6 |     在第三章,"图层几何学"中,我们将会探讨一下图层的几何,观察他们是如何放置和改变相互的尺寸的。 7 | -------------------------------------------------------------------------------- /chapter6/specialized-layers.md: -------------------------------------------------------------------------------- 1 | # 专用图层 2 | >复杂的组织都是专门化的 3 | 4 | >Catharine R. Stimpson 5 | 6 | 到目前为止,我们已经探讨过`CALayer`类了,同时我们也了解到了一些非常有用的绘图和动画功能。但是Core Animation图层不仅仅能作用于图片和颜色而已。本章就会学习其他的一些图层类,进一步扩展使用Core Animation绘图的能力。 7 | 8 | -------------------------------------------------------------------------------- /chapter10/summary.md: -------------------------------------------------------------------------------- 1 | 2 | ##总结 3 | 在这一章中,我们了解了有关缓冲和`CAMediaTimingFunction`类,它可以允许我们创建自定义的缓冲函数来完善我们的动画,同样了解了如何用`CAKeyframeAnimation`来避开`CAMediaTimingFunction`的限制,创建完全自定义的缓冲函数。 4 | 5 | 在下一章中,我们将要研究基于定时器的动画--另一个给我们对动画更多控制的选择,并且实现对动画的实时操纵。 -------------------------------------------------------------------------------- /chapter13/efficient-drawing.md: -------------------------------------------------------------------------------- 1 | # 高效绘图 2 | 3 | > 不必要的效率考虑往往是性能问题的万恶之源。 4 | > ——William Allan Wulf 5 | 6 |     在第12章『速度的曲率』我们学习如何用Instruments来诊断Core Animation性能问题。在构建一个iOS app的时候会遇到很多潜在的性能陷阱,但是在本章我们将着眼于有关*绘制*的性能问题。 7 | -------------------------------------------------------------------------------- /chapter5/summary.md: -------------------------------------------------------------------------------- 1 | # 总结 2 | 3 | 4 | 这一章涉及了一些2D和3D的变换。你学习了一些矩阵计算的基础,以及如何用Core Animation创建3D场景。你看到了图层背后到底是如何呈现的,并且知道了不能把扁平的图片做成真实的立体效果,最后我们用demo说明了触摸事件的处理,视图中图层添加的层级顺序会比屏幕上显示的顺序更有意义。 5 | 6 | 第六章我们会研究一些Core Animation提供不同功能的具体的`CALayer`子类。 -------------------------------------------------------------------------------- /chapter15/summary.md: -------------------------------------------------------------------------------- 1 | ## 总结 2 | 3 | 4 |     本章学习了使用Core Animation图层可能遇到的性能瓶颈,并讨论了如何避免或减小压力。你学习了如何管理包含上千虚拟图层的场景(事实上只创建了几百个)。同时也学习了一些有用的技巧,选择性地选取光栅化或者绘制图层内容在合适的时候重新分配给CPU和GPU。这些就是我们要讲的关于Core Animation的全部了(至少可以等到苹果发明什么新的玩意儿)。 5 | -------------------------------------------------------------------------------- /chapter3/summary.md: -------------------------------------------------------------------------------- 1 | # 总结 2 |     本章涉及了`CALayer`的集合结构,包括它的`frame`,`position`和`bounds`,介绍了三维空间内图层的概念,以及如何在独立的图层内响应事件,最后简单说明了在iOS平台,Core Animation对自动调整和自动布局支持的缺乏。 3 | 4 |     在第四章“视觉效果”当中,我们接着介绍一些图层外表的特性。 5 | -------------------------------------------------------------------------------- /chapter12/tuning-for-speed.md: -------------------------------------------------------------------------------- 1 | # 性能调优 2 | 3 | >*代码应该运行的尽量快,而不是更快* - 理查德 4 | 5 |     在第一和第二部分,我们了解了Core Animation提供的关于绘制和动画的一些特性。Core Animation功能和性能都非常强大,但如果你对背后的原理不清楚的话也会降低效率。让它达到最优的状态是一门艺术。在这章中,我们将探究一些动画运行慢的原因,以及如何去修复这些问题。 6 | 7 | -------------------------------------------------------------------------------- /chapter1/summary.md: -------------------------------------------------------------------------------- 1 | # 总结 2 | 3 |     这一章阐述了图层的树状结构,说明了如何在iOS中由`UIView`的层级关系形成的一种平行的`CALayer`层级关系,在后面的实验中,我们创建了自己的`CALayer`,并把它添加到图层树中。 4 | 5 |     在第二章,“图层关联的图片”,我们将要研究一下`CALayer`关联的图片,以及Core Animation提供的操作显示的一些特性。 -------------------------------------------------------------------------------- /chapter2/the-Backing-Image.md: -------------------------------------------------------------------------------- 1 | # 寄宿图 2 | 3 | # 寄宿图 4 | >图片胜过千言万语,界面抵得上千图片 ——Ben Shneiderman 5 | 6 |     我们在第一章『图层树』中介绍了CALayer类并创建了一个简单的有蓝色背景的图层。背景颜色还好啦,但是如果它仅仅是展现了一个单调的颜色未免也太无聊了。事实上CALayer类能够包含一张你喜欢的图片,这一章节我们将来探索CALayer的寄宿图(即图层中包含的图)。 7 | -------------------------------------------------------------------------------- /chapter11/timer-based-animation.md: -------------------------------------------------------------------------------- 1 | # 基于定时器的动画 2 | 3 | > *我可以指导你,但是你必须按照我说的做。* -- 骇客帝国 4 | 5 |     在第10章“缓冲”中,我们研究了`CAMediaTimingFunction`,它是一个通过控制动画缓冲来模拟物理效果例如加速或者减速来增强现实感的东西,那么如果想更加真实地模拟物理交互或者实时根据用户输入修改动画改怎么办呢?在这一章中,我们将继续探索一种能够允许我们精确地控制一帧一帧展示的基于定时器的动画。 6 | 7 | -------------------------------------------------------------------------------- /chapter4/visual-effcts.md: -------------------------------------------------------------------------------- 1 | # 视觉效果 2 | 3 | >嗯,圆和椭圆还不错,但如果是带圆角的矩形呢? 4 | 5 | >我们现在能做到那样了么? 6 | 7 | >史蒂芬·乔布斯 8 | 9 |     我们在第三章『图层几何学』中讨论了图层的frame,第二章『寄宿图』则讨论了图层的寄宿图。但是图层不仅仅可以是图片或是颜色的容器;还有一系列内建的特性使得创造美丽优雅的令人深刻的界面元素成为可能。在这一章,我们将会探索一些能够通过使用CALayer属性实现的视觉效果。 10 | -------------------------------------------------------------------------------- /chapter7/summary.md: -------------------------------------------------------------------------------- 1 | # 总结 2 | 3 | 这一章讨论了隐式动画,还有Core Animation对指定属性选择合适的动画行为的机制。同时你知道了UIKit是如何充分利用Core Animation的隐式动画机制来强化它的显式系统,以及动画是如何被默认禁用并且当需要的时候启用的。最后,你了解了呈现和模型图层,以及Core Animation是如何通过它们来判断出图层当前位置以及将要到达的位置。 4 | 5 | 在下一章中,我们将研究Core Animation提供的*显式*动画类型,既可以直接对图层属性做动画,也可以覆盖默认的图层行为。 6 | -------------------------------------------------------------------------------- /chapter3/flipped-geometry.md: -------------------------------------------------------------------------------- 1 | # 翻转的几何结构 2 | 3 |     常规说来,在iOS上,一个图层的`position`位于父图层的左上角,但是在Mac OS上,通常是位于左下角。Core Animation可以通过`geometryFlipped`属性来适配这两种情况,它决定了一个图层的坐标是否相对于父图层垂直翻转,是一个`BOOL`类型。在iOS上通过设置它为`YES`意味着它的子图层将会被垂直翻转,也就是将会沿着底部排版而不是通常的顶部(它的所有子图层也同理,除非把它们的`geometryFlipped`属性也设为`YES`)。 4 | -------------------------------------------------------------------------------- /chapter6/summarry.md: -------------------------------------------------------------------------------- 1 | # 总结 2 | 3 | 4 | 这一章我们简要概述了一些专用图层以及用他们实现的一些效果,我们只是了解到这些图层的皮毛,像`CATiledLayer`和`CAEMitterLayer`这些类可以单独写一章的。但是,重点是记住`CALayer`是用处很大的,而且它并没有为所有可能的场景进行优化。为了获得Core Animation最好的性能,你需要为你的工作选对正确的工具,希望你能够挖掘这些不同的`CALayer`子类的功能。 5 | 这一章我们通过`CAEmitterLayer`和`AVPlayerLayer`类简单地接触到了一些动画,在第二章,我们将继续深入研究动画,就从隐式动画开始。 -------------------------------------------------------------------------------- /chapter1/caLayer.md: -------------------------------------------------------------------------------- 1 | # CALayer 2 |     `CALayer`类在概念上和`UIView`类似,同样也是一些被层级关系树管理的矩形块,同样也可以包含一些内容(像图片,文本或者背景色),管理子图层的位置。它们有一些方法和属性用来做动画和变换。和`UIView`最大的不同是`CALayer`不处理用户的交互。 3 | 4 |     `CALayer`并不清楚具体的*响应链*(iOS通过视图层级关系用来传送触摸事件的机制),于是它并不能够响应事件,即使它提供了一些方法来判断是否一个触点在图层的范围之内(具体见第三章,“图层的几何学”) 5 | 6 | -------------------------------------------------------------------------------- /chapter2/masktobounds.md: -------------------------------------------------------------------------------- 1 | # maskToBounds 2 | 3 |     现在我们的雪人总算是显示了正确的大小,不过你也许已经发现了另外一些事情:他超出了视图的边界。默认情况下,UIView仍然会绘制超过边界的内容或是子视图,在CALayer下也是这样的。 4 | 5 |     UIView有一个叫做`clipsToBounds`的属性可以用来决定是否显示超出边界的内容,CALayer对应的属性叫做`masksToBounds`,把它设置为YES,雪人就在边界里啦~(如图2.5) 6 | 7 | ![图2.5](./2.5.png) 8 | 9 | 图2.5 使用`masksToBounds`来修建图层内容 -------------------------------------------------------------------------------- /chapter1/the-layer-tree.md: -------------------------------------------------------------------------------- 1 | #图层的树状结构 2 | 3 | >巨妖有图层,洋葱也有图层,你有吗?我们都有图层 -- 史莱克 4 | 5 |     Core Animation其实是一个令人误解的命名。你可能认为它只是用来做动画的,但实际上它是从一个叫做*Layer Kit*这么一个不怎么和动画有关的名字演变而来,所以做动画这只是Core Animation特性的冰山一角。 6 | 7 |     Core Animation是一个*复合引擎*,它的职责就是尽可能快地组合屏幕上不同的可视内容,这个内容是被分解成独立的*图层*,存储在一个叫做*图层树*的体系之中。于是这个树形成了**UIKit**以及在iOS应用程序当中你所能在屏幕上看见的一切的基础。 8 | 9 |     在我们讨论动画之前,我们将从图层树开始,涉及一下Core Animation的*静态*组合以及布局特性。 10 | 11 | 12 | -------------------------------------------------------------------------------- /chapter5/the-vanishing-point.md: -------------------------------------------------------------------------------- 1 | # 灭点 2 | 3 | 4 | 当在透视角度绘图的时候,远离相机视角的物体将会变小变远,当远离到一个极限距离,它们可能就缩成了一个点,于是所有的物体最后都汇聚消失在同一个点。 5 | 6 | 在现实中,这个点通常是视图的中心(图5.11),于是为了在应用中创建拟真效果的透视,这个点应该聚在屏幕中点,或者至少是包含所有3D对象的视图中点。 7 | 8 | 图5.11 9 | 10 | 图5.11 灭点 11 | 12 | Core Animation定义了这个点位于变换图层的`anchorPoint`(通常位于图层中心,但也有例外,见第三章)。这就是说,当图层发生变换时,这个点永远位于图层变换之前`anchorPoint`的位置。 13 | 14 | 当改变一个图层的`position`,你也改变了它的灭点,做3D变换的时候要时刻记住这一点,当你视图通过调整`m34`来让它更加有3D效果,应该首先把它放置于屏幕中央,然后通过平移来把它移动到指定位置(而不是直接改变它的`position`),这样所有的3D图层都共享一个灭点。 -------------------------------------------------------------------------------- /chapter5/backfaces.md: -------------------------------------------------------------------------------- 1 | 2 | 我们既然可以在3D场景下旋转图层,那么也可以从*背面*去观察它。如果我们在清单5.4中把角度修改为`M_PI`(180度)而不是当前的` M_PI_4`(45度),那么将会把图层完全旋转一个半圈,于是完全背对了相机视角。 3 | 4 | 那么从背部看图层是什么样的呢,见图5.14 5 | 6 | 图5.14 7 | 8 | 图5.14 视图的背面,一个镜像对称的图片 9 | 10 | 如你所见,图层是双面绘制的,反面显示的是正面的一个镜像图片。 11 | 12 | 但这并不是一个很好的特性,因为如果图层包含文本或者其他控件,那用户看到这些内容的镜像图片当然会感到困惑。另外也有可能造成资源的浪费:想象用这些图层形成一个不透明的固态立方体,既然永远都看不见这些图层的背面,那为什么浪费GPU来绘制它们呢? 13 | 14 | `CALayer`有一个叫做`doubleSided`的属性来控制图层的背面是否要被绘制。这是一个`BOOL`类型,默认为`YES`,如果设置为`NO`,那么当图层正面从相机视角消失的时候,它将不会被绘制。# 背面 15 | 16 | -------------------------------------------------------------------------------- /chapter1/layer-capabilities.md: -------------------------------------------------------------------------------- 1 | # 图层的能力 2 |     如果说`CALayer`是`UIView`内部实现细节,那我们为什么要全面地了解它呢?苹果当然为我们提供了优美简洁的`UIView`接口,那么我们是否就没必要直接去处理Core Animation的细节了呢? 3 | 4 |     某种意义上说的确是这样,对一些简单的需求来说,我们确实没必要处理`CALayer`,因为苹果已经通过`UIView`的高级API间接地使得动画变得很简单。 5 | 6 |     但是这种简单会不可避免地带来一些灵活上的缺陷。如果你略微想在底层做一些改变,或者使用一些苹果没有在`UIView`上实现的接口功能,这时除了介入Core Animation底层之外别无选择。 7 | 8 |     我们已经证实了图层不能像视图那样处理触摸事件,那么他能做哪些视图不能做的呢?这里有一些`UIView`没有暴露出来的CALayer的功能: 9 | 10 | * 阴影,圆角,带颜色的边框 11 | * 3D变换 12 | * 非矩形范围 13 | * 透明遮罩 14 | * 多级非线性动画 15 | 16 |     我们将会在后续章节中探索这些功能,首先我们要关注一下在应用程序当中`CALayer`是怎样被利用起来的。 17 | 18 | -------------------------------------------------------------------------------- /chapter13/software-drawing-vector-graphics.md: -------------------------------------------------------------------------------- 1 | ## 软件绘图 2 | 3 | 4 |     术语*绘图*通常在Core Animation的上下文中指代软件绘图(意即:不由GPU协助的绘图)。在iOS中,软件绘图通常是由Core Graphics框架完成来完成。但是,在一些必要的情况下,相比Core Animation和OpenGL,Core Graphics要慢了不少。 5 | 6 |     软件绘图不仅效率低,还会消耗可观的内存。`CALayer`只需要一些与自己相关的内存:只有它的寄宿图会消耗一定的内存空间。即使直接赋给`contents`属性一张图片,也不需要增加额外的照片存储大小。如果相同的一张图片被多个图层作为`contents`属性,那么他们将会共用同一块内存,而不是复制内存块。 7 | 8 |     但是一旦你实现了`CALayerDelegate`协议中的`-drawLayer:inContext:`方法或者`UIView`中的`-drawRect:`方法(其实就是前者的包装方法),图层就创建了一个绘制上下文,这个上下文需要的大小的内存可从这个算式得出:图层宽\*图层高\*4字节,宽高的单位均为像素。对于一个在Retina iPad上的全屏图层来说,这个内存量就是 2048\*1526\*4字节,相当于12MB内存,图层每次重绘的时候都需要重新抹掉内存然后重新分配。 9 | 10 |     软件绘图的代价昂贵,除非绝对必要,你应该避免重绘你的视图。提高绘制性能的秘诀就在于尽量避免去绘制。 11 | -------------------------------------------------------------------------------- /chapter5/touch-events.md: -------------------------------------------------------------------------------- 1 | # 点击事件 2 | 3 | 你应该能注意到现在可以在第三个表面的顶部看见按钮了,点击它,什么都没发生,为什么呢? 4 | 5 | 这并不是因为iOS在3D场景下正确地处理响应事件,实际上是可以做到的。问题在于*视图顺序*。在第三章中我们简要提到过,点击事件的处理由视图在父视图中的顺序决定的,并不是3D空间中的Z轴顺序。当给立方体添加视图的时候,我们实际上是按照一个顺序添加,所以按照视图/图层顺序来说,4,5,6在3的前面。 6 | 7 | 即使我们看不见4,5,6的表面(因为被1,2,3遮住了),iOS在事件响应上仍然保持之前的顺序。当试图点击表面3上的按钮,表面4,5,6截断了点击事件(取决于点击的位置),这就和普通的2D布局在按钮上覆盖物体一样。 8 | 9 | 你也许认为把`doubleSided`设置成`NO`可以解决这个问题,因为它不再渲染视图后面的内容,但实际上并不起作用。因为背对相机而隐藏的视图仍然会响应点击事件(这和通过设置`hidden`属性或者设置`alpha`为0而隐藏的视图不同,那两种方式将不会响应事件)。所以即使禁止了双面渲染仍然不能解决这个问题(虽然由于性能问题,还是需要把它设置成`NO`)。 10 | 11 | 这里有几种正确的方案:把除了表面3的其他视图`userInteractionEnabled`属性都设置成`NO`来禁止事件传递。或者简单通过代码把视图3覆盖在视图6上。无论怎样都可以点击按钮了(图5.23)。 12 | 13 | 图5.23 14 | 15 | 图5.23 背景视图不再阻碍按钮,我们可以点击它了 16 | -------------------------------------------------------------------------------- /chapter6/rounded-corners.md: -------------------------------------------------------------------------------- 1 | # 圆角 2 | 3 | 4 | 第二章里面提到了`CAShapeLayer`为创建圆角视图提供了一个方法,就是`CALayer`的`cornerRadius`属性(译者注:其实是在第四章提到的)。虽然使用`CAShapeLayer`类需要更多的工作,但是它有一个优势就是可以单独指定每个角。 5 | 6 | 我们创建圆角矩形其实就是人工绘制单独的直线和弧度,但是事实上`UIBezierPath`有自动绘制圆角矩形的构造方法,下面这段代码绘制了一个有三个圆角一个直角的矩形: 7 | 8 | ```objective-c 9 | //define path parameters 10 | CGRect rect = CGRectMake(50, 50, 100, 100); 11 | CGSize radii = CGSizeMake(20, 20); 12 | UIRectCorner corners = UIRectCornerTopRight | UIRectCornerBottomRight | UIRectCornerBottomLeft; 13 | //create path 14 | UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:radii]; 15 | ``` 16 | 17 | 我们可以通过这个图层路径绘制一个既有直角又有圆角的视图。如果我们想依照此图形来剪裁视图内容,我们可以把`CAShapeLayer`作为视图的宿主图层,而不是添加一个子视图(图层蒙板的详细解释见第四章『视觉效果』)。 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | iOS Core Animation: Advanced Techniques中文译本 2 | ======= 3 | 4 | [gitbook上的地址](https://zsisme.gitbooks.io/ios-/content/) 5 | 6 | 本书翻译自:[iOS Core Animation: Advanced Techniques](http://www.amazon.com/iOS-Core-Animation-Advanced-Techniques-ebook/dp/B00EHJCORC/ref=sr_1_1?ie=UTF8&qid=1423192842&sr=8-1&keywords=Core+Animation+Advanced+Techniques) 7 | 8 | 9 | 10 | >知识是人类进步的阶梯 11 | 12 | **翻译,喵~** 13 | 14 | 译者为: 15 | 16 | [AttackOnDobby](https://github.com/AttackOnDobby) 17 | 18 | [evenluo](https://github.com/evenluo/) 19 | 20 | (排名不分先后,感谢他俩的付出!) 21 | 22 | 我([ZsIsMe](https://github.com/ZsIsMe))参与校对,并将译者的稿件搬运至gitbook上,方便大家查看。 23 | 24 | 如果在阅读过程中发现有什么问题,请到[这里](https://github.com/ZsIsMe/ios_core_animation_advanced_techniques)(本电子书在github上的地址)开issue,我会尽快改过来。 25 | 26 | -------------------------------------------------------------------------------- /chapter15/blending-and-overdraw.md: -------------------------------------------------------------------------------- 1 | 2 | ##混合和过度绘制 3 | 4 |     在第12章有提到,GPU每一帧可以绘制的像素有一个最大限制(就是所谓的fill rate),这个情况下可以轻易地绘制整个屏幕的所有像素。但是如果由于重叠图层的关系需要不停地重绘同一区域的话,掉帧就可能发生了。 5 | 6 |     GPU会放弃绘制那些完全被其他图层遮挡的像素,但是要计算出一个图层是否被遮挡也是相当复杂并且会消耗处理器资源。同样,合并不同图层的透明重叠像素(即混合)消耗的资源也是相当客观的。所以为了加速处理进程,不到必须时刻不要使用透明图层。任何情况下,你应该这样做: 7 | 8 | * 给视图的`backgroundColor`属性设置一个固定的,不透明的颜色 9 | * 设置`opaque`属性为YES 10 | 11 |     这样做减少了混合行为(因为编译器知道在图层之后的东西都不会对最终的像素颜色产生影响)并且计算得到了加速,避免了过度绘制行为因为Core Animation可以舍弃所有被完全遮盖住的图层,而不用每个像素都去计算一遍。 12 | 13 |     如果用到了图像,尽量避免透明除非非常必要。如果图像要显示在一个固定的背景颜色或是固定的背景图之前,你没必要相对前景移动,你只需要预填充背景图片就可以避免运行时混色了。 14 | 15 |     如果是文本的话,一个白色背景的`UILabel`(或者其他颜色)会比透明背景要更高效。 16 | 17 |     最后,明智地使用`shouldRasterize`属性,可以将一个固定的图层体系折叠成单张图片,这样就不需要每一帧重新合成了,也就不会有因为子图层之间的混合和过度绘制的性能问题了。 -------------------------------------------------------------------------------- /chapter3/automatic-layout.md: -------------------------------------------------------------------------------- 1 | # 自动布局 2 | 3 | 4 |     你可能用过`UIViewAutoresizingMask`类型的一些常量,应用于当父视图改变尺寸的时候,相应`UIView`的`frame`也跟着更新的场景(通常用于横竖屏切换)。 5 | 6 |     在iOS6中,苹果介绍了*自动排版*机制,它和自动调整不同,并且更加复杂。 7 | 8 |     在Mac OS平台,`CALayer`有一个叫做`layoutManager`的属性可以通过`CALayoutManager`协议和`CAConstraintLayoutManager`类来实现自动排版的机制。但由于某些原因,这在iOS上并不适用。 9 | 10 |     当使用视图的时候,可以充分利用`UIView`类接口暴露出来的`UIViewAutoresizingMask`和`NSLayoutConstraint`API,但如果想随意控制`CALayer`的布局,就需要手工操作。最简单的方法就是使用`CALayerDelegate`如下函数: 11 | 12 | - (void)layoutSublayersOfLayer:(CALayer *)layer; 13 | 14 |     当图层的`bounds`发生改变,或者图层的`-setNeedsLayout`方法被调用的时候,这个函数将会被执行。这使得你可以手动地重新摆放或者重新调整子图层的大小,但是不能像`UIView`的`autoresizingMask`和`constraints`属性做到自适应屏幕旋转。 15 | 16 |     这也是为什么最好使用视图而不是单独的图层来构建应用程序的另一个重要原因之一。 17 | -------------------------------------------------------------------------------- /chapter5/the-shear-transform.md: -------------------------------------------------------------------------------- 1 | # 剪切变换 2 | 3 | 4 | Core Graphics为你提供了计算变换矩阵的一些方法,所以很少需要直接设置`CGAffineTransform`的值。除非需要创建一个*斜切*的变换,Core Graphics并没有提供直接的函数。 5 | 6 | 斜切变换是放射变换的第四种类型,较于平移,旋转和缩放并不常用(这也是Core Graphics没有提供相应函数的原因),但有些时候也会很有用。我们用一张图片可以很直接的说明效果(图5.5)。也许用“倾斜”描述更加恰当,具体做变换的代码见清单5.3。 7 | 8 | 图5.5 9 | 10 | 图5.5 水平方向的斜切变换 11 | 12 | 清单5.3 实现一个斜切变换 13 | 14 | ```objective-c 15 | @implementation ViewController 16 | 17 | CGAffineTransform CGAffineTransformMakeShear(CGFloat x, CGFloat y) 18 | { 19 | CGAffineTransform transform = CGAffineTransformIdentity; 20 | transform.c = -x; 21 | transform.b = y; 22 | return transform; 23 | } 24 | 25 | - (void)viewDidLoad 26 | { 27 | [super viewDidLoad]; 28 | //shear the layer at a 45-degree angle 29 | self.layerView.layer.affineTransform = CGAffineTransformMakeShear(1, 0); 30 | } 31 | 32 | @end 33 | ``` 34 | -------------------------------------------------------------------------------- /chapter1/parallel-hierarchies.md: -------------------------------------------------------------------------------- 1 | # 平行的层级关系 2 |     每一个`UIview`都有一个`CALayer`实例的图层属性,也就是所谓的*backing layer*,视图的职责就是创建并管理这个图层,以确保当子视图在层级关系中添加或者被移除的时候,他们关联的图层也同样对应在层级关系树当中有相同的操作(见图1.2)。 3 | 图1.2 4 | 图1.2 图层的树状结构(左边)以及对应的视图层级(右边) 5 | 6 |     实际上这些背后关联的图层才是真正用来在屏幕上显示和做动画,`UIView`仅仅是对它的一个封装,提供了一些iOS类似于处理触摸的具体功能,以及Core Animation底层方法的高级接口。 7 | 8 |     但是为什么iOS要基于`UIView`和`CALayer`提供两个平行的层级关系呢?为什么不用一个简单的层级来处理所有事情呢?原因在于要做职责分离,这样也能避免很多重复代码。在iOS和Mac OS两个平台上,事件和用户交互有很多地方的不同,基于多点触控的用户界面和基于鼠标键盘有着本质的区别,这就是为什么iOS有UIKit和`UIView`,但是Mac OS有AppKit和`NSView`的原因。他们功能上很相似,但是在实现上有着显著的区别。 9 | 10 |     绘图,布局和动画,相比之下就是类似Mac笔记本和桌面系列一样应用于iPhone和iPad触屏的概念。把这种功能的逻辑分开并应用到独立的Core Animation框架,苹果就能够在iOS和Mac OS之间共享代码,使得对苹果自己的OS开发团队和第三方开发者去开发两个平台的应用更加便捷。 11 | 12 |     实际上,这里并不是两个层级关系,而是四个,每一个都扮演不同的角色,除了视图层级和图层树之外,还存在*呈现树*和*渲染树*,将在第七章“隐式动画”和第十二章“性能调优”分别讨论。 13 | 14 | 15 | -------------------------------------------------------------------------------- /chapter3/layout.md: -------------------------------------------------------------------------------- 1 | # 布局 2 | 3 |     `UIView`有三个比较重要的布局属性:`frame`,`bounds`和`center`,`CALayer`对应地叫做`frame`,`bounds`和`position`。为了能清楚区分,图层用了“position”,视图用了“center”,但是他们都代表同样的值。 4 | 5 |     `frame`代表了图层的外部坐标(也就是在父图层上占据的空间),`bounds`是内部坐标({0, 0}通常是图层的左上角),`center`和`position`都代表了相对于父图层`anchorPoint`所在的位置。`anchorPoint`的属性将会在后续介绍到,现在把它想成图层的中心点就好了。图3.1显示了这些属性是如何相互依赖的。 6 | 7 | 图3.1 8 | 9 | 图3.1 `UIView`和`CALayer`的坐标系 10 | 11 |     视图的`frame`,`bounds`和`center`属性仅仅是*存取方法*,当操纵视图的`frame`,实际上是在改变位于视图下方`CALayer`的`frame`,不能够独立于图层之外改变视图的`frame`。 12 | 13 |     对于视图或者图层来说,`frame`并不是一个非常清晰的属性,它其实是一个虚拟属性,是根据`bounds`,`position`和`transform`计算而来,所以当其中任何一个值发生改变,frame都会变化。相反,改变frame的值同样会影响到他们当中的值 14 | 15 |     记住当对图层做变换的时候,比如旋转或者缩放,`frame`实际上代表了覆盖在图层旋转之后的整个轴对齐的矩形区域,也就是说`frame`的宽高可能和`bounds`的宽高不再一致了(图3.2) 16 | 17 | 图3.2 18 | 19 | 图3.2 旋转一个视图或者图层之后的`frame`属性 20 | 21 | -------------------------------------------------------------------------------- /chapter13/drawsasynchronously.md: -------------------------------------------------------------------------------- 1 | ## 异步绘制 2 | 3 | 4 |     UIKit的单线程天性意味着寄宿图通畅要在主线程上更新,这意味着绘制会打断用户交互,甚至让整个app看起来处于无响应状态。我们对此无能为力,但是如果能避免用户等待绘制完成就好多了。 5 | 6 |     针对这个问题,有一些方法可以用到:一些情况下,我们可以推测性地提前在另外一个线程上绘制内容,然后将由此绘出的图片直接设置为图层的内容。这实现起来可能不是很方便,但是在特定情况下是可行的。Core Animation提供了一些选择:`CATiledLayer`和`drawsAsynchronously`属性。 7 | 8 | ###CATiledLayer 9 | 10 |     我们在第六章简单探索了一下`CATiledLayer`。除了将图层再次分割成独立更新的小块(类似于脏矩形自动更新的概念),`CATiledLayer`还有一个有趣的特性:在多个线程中为每个小块同时调用`-drawLayer:inContext:`方法。这就避免了阻塞用户交互而且能够利用多核心新片来更快地绘制。只有一个小块的`CATiledLayer`是实现异步更新图片视图的简单方法。 11 | 12 | ###drawsAsynchronously 13 | 14 |     iOS 6中,苹果为`CALayer`引入了这个令人好奇的属性,`drawsAsynchronously`属性对传入`-drawLayer:inContext:`的CGContext进行改动,允许CGContext延缓绘制命令的执行以至于不阻塞用户交互。 15 | 16 |     它与`CATiledLayer`使用的异步绘制并不相同。它自己的`-drawLayer:inContext:`方法只会在主线程调用,但是CGContext并不等待每个绘制命令的结束。相反地,它会将命令加入队列,当方法返回时,在后台线程逐个执行真正的绘制。 17 | 18 |     根据苹果的说法。这个特性在需要频繁重绘的视图上效果最好(比如我们的绘图应用,或者诸如`UITableViewCell`之类的),对那些只绘制一次或很少重绘的图层内容来说没什么太大的帮助。 -------------------------------------------------------------------------------- /chapter15/inexplicit-drawing-text.md: -------------------------------------------------------------------------------- 1 | ## 隐式绘制 2 | 3 | 4 |     寄宿图可以通过Core Graphics直接绘制,也可以直接载入一个图片文件并赋值给`contents`属性,或事先绘制一个屏幕之外的`CGContext`上下文。在之前的两章中我们讨论了这些场景下的优化。但是除了常见的显式创建寄宿图,你也可以通过以下三种方式创建隐式的:1,使用特性的图层属性。2,特定的视图。3,特定的图层子类。 5 | 6 |     了解这个情况为什么发生何时发生是很重要的,它能够让你避免引入不必要的软件绘制行为。 7 | 8 | ###文本 9 | 10 |     `CATextLayer`和`UILabel`都是直接将文本绘制在图层的寄宿图中。事实上这两种方式用了完全不同的渲染方式:在iOS 6及之前,`UILabel`用WebKit的HTML渲染引擎来绘制文本,而`CATextLayer`用的是Core Text.后者渲染更迅速,所以在所有需要绘制大量文本的情形下都优先使用它吧。但是这两种方法都用了软件的方式绘制,因此他们实际上要比硬件加速合成方式要慢。 11 | 12 |     不论如何,尽可能地避免改变那些包含文本的视图的frame,因为这样做的话文本就需要重绘。例如,如果你想在图层的角落里显示一段静态的文本,但是这个图层经常改动,你就应该把文本放在一个子图层中。 13 | 14 | ###光栅化 15 | 16 |     在第四章『视觉效果』中我们提到了`CALayer`的`shouldRasterize`属性,它可以解决重叠透明图层的混合失灵问题。同样在第12章『速度的曲调』中,它也是作为绘制复杂图层树结构的优化方法。 17 | 18 |     启用`shouldRasterize`属性会将图层绘制到一个屏幕之外的图像。然后这个图像将会被缓存起来并绘制到实际图层的`contents`和子图层。如果有很多的子图层或者有复杂的效果应用,这样做就会比重绘所有事务的所有帧划得来得多。但是光栅化原始图像需要时间,而且还会消耗额外的内存。 19 | 20 |     当我们使用得当时,光栅化可以提供很大的性能优势(如你在第12章所见),但是一定要避免作用在内容不断变动的图层上,否则它缓存方面的好处就会消失,而且会让性能变的更糟。 21 | 22 |     为了检测你是否正确地使用了光栅化方式,用Instrument查看一下Color Hits Green和Misses Red项目,是否已光栅化图像被频繁地刷新(这样就说明图层并不是光栅化的好选择,或则你无意间触发了不必要的改变导致了重绘行为)。 23 | -------------------------------------------------------------------------------- /chapter4/ayer-borders.md: -------------------------------------------------------------------------------- 1 | # 图层边框 2 | 3 | 4 |   &nbp; CALayer另外两个非常有用属性就是`borderWidth`和`borderColor`。二者共同定义了图层边的绘制样式。这条线(也被称作stroke)沿着图层的`bounds`绘制,同时也包含图层的角。 5 | 6 |   &nbp; `borderWidth`是以点为单位的定义边框粗细的浮点数,默认为0.`borderColor`定义了边框的颜色,默认为黑色。 7 | 8 |   &nbp; `borderColor`是CGColorRef类型,而不是UIColor,所以它不是Cocoa的内置对象。不过呢,你肯定也清楚图层引用了`borderColor`,虽然属性声明并不能证明这一点。`CGColorRef`在引用/释放时候的行为表现得与`NSObject`极其相似。但是Objective-C语法并不支持这一做法,所以`CGColorRef`属性即便是强引用也只能通过assign关键字来声明。 9 | 10 |   &nbp; 边框是绘制在图层边界里面的,而且在所有子内容之前,也在子图层之前。如果我们在之前的示例中(清单4.2)加入图层的边框,你就能看到到底是怎么一回事了(如图4.3). 11 | 12 | 清单4.2 加上边框 13 | 14 | ```objective-c 15 | @implementation ViewController 16 | 17 | - (void)viewDidLoad 18 | { 19 | [super viewDidLoad]; 20 | 21 | //set the corner radius on our layers 22 | self.layerView1.layer.cornerRadius = 20.0f; 23 | self.layerView2.layer.cornerRadius = 20.0f; 24 | 25 | //add a border to our layers 26 | self.layerView1.layer.borderWidth = 5.0f; 27 | self.layerView2.layer.borderWidth = 5.0f; 28 | 29 | //enable clipping on the second layer 30 | self.layerView2.layer.masksToBounds = YES; 31 | } 32 | 33 | @end 34 | ``` 35 | 36 | ![图4.3](./4.3.png) 37 | 38 | 图4.3 给图层增加一个边框 39 | 40 |   &nbp; 仔细观察会发现边框并不会把寄宿图或子图层的形状计算进来,如果图层的子图层超过了边界,或者是寄宿图在透明区域有一个透明蒙板,边框仍然会沿着图层的边界绘制出来(如图4.4). 41 | 42 | ![图4.4](./4.4.png) 43 | 44 | 图4.4 边框是跟随图层的边界变化的,而不是图层里面的内容 -------------------------------------------------------------------------------- /chapter7/completion-blocks.md: -------------------------------------------------------------------------------- 1 | # 完成块 2 | 3 | 4 | 5 | 基于`UIView`的block的动画允许你在动画结束的时候提供一个完成的动作。`CATranscation`接口提供的`+setCompletionBlock:`方法也有同样的功能。我们来调整上个例子,在颜色变化结束之后执行一些操作。我们来添加一个完成之后的block,用来在每次颜色变化结束之后切换到另一个旋转90的动画。代码见清单7.3,运行结果见图7.2。 6 | 7 | 清单7.3 在颜色动画完成之后添加一个回调 8 | 9 | ```objective-c 10 | - (IBAction)changeColor 11 | { 12 | //begin a new transaction 13 | [CATransaction begin]; 14 | //set the animation duration to 1 second 15 | [CATransaction setAnimationDuration:1.0]; 16 | //add the spin animation on completion 17 | [CATransaction setCompletionBlock:^{ 18 | //rotate the layer 90 degrees 19 | CGAffineTransform transform = self.colorLayer.affineTransform; 20 | transform = CGAffineTransformRotate(transform, M_PI_2); 21 | self.colorLayer.affineTransform = transform; 22 | }]; 23 | //randomize the layer background color 24 | CGFloat red = arc4random() / (CGFloat)INT_MAX; 25 | CGFloat green = arc4random() / (CGFloat)INT_MAX; 26 | CGFloat blue = arc4random() / (CGFloat)INT_MAX; 27 | self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor; 28 | //commit the transaction 29 | [CATransaction commit]; 30 | } 31 | ``` 32 | 33 | 图7.2 34 | 35 | 图7.2 颜色渐变之完成之后再做一次旋转 36 | 37 | 注意旋转动画要比颜色渐变快得多,这是因为完成块是在颜色渐变的事务提交并出栈之后才被执行,于是,用默认的事务做变换,默认的时间也就变成了0.25秒。 -------------------------------------------------------------------------------- /chapter5/perspective-projection.md: -------------------------------------------------------------------------------- 1 | # 透视投影 2 | 3 | 4 | 在真实世界中,当物体远离我们的时候,由于视角的原因看起来会变小,理论上说远离我们的视图的边要比靠近视角的边跟短,但实际上并没有发生,而我们当前的视角是等距离的,也就是在3D变换中任然保持平行,和之前提到的仿射变换类似。 5 | 6 | 在等距投影中,远处的物体和近处的物体保持同样的缩放比例,这种投影也有它自己的用处(例如建筑绘图,颠倒,和伪3D视频),但当前我们并不需要。 7 | 8 | 为了做一些修正,我们需要引入*投影变换*(又称作*z变换*)来对除了旋转之外的变换矩阵做一些修改,Core Animation并没有给我们提供设置透视变换的函数,因此我们需要手动修改矩阵值,幸运的是,很简单: 9 | 10 | `CATransform3D`的透视效果通过一个矩阵中一个很简单的元素来控制:`m34`。`m34`(图5.9)用于按比例缩放X和Y的值来计算到底要离视角多远。 11 | 12 | 图5.9 13 | 14 | 图5.9 `CATransform3D`的`m34`元素,用来做透视 15 | 16 | `m34`的默认值是0,我们可以通过设置`m34`为-1.0 / `d`来应用透视效果,`d`代表了想象中视角相机和屏幕之间的距离,以像素为单位,那应该如何计算这个距离呢?实际上并不需要,大概估算一个就好了。 17 | 18 | 因为视角相机实际上并不存在,所以可以根据屏幕上的显示效果自由决定它的防止的位置。通常500-1000就已经很好了,但对于特定的图层有时候更小后者更大的值会看起来更舒服,减少距离的值会增强透视效果,所以一个非常微小的值会让它看起来更加失真,然而一个非常大的值会让它基本失去透视效果,对视图应用透视的代码见清单5.5,结果见图5.10。 19 | 20 | 清单5.5 对变换应用透视效果 21 | 22 | ```objective-c 23 | @implementation ViewController 24 | 25 | - (void)viewDidLoad 26 | { 27 | [super viewDidLoad]; 28 | //create a new transform 29 | CATransform3D transform = CATransform3DIdentity; 30 | //apply perspective 31 | transform.m34 = - 1.0 / 500.0; 32 | //rotate by 45 degrees along the Y axis 33 | transform = CATransform3DRotate(transform, M_PI_4, 0, 1, 0); 34 | //apply to layer 35 | self.layerView.layer.transform = transform; 36 | } 37 | 38 | @end 39 | ``` 40 | 41 | 图5.10 42 | 43 | 图5.10 应用透视效果之后再次对图层做旋转 -------------------------------------------------------------------------------- /chapter5/combining-transforms.md: -------------------------------------------------------------------------------- 1 | # 混合变换 2 | 3 | 4 | Core Graphics提供了一系列的函数可以在一个变换的基础上做更深层次的变换,如果做一个既要*缩放*又要*旋转*的变换,这就会非常有用了。例如下面几个函数: 5 | 6 | CGAffineTransformRotate(CGAffineTransform t, CGFloat angle) 7 | CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy) 8 | CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty) 9 | 10 | 当操纵一个变换的时候,初始生成一个什么都不做的变换很重要--也就是创建一个`CGAffineTransform`类型的空值,矩阵论中称作*单位矩阵*,Core Graphics同样也提供了一个方便的常量: 11 | 12 | CGAffineTransformIdentity 13 | 14 | 最后,如果需要混合两个已经存在的变换矩阵,就可以使用如下方法,在两个变换的基础上创建一个新的变换: 15 | 16 | CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2); 17 | 18 | 我们来用这些函数组合一个更加复杂的变换,先缩小50%,再旋转30度,最后向右移动200个像素(清单5.2)。图5.4显示了图层变换最后的结果。 19 | 20 | 清单5.2 使用若干方法创建一个复合变换 21 | 22 | ```objective-c 23 | - (void)viewDidLoad 24 | { 25 | [super viewDidLoad]; //create a new transform 26 | CGAffineTransform transform = CGAffineTransformIdentity; //scale by 50% 27 | transform = CGAffineTransformScale(transform, 0.5, 0.5); //rotate by 30 degrees 28 | transform = CGAffineTransformRotate(transform, M_PI / 180.0 * 30.0); //translate by 200 points 29 | transform = CGAffineTransformTranslate(transform, 200, 0); 30 | //apply transform to layer 31 | self.layerView.layer.affineTransform = transform; 32 | } 33 | ``` 34 | 35 | 图5.4 36 | 37 | 图5.4 顺序应用多个仿射变换之后的结果 38 | 39 | 图5.4中有些需要注意的地方:图片向右边发生了平移,但并没有指定距离那么远(200像素),另外它还有点向下发生了平移。原因在于当你按顺序做了变换,上一个变换的结果将会影响之后的变换,所以200像素的向右平移同样也被旋转了30度,缩小了50%,所以它实际上是斜向移动了100像素。 40 | 41 | 这意味着变换的顺序会影响最终的结果,也就是说旋转之后的平移和平移之后的旋转结果可能不同。 42 | -------------------------------------------------------------------------------- /chapter4/the-shadowpath-property.md: -------------------------------------------------------------------------------- 1 | # shadowPath属性 2 | 3 | 4 |     我们已经知道图层阴影并不总是方的,而是从图层内容的形状继承而来。这看上去不错,但是实时计算阴影也是一个非常消耗资源的,尤其是图层有多个子图层,每个图层还有一个有透明效果的寄宿图的时候。 5 | 6 |     如果你事先知道你的阴影形状会是什么样子的,你可以通过指定一个`shadowPath`来提高性能。`shadowPath`是一个`CGPathRef`类型(一个指向`CGPath`的指针)。`CGPath`是一个Core Graphics对象,用来指定任意的一个矢量图形。我们可以通过这个属性单独于图层形状之外指定阴影的形状。 7 | 8 | 图4.11 展示了同一寄宿图的不同阴影设定。如你所见,我们使用的图形很简单,但是它的阴影可以是你想要的任何形状。清单4.4是代码实现。 9 | 10 | ![图4.11](./4.11.png) 11 | 12 | 图4.11 用`shadowPath`指定任意阴影形状 13 | 14 | 清单4.4 创建简单的阴影形状 15 | 16 | ```objective-c 17 | @interface ViewController () 18 | 19 | @property (nonatomic, weak) IBOutlet UIView *layerView1; 20 | @property (nonatomic, weak) IBOutlet UIView *layerView2; 21 | @end 22 | 23 | @implementation ViewController 24 | 25 | - (void)viewDidLoad 26 | { 27 | [super viewDidLoad]; 28 | 29 | //enable layer shadows 30 | self.layerView1.layer.shadowOpacity = 0.5f; 31 | self.layerView2.layer.shadowOpacity = 0.5f; 32 | 33 | //create a square shadow 34 | CGMutablePathRef squarePath = CGPathCreateMutable(); 35 | CGPathAddRect(squarePath, NULL, self.layerView1.bounds); 36 | self.layerView1.layer.shadowPath = squarePath; CGPathRelease(squarePath); 37 | 38 | //create a circular shadow 39 | CGMutablePathRef circlePath = CGPathCreateMutable(); 40 | CGPathAddEllipseInRect(circlePath, NULL, self.layerView2.bounds); 41 | self.layerView2.layer.shadowPath = circlePath; CGPathRelease(circlePath); 42 | } 43 | @end 44 | ``` 45 | 46 |     如果是一个矩形或者是圆,用`CGPath`会相当简单明了。但是如果是更加复杂一点的图形,`UIBezierPath`类会更合适,它是一个由UIKit提供的在CGPath基础上的Objective-C包装类。 47 | -------------------------------------------------------------------------------- /chapter12/measure-dont-guess.md: -------------------------------------------------------------------------------- 1 | 2 | ##测量,而不是猜测 3 | 4 |     于是现在你知道有哪些点可能会影响动画性能,那该如何修复呢?好吧,其实不需要。有很多种诡计来优化动画,但如果盲目使用的话,可能会造成更多性能上的问题,而不是修复。 5 | 6 |     如何正确的测量而不是猜测这点很重要。根据性能相关的知识写出代码不同于仓促的优化。前者很好,后者实际上就是在浪费时间。 7 | 8 |     那该如何测量呢?第一步就是确保在真实环境下测试你的程序。 9 | 10 | ###真机测试,而不是模拟器 11 | 12 |     当你开始做一些性能方面的工作时,一定要在真机上测试,而不是模拟器。模拟器虽然是加快开发效率的一把利器,但它不能提供准确的真机性能参数。 13 | 14 |     模拟器运行在你的Mac上,然而Mac上的CPU往往比iOS设备要快。相反,Mac上的GPU和iOS设备的完全不一样,模拟器不得已要在软件层面(CPU)模拟设备的GPU,这意味着GPU相关的操作在模拟器上运行的更慢,尤其是使用`CAEAGLLayer`来写一些OpenGL的代码时候。 15 | 16 |     这就是说在模拟器上的测试出的性能会高度失真。如果动画在模拟器上运行流畅,可能在真机上十分糟糕。如果在模拟器上运行的很卡,也可能在真机上很平滑。你无法确定。 17 | 18 |     另一件重要的事情就是性能测试一定要用*发布*配置,而不是调试模式。因为当用发布环境打包的时候,编译器会引入一系列提高性能的优化,例如去掉调试符号或者移除并重新组织代码。你也可以自己做到这些,例如在发布环境禁用NSLog语句。你只关心发布性能,那才是你需要测试的点。 19 | 20 |     最后,最好在你支持的设备中性能最差的设备上测试:如果基于iOS6开发,这意味着最好在iPhone 3GS或者iPad2上测试。如果可能的话,测试不同的设备和iOS版本,因为苹果在不同的iOS版本和设备中做了一些改变,这也可能影响到一些性能。例如iPad3明显要在动画渲染上比iPad2慢很多,因为渲染4倍多的像素点(为了支持视网膜显示)。 21 | 22 | ###保持一致的帧率 23 | 24 |     为了做到动画的平滑,你需要以60FPS(帧每秒)的速度运行,以同步屏幕刷新速率。通过基于`NSTimer`或者`CADisplayLink`的动画你可以降低到30FPS,而且效果还不错,但是没办法通过Core Animation做到这点。如果不保持60FPS的速率,就可能随机丢帧,影响到体验。 25 | 26 |     你可以在使用的过程中明显感到有没有丢帧,但没办法通过肉眼来得到具体的数据,也没法知道你的做法有没有真的提高性能。你需要的是一系列精确的数据。 27 | 28 |     你可以在程序中用`CADisplayLink`来测量帧率(就像11章“基于定时器的动画”中那样),然后在屏幕上显示出来,但应用内的FPS显示并不能够完全真实测量出Core Animation性能,因为它仅仅测出应用内的帧率。我们知道很多动画都在应用之外发生(在渲染服务器进程中处理),但同时应用内FPS计数的确可以对某些性能问题提供参考,一旦找出一个问题的地方,你就需要得到更多精确详细的数据来定位到问题所在。苹果提供了一个强大的*Instruments*工具集来帮我们做到这些。 -------------------------------------------------------------------------------- /chapter5/creating-a-cgaffinetransform.md: -------------------------------------------------------------------------------- 1 | # 创建一个`CGAffineTransform` 2 | 3 | 4 | 对矩阵数学做一个全面的阐述就超出本书的讨论范围了,不过如果你对矩阵完全不熟悉的话,矩阵变换可能会使你感到畏惧。幸运的是,Core Graphics提供了一系列函数,对完全没有数学基础的开发者也能够简单地做一些变换。如下几个函数都创建了一个`CGAffineTransform`实例: 5 | 6 | CGAffineTransformMakeRotation(CGFloat angle) 7 | CGAffineTransformMakeScale(CGFloat sx, CGFloat sy) 8 | CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty) 9 | 10 | 旋转和缩放变换都可以很好解释--分别旋转或者缩放一个向量的值。平移变换是指每个点都移动了向量指定的x或者y值--所以如果向量代表了一个点,那它就平移了这个点的距离。 11 | 12 | 我们用一个很简单的项目来做个demo,把一个原始视图旋转45度角度(图5.3) 13 | 14 | 图5.3 15 | 16 | 图5.3 使用仿射变换旋转45度角之后的视图 17 | 18 | `UIView`可以通过设置`transform`属性做变换,但实际上它只是封装了内部图层的变换。 19 | 20 | `CALayer`同样也有一个`transform`属性,但它的类型是`CATransform3D`,而不是`CGAffineTransform`,本章后续将会详细解释。`CALayer`对应于`UIView`的`transform`属性叫做`affineTransform`,清单5.1的例子就是使用`affineTransform`对图层做了45度顺时针旋转。 21 | 22 | 清单5.1 使用`affineTransform`对图层旋转45度 23 | ```objective-c 24 | @interface ViewController () 25 | 26 | @property (nonatomic, weak) IBOutlet UIView *layerView; 27 | 28 | @end 29 | 30 | @implementation ViewController 31 | 32 | - (void)viewDidLoad 33 | { 34 | [super viewDidLoad]; 35 | //rotate the layer 45 degrees 36 | CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI_4); 37 | self.layerView.layer.affineTransform = transform; 38 | } 39 | 40 | @end 41 | ``` 42 | 43 | 注意我们使用的旋转常量是`M_PI_4`,而不是你想象的45,因为iOS的变换函数使用弧度而不是角度作为单位。弧度用数学常量pi的倍数表示,一个pi代表180度,所以四分之一的pi就是45度。 44 | 45 | C的数学函数库(iOS会自动引入)提供了pi的一些简便的换算,`M_PI_4`于是就是pi的四分之一,如果对换算不太清楚的话,可以用如下的宏做换算: 46 | 47 | #define RADIANS_TO_DEGREES(x) ((x)/M_PI*180.0) 48 | #define DEGREES_TO_RADIANS(x) ((x)/180.0*M_PI) -------------------------------------------------------------------------------- /chapter1/layers-and-trees.md: -------------------------------------------------------------------------------- 1 | ## 图层与视图 2 | 3 | 4 |     如果你曾经在iOS或者Mac OS平台上写过应用程序,你可能会对*视图*的概念比较熟悉。一个视图就是在屏幕上显示的一个矩形块(比如图片,文字或者视频),它能够拦截类似于鼠标点击或者触摸手势等用户输入。视图在层级关系中可以互相嵌套,一个视图可以管理它的所有子视图的位置。图1.1显示了一种典型的视图层级关系 5 | 6 | 图1.1 7 | 8 | 图1.1 一种典型的iOS屏幕(左边)和形成视图的层级关系(右边) 9 | 10 |   在iOS当中,所有的视图都从一个叫做`UIVIew`的基类派生而来,`UIView`可以处理触摸事件,可以支持基于*Core Graphics*绘图,可以做仿射变换(例如旋转或者缩放),或者简单的类似于滑动或者渐变的动画。 11 | 12 | ###CALayer 13 | `CALayer`类在概念上和`UIView`类似,同样也是一些被层级关系树管理的矩形块,同样也可以包含一些内容(像图片,文本或者背景色),管理子图层的位置。它们有一些方法和属性用来做动画和变换。和`UIView`最大的不同是`CALayer`不处理用户的交互。 14 | 15 | `CALayer`并不清楚具体的*响应链*(iOS通过视图层级关系用来传送触摸事件的机制),于是它并不能够响应事件,即使它提供了一些方法来判断是否一个触点在图层的范围之内(具体见第三章,“图层的几何学”) 16 | 17 | ### 平行的层级关系 18 |     每一个`UIview`都有一个`CALayer`实例的图层属性,也就是所谓的*backing layer*,视图的职责就是创建并管理这个图层,以确保当子视图在层级关系中添加或者被移除的时候,他们关联的图层也同样对应在层级关系树当中有相同的操作(见图1.2)。 19 | 图1.2 20 | 图1.2 图层的树状结构(左边)以及对应的视图层级(右边) 21 | 22 |     实际上这些背后关联的图层才是真正用来在屏幕上显示和做动画,`UIView`仅仅是对它的一个封装,提供了一些iOS类似于处理触摸的具体功能,以及Core Animation底层方法的高级接口。 23 | 24 |     但是为什么iOS要基于`UIView`和`CALayer`提供两个平行的层级关系呢?为什么不用一个简单的层级来处理所有事情呢?原因在于要做职责分离,这样也能避免很多重复代码。在iOS和Mac OS两个平台上,事件和用户交互有很多地方的不同,基于多点触控的用户界面和基于鼠标键盘有着本质的区别,这就是为什么iOS有UIKit和`UIView`,但是Mac OS有AppKit和`NSView`的原因。他们功能上很相似,但是在实现上有着显著的区别。 25 | 26 |     绘图,布局和动画,相比之下就是类似Mac笔记本和桌面系列一样应用于iPhone和iPad触屏的概念。把这种功能的逻辑分开并应用到独立的Core Animation框架,苹果就能够在iOS和Mac OS之间共享代码,使得对苹果自己的OS开发团队和第三方开发者去开发两个平台的应用更加便捷。 27 | 28 |     实际上,这里并不是两个层级关系,而是四个,每一个都扮演不同的角色,除了视图层级和图层树之外,还存在*呈现树*和*渲染树*,将在第七章“隐式动画”和第十二章“性能调优”分别讨论。 29 | 30 | 31 | -------------------------------------------------------------------------------- /chapter3/the-z-axis.md: -------------------------------------------------------------------------------- 1 | # Z坐标轴 2 | 3 | 4 |     和`UIView`严格的二维坐标系不同,`CALayer`存在于一个三维空间当中。除了我们已经讨论过的`position`和`anchorPoint`属性之外,`CALayer`还有另外两个属性,`zPosition`和`anchorPointZ`,二者都是在Z轴上描述图层位置的浮点类型。 5 | 6 |     注意这里并没有更*深*的属性来描述由宽和高做成的`bounds`了,图层是一个完全扁平的对象,你可以把它们想象成类似于一页二维的坚硬的纸片,用胶水粘成一个空洞,就像三维结构的折纸一样。 7 | 8 |     `zPosition`属性在大多数情况下其实并不常用。在第五章,我们将会涉及`CATransform3D`,你会知道如何在三维空间移动和旋转图层,除了做变换之外,`zPosition`最实用的功能就是改变图层的*显示顺序*了。 9 | 10 |     通常,图层是根据它们子图层的`sublayers`出现的顺序来类绘制的,这就是所谓的*画家的算法*--就像一个画家在墙上作画--后被绘制上的图层将会遮盖住之前的图层,但是通过增加图层的`zPosition`,就可以把图层向相机方向*前置*,于是它就在所有其他图层的*前面*了(或者至少是小于它的`zPosition`值的图层的前面)。 11 | 12 |     这里所谓的“相机”实际上是相对于用户是视角,这里和iPhone背后的内置相机没任何关系。 13 | 14 | 图3.8显示了在Interface Builder内的一对视图,正如你所见,首先出现在视图层级绿色的视图被绘制在红色视图的后面。 15 | 16 | 图3.8 17 | 18 | 图3.8 在视图层级中绿色视图被绘制在红色视图的后面 19 | 20 |     我们希望在真实的应用中也能显示出绘图的顺序,同样地,如果我们提高绿色视图的`zPosition`(清单3.3),我们会发现顺序就反了(图3.9)。其实并不需要增加太多,视图都非常地薄,所以给`zPosition`提高一个像素就可以让绿色视图前置,当然0.1或者0.0001也能够做到,但是最好不要这样,因为浮点类型四舍五入的计算可能会造成一些不便的麻烦。 21 | 22 | 清单3.3 23 | 24 | ```objective-c 25 | @interface ViewController () 26 | 27 | @property (nonatomic, weak) IBOutlet UIView *greenView; 28 | @property (nonatomic, weak) IBOutlet UIView *redView; 29 | 30 | @end 31 | 32 | @implementation ViewController 33 | 34 | - (void)viewDidLoad 35 | { 36 | [super viewDidLoad]; 37 |  38 | //move the green view zPosition nearer to the camera 39 | self.greenView.layer.zPosition = 1.0f; 40 | } 41 | @end 42 | ``` 43 | 44 | 图3.9 45 | 46 | 图3.9 绿色视图被绘制在红色视图的前面 -------------------------------------------------------------------------------- /chapter4/rounded-corners.md: -------------------------------------------------------------------------------- 1 | # 圆角 2 | 3 |     圆角矩形是iOS的一个标志性审美特性。这在iOS的每一个地方都得到了体现,不论是主屏幕图标,还是警告弹框,甚至是文本框。按照这流行程度,你可能会认为一定有不借助Photoshop就能轻易创建圆角举行的方法。恭喜你,猜对了。 4 | 5 |     CALayer有一个叫做`conrnerRadius`的属性控制着图层角的曲率。它是一个浮点数,默认为0(为0的时候就是直角),但是你可以把它设置成任意值。默认情况下,这个曲率值只影响背景颜色而不影响背景图片或是子图层。不过,如果把`masksToBounds`设置成YES的话,图层里面的所有东西都会被截取。 6 | 7 |     我们可以通过一个简单的项目来演示这个效果。在Interface Builder中,我们放置一些视图,他们有一些子视图。而且这些子视图有一些超出了边界(如图4.1)。你可能无法看到他们超出了边界,因为在编辑界面的时候,超出的部分总是被Interface Builder裁切掉了。不过,你相信我就好了 :) 8 | 9 | ![图4.1](./4.1.png) 10 | 11 | 图4.1 两个白色的大视图,他们都包含了小一些的红色视图。 12 | 13 |     然后在代码中,我们设置角的半径为20个点,并裁剪掉第一个视图的超出部分(见清单4.1)。技术上来说,这些属性都可以在Interface Builder的探测板中分别通过『用户定义运行时属性』和勾选『裁剪子视图』(Clip Subviews)选择框来直接设置属性的值。不过,在这个示例中,代码能够表示得更清楚。图4.2是运行代码的结果 14 | 15 | 清单4.1 设置`cornerRadius`和`masksToBounds` 16 | 17 | ```objective-c 18 | @interface ViewController () 19 | 20 | @property (nonatomic, weak) IBOutlet UIView *layerView1; 21 | @property (nonatomic, weak) IBOutlet UIView *layerView2; 22 | 23 | @end 24 | 25 | @implementation ViewController 26 | - (void)viewDidLoad 27 | { 28 | [super viewDidLoad]; 29 | 30 | //set the corner radius on our layers 31 | self.layerView1.layer.cornerRadius = 20.0f; 32 | self.layerView2.layer.cornerRadius = 20.0f; 33 | 34 | //enable clipping on the second layer 35 | self.layerView2.layer.masksToBounds = YES; 36 | } 37 | @end 38 | ``` 39 | 40 | ![图4.2](./4.2.png) 41 | 42 |     右图中,红色的子视图沿角半径被裁剪了 43 | 44 |     如你所见,右边的子视图沿边界被裁剪了。 45 | 46 |     单独控制每个层的圆角曲率也不是不可能的。如果想创建有些圆角有些直角的图层或视图时,你可能需要一些不同的方法。比如使用一个图层蒙板(本章稍后会讲到)或者是CAShapeLayer(见第六章『专用图层』)。 47 | -------------------------------------------------------------------------------- /chapter4/layer-masking.md: -------------------------------------------------------------------------------- 1 | # 图层蒙板 2 | 3 |     通过`masksToBounds`属性,我们可以沿边界裁剪图形;通过`cornerRadius`属性,我们还可以设定一个圆角。但是有时候你希望展现的内容不是在一个矩形或圆角矩形。比如,你想展示一个有星形框架的图片,又或者想让一些古卷文字慢慢渐变成背景色,而不是一个突兀的边界。 4 | 5 |     使用一个32位有alpha通道的png图片通常是创建一个无矩形视图最方便的方法,你可以给它指定一个透明蒙板来实现。但是这个方法不能让你以编码的方式动态地生成蒙板,也不能让子图层或子视图裁剪成同样的形状。 6 | 7 |     CALayer有一个属性叫做`mask`可以解决这个问题。这个属性本身就是个CALayer类型,有和其他图层一样的绘制和布局属性。它类似于一个子图层,相对于父图层(即拥有该属性的图层)布局,但是它却不是一个普通的子图层。不同于那些绘制在父图层中的子图层,`mask`图层定义了父图层的部分可见区域。 8 | 9 |     `mask`图层的`Color`属性是无关紧要的,真正重要的是图层的轮廓。`mask`属性就像是一个饼干切割机,`mask`图层实心的部分会被保留下来,其他的则会被抛弃。(如图4.12) 10 | 11 |     如果`mask`图层比父图层要小,只有在`mask`图层里面的内容才是它关心的,除此以外的一切都会被隐藏起来。 12 | 13 | ![图4.12](./4.12.png) 14 | 15 | 图4.12 把图片和蒙板图层作用在一起的效果 16 | 17 |     我们将代码演示一下这个过程,创建一个简单的项目,通过图层的`mask`属性来作用于图片之上。为了简便一些,我们用Interface Builder来创建一个包含UIImageView的图片图层。这样我们就只要代码实现蒙板图层了。清单4.5是最终的代码,图4.13是运行后的结果。 18 | 19 | 清单4.5 应用蒙板图层 20 | 21 | ```objective-c 22 | @interface ViewController () 23 | 24 | @property (nonatomic, weak) IBOutlet UIImageView *imageView; 25 | @end 26 | 27 | @implementation ViewController 28 | 29 | - (void)viewDidLoad 30 | { 31 | [super viewDidLoad]; 32 | 33 | //create mask layer 34 | CALayer *maskLayer = [CALayer layer]; 35 | maskLayer.frame = self.layerView.bounds; 36 | UIImage *maskImage = [UIImage imageNamed:@"Cone.png"]; 37 | maskLayer.contents = (__bridge id)maskImage.CGImage; 38 | 39 | //apply mask to image layer 40 | self.imageView.layer.mask = maskLayer; 41 | } 42 | @end 43 | ``` 44 | 45 | ![图4.13](./4.13.png) 46 | 47 | 图4.13 使用了`mask`之后的UIImageView 48 | 49 |     CALayer蒙板图层真正厉害的地方在于蒙板图不局限于静态图。任何有图层构成的都可以作为`mask`属性,这意味着你的蒙板可以通过代码甚至是动画实时生成。 50 | -------------------------------------------------------------------------------- /chapter5/the-sublayertransfrom-property.md: -------------------------------------------------------------------------------- 1 | # `sublayerTransform`属性 2 | 3 | 4 | 如果有多个视图或者图层,每个都做3D变换,那就需要分别设置相同的m34值,并且确保在变换之前都在屏幕中央共享同一个`position`,如果用一个函数封装这些操作的确会更加方便,但仍然有限制(例如,你不能在Interface Builder中摆放视图),这里有一个更好的方法。 5 | 6 | `CALayer`有一个属性叫做`sublayerTransform`。它也是`CATransform3D`类型,但和对一个图层的变换不同,它影响到所有的子图层。这意味着你可以一次性对包含这些图层的容器做变换,于是所有的子图层都自动继承了这个变换方法。 7 | 8 | 相较而言,通过在一个地方设置透视变换会很方便,同时它会带来另一个显著的优势:灭点被设置在*容器图层*的中点,从而不需要再对子图层分别设置了。这意味着你可以随意使用`position`和`frame`来放置子图层,而不需要把它们放置在屏幕中点,然后为了保证统一的灭点用变换来做平移。 9 | 10 | 我们来用一个demo举例说明。这里用Interface Builder并排放置两个视图(图5.12),然后通过设置它们容器视图的透视变换,我们可以保证它们有相同的透视和灭点,代码见清单5.6,结果见图5.13。 11 | 12 | 图5.12 13 | 14 | 图5.12 在一个视图容器内并排放置两个视图 15 | 16 | 清单5.6 应用`sublayerTransform` 17 | 18 | ```objective-c 19 | @interface ViewController () 20 | 21 | @property (nonatomic, weak) IBOutlet UIView *containerView; 22 | @property (nonatomic, weak) IBOutlet UIView *layerView1; 23 | @property (nonatomic, weak) IBOutlet UIView *layerView2; 24 | 25 | @end 26 | 27 | @implementation ViewController 28 | 29 | - (void)viewDidLoad 30 | { 31 | [super viewDidLoad]; 32 | //apply perspective transform to container 33 | CATransform3D perspective = CATransform3DIdentity; 34 | perspective.m34 = - 1.0 / 500.0; 35 | self.containerView.layer.sublayerTransform = perspective; 36 | //rotate layerView1 by 45 degrees along the Y axis 37 | CATransform3D transform1 = CATransform3DMakeRotation(M_PI_4, 0, 1, 0); 38 | self.layerView1.layer.transform = transform1; 39 | //rotate layerView2 by 45 degrees along the Y axis 40 | CATransform3D transform2 = CATransform3DMakeRotation(-M_PI_4, 0, 1, 0); 41 | self.layerView2.layer.transform = transform2; 42 | } 43 | ``` 44 | 45 | 图5.13 46 | 47 | 图5.13 通过相同的透视效果分别对视图做变换 48 | -------------------------------------------------------------------------------- /chapter6/creating-a-cgpath.md: -------------------------------------------------------------------------------- 1 | # 创建一个`CGPath` 2 | 3 | 4 | `CAShapeLayer`可以用来绘制所有能够通过`CGPath`来表示的形状。这个形状不一定要闭合,图层路径也不一定要不可破,事实上你可以在一个图层上绘制好几个不同的形状。你可以控制一些属性比如`lineWith`(线宽,用点表示单位),`lineCap`(线条结尾的样子),和`lineJoin`(线条之间的结合点的样子);但是在图层层面你只有一次机会设置这些属性。如果你想用不同颜色或风格来绘制多个形状,就不得不为每个形状准备一个图层了。 5 | 6 | 清单6.1 的代码用一个`CAShapeLayer`渲染一个简单的火柴人。`CAShapeLayer`属性是`CGPathRef`类型,但是我们用`UIBezierPath`帮助类创建了图层路径,这样我们就不用考虑人工释放`CGPath`了。图6.1是代码运行的结果。虽然还不是很完美,但是总算知道了大意对吧! 7 | 8 | 清单6.1 用`CAShapeLayer`绘制一个火柴人 9 | 10 | ```objective-c 11 | #import "DrawingView.h" 12 | #import 13 | 14 | @interface ViewController () 15 | 16 | @property (nonatomic, weak) IBOutlet UIView *containerView; 17 | 18 | @end 19 | 20 | @implementation ViewController 21 | 22 | - (void)viewDidLoad 23 | { 24 | [super viewDidLoad]; 25 | //create path 26 | UIBezierPath *path = [[UIBezierPath alloc] init]; 27 | [path moveToPoint:CGPointMake(175, 100)]; 28 |  29 | [path addArcWithCenter:CGPointMake(150, 100) radius:25 startAngle:0 endAngle:2*M_PI clockwise:YES]; 30 | [path moveToPoint:CGPointMake(150, 125)]; 31 | [path addLineToPoint:CGPointMake(150, 175)]; 32 | [path addLineToPoint:CGPointMake(125, 225)]; 33 | [path moveToPoint:CGPointMake(150, 175)]; 34 | [path addLineToPoint:CGPointMake(175, 225)]; 35 | [path moveToPoint:CGPointMake(100, 150)]; 36 | [path addLineToPoint:CGPointMake(200, 150)]; 37 | 38 | //create shape layer 39 | CAShapeLayer *shapeLayer = [CAShapeLayer layer]; 40 | shapeLayer.strokeColor = [UIColor redColor].CGColor; 41 | shapeLayer.fillColor = [UIColor clearColor].CGColor; 42 | shapeLayer.lineWidth = 5; 43 | shapeLayer.lineJoin = kCALineJoinRound; 44 | shapeLayer.lineCap = kCALineCapRound; 45 | shapeLayer.path = path.CGPath; 46 | //add it to our view 47 | [self.containerView.layer addSublayer:shapeLayer]; 48 | } 49 | @end 50 | ``` 51 | 52 | ![图6.1](./6.1.png) 53 | 54 | 图6.1 用`CAShapeLayer`绘制一个简单的火柴人 55 | -------------------------------------------------------------------------------- /chapter2/contentscenter.md: -------------------------------------------------------------------------------- 1 | # contentsCenter 2 | 3 |     本章我们介绍的最后一个和内容有关的属性是`contentsCenter`,看名字你可能会以为它可能跟图片的位置有关,不过这名字着实误导了你。`contentsCenter`其实是一个CGRect,它定义了一个固定的边框和一个在图层上可拉伸的区域。 改变`contentsCenter`的值并不会影响到寄宿图的显示,除非这个图层的大小改变了,你才看得到效果。 4 | 5 |     默认情况下,`contentsCenter`是{0, 0, 1, 1},这意味着如果大小(由`conttensGravity`决定)改变了,那么寄宿图将会均匀地拉伸开。但是如果我们增加原点的值并减小尺寸。我们会在图片的周围创造一个边框。图2.9展示了`contentsCenter`设置为{0.25, 0.25, 0.5, 0.5}的效果。 6 | 7 | ![图2.9](./2.9.png) 8 | 9 | 图2.9 `contentsCenter`的例子 10 | 11 |     这意味着我们可以随意重设尺寸,边框仍然会是连续的。他工作起来的效果和UIImage里的-resizableImageWithCapInsets: 方法效果非常类似,只是它可以运用到任何寄宿图,甚至包括在Core Graphics运行时绘制的图形(本章稍后会讲到)。 12 | 13 | ![图2.10](./2.10.png) 14 | 15 | 图2.10 同一图片使用不同的`contentsCenter` 16 | 17 |     清单2.4 演示了如何编写这些可拉伸视图。不过,contentsCenter的另一个很酷的特性就是,它可以在Interface Builder里面配置,根本不用写代码。如图2.11 18 | 19 | 清单2.4 用`contentsCenter`设置可拉伸视图 20 | 21 | ```objective-c 22 | @interface ViewController () 23 | 24 | @property (nonatomic, weak) IBOutlet UIView *button1; 25 | @property (nonatomic, weak) IBOutlet UIView *button2; 26 | 27 | @end 28 | 29 | @implementation ViewController 30 | 31 | - (void)addStretchableImage:(UIImage *)image withContentCenter:(CGRect)rect toLayer:(CALayer *)layer 32 | { 33 | //set image 34 | layer.contents = (__bridge id)image.CGImage; 35 | 36 | //set contentsCenter 37 | layer.contentsCenter = rect; 38 | } 39 | 40 | - (void)viewDidLoad 41 | { 42 | [super viewDidLoad]; //load button image 43 | UIImage *image = [UIImage imageNamed:@"Button.png"]; 44 | 45 | //set button 1 46 | [self addStretchableImage:image withContentCenter:CGRectMake(0.25, 0.25, 0.5, 0.5) toLayer:self.button1.layer]; 47 | 48 | //set button 2 49 | [self addStretchableImage:image withContentCenter:CGRectMake(0.25, 0.25, 0.5, 0.5) toLayer:self.button2.layer]; 50 | } 51 | 52 | @end 53 | ``` 54 | ![图2.11](./2.11.png) 55 | 56 | 图2.11 用Interface Builder 探测窗口控制`contentsCenter`属性 57 | -------------------------------------------------------------------------------- /chapter9/hierarchical-time.md: -------------------------------------------------------------------------------- 1 | # 层级关系时间 2 | 3 | 4 | 在第三章“图层几何学”中,你已经了解到每个图层是如何相对在图层树中的父图层定义它的坐标系的。动画时间和它类似,每个动画和图层在时间上都有它自己的层级概念,相对于它的父亲来测量。对图层调整时间将会影响到它本身和子图层的动画,但不会影响到父图层。另一个相似点是所有的动画都被按照层级组合(使用`CAAnimationGroup`实例)。 5 | 6 | 对`CALayer`或者`CAGroupAnimation`调整`duration`和`repeatCount`/`repeatDuration`属性并不会影响到子动画。但是`beginTime`,`timeOffset`和`speed`属性将会影响到子动画。然而在层级关系中,`beginTime`指定了父图层开始动画(或者组合关系中的父动画)和对象将要开始自己动画之间的偏移。类似的,调整`CALayer`和`CAGroupAnimation`的`speed`属性将会对动画以及子动画速度应用一个缩放的因子。 7 | 8 | ###全局时间和本地时间 9 | 10 | CoreAnimation有一个*全局时间*的概念,也就是所谓的*马赫时间*(“马赫”实际上是iOS和Mac OS系统内核的命名)。马赫时间在设备上所有进程都是全局的--但是在不同设备上并不是全局的--不过这已经足够对动画的参考点提供便利了,你可以使用`CACurrentMediaTime`函数来访问马赫时间: 11 | 12 | CFTimeInterval time = CACurrentMediaTime(); 13 | 14 | 15 | 这个函数返回的值其实无关紧要(它返回了设备自从上次启动后的秒数,并不是你所关心的),它真实的作用在于对动画的时间测量提供了一个相对值。注意当设备休眠的时候马赫时间会暂停,也就是所有的`CAAnimations`(基于马赫时间)同样也会暂停。 16 | 17 | 18 | 因此马赫时间对长时间测量并不有用。比如用`CACurrentMediaTime`去更新一个实时闹钟并不明智。(可以用`[NSDate date]`代替,就像第三章例子所示)。 19 | 20 | 21 | 每个`CALayer`和`CAAnimation`实例都有自己*本地*时间的概念,是根据父图层/动画层级关系中的`beginTime`,`timeOffset`和`speed`属性计算。就和转换不同图层之间坐标关系一样,`CALayer`同样也提供了方法来转换不同图层之间的*本地时间*。如下: 22 | 23 | - (CFTimeInterval)convertTime:(CFTimeInterval)t fromLayer:(CALayer *)l; 24 | - (CFTimeInterval)convertTime:(CFTimeInterval)t toLayer:(CALayer *)l; 25 | 26 | 当用来同步不同图层之间有不同的`speed`,`timeOffset`和`beginTime`的动画,这些方法会很有用。 27 | 28 | 29 | ###暂停,倒回和快进 30 | 31 | 设置动画的`speed`属性为0可以暂停动画,但在动画被添加到图层之后不太可能再修改它了,所以不能对正在进行的动画使用这个属性。给图层添加一个`CAAnimation`实际上是给动画对象做了一个不可改变的拷贝,所以对原始动画对象属性的改变对真实的动画并没有作用。相反,直接用`-animationForKey:`来检索图层正在进行的动画可以返回正确的动画对象,但是修改它的属性将会抛出异常。 32 | 33 | 如果移除图层正在进行的动画,图层将会急速返回动画之前的状态。但如果在动画移除之前拷贝呈现图层到模型图层,动画将会看起来暂停在那里。但是不好的地方在于之后就不能再恢复动画了。 34 | 35 | 一个简单的方法是可以利用`CAMediaTiming`来暂停*图层*本身。如果把图层的`speed`设置成0,它会暂停任何添加到图层上的动画。类似的,设置`speed`大于1.0将会快进,设置成一个负值将会倒回动画。 36 | 37 | 38 | 通过增加主窗口图层的`speed`,可以暂停整个应用程序的动画。这对UI自动化提供了好处,我们可以加速所有的视图动画来进行自动化测试(注意对于在主窗口之外的视图并不会被影响,比如`UIAlertview`)。可以在app delegate设置如下进行验证: 39 | 40 | self.window.layer.speed = 100; 41 | 42 | 你也可以通过这种方式来*减速*,但其实也可以在模拟器通过切换慢速动画来实现。 43 | -------------------------------------------------------------------------------- /chapter2/contentscale.md: -------------------------------------------------------------------------------- 1 | # contentsScale 2 | 3 |     `contentsScale`属性定义了寄宿图的像素尺寸和视图大小的比例,默认情况下它是一个值为1.0的浮点数。 4 | 5 |     `contentsScale`的目的并不是那么明显。它并不是总会对屏幕上的寄宿图有影响。如果你尝试对我们的例子设置不同的值,你就会发现根本没任何影响。因为`contents`由于设置了`contentsGravity`属性,所以它已经被拉伸以适应图层的边界。 6 | 7 |     如果你只是单纯地想放大图层的`contents`图片,你可以通过使用图层的`transform`和`affineTransform`属性来达到这个目的(见第五章『Transforms』,里面对此有解释),这(指放大)也不是`contengsScale`的目的所在. 8 | 9 |     `contentsScale`属性其实属于支持高分辨率(又称Hi-DPI或Retina)屏幕机制的一部分。它用来判断在绘制图层的时候应该为寄宿图创建的空间大小,和需要显示的图片的拉伸度(假设并没有设置`contentsGravity`属性)。UIView有一个类似功能但是非常少用到的`contentScaleFactor`属性。 10 | 11 |     如果`contentsScale`设置为1.0,将会以每个点1个像素绘制图片,如果设置为2.0,则会以每个点2个像素绘制图片,这就是我们熟知的Retina屏幕。(如果你对像素和点的概念不是很清楚的话,这个章节的后面部分将会对此做出解释)。 12 | 13 |     这并不会对我们在使用kCAGravityResizeAspect时产生任何影响,因为它就是拉伸图片以适应图层而已,根本不会考虑到分辨率问题。但是如果我们把`contentsGravity`设置为kCAGravityCenter(这个值并不会拉伸图片),那将会有很明显的变化(如图2.3) 14 | 15 | ![图2.3](./2.3.png) 16 | 17 | 图2.3 用错误的`contentsScale`属性显示Retina图片 18 | 19 |     如你所见,我们的雪人不仅有点大还有点像素的颗粒感。那是因为和UIImage不同,CGImage没有拉伸的概念。当我们使用UIImage类去读取我们的雪人图片的时候,他读取了高质量的Retina版本的图片。但是当我们用CGImage来设置我们的图层的内容时,拉伸这个因素在转换的时候就丢失了。不过我们可以通过手动设置`contentsScale`来修复这个问题(如2.2清单),图2.4是结果 20 | 21 | ```objective-c 22 | @implementation ViewController 23 | 24 | - (void)viewDidLoad 25 | { 26 | [super viewDidLoad]; //load an image 27 | UIImage *image = [UIImage imageNamed:@"Snowman.png"]; //add it directly to our view's layer 28 | self.layerView.layer.contents = (__bridge id)image.CGImage; //center the image 29 | self.layerView.layer.contentsGravity = kCAGravityCenter; 30 | 31 | //set the contentsScale to match image 32 | self.layerView.layer.contentsScale = image.scale; 33 | } 34 | 35 | @end 36 | ``` 37 | 38 | ![图2.4](./2.4.png) 39 | 40 | 图2.4 同样的Retina图片设置了正确的`contentsScale`之后 41 | 42 |     当用代码的方式来处理寄宿图的时候,一定要记住要手动的设置图层的`contentsScale`属性,否则,你的图片在Retina设备上就显示得不正确啦。代码如下: 43 | 44 | ```objective-c 45 | layer.contentsScale = [UIScreen mainScreen].scale; 46 | ``` 47 | -------------------------------------------------------------------------------- /chapter4/shadow-clipping.md: -------------------------------------------------------------------------------- 1 | # 阴影裁剪 2 | 3 | 4 | &nbps;   和图层边框不同,图层的阴影继承自内容的外形,而不是根据边界和角半径来确定。为了计算出阴影的形状,Core Animation会将寄宿图(包括子视图,如果有的话)考虑在内,然后通过这些来完美搭配图层形状从而创建一个阴影(见图4.7)。 5 | 6 | ![图4.7](./4.7.png) 7 | 8 | 图4.7 阴影是根据寄宿图的轮廓来确定的 9 | 10 | &nbps;   当阴影和裁剪扯上关系的时候就有一个头疼的限制:阴影通常就是在Layer的边界之外,如果你开启了`masksToBounds`属性,所有从图层中突出来的内容都会被才剪掉。如果我们在我们之前的边框示例项目中增加图层的阴影属性时,你就会发现问题所在(见图4.8). 11 | 12 | ![图4.8](./4.8.png) 13 | 14 | 图4.8 `maskToBounds`属性裁剪掉了阴影和内容 15 | 16 | &nbps;   从技术角度来说,这个结果是可以是可以理解的,但确实又不是我们想要的效果。如果你想沿着内容裁切,你需要用到两个图层:一个只画阴影的空的外图层,和一个用`masksToBounds`裁剪内容的内图层。 17 | 18 | &nbps;   如果我们把之前项目的右边用单独的视图把裁剪的视图包起来,我们就可以解决这个问题(如图4.9). 19 | 20 | ![图4.9](./4.9.png) 21 | 22 | 图4.9 右边,用额外的阴影转换视图包裹被裁剪的视图 23 | 24 | &nbps;   我们只把阴影用在最外层的视图上,内层视图进行裁剪。清单4.3是代码实现,图4.10是运行结果。 25 | 26 | 清单4.3 用一个额外的视图来解决阴影裁切的问题 27 | 28 | ```objective-c 29 | @interface ViewController () 30 | 31 | @property (nonatomic, weak) IBOutlet UIView *layerView1; 32 | @property (nonatomic, weak) IBOutlet UIView *layerView2; 33 | @property (nonatomic, weak) IBOutlet UIView *shadowView; 34 | 35 | @end 36 | 37 | @implementation ViewController 38 |  39 | - (void)viewDidLoad 40 | { 41 | [super viewDidLoad]; 42 | 43 | //set the corner radius on our layers 44 | self.layerView1.layer.cornerRadius = 20.0f; 45 | self.layerView2.layer.cornerRadius = 20.0f; 46 | 47 | //add a border to our layers 48 | self.layerView1.layer.borderWidth = 5.0f; 49 | self.layerView2.layer.borderWidth = 5.0f; 50 | 51 | //add a shadow to layerView1 52 | self.layerView1.layer.shadowOpacity = 0.5f; 53 | self.layerView1.layer.shadowOffset = CGSizeMake(0.0f, 5.0f); 54 | self.layerView1.layer.shadowRadius = 5.0f; 55 | 56 | //add same shadow to shadowView (not layerView2) 57 | self.shadowView.layer.shadowOpacity = 0.5f; 58 | self.shadowView.layer.shadowOffset = CGSizeMake(0.0f, 5.0f); 59 | self.shadowView.layer.shadowRadius = 5.0f; 60 | 61 | //enable clipping on the second layer 62 | self.layerView2.layer.masksToBounds = YES; 63 | } 64 | 65 | @end 66 | ``` 67 | 68 | ![图4.10](./4.10.png) 69 | 70 | 图4.10 右边视图,不受裁切阴影的阴影视图。 -------------------------------------------------------------------------------- /chapter8/animation-groups.md: -------------------------------------------------------------------------------- 1 | # 动画组 2 | 3 | ##动画组 4 | 5 | `CABasicAnimation`和`CAKeyframeAnimation`仅仅作用于单独的属性,而`CAAnimationGroup`可以把这些动画组合在一起。`CAAnimationGroup`是另一个继承于`CAAnimation`的子类,它添加了一个`animations`数组的属性,用来组合别的动画。我们把清单8.6那种关键帧动画和调整图层背景色的基础动画组合起来(清单8.10),结果如图8.3所示。 6 | 7 | 清单8.10 组合关键帧动画和基础动画 8 | 9 | ```objective-c 10 | - (void)viewDidLoad 11 | { 12 | [super viewDidLoad]; 13 | //create a path 14 | UIBezierPath *bezierPath = [[UIBezierPath alloc] init]; 15 | [bezierPath moveToPoint:CGPointMake(0, 150)]; 16 | [bezierPath addCurveToPoint:CGPointMake(300, 150) controlPoint1:CGPointMake(75, 0) controlPoint2:CGPointMake(225, 300)]; 17 | //draw the path using a CAShapeLayer 18 | CAShapeLayer *pathLayer = [CAShapeLayer layer]; 19 | pathLayer.path = bezierPath.CGPath; 20 | pathLayer.fillColor = [UIColor clearColor].CGColor; 21 | pathLayer.strokeColor = [UIColor redColor].CGColor; 22 | pathLayer.lineWidth = 3.0f; 23 | [self.containerView.layer addSublayer:pathLayer]; 24 | //add a colored layer 25 | CALayer *colorLayer = [CALayer layer]; 26 | colorLayer.frame = CGRectMake(0, 0, 64, 64); 27 | colorLayer.position = CGPointMake(0, 150); 28 | colorLayer.backgroundColor = [UIColor greenColor].CGColor; 29 | [self.containerView.layer addSublayer:colorLayer]; 30 | //create the position animation 31 | CAKeyframeAnimation *animation1 = [CAKeyframeAnimation animation]; 32 | animation1.keyPath = @"position"; 33 | animation1.path = bezierPath.CGPath; 34 | animation1.rotationMode = kCAAnimationRotateAuto; 35 | //create the color animation 36 | CABasicAnimation *animation2 = [CABasicAnimation animation]; 37 | animation2.keyPath = @"backgroundColor"; 38 | animation2.toValue = (__bridge id)[UIColor redColor].CGColor; 39 | //create group animation 40 | CAAnimationGroup *groupAnimation = [CAAnimationGroup animation]; 41 | groupAnimation.animations = @[animation1, animation2]; 42 | groupAnimation.duration = 4.0; 43 | //add the animation to the color layer 44 | [colorLayer addAnimation:groupAnimation forKey:nil]; 45 | } 46 | ``` 47 | 48 | 图8.3 49 | 50 | 图8.3 关键帧路径和基础动画的组合 51 | -------------------------------------------------------------------------------- /chapter8/changes-custom-transitions.md: -------------------------------------------------------------------------------- 1 | # 在动画过程中取消动画 2 | 3 | 4 | 之前提到过,你可以用`-addAnimation:forKey:`方法中的`key`参数来在添加动画之后检索一个动画,使用如下方法: 5 | 6 | - (CAAnimation *)animationForKey:(NSString *)key; 7 | 8 | 但并不支持在动画运行过程中修改动画,所以这个方法主要用来检测动画的属性,或者判断它是否被添加到当前图层中。 9 | 10 | 为了终止一个指定的动画,你可以用如下方法把它从图层移除掉: 11 | 12 | - (void)removeAnimationForKey:(NSString *)key; 13 | 14 | 或者移除所有动画: 15 | 16 | - (void)removeAllAnimations; 17 | 18 | 动画一旦被移除,图层的外观就立刻更新到当前的模型图层的值。一般说来,动画在结束之后被自动移除,除非设置`removedOnCompletion`为`NO`,如果你设置动画在结束之后不被自动移除,那么当它不需要的时候你要手动移除它;否则它会一直存在于内存中,直到图层被销毁。 19 | 20 | 我们来扩展之前旋转飞船的示例,这里添加一个按钮来停止或者启动动画。这一次我们用一个非`nil`的值作为动画的键,以便之后可以移除它。`-animationDidStop:finished:`方法中的`flag`参数表明了动画是自然结束还是被打断,我们可以在控制台打印出来。如果你用停止按钮来终止动画,它会打印`NO`,如果允许它完成,它会打印`YES`。 21 | 22 | 清单8.15是更新后的示例代码,图8.6显示了结果。 23 | 24 | 清单8.15 开始和停止一个动画 25 | 26 | ```objective-c 27 | @interface ViewController () 28 | 29 | @property (nonatomic, weak) IBOutlet UIView *containerView; 30 | @property (nonatomic, strong) CALayer *shipLayer; 31 | 32 | @end 33 | 34 | @implementation ViewController 35 | 36 | - (void)viewDidLoad 37 | { 38 | [super viewDidLoad]; 39 | //add the ship 40 | self.shipLayer = [CALayer layer]; 41 | self.shipLayer.frame = CGRectMake(0, 0, 128, 128); 42 | self.shipLayer.position = CGPointMake(150, 150); 43 | self.shipLayer.contents = (__bridge id)[UIImage imageNamed: @"Ship.png"].CGImage; 44 | [self.containerView.layer addSublayer:self.shipLayer]; 45 | } 46 | 47 | - (IBAction)start 48 | { 49 | //animate the ship rotation 50 | CABasicAnimation *animation = [CABasicAnimation animation]; 51 | animation.keyPath = @"transform.rotation"; 52 | animation.duration = 2.0; 53 | animation.byValue = @(M_PI * 2); 54 | animation.delegate = self; 55 | [self.shipLayer addAnimation:animation forKey:@"rotateAnimation"]; 56 | } 57 | 58 | - (IBAction)stop 59 | { 60 | [self.shipLayer removeAnimationForKey:@"rotateAnimation"]; 61 | } 62 | 63 | - (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag 64 | { 65 | //log that the animation stopped 66 | NSLog(@"The animation stopped (finished: %@)", flag? @"YES": @"NO"); 67 | } 68 | 69 | @end 70 | ``` 71 | 72 | 图8.6 73 | 74 | 图8.6 通过开始和停止按钮控制的旋转动画 75 | -------------------------------------------------------------------------------- /chapter3/coordinate-systens.md: -------------------------------------------------------------------------------- 1 | # 坐标系 2 | 3 | 和视图一样,图层在图层树当中也是相对于父图层按层级关系放置,一个图层的`position`依赖于它父图层的`bounds`,如果父图层发生了移动,它的所有子图层也会跟着移动。 4 | 5 | 这样对于放置图层会更加方便,因为你可以通过移动根图层来将它的子图层作为一个整体来移动,但是有时候你需要知道一个图层的*绝对*位置,或者是相对于另一个图层的位置,而不是它当前父图层的位置。 6 | 7 | `CALayer`给不同坐标系之间的图层转换提供了一些工具类方法: 8 | 9 | - (CGPoint)convertPoint:(CGPoint)point fromLayer:(CALayer *)layer; 10 | - (CGPoint)convertPoint:(CGPoint)point toLayer:(CALayer *)layer; 11 | - (CGRect)convertRect:(CGRect)rect fromLayer:(CALayer *)layer; 12 | - (CGRect)convertRect:(CGRect)rect toLayer:(CALayer *)layer; 13 | 14 | 这些方法可以把定义在一个图层坐标系下的点或者矩形转换成另一个图层坐标系下的点或者矩形. 15 | 16 | ## 翻转的几何结构 17 | 18 |     常规说来,在iOS上,一个图层的`position`位于父图层的左上角,但是在Mac OS上,通常是位于左下角。Core Animation可以通过`geometryFlipped`属性来适配这两种情况,它决定了一个图层的坐标是否相对于父图层垂直翻转,是一个`BOOL`类型。在iOS上通过设置它为`YES`意味着它的子图层将会被垂直翻转,也就是将会沿着底部排版而不是通常的顶部(它的所有子图层也同理,除非把它们的`geometryFlipped`属性也设为`YES`)。 19 | 20 | ## Z坐标轴 21 | 22 | 23 |     和`UIView`严格的二维坐标系不同,`CALayer`存在于一个三维空间当中。除了我们已经讨论过的`position`和`anchorPoint`属性之外,`CALayer`还有另外两个属性,`zPosition`和`anchorPointZ`,二者都是在Z轴上描述图层位置的浮点类型。 24 | 25 |     注意这里并没有更*深*的属性来描述由宽和高做成的`bounds`了,图层是一个完全扁平的对象,你可以把它们想象成类似于一页二维的坚硬的纸片,用胶水粘成一个空洞,就像三维结构的折纸一样。 26 | 27 |     `zPosition`属性在大多数情况下其实并不常用。在第五章,我们将会涉及`CATransform3D`,你会知道如何在三维空间移动和旋转图层,除了做变换之外,`zPosition`最实用的功能就是改变图层的*显示顺序*了。 28 | 29 |     通常,图层是根据它们子图层的`sublayers`出现的顺序来类绘制的,这就是所谓的*画家的算法*--就像一个画家在墙上作画--后被绘制上的图层将会遮盖住之前的图层,但是通过增加图层的`zPosition`,就可以把图层向相机方向*前置*,于是它就在所有其他图层的*前面*了(或者至少是小于它的`zPosition`值的图层的前面)。 30 | 31 |     这里所谓的“相机”实际上是相对于用户是视角,这里和iPhone背后的内置相机没任何关系。 32 | 33 | 图3.8显示了在Interface Builder内的一对视图,正如你所见,首先出现在视图层级绿色的视图被绘制在红色视图的后面。 34 | 35 | 图3.8 36 | 37 | 图3.8 在视图层级中绿色视图被绘制在红色视图的后面 38 | 39 |     我们希望在真实的应用中也能显示出绘图的顺序,同样地,如果我们提高绿色视图的`zPosition`(清单3.3),我们会发现顺序就反了(图3.9)。其实并不需要增加太多,视图都非常地薄,所以给`zPosition`提高一个像素就可以让绿色视图前置,当然0.1或者0.0001也能够做到,但是最好不要这样,因为浮点类型四舍五入的计算可能会造成一些不便的麻烦。 40 | 41 | 清单3.3 42 | 43 | ```objective-c 44 | @interface ViewController () 45 | 46 | @property (nonatomic, weak) IBOutlet UIView *greenView; 47 | @property (nonatomic, weak) IBOutlet UIView *redView; 48 | 49 | @end 50 | 51 | @implementation ViewController 52 | 53 | - (void)viewDidLoad 54 | { 55 | [super viewDidLoad]; 56 |  57 | //move the green view zPosition nearer to the camera 58 | self.greenView.layer.zPosition = 1.0f; 59 | } 60 | @end 61 | ``` 62 | 63 | 图3.9 64 | 65 | 图3.9 绿色视图被绘制在红色视图的前面 -------------------------------------------------------------------------------- /chapter6/caemitterlayer.md: -------------------------------------------------------------------------------- 1 | # CAEmitterLayer 2 | 3 | 4 | 在iOS 5中,苹果引入了一个新的`CALayer`子类叫做`CAEmitterLayer`。`CAEmitterLayer`是一个高性能的粒子引擎,被用来创建实时例子动画如:烟雾,火,雨等等这些效果。 5 | 6 | `CAEmitterLayer`看上去像是许多`CAEmitterCell`的容器,这些`CAEmitierCell`定义了一个例子效果。你将会为不同的例子效果定义一个或多个`CAEmitterCell`作为模版,同时`CAEmitterLayer`负责基于这些模版实例化一个粒子流。一个`CAEmitterCell`类似于一个`CALayer`:它有一个`contents`属性可以定义为一个`CGImage`,另外还有一些可设置属性控制着表现和行为。我们不会对这些属性逐一进行详细的描述,你们可以在`CAEmitterCell`类的头文件中找到。 7 | 8 | 我们来举个例子。我们将利用在一圆中发射不同速度和透明度的粒子创建一个火爆炸的效果。清单6.13包含了生成爆炸的代码。图6.13是运行结果 9 | 10 | 清单6.13 用`CAEmitterLayer`创建爆炸效果 11 | 12 | ```objetive-c 13 | #import "ViewController.h" 14 | #import 15 | 16 | @interface ViewController () 17 | 18 | @property (nonatomic, weak) IBOutlet UIView *containerView; 19 | 20 | @end 21 | 22 | 23 | @implementation ViewController 24 | 25 | - (void)viewDidLoad 26 | { 27 | [super viewDidLoad]; 28 |  29 | //create particle emitter layer 30 | CAEmitterLayer *emitter = [CAEmitterLayer layer]; 31 | emitter.frame = self.containerView.bounds; 32 | [self.containerView.layer addSublayer:emitter]; 33 | 34 | //configure emitter 35 | emitter.renderMode = kCAEmitterLayerAdditive; 36 | emitter.emitterPosition = CGPointMake(emitter.frame.size.width / 2.0, emitter.frame.size.height / 2.0); 37 | 38 | //create a particle template 39 | CAEmitterCell *cell = [[CAEmitterCell alloc] init]; 40 | cell.contents = (__bridge id)[UIImage imageNamed:@"Spark.png"].CGImage; 41 | cell.birthRate = 150; 42 | cell.lifetime = 5.0; 43 | cell.color = [UIColor colorWithRed:1 green:0.5 blue:0.1 alpha:1.0].CGColor; 44 | cell.alphaSpeed = -0.4; 45 | cell.velocity = 50; 46 | cell.velocityRange = 50; 47 | cell.emissionRange = M_PI * 2.0; 48 | 49 | //add particle template to emitter 50 | emitter.emitterCells = @[cell]; 51 | } 52 | @end 53 | ``` 54 | 55 | 图6.13 火焰爆炸效果 56 | 57 | `CAEMitterCell`的属性基本上可以分为三种: 58 | 59 | * 这种粒子的某一属性的初始值。比如,`color`属性指定了一个可以混合图片内容颜色的混合色。在示例中,我们将它设置为桔色。 60 | * 例子某一属性的变化范围。比如`emissionRange`属性的值是2π,这意味着例子可以从360度任意位置反射出来。如果指定一个小一些的值,就可以创造出一个圆锥形 61 | * 指定值在时间线上的变化。比如,在示例中,我们将`alphaSpeed`设置为-0.4,就是说例子的透明度每过一秒就是减少0.4,这样就有发射出去之后逐渐小时的效果。 62 | 63 | `CAEmitterLayer`的属性它自己控制着整个例子系统的位置和形状。一些属性比如`birthRate`,`lifetime`和`celocity`,这些属性在`CAEmitterCell`中也有。这些属性会以相乘的方式作用在一起,这样你就可以用一个值来加速或者扩大整个例子系统。其他值得提到的属性有以下这些: 64 | 65 | * `preservesDepth`,是否将3D例子系统平面化到一个图层(默认值)或者可以在3D空间中混合其他的图层 66 | * `renderMode`,控制着在视觉上粒子图片是如何混合的。你可能已经注意到了示例中我们把它设置为`kCAEmitterLayerAdditive`,它实现了这样一个效果:合并例子重叠部分的亮度使得看上去更亮。如果我们把它设置为默认的`kCAEmitterLayerUnordered`,效果就没那么好看了(见图6.14). 67 | 68 | ![图6.14](./6.14.png) 69 | 70 | 图6.14 禁止混色之后的火焰粒子 71 | -------------------------------------------------------------------------------- /chapter9/manual-animation.md: -------------------------------------------------------------------------------- 1 | 2 | ##手动动画 3 | 4 | `timeOffset`一个很有用的功能在于你可以它可以让你手动控制动画进程,通过设置`speed`为0,可以禁用动画的自动播放,然后来使用`timeOffset`来来回显示动画序列。这可以使得运用手势来手动控制动画变得很简单。 5 | 6 | 举个简单的例子:还是之前关门的动画,修改代码来用手势控制动画。我们给视图添加一个`UIPanGestureRecognizer`,然后用`timeOffset`左右摇晃。 7 | 8 | 因为在动画添加到图层之后不能再做修改了,我们来通过调整`layer`的`timeOffset`达到同样的效果(清单9.4)。 9 | 10 | 清单9.4 通过触摸手势手动控制动画 11 | 12 | ```objective-c 13 | @interface ViewController () 14 | 15 | @property (nonatomic, weak) UIView *containerView; 16 | @property (nonatomic, strong) CALayer *doorLayer; 17 | 18 | @end 19 | 20 | @implementation ViewController 21 | 22 | - (void)viewDidLoad 23 | { 24 | [super viewDidLoad]; 25 | //add the door 26 | self.doorLayer = [CALayer layer]; 27 | self.doorLayer.frame = CGRectMake(0, 0, 128, 256); 28 | self.doorLayer.position = CGPointMake(150 - 64, 150); 29 | self.doorLayer.anchorPoint = CGPointMake(0, 0.5); 30 | self.doorLayer.contents = (__bridge id)[UIImage imageNamed:@"Door.png"].CGImage; 31 | [self.containerView.layer addSublayer:self.doorLayer]; 32 | //apply perspective transform 33 | CATransform3D perspective = CATransform3DIdentity; 34 | perspective.m34 = -1.0 / 500.0; 35 | self.containerView.layer.sublayerTransform = perspective; 36 | //add pan gesture recognizer to handle swipes 37 | UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] init]; 38 | [pan addTarget:self action:@selector(pan:)]; 39 | [self.view addGestureRecognizer:pan]; 40 | //pause all layer animations 41 | self.doorLayer.speed = 0.0; 42 | //apply swinging animation (which won't play because layer is paused) 43 | CABasicAnimation *animation = [CABasicAnimation animation]; 44 | animation.keyPath = @"transform.rotation.y"; 45 | animation.toValue = @(-M_PI_2); 46 | animation.duration = 1.0; 47 | [self.doorLayer addAnimation:animation forKey:nil]; 48 | } 49 | 50 | - (void)pan:(UIPanGestureRecognizer *)pan 51 | { 52 | //get horizontal component of pan gesture 53 | CGFloat x = [pan translationInView:self.view].x; 54 | //convert from points to animation duration //using a reasonable scale factor 55 | x /= 200.0f; 56 | //update timeOffset and clamp result 57 | CFTimeInterval timeOffset = self.doorLayer.timeOffset; 58 | timeOffset = MIN(0.999, MAX(0.0, timeOffset - x)); 59 | self.doorLayer.timeOffset = timeOffset; 60 | //reset pan gesture 61 | [pan setTranslation:CGPointZero inView:self.view]; 62 | } 63 | 64 | @end 65 | ``` 66 | 67 | 这其实是个小诡计,也许相对于设置个动画然后每次显示一帧而言,用移动手势来直接设置门的`transform`会更简单。 68 | 69 | 在这个例子中的确是这样,但是对于比如说关键这这样更加复杂的情况,或者有多个图层的动画组,相对于实时计算每个图层的属性而言,这就显得方便的多了。 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /chapter5/layering-flattening.md: -------------------------------------------------------------------------------- 1 | 如果对包含已经做过变换的图层的图层做反方向的变换将会发什么什么呢?是不是有点困惑?见图5.15 2 | 3 | 图5.15 4 | 5 | 图5.15 反方向变换的嵌套图层 6 | 7 | 注意做了-45度旋转的内部图层是怎样抵消旋转45度的图层,从而恢复正常状态的。 8 | 9 | 如果内部图层相对外部图层做了相反的变换(这里是绕Z轴的旋转),那么按照逻辑这两个变换将被相互抵消。 10 | 11 | 验证一下,相应代码见清单5.7,结果见5.16 12 | 13 | 清单5.7 绕Z轴做相反的旋转变换 14 | 15 | ```objective-c 16 | @interface ViewController () 17 | 18 | @property (nonatomic, weak) IBOutlet UIView *outerView; 19 | @property (nonatomic, weak) IBOutlet UIView *innerView; 20 | 21 | @end 22 | 23 | @implementation ViewController 24 | 25 | - (void)viewDidLoad 26 | { 27 | [super viewDidLoad]; 28 | //rotate the outer layer 45 degrees 29 | CATransform3D outer = CATransform3DMakeRotation(M_PI_4, 0, 0, 1); 30 | self.outerView.layer.transform = outer; 31 | //rotate the inner layer -45 degrees 32 | CATransform3D inner = CATransform3DMakeRotation(-M_PI_4, 0, 0, 1); 33 | self.innerView.layer.transform = inner; 34 | } 35 | 36 | @end 37 | ``` 38 | 39 | 图5.16 40 | 41 | 图5.16 旋转后的视图 42 | 43 | 运行结果和我们预期的一致。现在在3D情况下再试一次。修改代码,让内外两个视图绕Y轴旋转而不是Z轴,再加上透视效果,以便我们观察。注意不能用`sublayerTransform`属性,因为内部的图层并不直接是容器图层的子图层,所以这里分别对图层设置透视变换(清单5.8)。 44 | 45 | 清单5.8 绕Y轴相反的旋转变换 46 | 47 | ```objective-c 48 | - (void)viewDidLoad 49 | { 50 | [super viewDidLoad]; 51 | //rotate the outer layer 45 degrees 52 | CATransform3D outer = CATransform3DIdentity; 53 | outer.m34 = -1.0 / 500.0; 54 | outer = CATransform3DRotate(outer, M_PI_4, 0, 1, 0); 55 | self.outerView.layer.transform = outer; 56 | //rotate the inner layer -45 degrees 57 | CATransform3D inner = CATransform3DIdentity; 58 | inner.m34 = -1.0 / 500.0; 59 | inner = CATransform3DRotate(inner, -M_PI_4, 0, 1, 0); 60 | self.innerView.layer.transform = inner; 61 | } 62 | ``` 63 | 64 | 预期的效果应该如图5.17所示。 65 | 66 | 图5.17 67 | 68 | 图5.17 绕Y轴做相反旋转的预期结果。 69 | 70 | 但其实这并不是我们所看到的,相反,我们看到的结果如图5.18所示。发什么了什么呢?内部的图层仍然向左侧旋转,并且发生了扭曲,但按道理说它应该保持正面朝上,并且显示正常的方块。 71 | 72 | 这是由于尽管Core Animation图层存在于3D空间之内,但它们并不都存在*同一个*3D空间。每个图层的3D场景其实是扁平化的,当你从正面观察一个图层,看到的实际上由子图层创建的想象出来的3D场景,但当你倾斜这个图层,你会发现实际上这个3D场景仅仅是被绘制在图层的表面。 73 | 74 | 图5.18 75 | 76 | 图5.18 绕Y轴做相反旋转的真实结果 77 | 78 | 类似的,当你在玩一个3D游戏,实际上仅仅是把屏幕做了一次倾斜,或许在游戏中可以看见有一面墙在你面前,但是倾斜屏幕并不能够看见墙里面的东西。所有场景里面绘制的东西并不会随着你观察它的角度改变而发生变化;图层也是同样的道理。 79 | 80 | 这使得用Core Animation创建非常复杂的3D场景变得十分困难。你不能够使用图层树去创建一个3D结构的层级关系--在相同场景下的任何3D表面必须和同样的图层保持一致,这是因为每个的父视图都把它的子视图扁平化了。 81 | 82 | 至少当你用正常的`CALayer`的时候是这样,`CALayer`有一个叫做`CATransformLayer`的子类来解决这个问题。具体在第六章“特殊的图层”中将会具体讨论。 -------------------------------------------------------------------------------- /chapter6/avplayerlayer.md: -------------------------------------------------------------------------------- 1 | # AVPlayerLayer 2 | 3 | 4 | 最后一个图层类型是`AVPlayerLayer`。尽管它不是Core Animation框架的一部分(AV前缀看上去像),`AVPlayerLayer`是有别的框架(AVFoundation)提供的,它和Core Animation紧密地结合在一起,提供了一个`CALayer`子类来显示自定义的内容类型。 5 | 6 | `AVPlayerLayer`是用来在iOS上播放视频的。他是高级接口例如`MPMoivePlayer`的底层实现,提供了显示视频的底层控制。`AVPlayerLayer`的使用相当简单:你可以用`+playerLayerWithPlayer:`方法创建一个已经绑定了视频播放器的图层,或者你可以先创建一个图层,然后用`player`属性绑定一个`AVPlayer`实例。 7 | 8 | 在我们开始之前,我们需要添加AVFoundation到我们的项目中。然后,清单6.15创建了一个简单的电影播放器,图6.16是代码运行结果。 9 | 10 | 清单6.15 用`AVPlayerLayer`播放视频 11 | 12 | ```objective-c 13 | #import "ViewController.h" 14 | #import 15 | #import 16 | 17 | @interface ViewController () 18 | 19 | @property (nonatomic, weak) IBOutlet UIView *containerView; @end 20 | 21 | @implementation ViewController 22 | 23 | - (void)viewDidLoad 24 | { 25 | [super viewDidLoad]; 26 | //get video URL 27 | NSURL *URL = [[NSBundle mainBundle] URLForResource:@"Ship" withExtension:@"mp4"]; 28 | 29 | //create player and player layer 30 | AVPlayer *player = [AVPlayer playerWithURL:URL]; 31 | AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player]; 32 | 33 | //set player layer frame and attach it to our view 34 | playerLayer.frame = self.containerView.bounds; 35 | [self.containerView.layer addSublayer:playerLayer]; 36 | 37 | //play the video 38 | [player play]; 39 | } 40 | @end 41 | ``` 42 | 43 | ![](./6.16.png) 44 | 45 | 图6.16 用`AVPlayerLayer`图层播放视频的截图 46 | 47 | 我们用代码创建了一个`AVPlayerLayer`,但是我们仍然把它添加到了一个容器视图中,而不是直接在controller中的主视图上添加。这样其实是为了可以使用自动布局限制使得图层在最中间;否则,一旦设备被旋转了我们就要手动重新放置位置,因为Core Animation并不支持自动大小和自动布局(见第三章『图层几何学』)。 48 | 49 | 当然,因为`AVPlayerLayer`是`CALayer`的子类,它继承了父类的所有特性。我们并不会受限于要在一个矩形中播放视频;清单6.16演示了在3D,圆角,有色边框,蒙板,阴影等效果(见图6.17). 50 | 51 | 清单6.16 给视频增加变换,边框和圆角 52 | 53 | ```objective-c 54 | - (void)viewDidLoad 55 | { 56 | ... 57 | //set player layer frame and attach it to our view 58 | playerLayer.frame = self.containerView.bounds; 59 | [self.containerView.layer addSublayer:playerLayer]; 60 | 61 | //transform layer 62 | CATransform3D transform = CATransform3DIdentity; 63 | transform.m34 = -1.0 / 500.0; 64 | transform = CATransform3DRotate(transform, M_PI_4, 1, 1, 0); 65 | playerLayer.transform = transform; 66 |  67 | //add rounded corners and border 68 | playerLayer.masksToBounds = YES; 69 | playerLayer.cornerRadius = 20.0; 70 | playerLayer.borderColor = [UIColor redColor].CGColor; 71 | playerLayer.borderWidth = 5.0; 72 | 73 | //play the video 74 | [player play]; 75 | } 76 | ``` 77 | 78 | ![](./6.17.png) 79 | 80 | 图6.17 3D视角下的边框和圆角`AVPlayerLayer` 81 | -------------------------------------------------------------------------------- /chapter6/cagradientLayer.md: -------------------------------------------------------------------------------- 1 | ##CAGradientLayer 2 | 3 | `CAGradientLayer`是用来生成两种或更多颜色平滑渐变的。用Core Graphics复制一个`CAGradientLayer`并将内容绘制到一个普通图层的寄宿图也是有可能的,但是`CAGradientLayer`的真正好处在于绘制使用了硬件加速。 4 | 5 | ###基础渐变 6 | 7 | 我们将从一个简单的红变蓝的对角线渐变开始(见清单6.6).这些渐变色彩放在一个数组中,并赋给`colors`属性。这个数组成员接受`CGColorRef`类型的值(并不是从`NSObject`派生而来),所以我们要用通过bridge转换以确保编译正常。 8 | 9 | `CAGradientLayer`也有`startPoint`和`endPoint`属性,他们决定了渐变的方向。这两个参数是以单位坐标系进行的定义,所以左上角坐标是{0, 0},右下角坐标是{1, 1}。代码运行结果如图6.6 10 | 11 | 清单6.6 简单的两种颜色的对角线渐变 12 | 13 | ```objective-c 14 | @interface ViewController () 15 | 16 | @property (nonatomic, weak) IBOutlet UIView *containerView; 17 | 18 | @end 19 | 20 | @implementation ViewController 21 | 22 | - (void)viewDidLoad 23 | { 24 | [super viewDidLoad]; 25 | //create gradient layer and add it to our container view 26 | CAGradientLayer *gradientLayer = [CAGradientLayer layer]; 27 | gradientLayer.frame = self.containerView.bounds; 28 | [self.containerView.layer addSublayer:gradientLayer]; 29 | 30 | //set gradient colors 31 | gradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor, (__bridge id)[UIColor blueColor].CGColor]; 32 | 33 | //set gradient start and end points 34 | gradientLayer.startPoint = CGPointMake(0, 0); 35 | gradientLayer.endPoint = CGPointMake(1, 1); 36 | } 37 | @end 38 | ``` 39 | 40 | ![图6.6](./6.6.png) 41 | 42 | 图6.6 用`CAGradientLayer`实现简单的两种颜色的对角线渐变 43 | 44 | ###多重渐变 45 | 46 | 47 | 如果你愿意,`colors`属性可以包含很多颜色,所以创建一个彩虹一样的多重渐变也是很简单的。默认情况下,这些颜色在空间上均匀地被渲染,但是我们可以用`locations`属性来调整空间。`locations`属性是一个浮点数值的数组(以`NSNumber`包装)。这些浮点数定义了`colors`属性中每个不同颜色的位置,同样的,也是以单位坐标系进行标定。0.0代表着渐变的开始,1.0代表着结束。 48 | 49 | `locations`数组并不是强制要求的,但是如果你给它赋值了就一定要确保`locations`的数组大小和`colors`数组大小一定要相同,否则你将会得到一个空白的渐变。 50 | 51 | 清单6.7展示了一个基于清单6.6的对角线渐变的代码改造。现在变成了从红到黄最后到绿色的渐变。`locations`数组指定了0.0,0.25和0.5三个数值,这样这三个渐变就有点像挤在了左上角。(如图6.7). 52 | 53 | 清单6.7 在渐变上使用`locations` 54 | 55 | ```objective-c 56 | - (void)viewDidLoad { 57 | [super viewDidLoad]; 58 | 59 | //create gradient layer and add it to our container view 60 | CAGradientLayer *gradientLayer = [CAGradientLayer layer]; 61 | gradientLayer.frame = self.containerView.bounds; 62 | [self.containerView.layer addSublayer:gradientLayer]; 63 | 64 | //set gradient colors 65 | gradientLayer.colors = @[(__bridge id)[UIColor redColor].CGColor, (__bridge id) [UIColor yellowColor].CGColor, (__bridge id)[UIColor greenColor].CGColor]; 66 | 67 | //set locations 68 | gradientLayer.locations = @[@0.0, @0.25, @0.5]; 69 | 70 | //set gradient start and end points 71 | gradientLayer.startPoint = CGPointMake(0, 0); 72 | gradientLayer.endPoint = CGPointMake(1, 1); 73 | } 74 | ``` 75 | 76 | ![图6.7](./6.7.png) 77 | 78 | 图6.7 用`locations`构造偏移至左上角的三色渐变 79 | -------------------------------------------------------------------------------- /chapter1/working-with-layers.md: -------------------------------------------------------------------------------- 1 | # 使用图层 2 | 3 |     首先我们来创建一个简单的项目,来操纵一些`layer`的属性。打开Xcode,使用*Single View Application*模板创建一个工程。 4 | 5 |     在屏幕中央创建一个小视图(大约200 X 200的尺寸),当然你可以手工编码,或者使用Interface Builder(随你方便)。确保你的视图控制器要添加一个视图的属性以便可以直接访问它。我们把它称作`layerView`。 6 | 7 |     运行项目,应该能在浅灰色屏幕背景中看见一个白色方块(图1.3),如果没看见,可能需要调整一下背景window或者view的颜色 8 | 9 | 图1.3 10 | 11 | 图1.3 灰色背景上的一个白色`UIView` 12 | 13 |     这并没有什么令人激动的地方,我们来添加一个色块,在白色方块中间添加一个小的蓝色块。 14 | 15 |     我们当然可以简单地在已经存在的`UIView`上添加一个子视图(随意用代码或者IB),但这不能真正学到任何关于图层的东西。 16 | 17 |     于是我们来创建一个`CALayer`,并且把它作为我们视图相关图层的子图层。尽管`UIView`类的接口中暴露了图层属性,但是标准的Xcode项目模板并没有包含Core Animation相关头文件。所以如果我们不给项目添加合适的库,是不能够使用任何图层相关的方法或者访问它的属性。所以首先需要添加QuartzCore框架到Build Phases标签(图1.4),然后在vc的.m文件中引入库。 18 | 19 | 图1.4 20 | 21 | 图1.4 把QuartzCore库添加到项目 22 | 23 |     之后就可以在代码中直接引用`CALayer`的属性和方法。在清单1.1中,我们用创建了一个`CALayer`,设置了它的`backgroundColor`属性,然后添加到`layerView`背后相关图层的子图层(这段代码的前提是通过IB创建了`layerView`并做好了连接),图1.5显示了结果。 24 | 25 | 清单1.1 给视图添加一个蓝色子图层 26 | ``` objective-c 27 | #import "ViewController.h" 28 | #import 29 | @interface ViewController () 30 | 31 | @property (nonatomic, weak) IBOutlet UIView *layerView; 32 |  33 | @end 34 | 35 | @implementation ViewController 36 | 37 | - (void)viewDidLoad 38 | { 39 | [super viewDidLoad]; 40 | //create sublayer 41 | CALayer *blueLayer = [CALayer layer]; 42 | blueLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f); 43 | blueLayer.backgroundColor = [UIColor blueColor].CGColor; 44 | //add it to our view 45 | [self.layerView.layer addSublayer:blueLayer]; 46 | } 47 | @end 48 | ``` 49 | 图1.5 50 | 51 | 图1.5 白色`UIView`内部嵌套的蓝色`CALayer` 52 | 53 |     一个视图只有一个相关联的图层(自动创建),同时它也可以支持添加无数多个子图层,从清单1.1可以看出,你可以显示创建一个单独的图层,并且把它直接添加到视图关联图层的子图层。尽管可以这样添加图层,但往往我们只是见简单地处理视图,他们关联的图层并不需要额外地手动添加子图层。 54 | 55 |     在Mac OS平台,10.8版本之前,一个显著的性能缺陷就是由于用了视图层级而不是单独在一个视图内使用`CALayer`树状层级。但是在iOS平台,使用轻量级的`UIView`类并没有显著的性能影响(当然在Mac OS 10.8之后,`NSView`的性能同样也得到很大程度的提高)。 56 | 57 |     使用图层关联的视图而不是`CALayer`的好处在于,你能在使用所有`CALayer`底层特性的同时,也可以使用`UIView`的高级API(比如自动排版,布局和事件处理)。 58 | 59 |     然而,当满足以下条件的时候,你可能更需要使用`CALayer`而不是`UIView`: 60 | 61 | * 开发同时可以在Mac OS上运行的跨平台应用 62 | * 使用多种`CALayer`的子类(见第六章,“特殊的图层“),并且不想创建额外的`UIView`去包封装它们所有 63 | * 做一些对性能特别挑剔的工作,比如对`UIView`一些可忽略不计的操作都会引起显著的不同(尽管如此,你可能会直接想使用OpenGL绘图) 64 | 65 | 66 |     但是这些例子都很少见,总的来说,处理视图会比单独处理图层更加方便。 -------------------------------------------------------------------------------- /chapter4/group-opacity.md: -------------------------------------------------------------------------------- 1 | # 组透明 2 | 3 | 4 |     UIView有一个叫做`alpha`的属性来确定视图的透明度。CALayer有一个等同的属性叫做`opacity`,这两个属性都是影响子层级的。也就是说,如果你给一个图层设置了`opacity`属性,那它的子图层都会受此影响。 5 | 6 |     iOS常见的做法是把一个控件的alpha值设置为0.5(50%)以使其看上去呈现为不可用状态。对于独立的视图来说还不错,但是当一个控件有子视图的时候就有点奇怪了,图4.20展示了一个内嵌了UILabel的自定义UIButton;左边是一个不透明的按钮,右边是50%透明度的相同按钮。我们可以注意到,里面的标签的轮廓跟按钮的背景很不搭调。 7 | 8 | ![图4.20](./4.20.png) 9 | 10 | 图4.20 右边的渐隐按钮中,里面的标签清晰可见 11 | 12 |     这是由透明度的混合叠加造成的,当你显示一个50%透明度的图层时,图层的每个像素都会一半显示自己的颜色,另一半显示图层下面的颜色。这是正常的透明度的表现。但是如果图层包含一个同样显示50%透明的子图层时,你所看到的视图,50%来自子视图,25%来了图层本身的颜色,另外的25%则来自背景色。 13 | 14 |     在我们的示例中,按钮和表情都是白色背景。虽然他们都是50%的可见度,但是合起来的可见度是75%,所以标签所在的区域看上去就没有周围的部分那么透明。所以看上去子视图就高亮了,使得这个显示效果都糟透了。 15 | 16 |     理想状况下,当你设置了一个图层的透明度,你希望它包含的整个图层树像一个整体一样的透明效果。你可以通过设置Info.plist文件中的`UIViewGroupOpacity`为YES来达到这个效果,但是这个设置会影响到这个应用,整个app可能会受到不良影响。如果`UIViewGroupOpacity`并未设置,iOS 6和以前的版本会默认为NO(也许以后的版本会有一些改变)。 17 | 18 |     另一个方法就是,你可以设置CALayer的一个叫做`shouldRasterize`属性(见清单4.7)来实现组透明的效果,如果它被设置为YES,在应用透明度之前,图层及其子图层都会被整合成一个整体的图片,这样就没有透明度混合的问题了(如图4.21)。 19 | 20 |     为了启用`shouldRasterize`属性,我们设置了图层的`rasterizationScale`属性。默认情况下,所有图层拉伸都是1.0, 所以如果你使用了`shouldRasterize`属性,你就要确保你设置了`rasterizationScale`属性去匹配屏幕,以防止出现Retina屏幕像素化的问题。 21 | 22 |     当`shouldRasterize`和`UIViewGroupOpacity`一起的时候,性能问题就出现了(我们在第12章『速度』和第15章『图层性能』将做出介绍),但是性能碰撞都本地化了(译者注:这句话需要再翻译)。 23 | 24 | 清单4.7 使用`shouldRasterize`属性解决组透明问题 25 | 26 | ```objective-c 27 | @interface ViewController () 28 | @property (nonatomic, weak) IBOutlet UIView *containerView; 29 | @end 30 | 31 | @implementation ViewController 32 | 33 | - (UIButton *)customButton 34 | { 35 | //create button 36 | CGRect frame = CGRectMake(0, 0, 150, 50); 37 | UIButton *button = [[UIButton alloc] initWithFrame:frame]; 38 | button.backgroundColor = [UIColor whiteColor]; 39 | button.layer.cornerRadius = 10; 40 | 41 | //add label 42 | frame = CGRectMake(20, 10, 110, 30); 43 | UILabel *label = [[UILabel alloc] initWithFrame:frame]; 44 | label.text = @"Hello World"; 45 | label.textAlignment = NSTextAlignmentCenter; 46 | [button addSubview:label]; 47 | return button; 48 | } 49 | 50 | - (void)viewDidLoad 51 | { 52 | [super viewDidLoad]; 53 | 54 | //create opaque button 55 | UIButton *button1 = [self customButton]; 56 | button1.center = CGPointMake(50, 150); 57 | [self.containerView addSubview:button1]; 58 | 59 | //create translucent button 60 | UIButton *button2 = [self customButton]; 61 |  62 | button2.center = CGPointMake(250, 150); 63 | button2.alpha = 0.5; 64 | [self.containerView addSubview:button2]; 65 | 66 | //enable rasterization for the translucent button 67 | button2.layer.shouldRasterize = YES; 68 | button2.layer.rasterizationScale = [UIScreen mainScreen].scale; 69 | } 70 | @end 71 | ``` 72 | 73 | ![图4.12](./4.21.png) 74 | 75 | 图4.21 修正后的图 76 | -------------------------------------------------------------------------------- /chapter6/rich-text.md: -------------------------------------------------------------------------------- 1 | # 富文本 2 | 3 | 4 | iOS 6中,Apple给`UILabel`和其他UIKit文本视图添加了直接的属性化字符串的支持,应该说这是一个很方便的特性。不过事实上从iOS3.2开始`CATextLayer`就已经支持属性化字符串了。这样的话,如果你想要支持更低版本的iOS系统,`CATextLayer`无疑是你向界面中增加富文本的好办法,而且也不用去跟复杂的Core Text打交道,也省了用`UIWebView`的麻烦。 5 | 6 | 让我们编辑一下示例使用到`NSAttributedString`(见清单6.3).iOS 6及以上我们可以用新的`NSTextAttributeName`实例来设置我们的字符串属性,但是练习的目的是为了演示在iOS 5及以下,所以我们用了Core Text,也就是说你需要把Core Text framework添加到你的项目中。否则,编译器是无法识别属性常量的。 7 | 8 | 图6.4是代码运行结果(注意那个红色的下划线文本) 9 | 10 | 清单6.3 用NSAttributedString实现一个富文本标签。 11 | 12 | ```objective-c 13 | #import "DrawingView.h" 14 | #import 15 | #import 16 | 17 | @interface ViewController () 18 | 19 | @property (nonatomic, weak) IBOutlet UIView *labelView; 20 | 21 | @end 22 | 23 | @implementation ViewController 24 | 25 | - (void)viewDidLoad 26 | { 27 | [super viewDidLoad]; 28 | 29 | //create a text layer 30 | CATextLayer *textLayer = [CATextLayer layer]; 31 | textLayer.frame = self.labelView.bounds; 32 | textLayer.contentsScale = [UIScreen mainScreen].scale; 33 | [self.labelView.layer addSublayer:textLayer]; 34 | 35 | //set text attributes 36 | textLayer.alignmentMode = kCAAlignmentJustified; 37 | textLayer.wrapped = YES; 38 | 39 | //choose a font 40 | UIFont *font = [UIFont systemFontOfSize:15]; 41 | 42 | //choose some text 43 | NSString *text = @"Lorem ipsum dolor sit amet, consectetur adipiscing \ elit. Quisque massa arcu, eleifend vel varius in, facilisis pulvinar \ leo. Nunc quis nunc at mauris pharetra condimentum ut ac neque. Nunc \ elementum, libero ut porttitor dictum, diam odio congue lacus, vel \ fringilla sapien diam at purus. Etiam suscipit pretium nunc sit amet \ lobortis"; 44 |  45 | //create attributed string 46 | NSMutableAttributedString *string = nil; 47 | string = [[NSMutableAttributedString alloc] initWithString:text]; 48 | 49 | //convert UIFont to a CTFont 50 | CFStringRef fontName = (__bridge CFStringRef)font.fontName; 51 | CGFloat fontSize = font.pointSize; 52 | CTFontRef fontRef = CTFontCreateWithName(fontName, fontSize, NULL); 53 | 54 | //set text attributes 55 | NSDictionary *attribs = @{ 56 | (__bridge id)kCTForegroundColorAttributeName:(__bridge id)[UIColor blackColor].CGColor, 57 | (__bridge id)kCTFontAttributeName: (__bridge id)fontRef 58 | }; 59 | 60 | [string setAttributes:attribs range:NSMakeRange(0, [text length])]; 61 | attribs = @{ 62 | (__bridge id)kCTForegroundColorAttributeName: (__bridge id)[UIColor redColor].CGColor, 63 | (__bridge id)kCTUnderlineStyleAttributeName: @(kCTUnderlineStyleSingle), 64 | (__bridge id)kCTFontAttributeName: (__bridge id)fontRef 65 | }; 66 | [string setAttributes:attribs range:NSMakeRange(6, 5)]; 67 | 68 | //release the CTFont we created earlier 69 | CFRelease(fontRef); 70 | 71 | //set layer text 72 | textLayer.string = string; 73 | } 74 | @end 75 | ``` 76 | 77 | ![图6.4](./6.4.png) 78 | 79 | 图6.4 用CATextLayer实现一个富文本标签。 80 | -------------------------------------------------------------------------------- /chapter15/offscreen-rendering.md: -------------------------------------------------------------------------------- 1 | ## 离屏渲染 2 | 3 |     当图层属性的混合体被指定为在未预合成之前不能直接在屏幕中绘制时,屏幕外渲染就被唤起了。屏幕外渲染并不意味着软件绘制,但是它意味着图层必须在被显示之前在一个屏幕外上下文中被渲染(不论CPU还是GPU)。图层的以下属性将会触发屏幕外绘制: 4 | 5 | * 圆角(当和`maskToBounds`一起使用时) 6 | * 图层蒙板 7 | * 阴影 8 | 9 |     屏幕外渲染和我们启用光栅化时相似,除了它并没有像光栅化图层那么消耗大,子图层并没有被影响到,而且结果也没有被缓存,所以不会有长期的内存占用。但是,如果太多图层在屏幕外渲染依然会影响到性能。 10 | 11 |     有时候我们可以把那些需要屏幕外绘制的图层开启光栅化以作为一个优化方式,前提是这些图层并不会被频繁地重绘。 12 | 13 |     对于那些需要动画而且要在屏幕外渲染的图层来说,你可以用`CAShapeLayer`,`contentsCenter`或者`shadowPath`来获得同样的表现而且较少地影响到性能。 14 | 15 | ### CAShapeLayer 16 | 17 |     `cornerRadius`和`maskToBounds`独立作用的时候都不会有太大的性能问题,但是当他俩结合在一起,就触发了屏幕外渲染。有时候你想显示圆角并沿着图层裁切子图层的时候,你可能会发现你并不需要沿着圆角裁切,这个情况下用`CAShapeLayer`就可以避免这个问题了。 18 | 19 |     你想要的只是圆角且沿着矩形边界裁切,同时还不希望引起性能问题。其实你可以用现成的`UIBezierPath`的构造器`+bezierPathWithRoundedRect:cornerRadius:`(见清单15.1).这样做并不会比直接用`cornerRadius`更快,但是它避免了性能问题。 20 | 21 | 清单15.1 用`CAShapeLayer`画一个圆角矩形 22 | 23 | ```objective-c 24 | #import "ViewController.h" 25 | #import 26 | 27 | @interface ViewController () 28 | 29 | @property (nonatomic, weak) IBOutlet UIView *layerView; 30 | 31 | @end 32 | 33 | @implementation ViewController 34 | 35 | - (void)viewDidLoad 36 | { 37 | [super viewDidLoad]; 38 | 39 | //create shape layer 40 | CAShapeLayer *blueLayer = [CAShapeLayer layer]; 41 | blueLayer.frame = CGRectMake(50, 50, 100, 100); 42 | blueLayer.fillColor = [UIColor blueColor].CGColor; 43 | blueLayer.path = [UIBezierPath bezierPathWithRoundedRect: 44 | CGRectMake(0, 0, 100, 100) cornerRadius:20].CGPath; 45 |  46 | //add it to our view 47 | [self.layerView.layer addSublayer:blueLayer]; 48 | } 49 | @end 50 | ``` 51 | 52 | ###可伸缩图片 53 | 54 |     另一个创建圆角矩形的方法就是用一个圆形内容图片并结合第二章『寄宿图』提到的`contensCenter`属性去创建一个可伸缩图片(见清单15.2).理论上来说,这个应该比用`CAShapeLayer`要快,因为一个可拉伸图片只需要18个三角形(一个图片是由一个3*3网格渲染而成),然而,许多都需要渲染成一个顺滑的曲线。在实际应用上,二者并没有太大的区别。 55 | 56 | 清单15.2 用可伸缩图片绘制圆角矩形 57 | 58 | ```objective-c 59 | @implementation ViewController 60 | 61 | - (void)viewDidLoad 62 | { 63 | [super viewDidLoad]; 64 | 65 | //create layer 66 | CALayer *blueLayer = [CALayer layer]; 67 | blueLayer.frame = CGRectMake(50, 50, 100, 100); 68 | blueLayer.contentsCenter = CGRectMake(0.5, 0.5, 0.0, 0.0); 69 | blueLayer.contentsScale = [UIScreen mainScreen].scale; 70 | blueLayer.contents = (__bridge id)[UIImage imageNamed:@"Circle.png"].CGImage; 71 | //add it to our view 72 | [self.layerView.layer addSublayer:blueLayer]; 73 | } 74 | @end 75 | ``` 76 | 77 |     使用可伸缩图片的优势在于它可以绘制成任意边框效果而不需要额外的性能消耗。举个例子,可伸缩图片甚至还可以显示出矩形阴影的效果。 78 | 79 | ###shadowPath 80 | 81 |     在第2章我们有提到`shadowPath`属性。如果图层是一个简单几何图形如矩形或者圆角矩形(假设不包含任何透明部分或者子图层),创建出一个对应形状的阴影路径就比较容易,而且Core Animation绘制这个阴影也相当简单,避免了屏幕外的图层部分的预排版需求。这对性能来说很有帮助。 82 | 83 |     如果你的图层是一个更复杂的图形,生成正确的阴影路径可能就比较难了,这样子的话你可以考虑用绘图软件预先生成一个阴影背景图。 84 | 85 | -------------------------------------------------------------------------------- /chapter6/a-uilabel-replacement.md: -------------------------------------------------------------------------------- 1 | # `UILabel`的替代品 2 | 3 | 4 | 5 | 我们已经证实了`CATextLayer`比`UILabel`有着更好的性能表现,同时还有额外的布局选项并且在iOS 5上支持富文本。但是与一般的标签比较而言会更加繁琐一些。如果我们真的在需求一个`UILabel`的可用替代品,最好是能够在Interface Builder上创建我们的标签,而且尽可能地像一般的视图一样正常工作。 6 | 7 | 我们应该继承`UILabel`,然后添加一个子图层`CATextLayer`并重写显示文本的方法。但是仍然会有由`UILabel`的`-drawRect:`方法创建的空寄宿图。而且由于`CALayer`不支持自动缩放和自动布局,子视图并不是主动跟踪视图边界的大小,所以每次视图大小被更改,我们不得不手动更新子图层的边界。 8 | 9 | 我们真正想要的是一个用`CATextLayer`作为宿主图层的`UILabel`子类,这样就可以随着视图自动调整大小而且也没有冗余的寄宿图啦。 10 | 11 | 就像我们在第一章『图层树』讨论的一样,每一个`UIView`都是寄宿在一个`CALayer`的示例上。这个图层是由视图自动创建和管理的,那我们可以用别的图层类型替代它么?一旦被创建,我们就无法代替这个图层了。但是如果我们继承了`UIView`,那我们就可以重写`+layerClass`方法使得在创建的时候能返回一个不同的图层子类。`UIView`会在初始化的时候调用`+layerClass`方法,然后用它的返回类型来创建宿主图层。 12 | 13 | 清单6.4 演示了一个`UILabel`子类`LayerLabel`用`CATextLayer`绘制它的问题,而不是调用一般的`UILabel`使用的较慢的`-drawRect:`方法。`LayerLabel`示例既可以用代码实现,也可以在Interface Builder实现,只要把普通的标签拖入视图之中,然后设置它的类是LayerLabel就可以了。 14 | 15 | 清单6.4 使用`CATextLayer`的`UILabel`子类:`LayerLabel` 16 | 17 | ```objective-c 18 | #import "LayerLabel.h" 19 | #import 20 | 21 | @implementation LayerLabel 22 | + (Class)layerClass 23 | { 24 | //this makes our label create a CATextLayer //instead of a regular CALayer for its backing layer 25 | return [CATextLayer class]; 26 | } 27 | 28 | - (CATextLayer *)textLayer 29 | { 30 | return (CATextLayer *)self.layer; 31 | } 32 | 33 | - (void)setUp 34 | { 35 | //set defaults from UILabel settings 36 | self.text = self.text; 37 | self.textColor = self.textColor; 38 | self.font = self.font; 39 | 40 | //we should really derive these from the UILabel settings too 41 | //but that's complicated, so for now we'll just hard-code them 42 | [self textLayer].alignmentMode = kCAAlignmentJustified; 43 |  44 | [self textLayer].wrapped = YES; 45 | [self.layer display]; 46 | } 47 | 48 | - (id)initWithFrame:(CGRect)frame 49 | { 50 | //called when creating label programmatically 51 | if (self = [super initWithFrame:frame]) { 52 | [self setUp]; 53 | } 54 | return self; 55 | } 56 | 57 | - (void)awakeFromNib 58 | { 59 | //called when creating label using Interface Builder 60 | [self setUp]; 61 | } 62 | 63 | - (void)setText:(NSString *)text 64 | { 65 | super.text = text; 66 | //set layer text 67 | [self textLayer].string = text; 68 | } 69 | 70 | - (void)setTextColor:(UIColor *)textColor 71 | { 72 | super.textColor = textColor; 73 | //set layer text color 74 | [self textLayer].foregroundColor = textColor.CGColor; 75 | } 76 | 77 | - (void)setFont:(UIFont *)font 78 | { 79 | super.font = font; 80 | //set layer font 81 | CFStringRef fontName = (__bridge CFStringRef)font.fontName; 82 | CGFontRef fontRef = CGFontCreateWithFontName(fontName); 83 | [self textLayer].font = fontRef; 84 | [self textLayer].fontSize = font.pointSize; 85 |  86 | CGFontRelease(fontRef); 87 | } 88 | @end 89 | ``` 90 | 91 | 如果你运行代码,你会发现文本并没有像素化,而我们也没有设置`contentsScale`属性。把`CATextLayer`作为宿主图层的另一好处就是视图自动设置了`contentsScale`属性。 92 | 93 | 在这个简单的例子中,我们只是实现了`UILabel`的一部分风格和布局属性,不过稍微再改进一下我们就可以创建一个支持`UILabel`所有功能甚至更多功能的`LayerLabel`类(你可以在一些线上的开源项目中找到)。 94 | 95 | 如果你打算支持iOS 6及以上,基于`CATextLayer`的标签可能就有有些局限性。但是总得来说,如果想在app里面充分利用`CALayer`子类,用`+layerClass`来创建基于不同图层的视图是一个简单可复用的方法。 96 | -------------------------------------------------------------------------------- /chapter6/cashapelayer.md: -------------------------------------------------------------------------------- 1 | 2 | ##CAShapeLayer 3 | 4 | 在第四章『视觉效果』我们学习到了不使用图片的情况下用`CGPath`去构造任意形状的阴影。如果我们能用同样的方式创建相同形状的图层就好了。 5 | 6 | `CAShapeLayer`是一个通过矢量图形而不是bitmap来绘制的图层子类。你指定诸如颜色和线宽等属性,用`CGPath`来定义想要绘制的图形,最后`CAShapeLayer`就自动渲染出来了。当然,你也可以用Core Graphics直接向原始的`CALyer`的内容中绘制一个路径,相比直下,使用`CAShapeLayer`有以下一些优点: 7 | 8 | * 渲染快速。`CAShapeLayer`使用了硬件加速,绘制同一图形会比用Core Graphics快很多。 9 | * 高效使用内存。一个`CAShapeLayer`不需要像普通`CALayer`一样创建一个寄宿图形,所以无论有多大,都不会占用太多的内存。 10 | * 不会被图层边界剪裁掉。一个`CAShapeLayer`可以在边界之外绘制。你的图层路径不会像在使用Core Graphics的普通`CALayer`一样被剪裁掉(如我们在第二章所见)。 11 | * 不会出现像素化。当你给`CAShapeLayer`做3D变换时,它不像一个有寄宿图的普通图层一样变得像素化。 12 | 13 | ###创建一个`CGPath` 14 | 15 | `CAShapeLayer`可以用来绘制所有能够通过`CGPath`来表示的形状。这个形状不一定要闭合,图层路径也不一定要不可破,事实上你可以在一个图层上绘制好几个不同的形状。你可以控制一些属性比如`lineWith`(线宽,用点表示单位),`lineCap`(线条结尾的样子),和`lineJoin`(线条之间的结合点的样子);但是在图层层面你只有一次机会设置这些属性。如果你想用不同颜色或风格来绘制多个形状,就不得不为每个形状准备一个图层了。 16 | 17 | 清单6.1 的代码用一个`CAShapeLayer`渲染一个简单的火柴人。`CAShapeLayer`属性是`CGPathRef`类型,但是我们用`UIBezierPath`帮助类创建了图层路径,这样我们就不用考虑人工释放`CGPath`了。图6.1是代码运行的结果。虽然还不是很完美,但是总算知道了大意对吧! 18 | 19 | 清单6.1 用`CAShapeLayer`绘制一个火柴人 20 | 21 | ```objective-c 22 | #import "DrawingView.h" 23 | #import 24 | 25 | @interface ViewController () 26 | 27 | @property (nonatomic, weak) IBOutlet UIView *containerView; 28 | 29 | @end 30 | 31 | @implementation ViewController 32 | 33 | - (void)viewDidLoad 34 | { 35 | [super viewDidLoad]; 36 | //create path 37 | UIBezierPath *path = [[UIBezierPath alloc] init]; 38 | [path moveToPoint:CGPointMake(175, 100)]; 39 |  40 | [path addArcWithCenter:CGPointMake(150, 100) radius:25 startAngle:0 endAngle:2*M_PI clockwise:YES]; 41 | [path moveToPoint:CGPointMake(150, 125)]; 42 | [path addLineToPoint:CGPointMake(150, 175)]; 43 | [path addLineToPoint:CGPointMake(125, 225)]; 44 | [path moveToPoint:CGPointMake(150, 175)]; 45 | [path addLineToPoint:CGPointMake(175, 225)]; 46 | [path moveToPoint:CGPointMake(100, 150)]; 47 | [path addLineToPoint:CGPointMake(200, 150)]; 48 | 49 | //create shape layer 50 | CAShapeLayer *shapeLayer = [CAShapeLayer layer]; 51 | shapeLayer.strokeColor = [UIColor redColor].CGColor; 52 | shapeLayer.fillColor = [UIColor clearColor].CGColor; 53 | shapeLayer.lineWidth = 5; 54 | shapeLayer.lineJoin = kCALineJoinRound; 55 | shapeLayer.lineCap = kCALineCapRound; 56 | shapeLayer.path = path.CGPath; 57 | //add it to our view 58 | [self.containerView.layer addSublayer:shapeLayer]; 59 | } 60 | @end 61 | ``` 62 | 63 | ![图6.1](./6.1.png) 64 | 65 | 图6.1 用`CAShapeLayer`绘制一个简单的火柴人 66 | 67 | ###圆角 68 | 69 | 第二章里面提到了`CAShapeLayer`为创建圆角视图提供了一个方法,就是`CALayer`的`cornerRadius`属性(译者注:其实是在第四章提到的)。虽然使用`CAShapeLayer`类需要更多的工作,但是它有一个优势就是可以单独指定每个角。 70 | 71 | 我们创建圆角矩形其实就是人工绘制单独的直线和弧度,但是事实上`UIBezierPath`有自动绘制圆角矩形的构造方法,下面这段代码绘制了一个有三个圆角一个直角的矩形: 72 | 73 | ```objective-c 74 | //define path parameters 75 | CGRect rect = CGRectMake(50, 50, 100, 100); 76 | CGSize radii = CGSizeMake(20, 20); 77 | UIRectCorner corners = UIRectCornerTopRight | UIRectCornerBottomRight | UIRectCornerBottomLeft; 78 | //create path 79 | UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:corners cornerRadii:radii]; 80 | ``` 81 | 82 | 我们可以通过这个图层路径绘制一个既有直角又有圆角的视图。如果我们想依照此图形来剪裁视图内容,我们可以把`CAShapeLayer`作为视图的宿主图层,而不是添加一个子视图(图层蒙板的详细解释见第四章『视觉效果』)。 83 | -------------------------------------------------------------------------------- /chapter2/custom-drawing.md: -------------------------------------------------------------------------------- 1 | # Custom Drawing 2 | 3 | 4 |     给`contents`赋CGImage的值不是唯一的设置寄宿图的方法。我们也可以直接用Core Graphics直接绘制寄宿图。能够通过继承UIView并实现`-drawRect:`方法来自定义绘制。 5 | 6 |     `-drawRect:` 方法没有默认的实现,因为对UIView来说,寄宿图并不是必须的,它不在意那到底是单调的颜色还是有一个图片的实例。如果UIView检测到`-drawRect:` 方法被调用了,它就会为视图分配一个寄宿图,这个寄宿图的像素尺寸等于视图大小乘以 `contentsScale`的值。 7 | 8 |     如果你不需要寄宿图,那就不要创建这个方法了,这会造成CPU资源和内存的浪费,这也是为什么苹果建议:如果没有自定义绘制的任务就不要在子类中写一个空的-drawRect:方法。 9 | 10 |     当视图在屏幕上出现的时候 `-drawRect:`方法就会被自动调用。`-drawRect:`方法里面的代码利用Core Graphics去绘制一个寄宿图,然后内容就会被缓存起来直到它需要被更新(通常是因为开发者调用了`-setNeedsDisplay`方法,尽管影响到表现效果的属性值被更改时,一些视图类型会被自动重绘,如`bounds`属性)。虽然`-drawRect:`方法是一个UIView方法,事实上都是底层的CALayer安排了重绘工作和保存了因此产生的图片。 11 | 12 |     CALayer有一个可选的`delegate`属性,实现了`CALayerDelegate`协议,当CALayer需要一个内容特定的信息时,就会从协议中请求。CALayerDelegate是一个非正式协议,其实就是说没有CALayerDelegate @protocol可以让你在类里面引用啦。你只需要调用你想调用的方法,CALayer会帮你做剩下的。(`delegate`属性被声明为id类型,所有的代理方法都是可选的)。 13 | 14 |     当需要被重绘时,CALayer会请求它的代理给他一个寄宿图来显示。它通过调用下面这个方法做到的: 15 | 16 | ```objective-c 17 | (void)displayLayer:(CALayerCALayer *)layer; 18 | ``` 19 | 20 |     趁着这个机会,如果代理想直接设置`contents`属性的话,它就可以这么做,不然没有别的方法可以调用了。如果代理不实现`-displayLayer:`方法,CALayer就会转而尝试调用下面这个方法: 21 | 22 | ```objective-c 23 | - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx; 24 | ``` 25 | 26 |     在调用这个方法之前,CALayer创建了一个合适尺寸的空寄宿图(尺寸由`bounds`和`contentsScale`决定)和一个Core Graphics的绘制上下文环境,为绘制寄宿图做准备,他作为ctx参数传入。 27 | 28 |     让我们来继续第一章的项目让它实现CALayerDelegate并做一些绘图工作吧(见清单2.5).图2.12是他的结果 29 | 30 | 清单2.5 实现CALayerDelegate 31 | 32 | ```objective-c 33 | @implementation ViewController 34 | - (void)viewDidLoad 35 | { 36 | [super viewDidLoad]; 37 |  38 | //create sublayer 39 | CALayer *blueLayer = [CALayer layer]; 40 | blueLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f); 41 | blueLayer.backgroundColor = [UIColor blueColor].CGColor; 42 | 43 | //set controller as layer delegate 44 | blueLayer.delegate = self; 45 | 46 | //ensure that layer backing image uses correct scale 47 | blueLayer.contentsScale = [UIScreen mainScreen].scale; //add layer to our view 48 | [self.layerView.layer addSublayer:blueLayer]; 49 | 50 | //force layer to redraw 51 | [blueLayer display]; 52 | } 53 | 54 | - (void)drawLayer:(CALayer *)layer inContext:(CGContextRef)ctx 55 | { 56 | //draw a thick red circle 57 | CGContextSetLineWidth(ctx, 10.0f); 58 | CGContextSetStrokeColorWithColor(ctx, [UIColor redColor].CGColor); 59 | CGContextStrokeEllipseInRect(ctx, layer.bounds); 60 | } 61 | @end 62 | ``` 63 | 64 | ![图2.12](./2.12.png) 65 | 66 | 图2.12 实现CALayerDelegate来绘制图层 67 | 68 | 注意一下一些有趣的事情: 69 | 70 | * 我们在blueLayer上显式地调用了`-display`。不同于UIView,当图层显示在屏幕上时,CALayer不会自动重绘它的内容。它把重绘的决定权交给了开发者。 71 | * 尽管我们没有用`masksToBounds`属性,绘制的那个圆仍然沿边界被裁剪了。这是因为当你使用CALayerDelegate绘制寄宿图的时候,并没有对超出边界外的内容提供绘制支持。 72 | 73 |     现在你理解了CALayerDelegate,并知道怎么使用它。但是除非你创建了一个单独的图层,你几乎没有机会用到CALayerDelegate协议。因为当UIView创建了它的宿主图层时,它就会自动地把图层的delegate设置为它自己,并提供了一个`-displayLayer:`的实现,那所有的问题就都没了。 74 | 75 |     当使用寄宿了视图的图层的时候,你也不必实现`-displayLayer:`和`-drawLayer:inContext:`方法来绘制你的寄宿图。通常做法是实现UIView的`-drawRect:`方法,UIView就会帮你做完剩下的工作,包括在需要重绘的时候调用`-display`方法。 76 | -------------------------------------------------------------------------------- /chapter6/cascrollLayer.md: -------------------------------------------------------------------------------- 1 | 2 | ##CAScrollLayer 3 | 4 | 对于一个未转换的图层,它的`bounds`和它的`frame`是一样的,`frame`属性是由`bounds`属性自动计算而出的,所以更改任意一个值都会更新其他值。 5 | 6 | 但是如果你只想显示一个大图层里面的一小部分呢。比如说,你可能有一个很大的图片,你希望用户能够随意滑动,或者是一个数据或文本的长列表。在一个典型的iOS应用中,你可能会用到`UITableView`或是`UIScrollView`,但是对于独立的图层来说,什么会等价于刚刚提到的`UITableView`和`UIScrollView`呢? 7 | 8 | 在第二章中,我们探索了图层的`contentsRect`属性的用法,它的确是能够解决在图层中小地方显示大图片的解决方法。但是如果你的图层包含子图层那它就不是一个非常好的解决方案,因为,这样做的话每次你想『滑动』可视区域的时候,你就需要手工重新计算并更新所有的子图层位置。 9 | 10 | 这个时候就需要`CAScrollLayer`了。`CAScrollLayer`有一个`-scrollToPoint:`方法,它自动适应`bounds`的原点以便图层内容出现在滑动的地方。注意,这就是它做的所有事情。前面提到过,Core Animation并不处理用户输入,所以`CAScrollLayer`并不负责将触摸事件转换为滑动事件,既不渲染滚动条,也不实现任何iOS指定行为例如滑动反弹(当视图滑动超多了它的边界的将会反弹回正确的地方)。 11 | 12 | 让我们来用`CAScrollLayer`来常见一个基本的`UIScrollView`替代品。我们将会用`CAScrollLayer`作为视图的宿主图层,并创建一个自定义的`UIView`,然后用`UIPanGestureRecognizer`实现触摸事件响应。这段代码见清单6.10. 图6.11是运行效果:`ScrollView`显示了一个大于它的`frame`的`UIImageView`。 13 | 14 | 清单6.10 用`CAScrollLayer`实现滑动视图 15 | 16 | ```objective-c 17 | #import "ScrollView.h" 18 | #import @implementation ScrollView 19 | + (Class)layerClass 20 | { 21 | return [CAScrollLayer class]; 22 | } 23 | 24 | - (void)setUp 25 | { 26 | //enable clipping 27 | self.layer.masksToBounds = YES; 28 | 29 | //attach pan gesture recognizer 30 | UIPanGestureRecognizer *recognizer = nil; 31 | recognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)]; 32 | [self addGestureRecognizer:recognizer]; 33 | } 34 | 35 | - (id)initWithFrame:(CGRect)frame 36 | { 37 | //this is called when view is created in code 38 | if ((self = [super initWithFrame:frame])) { 39 | [self setUp]; 40 | } 41 | return self; 42 | } 43 | 44 | - (void)awakeFromNib { 45 | //this is called when view is created from a nib 46 | [self setUp]; 47 | } 48 | 49 | - (void)pan:(UIPanGestureRecognizer *)recognizer 50 | { 51 | //get the offset by subtracting the pan gesture 52 | //translation from the current bounds origin 53 | CGPoint offset = self.bounds.origin; 54 | offset.x -= [recognizer translationInView:self].x; 55 | offset.y -= [recognizer translationInView:self].y; 56 | 57 | //scroll the layer 58 | [(CAScrollLayer *)self.layer scrollToPoint:offset]; 59 | 60 | //reset the pan gesture translation 61 | [recognizer setTranslation:CGPointZero inView:self]; 62 | } 63 | @end 64 | ``` 65 | 66 | 图6.11 用`UIScrollView`创建一个凑合的滑动视图 67 | 68 | 不同于`UIScrollView`,我们定制的滑动视图类并没有实现任何形式的边界检查(bounds checking)。图层内容极有可能滑出视图的边界并无限滑下去。`CAScrollLayer`并没有等同于`UIScrollView`中`contentSize`的属性,所以当`CAScrollLayer`滑动的时候完全没有一个全局的可滑动区域的概念,也无法自适应它的边界原点至你指定的值。它之所以不能自适应边界大小是因为它不需要,内容完全可以超过边界。 69 | 70 | 那你一定会奇怪用`CAScrollLayer`的意义到底何在,因为你可以简单地用一个普通的`CALayer`然后手动适应边界原点啊。真相其实并不复杂,`UIScrollView`并没有用`CAScrollLayer`,事实上,就是简单的通过直接操作图层边界来实现滑动。 71 | 72 | `CAScrollLayer`有一个潜在的有用特性。如果你查看`CAScrollLayer`的头文件,你就会注意到有一个扩展分类实现了一些方法和属性: 73 | 74 | ```objective-c 75 | - (void)scrollPoint:(CGPoint)p; 76 | - (void)scrollRectToVisible:(CGRect)r; 77 | @property(readonly) CGRect visibleRect; 78 | ``` 79 | 80 | 看到这些方法和属性名,你也许会以为这些方法给每个`CALayer`实例增加了滑动功能。但是事实上他们只是放置在`CAScrollLayer`中的图层的实用方法。`scrollPoint:`方法从图层树中查找并找到第一个可用的`CAScrollLayer`,然后滑动它使得指定点成为可视的。`scrollRectToVisible:`方法实现了同样的事情只不过是作用在一个矩形上的。`visibleRect`属性决定图层(如果存在的话)的哪部分是当前的可视区域。如果你自己实现这些方法就会相对容易明白一点,但是`CAScrollLayer`帮你省了这些麻烦,所以当涉及到实现图层滑动的时候就可以用上了。 81 | -------------------------------------------------------------------------------- /chapter7/presentation-versus-model.md: -------------------------------------------------------------------------------- 1 | # 呈现与模型 2 | 3 | 4 | `CALayer`的属性行为其实很不正常,因为改变一个图层的属性并没有立刻生效,而是通过一段时间渐变更新。这是怎么做到的呢? 5 | 6 | 当你改变一个图层的属性,属性值的确是立刻更新的(如果你读取它的数据,你会发现它的值在你设置它的那一刻就已经生效了),但是屏幕上并没有马上发生改变。这是因为你设置的属性并没有直接调整图层的外观,相反,他只是定义了图层动画结束之后将要变化的外观。 7 | 8 | 当设置`CALayer`的属性,实际上是在定义当前事务结束之后图层如何显示的*模型*。Core Animation扮演了一个*控制器*的角色,并且负责根据图层行为和事务设置去不断更新*视图*的这些属性在屏幕上的状态。 9 | 10 | 我们讨论的就是一个典型的*微型MVC模式*。`CALayer`是一个连接用户界面(就是MVC中的*view*)虚构的类,但是在界面本身这个场景下,`CALayer`的行为更像是存储了视图如何显示和动画的数据模型。实际上,在苹果自己的文档中,图层树通常都是值的图层树模型。 11 | 12 | 在iOS中,屏幕每秒钟重绘60次。如果动画时长比60分之一秒要长,Core Animation就需要在设置一次新值和新值生效之间,对屏幕上的图层进行重新组织。这意味着`CALayer`除了“真实”值(就是你设置的值)之外,必须要知道当前*显示*在屏幕上的属性值的记录。 13 | 14 | 每个图层属性的显示值都被存储在一个叫做*呈现图层*的独立图层当中,他可以通过`-presentationLayer`方法来访问。这个呈现图层实际上是模型图层的复制,但是它的属性值代表了在任何指定时刻当前外观效果。换句话说,你可以通过呈现图层的值来获取当前屏幕上真正显示出来的值(图7.4)。 15 | 16 | 我们在第一章中提到除了图层树,另外还有*呈现树*。呈现树通过图层树中所有图层的呈现图层所形成。注意呈现图层仅仅当图层首次被*提交*(就是首次第一次在屏幕上显示)的时候创建,所以在那之前调用`-presentationLayer`将会返回`nil`。 17 | 18 | 你可能注意到有一个叫做`–modelLayer`的方法。在呈现图层上调用`–modelLayer`将会返回它正在呈现所依赖的`CALayer`。通常在一个图层上调用`-modelLayer`会返回`–self`(实际上我们已经创建的原始图层就是一种数据模型)。 19 | 20 | 图7.4 21 | 22 | 图7.4 一个移动的图层是如何通过数据模型呈现的 23 | 24 | 大多数情况下,你不需要直接访问呈现图层,你可以通过和模型图层的交互,来让Core Animation更新显示。两种情况下呈现图层会变得很有用,一个是同步动画,一个是处理用户交互。 25 | 26 | * 如果你在实现一个基于定时器的动画(见第11章“基于定时器的动画”),而不仅仅是基于事务的动画,这个时候准确地知道在某一时刻图层显示在什么位置就会对正确摆放图层很有用了。 27 | * 如果你想让你做动画的图层响应用户输入,你可以使用`-hitTest:`方法(见第三章“图层几何学”)来判断指定图层是否被触摸,这时候对*呈现*图层而不是*模型*图层调用`-hitTest:`会显得更有意义,因为呈现图层代表了用户当前看到的图层位置,而不是当前动画结束之后的位置。 28 | 29 | 我们可以用一个简单的案例来证明后者(见清单7.7)。在这个例子中,点击屏幕上的任意位置将会让图层平移到那里。点击图层本身可以随机改变它的颜色。我们通过对呈现图层调用`-hitTest:`来判断是否被点击。 30 | 31 | 如果修改代码让`-hitTest:`直接作用于*colorLayer*而不是呈现图层,你会发现当图层移动的时候它并不能正确显示。这时候你就需要点击图层将要移动到的位置而不是图层本身来响应点击(这就是为什么用呈现图层来响应交互的原因)。 32 | 33 | 清单7.7 使用`presentationLayer`图层来判断当前图层位置 34 | 35 | ```objective-c 36 | @interface ViewController () 37 | 38 | @property (nonatomic, strong) CALayer *colorLayer; 39 | 40 | @end 41 | 42 | @implementation ViewController 43 | 44 | - (void)viewDidLoad 45 | { 46 | [super viewDidLoad]; 47 | //create a red layer 48 | self.colorLayer = [CALayer layer]; 49 | self.colorLayer.frame = CGRectMake(0, 0, 100, 100); 50 | self.colorLayer.position = CGPointMake(self.view.bounds.size.width / 2, self.view.bounds.size.height / 2); 51 | self.colorLayer.backgroundColor = [UIColor redColor].CGColor; 52 | [self.view.layer addSublayer:self.colorLayer]; 53 | } 54 | 55 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 56 | { 57 | //get the touch point 58 | CGPoint point = [[touches anyObject] locationInView:self.view]; 59 | //check if we've tapped the moving layer 60 | if ([self.colorLayer.presentationLayer hitTest:point]) { 61 | //randomize the layer background color 62 | CGFloat red = arc4random() / (CGFloat)INT_MAX; 63 | CGFloat green = arc4random() / (CGFloat)INT_MAX; 64 | CGFloat blue = arc4random() / (CGFloat)INT_MAX; 65 | self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor; 66 | } else { 67 | //otherwise (slowly) move the layer to new position 68 | [CATransaction begin]; 69 | [CATransaction setAnimationDuration:4.0]; 70 | self.colorLayer.position = point; 71 | [CATransaction commit]; 72 | } 73 | } 74 | ``` 75 | @end 76 | -------------------------------------------------------------------------------- /chapter2/contentsrect.md: -------------------------------------------------------------------------------- 1 | # contentsRect 2 | 3 | 4 |     CALayer的`contentsRect`属性允许我们在图层边框里显示寄宿图的一个子域。这涉及到图片是如何显示和拉伸的,所以要比`contentsGravity`灵活多了 5 | 6 |     和`bounds`,`frame`不同,`contentsRect`不是按点来计算的,它使用了*单位坐标*,单位坐标指定在0到1之间,是一个相对值(像素和点就是绝对值)。所以他们是相对与寄宿图的尺寸的。iOS使用了以下的坐标系统: 7 | 8 | * 点 —— 在iOS和Mac OS中最常见的坐标体系。点就像是虚拟的像素,也被称作逻辑像素。在标准设备上,一个点就是一个像素,但是在Retina设备上,一个点等于2*2个像素。iOS用点作为屏幕的坐标测算体系就是为了在Retina设备和普通设备上能有一致的视觉效果。 9 | * 像素 —— 物理像素坐标并不会用来屏幕布局,但是仍然与图片有相对关系。UIImage是一个屏幕分辨率解决方案,所以指定点来度量大小。但是一些底层的图片表示如CGImage就会使用像素,所以你要清楚在Retina设备和普通设备上,他们表现出来了不同的大小。 10 | * 单位 —— 对于与图片大小或是图层边界相关的显示,单位坐标是一个方便的度量方式, 当大小改变的时候,也不需要再次调整。单位坐标在OpenGL这种纹理坐标系统中用得很多,Core Animation中也用到了单位坐标。 11 | 12 |     默认的`contentsRect`是{0, 0, 1, 1},这意味着整个寄宿图默认都是可见的,如果我们指定一个小一点的矩形,图片就会被裁剪(如图2.6) 13 | 14 | ![图2.6](./2.6.png) 15 | 16 | 图2.6 一个自定义的`contentsRect`(左)和之前显示的内容(右) 17 | 18 |     事实上给`contentsRect`设置一个负数的原点或是大于{1, 1}的尺寸也是可以的。这种情况下,最外面的像素会被拉伸以填充剩下的区域。 19 | 20 |     `contentsRect`在app中最有趣的地方在于一个叫做*image sprites*(图片拼合)的用法。如果你有游戏编程的经验,那么你一定对图片拼合的概念很熟悉,图片能够在屏幕上独立地变更位置。抛开游戏编程不谈,这个技术常用来指代载入拼合的图片,跟移动图片一点关系也没有。 21 | 22 |     典型地,图片拼合后可以打包整合到一张大图上一次性载入。相比多次载入不同的图片,这样做能够带来很多方面的好处:内存使用,载入时间,渲染性能等等 23 | 24 |     2D游戏引擎入Cocos2D使用了拼合技术,它使用OpenGL来显示图片。不过我们可以使用拼合在一个普通的UIKit应用中,对!就是使用`contentsRect` 25 | 26 |     首先,我们需要一个拼合后的图表 —— 一个包含小一些的拼合图的大图片。如图2.7所示: 27 | 28 | ![图2.7](./2.7.png) 29 | 30 |     接下来,我们要在app中载入并显示这些拼合图。规则很简单:像平常一样载入我们的大图,然后把它赋值给四个独立的图层的`contents`,然后设置每个图层的`contentsRect`来去掉我们不想显示的部分。 31 | 32 |     我们的工程中需要一些额外的视图。(为了避免太多代码。我们将使用Interface Builder来拜访他们的位置,如果你愿意还是可以用代码的方式来实现的)。清单2.3有需要的代码,图2.8展示了结果 33 | 34 | ```objective-c 35 | 36 | @interface ViewController () 37 | @property (nonatomic, weak) IBOutlet UIView *coneView; 38 | @property (nonatomic, weak) IBOutlet UIView *shipView; 39 | @property (nonatomic, weak) IBOutlet UIView *iglooView; 40 | @property (nonatomic, weak) IBOutlet UIView *anchorView; 41 | @end 42 | 43 | @implementation ViewController 44 | 45 | - (void)addSpriteImage:(UIImage *)image withContentRect:(CGRect)rect toLayer:(CALayer *)layer //set image 46 | { 47 | layer.contents = (__bridge id)image.CGImage; 48 | 49 | //scale contents to fit 50 | layer.contentsGravity = kCAGravityResizeAspect; 51 | 52 | //set contentsRect 53 | layer.contentsRect = rect; 54 | } 55 | 56 | - (void)viewDidLoad 57 | { 58 | [super viewDidLoad]; //load sprite sheet 59 | UIImage *image = [UIImage imageNamed:@"Sprites.png"]; 60 | //set igloo sprite 61 | [self addSpriteImage:image withContentRect:CGRectMake(0, 0, 0.5, 0.5) toLayer:self.iglooView.layer]; 62 | //set cone sprite 63 | [self addSpriteImage:image withContentRect:CGRectMake(0.5, 0, 0.5, 0.5) toLayer:self.coneView.layer]; 64 | //set anchor sprite 65 | [self addSpriteImage:image withContentRect:CGRectMake(0, 0.5, 0.5, 0.5) toLayer:self.anchorView.layer]; 66 | //set spaceship sprite 67 | [self addSpriteImage:image withContentRect:CGRectMake(0.5, 0.5, 0.5, 0.5) toLayer:self.shipView.layer]; 68 | } 69 | @end 70 | ``` 71 | ![图2.8](./2.8.png) 72 | 73 |     拼合不仅给app提供了一个整洁的载入方式,还有效地提高了载入性能(单张大图比多张小图载入地更快),但是如果有手动安排的话,他们还是有一些不方便的,如果你需要在一个已经创建好的品和图上做一些尺寸上的修改或者其他变动,无疑是比较麻烦的。 74 | 75 |     Mac上有一些商业软件可以为你自动拼合图片,这些工具自动生成一个包含拼合后的坐标的XML或者plist文件,拼合图片的使用大大简化。这个文件可以和图片一同载入,并给每个拼合的图层设置`contentsRect`,这样开发者就不用手动写代码来摆放位置了。 76 | 77 |     这些文件通常在OpenGL游戏中使用,不过呢,你要是有兴趣在一些常见的app中使用拼合技术,那么一个叫做LayerSprites的开源库([https://github.com/nicklockwood/LayerSprites](https://github.com/nicklockwood/LayerSprites)),它能够读取Cocos2D格式中的拼合图并在普通的Core Animation层中显示出来。 78 | -------------------------------------------------------------------------------- /chapter7/transactions.md: -------------------------------------------------------------------------------- 1 | # 事务 2 | 3 | Core Animation基于一个假设,说屏幕上的任何东西都可以(或者可能)做动画。动画并不需要你在Core Animation中手动打开,相反需要明确地关闭,否则他会一直存在。 4 | 5 | 当你改变`CALayer`的一个可做动画的属性,它并不能立刻在屏幕上体现出来。相反,它是从先前的值平滑过渡到新的值。这一切都是默认的行为,你不需要做额外的操作。 6 | 7 | 这看起来这太棒了,似乎不太真实,我们来用一个demo解释一下:首先和第一章“图层树”一样创建一个蓝色的方块,然后添加一个按钮,随机改变它的颜色。代码见清单7.1。点击按钮,你会发现图层的颜色平滑过渡到一个新值,而不是跳变(图7.1)。 8 | 9 | 清单7.1 随机改变图层颜色 10 | 11 | ```objective-c 12 | @interface ViewController () 13 | 14 | @property (nonatomic, weak) IBOutlet UIView *layerView; 15 | @property (nonatomic, weak) IBOutlet CALayer *colorLayer;/*热心人发现这里应该改为@property (nonatomic, strong) CALayer *colorLayer;否则运行结果不正确。 16 | */ 17 | @end 18 | 19 | @implementation ViewController 20 | 21 | - (void)viewDidLoad 22 | { 23 | [super viewDidLoad]; 24 | //create sublayer 25 | self.colorLayer = [CALayer layer]; 26 | self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f); 27 | self.colorLayer.backgroundColor = [UIColor blueColor].CGColor; 28 | //add it to our view 29 | [self.layerView.layer addSublayer:self.colorLayer]; 30 | } 31 | 32 | - (IBAction)changeColor 33 | { 34 | //randomize the layer background color 35 | CGFloat red = arc4random() / (CGFloat)INT_MAX; 36 | CGFloat green = arc4random() / (CGFloat)INT_MAX; 37 | CGFloat blue = arc4random() / (CGFloat)INT_MAX; 38 | self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor;  39 | } 40 | 41 | @end 42 | ``` 43 | 44 | 图7.1 45 | 46 | 图7.1 添加一个按钮来控制图层颜色 47 | 48 | 这其实就是所谓的*隐式*动画。之所以叫隐式是因为我们并没有指定任何动画的类型。我们仅仅改变了一个属性,然后Core Animation来决定如何并且何时去做动画。Core Animaiton同样支持*显式*动画,下章详细说明。 49 | 50 | 但当你改变一个属性,Core Animation是如何判断动画类型和持续时间的呢?实际上动画执行的时间取决于当前*事务*的设置,动画类型取决于*图层行为*。 51 | 52 | 事务实际上是Core Animation用来包含一系列属性动画集合的机制,任何用指定事务去改变可以做动画的图层属性都不会立刻发生变化,而是当事务一旦*提交*的时候开始用一个动画过渡到新值。 53 | 54 | 事务是通过`CATransaction`类来做管理,这个类的设计有些奇怪,不像你从它的命名预期的那样去管理一个简单的事务,而是管理了一叠你不能访问的事务。`CATransaction`没有属性或者实例方法,并且也不能用`+alloc`和`-init`方法创建它。但是可以用`+begin`和`+commit`分别来入栈或者出栈。 55 | 56 | 任何可以做动画的图层属性都会被添加到栈顶的事务,你可以通过`+setAnimationDuration:`方法设置当前事务的动画时间,或者通过`+animationDuration`方法来获取值(默认0.25秒)。 57 | 58 | Core Animation在每个*run loop*周期中自动开始一次新的事务(run loop是iOS负责收集用户输入,处理定时器或者网络事件并且重新绘制屏幕的东西),即使你不显式的用`[CATransaction begin]`开始一次事务,任何在一次run loop循环中属性的改变都会被集中起来,然后做一次0.25秒的动画。 59 | 60 | 明白这些之后,我们就可以轻松修改变色动画的时间了。我们当然可以用当前事务的`+setAnimationDuration:`方法来修改动画时间,但在这里我们首先起一个新的事务,于是修改时间就不会有别的副作用。因为修改当前事务的时间可能会导致同一时刻别的动画(如屏幕旋转),所以最好还是在调整动画之前压入一个新的事务。 61 | 62 | 修改后的代码见清单7.2。运行程序,你会发现色块颜色比之前变得更慢了。 63 | 64 | 清单7.2 使用`CATransaction`控制动画时间 65 | 66 | ```objective-c 67 | - (IBAction)changeColor 68 | { 69 | //begin a new transaction 70 | [CATransaction begin]; 71 | //set the animation duration to 1 second 72 | [CATransaction setAnimationDuration:1.0]; 73 | //randomize the layer background color 74 | CGFloat red = arc4random() / (CGFloat)INT_MAX; 75 | CGFloat green = arc4random() / (CGFloat)INT_MAX; 76 | CGFloat blue = arc4random() / (CGFloat)INT_MAX; 77 | self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor; 78 | //commit the transaction 79 | [CATransaction commit]; 80 | } 81 | ``` 82 | 83 | 如果你用过`UIView`的动画方法做过一些动画效果,那么应该对这个模式不陌生。`UIView`有两个方法,`+beginAnimations:context:`和`+commitAnimations`,和`CATransaction`的`+begin`和`+commit`方法类似。实际上在`+beginAnimations:context:`和`+commitAnimations`之间所有视图或者图层属性的改变而做的动画都是由于设置了`CATransaction`的原因。 84 | 85 | 在iOS4中,苹果对UIView添加了一种基于block的动画方法:`+animateWithDuration:animations:`。这样写对做一堆的属性动画在语法上会更加简单,但实质上它们都是在做同样的事情。 86 | 87 | `CATransaction`的`+begin`和`+commit`方法在`+animateWithDuration:animations:`内部自动调用,这样block中所有属性的改变都会被事务所包含。这样也可以避免开发者由于对`+begin`和`+commit`匹配的失误造成的风险。 88 | -------------------------------------------------------------------------------- /chapter3/hit-testing.md: -------------------------------------------------------------------------------- 1 | # Hit Testing 2 | 3 | 第一章“图层树”证实了最好使用图层相关视图,而不是创建独立的图层关系。其中一个原因就是要处理额外复杂的触摸事件。 4 | 5 | `CALayer`并不关心任何响应链事件,所以不能直接处理触摸事件或者手势。但是它有一系列的方法帮你处理事件:`-containsPoint:`和`-hitTest:`。 6 | 7 | ` -containsPoint: `接受一个在本图层坐标系下的`CGPoint`,如果这个点在图层`frame`范围内就返回`YES`。如清单3.4所示第一章的项目的另一个合适的版本,也就是使用`-containsPoint:`方法来判断到底是白色还是蓝色的图层被触摸了 8 | (图3.10)。这需要把触摸坐标转换成每个图层坐标系下的坐标,结果很不方便。 9 | 10 | 清单3.4 使用containsPoint判断被点击的图层 11 | 12 | ```objective-c 13 | @interface ViewController () 14 | 15 | @property (nonatomic, weak) IBOutlet UIView *layerView; 16 | @property (nonatomic, weak) CALayer *blueLayer; 17 | 18 | @end 19 | 20 | @implementation ViewController 21 | 22 | - (void)viewDidLoad 23 | { 24 | [super viewDidLoad]; 25 | //create sublayer 26 | self.blueLayer = [CALayer layer]; 27 | self.blueLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f); 28 | self.blueLayer.backgroundColor = [UIColor blueColor].CGColor; 29 | //add it to our view 30 | [self.layerView.layer addSublayer:self.blueLayer]; 31 | } 32 | 33 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 34 | { 35 | //get touch position relative to main view 36 | CGPoint point = [[touches anyObject] locationInView:self.view]; 37 | //convert point to the white layer's coordinates 38 | point = [self.layerView.layer convertPoint:point fromLayer:self.view.layer]; 39 | //get layer using containsPoint: 40 | if ([self.layerView.layer containsPoint:point]) { 41 | //convert point to blueLayer’s coordinates 42 | point = [self.blueLayer convertPoint:point fromLayer:self.layerView.layer]; 43 | if ([self.blueLayer containsPoint:point]) { 44 | [[[UIAlertView alloc] initWithTitle:@"Inside Blue Layer" 45 | message:nil 46 | delegate:nil 47 | cancelButtonTitle:@"OK" 48 | otherButtonTitles:nil] show]; 49 | } else { 50 | [[[UIAlertView alloc] initWithTitle:@"Inside White Layer" 51 | message:nil 52 | delegate:nil 53 | cancelButtonTitle:@"OK" 54 | otherButtonTitles:nil] show]; 55 | } 56 | } 57 | } 58 | 59 | @end 60 | ``` 61 | 62 | 图3.10 63 | 64 | 图3.10 点击图层被正确标识 65 | 66 | `-hitTest:`方法同样接受一个`CGPoint`类型参数,而不是`BOOL`类型,它返回图层本身,或者包含这个坐标点的叶子节点图层。这意味着不再需要像使用`-containsPoint:`那样,人工地在每个子图层变换或者测试点击的坐标。如果这个点在最外面图层的范围之外,则返回nil。具体使用`-hitTest:`方法被点击图层的代码如清单3.5所示。 67 | 68 | 清单3.5 使用hitTest判断被点击的图层 69 | 70 | ```objective-c 71 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 72 | { 73 | //get touch position 74 | CGPoint point = [[touches anyObject] locationInView:self.view]; 75 | //get touched layer 76 | CALayer *layer = [self.layerView.layer hitTest:point]; 77 | //get layer using hitTest 78 | if (layer == self.blueLayer) { 79 | [[[UIAlertView alloc] initWithTitle:@"Inside Blue Layer" 80 | message:nil 81 | delegate:nil 82 | cancelButtonTitle:@"OK" 83 | otherButtonTitles:nil] show]; 84 | } else if (layer == self.layerView.layer) { 85 | [[[UIAlertView alloc] initWithTitle:@"Inside White Layer" 86 | message:nil 87 | delegate:nil 88 | cancelButtonTitle:@"OK" 89 | otherButtonTitles:nil] show]; 90 | } 91 | } 92 | ``` 93 | 94 | 注意当调用图层的`-hitTest:`方法时,测算的顺序严格依赖于图层树当中的图层顺序(和UIView处理事件类似)。之前提到的`zPosition`属性可以明显改变屏幕上图层的顺序,但不能改变事件传递的顺序。 95 | 96 | 这意味着如果改变了图层的z轴顺序,你会发现将不能够检测到最前方的视图点击事件,这是因为被另一个图层遮盖住了,虽然它的`zPosition`值较小,但是在图层树中的顺序靠前。我们将在第五章详细讨论这个问题。 97 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [Introduction](README.md) 4 | * [图层树](chapter1/the-layer-tree.md) 5 | * [图层与视图](chapter1/layers-and-trees.md) 6 | * [图层的能力](chapter1/layer-capabilities.md) 7 | * [使用图层](chapter1/working-with-layers.md) 8 | * [总结](chapter1/summary.md) 9 | * [寄宿图](chapter2/the-Backing-Image.md) 10 | * [contents属性](chapter2/the-contents-image.md) 11 | * [Custom Drawing](chapter2/custom-drawing.md) 12 | * [总结](chapter2/summary.md) 13 | * [图层几何学](chapter3/layer-geometry.md) 14 | * [布局](chapter3/layout.md) 15 | * [锚点](chapter3/anchor.md) 16 | * [坐标系](chapter3/coordinate-systens.md) 17 | * [Hit Testing](chapter3/hit-testing.md) 18 | * [自动布局](chapter3/automatic-layout.md) 19 | * [总结](chapter3/summary.md) 20 | * [视觉效果](chapter4/visual-effcts.md) 21 | * [圆角](chapter4/rounded-corners.md) 22 | * [图层边框](chapter4/ayer-borders.md) 23 | * [阴影](chapter4/drop-shadows.md) 24 | * [图层蒙板](chapter4/layer-masking.md) 25 | * [拉伸过滤](chapter4/scaling-filters.md) 26 | * [组透明](chapter4/group-opacity.md) 27 | * [总结](chapter4/summary.md) 28 | * [变换](chapter5/transforms.md) 29 | * [仿射变换](chapter5/affine-fransforms.md) 30 | * [3D变换](chapter5/3d-transform.md) 31 | * [固体对象](chapter5/solid-objects.md) 32 | * [总结](chapter5/summary.md) 33 | * [专用图层](chapter6/specialized-layers.md) 34 | * [CAShapeLayer](chapter6/cashapelayer.md) 35 | * [CATextLayer](chapter6/CATextLayer.md) 36 | * [CATransformLayer](chapter6/catransformlayer.md) 37 | * [CAGradientLayer](chapter6/cagradientLayer.md) 38 | * [CAReplicatorLayer](chapter6/careplicatorLayer.md) 39 | * [CAScrollLayer](chapter6/cascrollLayer.md) 40 | * [CATiledLayer](chapter6/catiledLayer.md) 41 | * [CAEmitterLayer](chapter6/caemitterlayer.md) 42 | * [CAEAGLLayer](chapter6/caeagllayer.md) 43 | * [AVPlayerLayer](chapter6/avplayerlayer.md) 44 | * [总结](chapter6/summarry.md) 45 | * [隐式动画](chapter7/implicit-animations.md) 46 | * [事务](chapter7/transactions.md) 47 | * [完成块](chapter7/completion-blocks.md) 48 | * [图层行为](chapter7/layer-actions.md) 49 | * [呈现与模型](chapter7/presentation-versus-model.md) 50 | * [总结](chapter7/summary.md) 51 | * [显式动画](chapter8/explicit-animations.md) 52 | * [属性动画](chapter8/property-animations.md) 53 | * [动画组](chapter8/animation-groups.md) 54 | * [过渡](chapter8/transitions.md) 55 | * [在动画过程中取消动画](chapter8/changes-custom-transitions.md) 56 | * [总结](chapter8/summary.md) 57 | * [图层时间](chapter9/layer-time.md) 58 | * [`CAMediaTiming`协议](chapter9/the-cAMediaTiming-protocol.md) 59 | * [层级关系时间](chapter9/hierarchical-time.md) 60 | * [手动动画](chapter9/manual-animation.md) 61 | * [总结](chapter9/summary.md) 62 | * [缓冲](chapter10/easing.md) 63 | * [动画速度](chapter10/animation-velocity.md) 64 | * [自定义缓冲函数](chapter10/custom-easing-functions.md) 65 | * [总结](chapter10/summary.md) 66 | * [基于定时器的动画](chapter11/timer-based-animation.md) 67 | * [定时帧](chapter11/frame-timing.md) 68 | * [物理模拟](chapter11/physical-simulation.md) 69 | * [总结](chapter11/summary.md) 70 | * [性能调优](chapter12/tuning-for-speed.md) 71 | * [CPU VS GPU](chapter12/cpu-versus-gpu.md) 72 | * [测量,而不是猜测](chapter12/measure-dont-guess.md) 73 | * [Instruments](chapter12/instruments.md) 74 | * [总结](chapter12/summary.md) 75 | * [高效绘图](chapter13/efficient-drawing.md) 76 | * [软件绘图](chapter13/software-drawing-vector-graphics.md) 77 | * [矢量图形](chapter13/catiledLayer.md) 78 | * [脏矩形](chapter15/dirty-rectangles.md) 79 | * [异步绘制](chapter13/drawsasynchronously.md) 80 | * [总结](chapter13/summary.md) 81 | * [图像IO](chapter14/image-ioi.md) 82 | * [加载和潜伏](chapter14/loading-and-latency.md) 83 | * [缓存](chapter14/caching.md) 84 | * [文件格式](chapter14/file-format.md) 85 | * [总结](chapter14/summary.md) 86 | * [图层性能](chapter15/layer-performance.md) 87 | * [隐式绘制](chapter15/inexplicit-drawing-text.md) 88 | * [离屏渲染](chapter15/offscreen-rendering.md) 89 | * [混合和过度绘制](chapter15/blending-and-overdraw.md) 90 | * [减少图层数量](chapter15/blending-and-overdraw-reducing-layer-count.md) 91 | * [总结](chapter15/summary.md) 92 | 93 | -------------------------------------------------------------------------------- /chapter13/catiledLayer.md: -------------------------------------------------------------------------------- 1 | 2 | ##矢量图形 3 | 4 |     我们用Core Graphics来绘图的一个通常原因就是只是用图片或是图层效果不能轻易地绘制出矢量图形。矢量绘图包含一下这些: 5 | 6 | * 任意多边形(不仅仅是一个矩形) 7 | * 斜线或曲线 8 | * 文本 9 | * 渐变 10 | 11 |     举个例子,清单13.1 展示了一个基本的画线应用。这个应用将用户的触摸手势转换成一个`UIBezierPath`上的点,然后绘制成视图。我们在一个`UIView`子类`DrawingView`中实现了所有的绘制逻辑,这个情况下我们没有用上view controller。但是如果你喜欢你可以在view controller中实现触摸事件处理。图13.1是代码运行结果。 12 | 13 | 清单13.1 用Core Graphics实现一个简单的绘图应用 14 | 15 | ```objective-c 16 | #import "DrawingView.h" 17 | 18 | @interface DrawingView () 19 | 20 | @property (nonatomic, strong) UIBezierPath *path; 21 | 22 | @end 23 | 24 | @implementation DrawingView 25 | 26 | - (void)awakeFromNib 27 | { 28 | //create a mutable path 29 | self.path = [[UIBezierPath alloc] init]; 30 | self.path.lineJoinStyle = kCGLineJoinRound; 31 | self.path.lineCapStyle = kCGLineCapRound; 32 |  33 | self.path.lineWidth = 5; 34 | } 35 | 36 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 37 | { 38 | //get the starting point 39 | CGPoint point = [[touches anyObject] locationInView:self]; 40 | 41 | //move the path drawing cursor to the starting point 42 | [self.path moveToPoint:point]; 43 | } 44 | 45 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 46 | { 47 | //get the current point 48 | CGPoint point = [[touches anyObject] locationInView:self]; 49 | 50 | //add a new line segment to our path 51 | [self.path addLineToPoint:point]; 52 | 53 | //redraw the view 54 | [self setNeedsDisplay]; 55 | } 56 | 57 | - (void)drawRect:(CGRect)rect 58 | { 59 | //draw path 60 | [[UIColor clearColor] setFill]; 61 | [[UIColor redColor] setStroke]; 62 | [self.path stroke]; 63 | } 64 | @end 65 | ``` 66 | 67 | ![图13.1](./13.1.png) 68 | 69 | 图13.1 用Core Graphics做一个简单的『素描』 70 | 71 |     这样实现的问题在于,我们画得越多,程序就会越慢。因为每次移动手指的时候都会重绘整个贝塞尔路径(`UIBezierPath`),随着路径越来越复杂,每次重绘的工作就会增加,直接导致了帧数的下降。看来我们需要一个更好的方法了。 72 | 73 |     Core Animation为这些图形类型的绘制提供了专门的类,并给他们提供硬件支持(第六章『专有图层』有详细提到)。`CAShapeLayer`可以绘制多边形,直线和曲线。`CATextLayer`可以绘制文本。`CAGradientLayer`用来绘制渐变。这些总体上都比Core Graphics更快,同时他们也避免了创造一个寄宿图。 74 | 75 |     如果稍微将之前的代码变动一下,用`CAShapeLayer`替代Core Graphics,性能就会得到提高(见清单13.2).虽然随着路径复杂性的增加,绘制性能依然会下降,但是只有当非常非常浮躁的绘制时才会感到明显的帧率差异。 76 | 77 | 清单13.2 用`CAShapeLayer`重新实现绘图应用 78 | 79 | ```objective-c 80 | #import "DrawingView.h" 81 | #import 82 | 83 | @interface DrawingView () 84 | 85 | @property (nonatomic, strong) UIBezierPath *path; 86 | 87 | @end 88 |  89 | @implementation DrawingView 90 | 91 | + (Class)layerClass 92 | { 93 | //this makes our view create a CAShapeLayer 94 | //instead of a CALayer for its backing layer 95 | return [CAShapeLayer class]; 96 | } 97 | 98 | - (void)awakeFromNib 99 | { 100 | //create a mutable path 101 | self.path = [[UIBezierPath alloc] init]; 102 | 103 | //configure the layer 104 | CAShapeLayer *shapeLayer = (CAShapeLayer *)self.layer; 105 | shapeLayer.strokeColor = [UIColor redColor].CGColor; 106 | shapeLayer.fillColor = [UIColor clearColor].CGColor; 107 | shapeLayer.lineJoin = kCALineJoinRound; 108 | shapeLayer.lineCap = kCALineCapRound; 109 | shapeLayer.lineWidth = 5; 110 | } 111 | 112 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 113 | { 114 | //get the starting point 115 | CGPoint point = [[touches anyObject] locationInView:self]; 116 | 117 | //move the path drawing cursor to the starting point 118 | [self.path moveToPoint:point]; 119 | } 120 | 121 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 122 | { 123 | //get the current point 124 | CGPoint point = [[touches anyObject] locationInView:self]; 125 | 126 | //add a new line segment to our path 127 | [self.path addLineToPoint:point]; 128 | 129 | //update the layer with a copy of the path 130 | ((CAShapeLayer *)self.layer).path = self.path.CGPath; 131 | } 132 | @end 133 | ``` -------------------------------------------------------------------------------- /chapter6/careplicatorLayer.md: -------------------------------------------------------------------------------- 1 | 2 | ##CAReplicatorLayer 3 | 4 | `CAReplicatorLayer`的目的是为了高效生成许多相似的图层。它会绘制一个或多个图层的子图层,并在每个复制体上应用不同的变换。看上去演示能够更加解释这些,我们来写个例子吧。 5 | 6 | ###重复图层(Repeating Layers) 7 | 8 | 清单6.8中,我们在屏幕的中间创建了一个小白色方块图层,然后用`CAReplicatorLayer`生成十个图层组成一个圆圈。`instanceCount`属性指定了图层需要重复多少次。`instanceTransform`指定了一个`CATransform3D`3D变换(这种情况下,下一图层的位移和旋转将会移动到圆圈的下一个点)。 9 | 10 | 变换是逐步增加的,每个实例都是相对于前一实例布局。这就是为什么这些复制体最终不会出现在同意位置上,图6.8是代码运行结果。 11 | 12 | 清单6.8 用`CAReplicatorLayer`重复图层 13 | 14 | ```objective-c 15 | @interface ViewController () 16 | 17 | @property (nonatomic, weak) IBOutlet UIView *containerView; 18 | 19 | @end 20 | 21 | @implementation ViewController 22 | - (void)viewDidLoad 23 | { 24 | [super viewDidLoad]; 25 | //create a replicator layer and add it to our view 26 | CAReplicatorLayer *replicator = [CAReplicatorLayer layer]; 27 | replicator.frame = self.containerView.bounds; 28 | [self.containerView.layer addSublayer:replicator]; 29 | 30 | //configure the replicator 31 | replicator.instanceCount = 10; 32 | 33 | //apply a transform for each instance 34 | CATransform3D transform = CATransform3DIdentity; 35 | transform = CATransform3DTranslate(transform, 0, 200, 0); 36 | transform = CATransform3DRotate(transform, M_PI / 5.0, 0, 0, 1); 37 | transform = CATransform3DTranslate(transform, 0, -200, 0); 38 | replicator.instanceTransform = transform; 39 | 40 | //apply a color shift for each instance 41 | replicator.instanceBlueOffset = -0.1; 42 | replicator.instanceGreenOffset = -0.1; 43 | 44 | //create a sublayer and place it inside the replicator 45 | CALayer *layer = [CALayer layer]; 46 | layer.frame = CGRectMake(100.0f, 100.0f, 100.0f, 100.0f); 47 | layer.backgroundColor = [UIColor whiteColor].CGColor; 48 | [replicator addSublayer:layer]; 49 | } 50 | @end 51 | ``` 52 | 53 | ![图6.8](./6.8.png) 54 | 55 | 图6.8 用`CAReplicatorLayer`创建一圈图层 56 | 57 | 注意到当图层在重复的时候,他们的颜色也在变化:这是用`instanceBlueOffset`和`instanceGreenOffset`属性实现的。通过逐步减少蓝色和绿色通道,我们逐渐将图层颜色转换成了红色。这个复制效果看起来很酷,但是`CAReplicatorLayer`真正应用到实际程序上的场景比如:一个游戏中导弹的轨迹云,或者粒子爆炸(尽管iOS 5已经引入了`CAEmitterLayer`,它更适合创建任意的粒子效果)。除此之外,还有一个实际应用是:反射。 58 | 59 | ###反射 60 | 61 | 使用`CAReplicatorLayer`并应用一个负比例变换于一个复制图层,你就可以创建指定视图(或整个视图层次)内容的镜像图片,这样就创建了一个实时的『反射』效果。让我们来尝试实现这个创意:指定一个继承于`UIView`的`ReflectionView`,它会自动产生内容的反射效果。实现这个效果的代码很简单(见清单6.9),实际上用`ReflectionView`实现这个效果会更简单,我们只需要把`ReflectionView`的实例放置于Interface Builder(见图6.9),它就会实时生成子视图的反射,而不需要别的代码(见图6.10). 62 | 63 | 清单6.9 用`CAReplicatorLayer`自动绘制反射 64 | 65 | ```objective-c 66 | #import "ReflectionView.h" 67 | #import 68 | 69 | @implementation ReflectionView 70 | 71 | + (Class)layerClass 72 | { 73 | return [CAReplicatorLayer class]; 74 | } 75 | 76 | - (void)setUp 77 | { 78 | //configure replicator 79 | CAReplicatorLayer *layer = (CAReplicatorLayer *)self.layer; 80 | layer.instanceCount = 2; 81 | 82 | //move reflection instance below original and flip vertically 83 | CATransform3D transform = CATransform3DIdentity; 84 | CGFloat verticalOffset = self.bounds.size.height + 2; 85 | transform = CATransform3DTranslate(transform, 0, verticalOffset, 0); 86 | transform = CATransform3DScale(transform, 1, -1, 0); 87 | layer.instanceTransform = transform; 88 | 89 | //reduce alpha of reflection layer 90 | layer.instanceAlphaOffset = -0.6; 91 | } 92 |  93 | - (id)initWithFrame:(CGRect)frame 94 | { 95 | //this is called when view is created in code 96 | if ((self = [super initWithFrame:frame])) { 97 | [self setUp]; 98 | } 99 | return self; 100 | } 101 | 102 | - (void)awakeFromNib 103 | { 104 | //this is called when view is created from a nib 105 | [self setUp]; 106 | } 107 | @end 108 | ``` 109 | 110 | ![图6.9](./6.9.png) 111 | 112 | 图6.9 在Interface Builder中使用`ReflectionView` 113 | 114 | ![图6.10](./6.10.png) 115 | 116 | 图6.10 `ReflectionView`自动实时产生反射效果。 117 | 118 | 开源代码`ReflectionView`完成了一个自适应的渐变淡出效果(用`CAGradientLayer`和图层蒙板实现),代码见 https://github.com/nicklockwood/ReflectionView 119 | -------------------------------------------------------------------------------- /chapter5/affine-fransforms.md: -------------------------------------------------------------------------------- 1 | # 仿射变换 2 | 3 | 4 | 在第三章“图层几何学”中,我们使用了`UIView`的`transform`属性旋转了钟的指针,但并没有解释背后运作的原理,实际上`UIView`的`transform`属性是一个`CGAffineTransform`类型,用于在二维空间做旋转,缩放和平移。`CGAffineTransform`是一个可以和二维空间向量(例如`CGPoint`)做乘法的3X2的矩阵(见图5.1)。 5 | 6 | 图5.1 7 | 8 | 图5.1 用矩阵表示的`CGAffineTransform`和`CGPoint` 9 | 10 | 用`CGPoint`的每一列和`CGAffineTransform`矩阵的每一行对应元素相乘再求和,就形成了一个新的`CGPoint`类型的结果。要解释一下图中显示的灰色元素,为了能让矩阵做乘法,左边矩阵的列数一定要和右边矩阵的行数个数相同,所以要给矩阵填充一些标志值,使得既可以让矩阵做乘法,又不改变运算结果,并且没必要存储这些添加的值,因为它们的值不会发生变化,但是要用来做运算。 11 | 12 | 因此,通常会用3×3(而不是2×3)的矩阵来做二维变换,你可能会见到3行2列格式的矩阵,这是所谓的以列为主的格式,图5.1所示的是以行为主的格式,只要能保持一致,用哪种格式都无所谓。 13 | 14 | 当对图层应用变换矩阵,图层矩形内的每一个点都被相应地做变换,从而形成一个新的四边形的形状。`CGAffineTransform`中的“仿射”的意思是无论变换矩阵用什么值,图层中平行的两条线在变换之后任然保持平行,`CGAffineTransform`可以做出任意符合上述标注的变换,图5.2显示了一些仿射的和非仿射的变换: 15 | 16 | 图5.2 17 | 18 | ### 创建一个`CGAffineTransform` 19 | 20 | 21 | 对矩阵数学做一个全面的阐述就超出本书的讨论范围了,不过如果你对矩阵完全不熟悉的话,矩阵变换可能会使你感到畏惧。幸运的是,Core Graphics提供了一系列函数,对完全没有数学基础的开发者也能够简单地做一些变换。如下几个函数都创建了一个`CGAffineTransform`实例: 22 | 23 | CGAffineTransformMakeRotation(CGFloat angle) 24 | CGAffineTransformMakeScale(CGFloat sx, CGFloat sy) 25 | CGAffineTransformMakeTranslation(CGFloat tx, CGFloat ty) 26 | 27 | 旋转和缩放变换都可以很好解释--分别旋转或者缩放一个向量的值。平移变换是指每个点都移动了向量指定的x或者y值--所以如果向量代表了一个点,那它就平移了这个点的距离。 28 | 29 | 我们用一个很简单的项目来做个demo,把一个原始视图旋转45度角度(图5.3) 30 | 31 | 图5.3 32 | 33 | 图5.3 使用仿射变换旋转45度角之后的视图 34 | 35 | `UIView`可以通过设置`transform`属性做变换,但实际上它只是封装了内部图层的变换。 36 | 37 | `CALayer`同样也有一个`transform`属性,但它的类型是`CATransform3D`,而不是`CGAffineTransform`,本章后续将会详细解释。`CALayer`对应于`UIView`的`transform`属性叫做`affineTransform`,清单5.1的例子就是使用`affineTransform`对图层做了45度顺时针旋转。 38 | 39 | 清单5.1 使用`affineTransform`对图层旋转45度 40 | ```objective-c 41 | @interface ViewController () 42 | 43 | @property (nonatomic, weak) IBOutlet UIView *layerView; 44 | 45 | @end 46 | 47 | @implementation ViewController 48 | 49 | - (void)viewDidLoad 50 | { 51 | [super viewDidLoad]; 52 | //rotate the layer 45 degrees 53 | CGAffineTransform transform = CGAffineTransformMakeRotation(M_PI_4); 54 | self.layerView.layer.affineTransform = transform; 55 | } 56 | 57 | @end 58 | ``` 59 | 60 | 注意我们使用的旋转常量是`M_PI_4`,而不是你想象的45,因为iOS的变换函数使用弧度而不是角度作为单位。弧度用数学常量pi的倍数表示,一个pi代表180度,所以四分之一的pi就是45度。 61 | 62 | C的数学函数库(iOS会自动引入)提供了pi的一些简便的换算,`M_PI_4`于是就是pi的四分之一,如果对换算不太清楚的话,可以用如下的宏做换算: 63 | 64 | #define RADIANS_TO_DEGREES(x) ((x)/M_PI*180.0) 65 | ### 混合变换 66 | 67 | 68 | Core Graphics提供了一系列的函数可以在一个变换的基础上做更深层次的变换,如果做一个既要*缩放*又要*旋转*的变换,这就会非常有用了。例如下面几个函数: 69 | 70 | CGAffineTransformRotate(CGAffineTransform t, CGFloat angle) 71 | CGAffineTransformScale(CGAffineTransform t, CGFloat sx, CGFloat sy) 72 | CGAffineTransformTranslate(CGAffineTransform t, CGFloat tx, CGFloat ty) 73 | 74 | 当操纵一个变换的时候,初始生成一个什么都不做的变换很重要--也就是创建一个`CGAffineTransform`类型的空值,矩阵论中称作*单位矩阵*,Core Graphics同样也提供了一个方便的常量: 75 | 76 | CGAffineTransformIdentity 77 | 78 | 最后,如果需要混合两个已经存在的变换矩阵,就可以使用如下方法,在两个变换的基础上创建一个新的变换: 79 | 80 | CGAffineTransformConcat(CGAffineTransform t1, CGAffineTransform t2); 81 | 82 | 我们来用这些函数组合一个更加复杂的变换,先缩小50%,再旋转30度,最后向右移动200个像素(清单5.2)。图5.4显示了图层变换最后的结果。 83 | 84 | 清单5.2 使用若干方法创建一个复合变换 85 | 86 | ```objective-c 87 | - (void)viewDidLoad 88 | { 89 | [super viewDidLoad]; 90 | //create a new transform 91 | CGAffineTransform transform = CGAffineTransformIdentity; 92 | //scale by 50% 93 | transform = CGAffineTransformScale(transform, 0.5, 0.5); 94 | //rotate by 30 degrees 95 | transform = CGAffineTransformRotate(transform, M_PI / 180.0 * 30.0); 96 | //translate by 200 points 97 | transform = CGAffineTransformTranslate(transform, 200, 0); 98 | //apply transform to layer 99 | self.layerView.layer.affineTransform = transform; 100 | } 101 | ``` 102 | 103 | 图5.4 104 | 105 | 图5.4 顺序应用多个仿射变换之后的结果 106 | 107 | 图5.4中有些需要注意的地方:图片向右边发生了平移,但并没有指定距离那么远(200像素),另外它还有点向下发生了平移。原因在于当你按顺序做了变换,上一个变换的结果将会影响之后的变换,所以200像素的向右平移同样也被旋转了30度,缩小了50%,所以它实际上是斜向移动了100像素。 108 | 109 | 这意味着变换的顺序会影响最终的结果,也就是说旋转之后的平移和平移之后的旋转结果可能不同。 110 | 111 | #define DEGREES_TO_RADIANS(x) ((x)/180.0*M_PI) 112 | -------------------------------------------------------------------------------- /chapter3/anchor.md: -------------------------------------------------------------------------------- 1 | # 锚点 2 | 3 |     之前提到过,视图的`center`属性和图层的`position`属性都指定了`anchorPoint`相对于父图层的位置。图层的`anchorPoint`通过`position`来控制它的`frame`的位置,你可以认为`anchorPoint`是用来移动图层的*把柄*。 4 | 5 |     默认来说,`anchorPoint`位于图层的中点,所以图层的将会以这个点为中心放置。`anchorPoint`属性并没有被`UIView`接口暴露出来,这也是视图的position属性被叫做“center”的原因。但是图层的`anchorPoint`可以被移动,比如你可以把它置于图层`frame`的左上角,于是图层的内容将会向右下角的`position`方向移动(图3.3),而不是居中了。 6 | 7 | 图3.3 8 | 9 | 图3.3 改变`anchorPoint`的效果 10 | 11 |     和第二章提到的`contentsRect`和`contentsCenter`属性类似,`anchorPoint`用*单位坐标*来描述,也就是图层的相对坐标,图层左上角是{0, 0},右下角是{1, 1},因此默认坐标是{0.5, 0.5}。`anchorPoint`可以通过指定x和y值小于0或者大于1,使它放置在图层范围之外。 12 | 13 |     注意在图3.3中,当改变了`anchorPoint`,`position`属性保持固定的值并没有发生改变,但是`frame`却移动了。 14 | 15 |     那在什么场合需要改变`anchorPoint`呢?既然我们可以随意改变图层位置,那改变`anchorPoint`不会造成困惑么?为了举例说明,我们来举一个实用的例子,创建一个模拟闹钟的项目。 16 | 17 |     钟面和钟表由四张图片组成(图3.4),为了简单说明,我们还是用传统的方式来装载和加载图片,使用四个`UIImageView`实例(当然你也可以用正常的视图,设置他们图层的`contents`图片)。 18 | 19 | 图3.4 20 | 21 | 图3.4 组成钟面和钟表的四张图片 22 | 23 |     闹钟的组件通过IB来排列(图3.5),这些图片视图嵌套在一个容器视图之内,并且自动调整和自动布局都被禁用了。这是因为自动调整会影响到视图的`frame`,而根据图3.2的演示,当视图旋转的时候,`frame`是会发生改变的,这将会导致一些布局上的失灵。 24 | 25 |     我们用`NSTimer`来更新闹钟,使用视图的`transform`属性来旋转钟表(如果你对这个属性不太熟悉,不要着急,我们将会在第5章“变换”当中详细说明),具体代码见清单3.1 26 | 27 | 图3.5 28 | 29 | 图3.5 在Interface Builder中布局闹钟视图 30 | 31 | 清单3.1 **Clock** 32 | ```objective-c 33 | @interface ViewController () 34 | 35 | @property (nonatomic, weak) IBOutlet UIImageView *hourHand; 36 | @property (nonatomic, weak) IBOutlet UIImageView *minuteHand; 37 | @property (nonatomic, weak) IBOutlet UIImageView *secondHand; 38 | @property (nonatomic, weak) NSTimer *timer; 39 | 40 | @end 41 | 42 | @implementation ViewController 43 | 44 | - (void)viewDidLoad 45 | { 46 | [super viewDidLoad]; 47 | //start timer 48 | self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(tick) userInfo:nil repeats:YES]; 49 |  50 | //set initial hand positions 51 | [self tick]; 52 | } 53 | 54 | - (void)tick 55 | { 56 | //convert time to hours, minutes and seconds 57 | NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar]; 58 | NSUInteger units = NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit; 59 | NSDateComponents *components = [calendar components:units fromDate:[NSDate date]]; 60 | CGFloat hoursAngle = (components.hour / 12.0) * M_PI * 2.0; 61 | //calculate hour hand angle //calculate minute hand angle 62 | CGFloat minsAngle = (components.minute / 60.0) * M_PI * 2.0; 63 | //calculate second hand angle 64 | CGFloat secsAngle = (components.second / 60.0) * M_PI * 2.0; 65 | //rotate hands 66 | self.hourHand.transform = CGAffineTransformMakeRotation(hoursAngle); 67 | self.minuteHand.transform = CGAffineTransformMakeRotation(minsAngle); 68 | self.secondHand.transform = CGAffineTransformMakeRotation(secsAngle); 69 | } 70 | 71 | @end 72 | ``` 73 | 74 |     运行项目,看起来有点奇怪(图3.6),因为钟表的图片在围绕着中心旋转,这并不是我们期待的一个支点。 75 | 76 | 图3.6 77 | 78 | 图3.6 钟面,和不对齐的钟指针 79 | 80 |     你也许会认为可以在Interface Builder当中调整指针图片的位置来解决,但其实并不能达到目的,因为如果不放在钟面中间的话,同样不能正确的旋转。 81 | 82 |     也许在图片末尾添加一个透明空间也是个解决方案,但这样会让图片变大,也会消耗更多的内存,这样并不优雅。 83 | 84 |     更好的方案是使用`anchorPoint`属性,我们来在`-viewDidLoad`方法中添加几行代码来给每个钟指针的`anchorPoint`做一些平移(清单3.2),图3.7显示了正确的结果。 85 | 86 | 清单3.2 87 | ```objective-c 88 | - (void)viewDidLoad 89 | { 90 | [super viewDidLoad]; 91 | // adjust anchor points 92 | 93 | self.secondHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f); 94 | self.minuteHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f); 95 | self.hourHand.layer.anchorPoint = CGPointMake(0.5f, 0.9f); 96 | 97 | 98 | // start timer 99 | } 100 | ``` 101 | 102 | 图3.7 103 | 104 | 图3.7 钟面,和正确对齐的钟指针 105 | -------------------------------------------------------------------------------- /chapter12/cpu-versus-gpu.md: -------------------------------------------------------------------------------- 1 | 2 | ##CPU VS GPU 3 | 4 |     关于绘图和动画有两种处理的方式:CPU(中央处理器)和GPU(图形处理器)。在现代iOS设备中,都有可以运行不同软件的可编程芯片,但是由于历史原因,我们可以说CPU所做的工作都在软件层面,而GPU在硬件层面。 5 | 6 |     总的来说,我们可以用软件(使用CPU)做任何事情,但是对于图像处理,通常用硬件会更快,因为GPU使用图像对高度并行浮点运算做了优化。由于某些原因,我们想尽可能把屏幕渲染的工作交给硬件去处理。问题在于GPU并没有无限制处理性能,而且一旦资源用完的话,性能就会开始下降了(即使CPU并没有完全占用) 7 | 8 |     大多数动画性能优化都是关于智能利用GPU和CPU,使得它们都不会超出负荷。于是我们首先需要知道Core Animation是如何在这两个处理器之间分配工作的。 9 | 10 | ###动画的舞台 11 | 12 |     Core Animation处在iOS的核心地位:应用内和应用间都会用到它。一个简单的动画可能同步显示多个app的内容,例如当在iPad上多个程序之间使用手势切换,会使得多个程序同时显示在屏幕上。在一个特定的应用中用代码实现它是没有意义的,因为在iOS中不可能实现这种效果(App都是被沙箱管理,不能访问别的视图)。 13 | 14 |     动画和屏幕上组合的图层实际上被一个单独的进程管理,而不是你的应用程序。这个进程就是所谓的*渲染服务*。在iOS5和之前的版本是*SpringBoard*进程(同时管理着iOS的主屏)。在iOS6之后的版本中叫做`BackBoard`。 15 | 16 |     当运行一段动画时候,这个过程会被四个分离的阶段被打破: 17 | 18 | * **布局** - 这是准备你的视图/图层的层级关系,以及设置图层属性(位置,背景色,边框等等)的阶段。 19 | 20 | * **显示** - 这是图层的寄宿图片被绘制的阶段。绘制有可能涉及你的`-drawRect:`和`-drawLayer:inContext:`方法的调用路径。 21 | 22 | * **准备** - 这是Core Animation准备发送动画数据到渲染服务的阶段。这同时也是Core Animation将要执行一些别的事务例如解码动画过程中将要显示的图片的时间点。 23 | 24 | * **提交** - 这是最后的阶段,Core Animation打包所有图层和动画属性,然后通过IPC(内部处理通信)发送到渲染服务进行显示。 25 | 26 |     但是这些仅仅阶段仅仅发生在你的应用程序之内,在动画在屏幕上显示之前仍然有更多的工作。一旦打包的图层和动画到达渲染服务进程,他们会被反序列化来形成另一个叫做*渲染树*的图层树(在第一章“图层树”中提到过)。使用这个树状结构,渲染服务对动画的每一帧做出如下工作: 27 | 28 | * 对所有的图层属性计算中间值,设置OpenGL几何形状(纹理化的三角形)来执行渲染 29 | 30 | * 在屏幕上渲染可见的三角形 31 | 32 |     所以一共有六个阶段;最后两个阶段在动画过程中不停地重复。前五个阶段都在软件层面处理(通过CPU),只有最后一个被GPU执行。而且,你真正只能控制前两个阶段:布局和显示。Core Animation框架在内部处理剩下的事务,你也控制不了它。 33 | 34 |     这并不是个问题,因为在布局和显示阶段,你可以决定哪些由CPU执行,哪些交给GPU去做。那么改如何判断呢? 35 | 36 | ###GPU相关的操作 37 | 38 |     GPU为一个具体的任务做了优化:它用来采集图片和形状(三角形),运行变换,应用纹理和混合然后把它们输送到屏幕上。现代iOS设备上可编程的GPU在这些操作的执行上又很大的灵活性,但是Core Animation并没有暴露出直接的接口。除非你想绕开Core Animation并编写你自己的OpenGL着色器,从根本上解决硬件加速的问题,那么剩下的所有都还是需要在CPU的软件层面上完成。 39 | 40 |     宽泛的说,大多数`CALayer`的属性都是用GPU来绘制。比如如果你设置图层背景或者边框的颜色,那么这些可以通过着色的三角板实时绘制出来。如果对一个`contents`属性设置一张图片,然后裁剪它 - 它就会被纹理的三角形绘制出来,而不需要软件层面做任何绘制。 41 | 42 |     但是有一些事情会降低(基于GPU)图层绘制,比如: 43 | 44 | * 太多的几何结构 - 这发生在需要太多的三角板来做变换,以应对处理器的栅格化的时候。现代iOS设备的图形芯片可以处理几百万个三角板,所以在Core Animation中几何结构并不是GPU的瓶颈所在。但由于图层在显示之前通过IPC发送到渲染服务器的时候(图层实际上是由很多小物体组成的特别重量级的对象),太多的图层就会引起CPU的瓶颈。这就限制了一次展示的图层个数(见本章后续“CPU相关操作”)。 45 | 46 | * 重绘 - 主要由重叠的半透明图层引起。GPU的*填充比率*(用颜色填充像素的比率)是有限的,所以需要避免*重绘*(每一帧用相同的像素填充多次)的发生。在现代iOS设备上,GPU都会应对重绘;即使是iPhone 3GS都可以处理高达2.5的重绘比率,并任然保持60帧率的渲染(这意味着你可以绘制一个半的整屏的冗余信息,而不影响性能),并且新设备可以处理更多。 47 | 48 | * 离屏绘制 - 这发生在当不能直接在屏幕上绘制,并且必须绘制到离屏图片的上下文中的时候。离屏绘制发生在基于CPU或者是GPU的渲染,或者是为离屏图片分配额外内存,以及切换绘制上下文,这些都会降低GPU性能。对于特定图层效果的使用,比如圆角,图层遮罩,阴影或者是图层光栅化都会强制Core Animation提前渲染图层的离屏绘制。但这不意味着你需要避免使用这些效果,只是要明白这会带来性能的负面影响。 49 | 50 | * 过大的图片 - 如果视图绘制超出GPU支持的2048x2048或者4096x4096尺寸的纹理,就必须要用CPU在图层每次显示之前对图片预处理,同样也会降低性能。 51 | 52 | ###CPU相关的操作 53 | 54 |     大多数工作在Core Animation的CPU都发生在动画开始之前。这意味着它不会影响到帧率,所以很好,但是他会延迟动画开始的时间,让你的界面看起来会比较迟钝。 55 | 56 |     以下CPU的操作都会延迟动画的开始时间: 57 | 58 | * 布局计算 - 如果你的视图层级过于复杂,当视图呈现或者修改的时候,计算图层帧率就会消耗一部分时间。特别是使用iOS6的自动布局机制尤为明显,它应该是比老版的自动调整逻辑加强了CPU的工作。 59 | 60 | * 视图懒加载 - iOS只会当视图控制器的视图显示到屏幕上时才会加载它。这对内存使用和程序启动时间很有好处,但是当呈现到屏幕上之前,按下按钮导致的许多工作都会不能被及时响应。比如控制器从数据库中获取数据,或者视图从一个nib文件中加载,或者涉及IO的图片显示(见后续“IO相关操作”),都会比CPU正常操作慢得多。 61 | 62 | * Core Graphics绘制 - 如果对视图实现了`-drawRect:`方法,或者`CALayerDelegate`的`-drawLayer:inContext:`方法,那么在绘制任何东西之前都会产生一个巨大的性能开销。为了支持对图层内容的任意绘制,Core Animation必须创建一个内存中等大小的寄宿图片。然后一旦绘制结束之后,必须把图片数据通过IPC传到渲染服务器。在此基础上,Core Graphics绘制就会变得十分缓慢,所以在一个对性能十分挑剔的场景下这样做十分不好。 63 | 64 | * 解压图片 - PNG或者JPEG压缩之后的图片文件会比同质量的位图小得多。但是在图片绘制到屏幕上之前,必须把它扩展成完整的未解压的尺寸(通常等同于图片宽 x 长 x 4个字节)。为了节省内存,iOS通常直到真正绘制的时候才去解码图片(14章“图片IO”会更详细讨论)。根据你加载图片的方式,第一次对图层内容赋值的时候(直接或者间接使用`UIImageView`)或者把它绘制到Core Graphics中,都需要对它解压,这样的话,对于一个较大的图片,都会占用一定的时间。 65 | 66 |     当图层被成功打包,发送到渲染服务器之后,CPU仍然要做如下工作:为了显示屏幕上的图层,Core Animation必须对渲染树种的每个可见图层通过OpenGL循环转换成纹理三角板。由于GPU并不知晓Core Animation图层的任何结构,所以必须要由CPU做这些事情。这里CPU涉及的工作和图层个数成正比,所以如果在你的层级关系中有太多的图层,就会导致CPU没一帧的渲染,即使这些事情不是你的应用程序可控的。 67 | 68 | ###IO相关操作 69 | 70 |     还有一项没涉及的就是IO相关工作。上下文中的IO(输入/输出)指的是例如闪存或者网络接口的硬件访问。一些动画可能需要从山村(甚至是远程URL)来加载。一个典型的例子就是两个视图控制器之间的过渡效果,这就需要从一个nib文件或者是它的内容中懒加载,或者一个旋转的图片,可能在内存中尺寸太大,需要动态滚动来加载。 71 | 72 |     IO比内存访问更慢,所以如果动画涉及到IO,就是一个大问题。总的来说,这就需要使用聪敏但尴尬的技术,也就是多线程,缓存和投机加载(提前加载当前不需要的资源,但是之后可能需要用到)。这些技术将会在第14章中讨论。 73 | -------------------------------------------------------------------------------- /chapter6/catransformlayer.md: -------------------------------------------------------------------------------- 1 | ## CATransformLayer 2 | 3 | 当我们在构造复杂的3D事物的时候,如果能够组织独立元素就太方便了。比如说,你想创造一个孩子的手臂:你就需要确定哪一部分是孩子的手腕,哪一部分是孩子的前臂,哪一部分是孩子的肘,哪一部分是孩子的上臂,哪一部分是孩子的肩膀等等。 4 | 5 | 当然是允许独立地移动每个区域的啦。以肘为指点会移动前臂和手,而不是肩膀。Core Animation图层很容易就可以让你在2D环境下做出这样的层级体系下的变换,但是3D情况下就不太可能,因为所有的图层都把他的孩子都平面化到一个场景中(第五章『变换』有提到)。 6 | 7 | `CATransformLayer`解决了这个问题,`CATransformLayer`不同于普通的`CALayer`,因为它不能显示它自己的内容。只有当存在了一个能作用域子图层的变换它才真正存在。`CATransformLayer`并不平面化它的子图层,所以它能够用于构造一个层级的3D结构,比如我的手臂示例。 8 | 9 | 用代码创建一个手臂需要相当多的代码,所以我就演示得更简单一些吧:在第五章的立方体示例,我们将通过旋转`camara`来解决图层平面化问题而不是像立方体示例代码中用的`sublayerTransform`。这是一个非常不错的技巧,但是只能作用域单个对象上,如果你的场景包含两个立方体,那我们就不能用这个技巧单独旋转他们了。 10 | 11 | 那么,就让我们来试一试`CATransformLayer`吧,第一个问题就来了:在第五章,我们是用多个视图来构造了我们的立方体,而不是单独的图层。我们不能在不打乱已有的视图层次的前提下在一个本身不是有寄宿图的图层中放置一个寄宿图图层。我们可以创建一个新的`UIView`子类寄宿在`CATransformLayer`(用`+layerClass`方法)之上。但是,为了简化案例,我们仅仅重建了一个单独的图层,而不是使用视图。这意味着我们不能像第五章一样在立方体表面显示按钮和标签,不过我们现在也用不到这个特性。 12 | 13 | 清单6.5就是代码。我们以我们在第五章使用过的相同基本逻辑放置立方体。但是并不像以前那样直接将立方面添加到容器视图的宿主图层,我们将他们放置到一个`CATransformLayer`中创建一个独立的立方体对象,然后将两个这样的立方体放进容器中。我们随机地给立方面染色以将他们区分开来,这样就不用靠标签或是光亮来区分他们。图6.5是运行结果。 14 | 15 | 清单6.5 用`CATransformLayer`装配一个3D图层体系 16 | 17 | ```objective-c 18 | @interface ViewController () 19 | 20 | @property (nonatomic, weak) IBOutlet UIView *containerView; 21 | 22 | @end 23 | 24 | @implementation ViewController 25 | 26 | - (CALayer *)faceWithTransform:(CATransform3D)transform 27 | { 28 | //create cube face layer 29 | CALayer *face = [CALayer layer]; 30 | face.frame = CGRectMake(-50, -50, 100, 100); 31 | 32 | //apply a random color 33 | CGFloat red = (rand() / (double)INT_MAX); 34 | CGFloat green = (rand() / (double)INT_MAX); 35 | CGFloat blue = (rand() / (double)INT_MAX); 36 | face.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor; 37 | 38 | //apply the transform and return 39 | face.transform = transform; 40 | return face; 41 | } 42 | 43 | - (CALayer *)cubeWithTransform:(CATransform3D)transform 44 | { 45 | //create cube layer 46 | CATransformLayer *cube = [CATransformLayer layer]; 47 | 48 | //add cube face 1 49 | CATransform3D ct = CATransform3DMakeTranslation(0, 0, 50); 50 | [cube addSublayer:[self faceWithTransform:ct]]; 51 | 52 | //add cube face 2 53 | ct = CATransform3DMakeTranslation(50, 0, 0); 54 | ct = CATransform3DRotate(ct, M_PI_2, 0, 1, 0); 55 | [cube addSublayer:[self faceWithTransform:ct]]; 56 | 57 | //add cube face 3 58 | ct = CATransform3DMakeTranslation(0, -50, 0); 59 | ct = CATransform3DRotate(ct, M_PI_2, 1, 0, 0); 60 | [cube addSublayer:[self faceWithTransform:ct]]; 61 | 62 | //add cube face 4 63 | ct = CATransform3DMakeTranslation(0, 50, 0); 64 | ct = CATransform3DRotate(ct, -M_PI_2, 1, 0, 0); 65 | [cube addSublayer:[self faceWithTransform:ct]]; 66 | 67 | //add cube face 5 68 | ct = CATransform3DMakeTranslation(-50, 0, 0); 69 | ct = CATransform3DRotate(ct, -M_PI_2, 0, 1, 0); 70 | [cube addSublayer:[self faceWithTransform:ct]]; 71 | 72 | //add cube face 6 73 | ct = CATransform3DMakeTranslation(0, 0, -50); 74 | ct = CATransform3DRotate(ct, M_PI, 0, 1, 0); 75 | [cube addSublayer:[self faceWithTransform:ct]]; 76 | 77 | //center the cube layer within the container 78 | CGSize containerSize = self.containerView.bounds.size; 79 | cube.position = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0); 80 | 81 | //apply the transform and return 82 | cube.transform = transform; 83 | return cube; 84 | } 85 | 86 | - (void)viewDidLoad 87 | { 88 | [super viewDidLoad]; 89 | 90 | //set up the perspective transform 91 | CATransform3D pt = CATransform3DIdentity; 92 | pt.m34 = -1.0 / 500.0; 93 | self.containerView.layer.sublayerTransform = pt; 94 | 95 | //set up the transform for cube 1 and add it 96 | CATransform3D c1t = CATransform3DIdentity; 97 | c1t = CATransform3DTranslate(c1t, -100, 0, 0); 98 | CALayer *cube1 = [self cubeWithTransform:c1t]; 99 | [self.containerView.layer addSublayer:cube1]; 100 | 101 | //set up the transform for cube 2 and add it 102 | CATransform3D c2t = CATransform3DIdentity; 103 | c2t = CATransform3DTranslate(c2t, 100, 0, 0); 104 | c2t = CATransform3DRotate(c2t, -M_PI_4, 1, 0, 0); 105 | c2t = CATransform3DRotate(c2t, -M_PI_4, 0, 1, 0); 106 | CALayer *cube2 = [self cubeWithTransform:c2t]; 107 | [self.containerView.layer addSublayer:cube2]; 108 | } 109 | @end 110 | ``` 111 | 112 | ![图6.5](./6.5.png) 113 | 114 | 图6.5 同一视角下的俩不同变换的立方体 115 | -------------------------------------------------------------------------------- /chapter4/scaling-filters.md: -------------------------------------------------------------------------------- 1 | # 拉伸过滤 2 | 3 | 4 |     最后我们再来谈谈`minificationFilter`和`magnificationFilter`属性。总得来讲,当我们视图显示一个图片的时候,都应该正确地显示这个图片(意即:以正确的比例和正确的1:1像素显示在屏幕上)。原因如下: 5 | 6 | * 能够显示最好的画质,像素既没有被压缩也没有被拉伸。 7 | * 能更好的使用内存,因为这就是所有你要存储的东西。 8 | * 最好的性能表现,CPU不需要为此额外的计算。 9 | 10 |     不过有时候,显示一个非真实大小的图片确实是我们需要的效果。比如说一个头像或是图片的缩略图,再比如说一个可以被拖拽和伸缩的大图。这些情况下,为同一图片的不同大小存储不同的图片显得又不切实际。 11 | 12 |     当图片需要显示不同的大小的时候,有一种叫做*拉伸过滤*的算法就起到作用了。它作用于原图的像素上并根据需要生成新的像素显示在屏幕上。 13 | 14 |     事实上,重绘图片大小也没有一个统一的通用算法。这取决于需要拉伸的内容,放大或是缩小的需求等这些因素。`CALayer`为此提供了三种拉伸过滤方法,他们是: 15 | 16 | * kCAFilterLinear 17 | * kCAFilterNearest 18 | * kCAFilterTrilinear 19 | 20 |     minification(缩小图片)和magnification(放大图片)默认的过滤器都是`kCAFilterLinear`,这个过滤器采用双线性滤波算法,它在大多数情况下都表现良好。双线性滤波算法通过对多个像素取样最终生成新的值,得到一个平滑的表现不错的拉伸。但是当放大倍数比较大的时候图片就模糊不清了。 21 | 22 |     `kCAFilterTrilinear`和`kCAFilterLinear`非常相似,大部分情况下二者都看不出来有什么差别。但是,较双线性滤波算法而言,三线性滤波算法存储了多个大小情况下的图片(也叫多重贴图),并三维取样,同时结合大图和小图的存储进而得到最后的结果。 23 | 24 |     这个方法的好处在于算法能够从一系列已经接近于最终大小的图片中得到想要的结果,也就是说不要对很多像素同步取样。这不仅提高了性能,也避免了小概率因舍入错误引起的取样失灵的问题 25 | 26 | ![图4.14](./4.14.png) 27 | 28 | 图4.14 对于大图来说,双线性滤波和三线性滤波表现得更出色 29 | 30 |     `kCAFilterNearest`是一种比较武断的方法。从名字不难看出,这个算法(也叫最近过滤)就是取样最近的单像素点而不管其他的颜色。这样做非常快,也不会使图片模糊。但是,最明显的效果就是,会使得压缩图片更糟,图片放大之后也显得块状或是马赛克严重。 31 | 32 | ![图4.15](./4.15.png) 33 | 34 | 图4.15 对于没有斜线的小图来说,最近过滤算法要好很多 35 | 36 |     总的来说,对于比较小的图或者是差异特别明显,极少斜线的大图,最近过滤算法会保留这种差异明显的特质以呈现更好的结果。但是对于大多数的图尤其是有很多斜线或是曲线轮廓的图片来说,最近过滤算法会导致更差的结果。换句话说,线性过滤保留了形状,最近过滤则保留了像素的差异。 37 | 38 |     让我们来实验一下。我们对第三章的时钟项目改动一下,用LCD风格的数字方式显示。我们用简单的像素字体(一种用像素构成字符的字体,而非矢量图形)创造数字显示方式,用图片存储起来,而且用第二章介绍过的拼合技术来显示(如图4.16)。 39 | 40 | ![图4.16](./4.16.png) 41 | 42 | 图4.16 一个简单的运用拼合技术显示的LCD数字风格的像素字体 43 | 44 |     我们在Interface Builder中放置了六个视图,小时、分钟、秒钟各两个,图4.17显示了这六个视图是如何在Interface Builder中放置的。如果每个都用一个淡出的outlets对象就会显得太多了,所以我们就用了一个`IBOutletCollection`对象把他们和控制器联系起来,这样我们就可以以数组的方式访问视图了。清单4.6是代码实现。 45 | 46 | 清单4.6 显示一个LCD风格的时钟 47 | 48 | ```objective-c 49 | @interface ViewController () 50 | 51 | @property (nonatomic, strong) IBOutletCollection(UIView) NSArray *digitViews; 52 | @property (nonatomic, weak) NSTimer *timer; 53 |  54 | @end 55 | 56 | @implementation ViewController 57 | 58 | - (void)viewDidLoad 59 | { 60 | [super viewDidLoad]; //get spritesheet image 61 | UIImage *digits = [UIImage imageNamed:@"Digits.png"]; 62 | 63 | //set up digit views 64 | for (UIView *view in self.digitViews) { 65 | //set contents 66 | view.layer.contents = (__bridge id)digits.CGImage; 67 | view.layer.contentsRect = CGRectMake(0, 0, 0.1, 1.0); 68 | view.layer.contentsGravity = kCAGravityResizeAspect; 69 | } 70 | 71 | //start timer 72 | self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(tick) userInfo:nil repeats:YES]; 73 | 74 | //set initial clock time 75 | [self tick]; 76 | } 77 | 78 | - (void)setDigit:(NSInteger)digit forView:(UIView *)view 79 | { 80 | //adjust contentsRect to select correct digit 81 | view.layer.contentsRect = CGRectMake(digit * 0.1, 0, 0.1, 1.0); 82 | } 83 | 84 | - (void)tick 85 | { 86 | //convert time to hours, minutes and seconds 87 | NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier: NSGregorianCalendar]; 88 | NSUInteger units = NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit; 89 |  90 | NSDateComponents *components = [calendar components:units fromDate:[NSDate date]]; 91 | 92 | //set hours 93 | [self setDigit:components.hour / 10 forView:self.digitViews[0]]; 94 | [self setDigit:components.hour % 10 forView:self.digitViews[1]]; 95 | 96 | //set minutes 97 | [self setDigit:components.minute / 10 forView:self.digitViews[2]]; 98 | [self setDigit:components.minute % 10 forView:self.digitViews[3]]; 99 | 100 | //set seconds 101 | [self setDigit:components.second / 10 forView:self.digitViews[4]]; 102 | [self setDigit:components.second % 10 forView:self.digitViews[5]]; 103 | } 104 | @end 105 | ``` 106 | 107 | 如图4.18,这样做的确起了效果,但是图片看起来模糊了。看起来默认的`kCAFilterLinear`选项让我们失望了。 108 | 109 | ![图4.18](./4.18.png) 110 | 111 | 图4.18 一个模糊的时钟,由默认的`kCAFilterLinear`引起 112 | 113 |     为了能像图4.19中那样,我们需要在for循环中加入如下代码: 114 | 115 | ```objective-c 116 | view.layer.magnificationFilter = kCAFilterNearest; 117 | ``` 118 | 119 | ![图4.19](./4.19.png) 120 | 121 | 图4.19 设置了最近过滤之后的清晰显示 122 | -------------------------------------------------------------------------------- /chapter5/light-and-shadow.md: -------------------------------------------------------------------------------- 1 | # 光亮和阴影 2 | 3 | 4 | 现在它看起来更像是一个立方体没错了,但是对每个面之间的连接还是很难分辨。Core Animation可以用3D显示图层,但是它对*光线*并没有概念。如果想让立方体看起来更加真实,需要自己做一个阴影效果。你可以通过改变每个面的背景颜色或者直接用带光亮效果的图片来调整。 5 | 6 | 如果需要*动态*地创建光线效果,你可以根据每个视图的方向应用不同的alpha值做出半透明的阴影图层,但为了计算阴影图层的不透明度,你需要得到每个面的*正太向量*(垂直于表面的向量),然后根据一个想象的光源计算出两个向量*叉乘*结果。叉乘代表了光源和图层之间的角度,从而决定了它有多大程度上的光亮。 7 | 8 | 清单5.10实现了这样一个结果,我们用GLKit框架来做向量的计算(你需要引入GLKit库来运行代码),每个面的`CATransform3D`都被转换成`GLKMatrix4`,然后通过`GLKMatrix4GetMatrix3`函数得出一个3×3的*旋转矩阵*。这个旋转矩阵指定了图层的方向,然后可以用它来得到正太向量的值。 9 | 10 | 结果如图5.22所示,试着调整`LIGHT_DIRECTION`和`AMBIENT_LIGHT`的值来切换光线效果 11 | 12 | 清单5.10 对立方体的表面应用动态的光线效果 13 | 14 | ```objective-c 15 | #import "ViewController.h" 16 | #import 17 | #import 18 | 19 | #define LIGHT_DIRECTION 0, 1, -0.5 20 | #define AMBIENT_LIGHT 0.5 21 | 22 | @interface ViewController () 23 | 24 | @property (nonatomic, weak) IBOutlet UIView *containerView; 25 | @property (nonatomic, strong) IBOutletCollection(UIView) NSArray *faces; 26 | 27 | @end 28 | 29 | @implementation ViewController 30 | 31 | - (void)applyLightingToFace:(CALayer *)face 32 | { 33 | //add lighting layer 34 | CALayer *layer = [CALayer layer]; 35 | layer.frame = face.bounds; 36 | [face addSublayer:layer]; 37 | //convert the face transform to matrix 38 | //(GLKMatrix4 has the same structure as CATransform3D) 39 | //译者注:GLKMatrix4和CATransform3D内存结构一致,但坐标类型有长度区别,所以理论上应该做一次float到CGFloat的转换,感谢[@zihuyishi](https://github.com/zihuyishi)同学~ 40 | CATransform3D transform = face.transform; 41 | GLKMatrix4 matrix4 = *(GLKMatrix4 *)&transform; 42 | GLKMatrix3 matrix3 = GLKMatrix4GetMatrix3(matrix4); 43 | //get face normal 44 | GLKVector3 normal = GLKVector3Make(0, 0, 1); 45 | normal = GLKMatrix3MultiplyVector3(matrix3, normal); 46 | normal = GLKVector3Normalize(normal); 47 | //get dot product with light direction 48 | GLKVector3 light = GLKVector3Normalize(GLKVector3Make(LIGHT_DIRECTION)); 49 | float dotProduct = GLKVector3DotProduct(light, normal); 50 | //set lighting layer opacity 51 | CGFloat shadow = 1 + dotProduct - AMBIENT_LIGHT; 52 | UIColor *color = [UIColor colorWithWhite:0 alpha:shadow]; 53 | layer.backgroundColor = color.CGColor; 54 | } 55 | 56 | - (void)addFace:(NSInteger)index withTransform:(CATransform3D)transform 57 | { 58 | //get the face view and add it to the container 59 | UIView *face = self.faces[index]; 60 | [self.containerView addSubview:face]; 61 | //center the face view within the container 62 | CGSize containerSize = self.containerView.bounds.size; 63 | face.center = CGPointMake(containerSize.width / 2.0, containerSize.height / 2.0); 64 | // apply the transform 65 | face.layer.transform = transform; 66 | //apply lighting 67 | [self applyLightingToFace:face.layer]; 68 | } 69 | 70 | - (void)viewDidLoad 71 | { 72 | [super viewDidLoad]; 73 | //set up the container sublayer transform 74 | CATransform3D perspective = CATransform3DIdentity; 75 | perspective.m34 = -1.0 / 500.0; 76 | perspective = CATransform3DRotate(perspective, -M_PI_4, 1, 0, 0); 77 | perspective = CATransform3DRotate(perspective, -M_PI_4, 0, 1, 0); 78 | self.containerView.layer.sublayerTransform = perspective; 79 | //add cube face 1 80 | CATransform3D transform = CATransform3DMakeTranslation(0, 0, 100); 81 | [self addFace:0 withTransform:transform]; 82 | //add cube face 2 83 | transform = CATransform3DMakeTranslation(100, 0, 0); 84 | transform = CATransform3DRotate(transform, M_PI_2, 0, 1, 0); 85 | [self addFace:1 withTransform:transform]; 86 | //add cube face 3 87 | transform = CATransform3DMakeTranslation(0, -100, 0); 88 | transform = CATransform3DRotate(transform, M_PI_2, 1, 0, 0); 89 | [self addFace:2 withTransform:transform]; 90 | //add cube face 4 91 | transform = CATransform3DMakeTranslation(0, 100, 0); 92 | transform = CATransform3DRotate(transform, -M_PI_2, 1, 0, 0); 93 | [self addFace:3 withTransform:transform]; 94 | //add cube face 5 95 | transform = CATransform3DMakeTranslation(-100, 0, 0); 96 | transform = CATransform3DRotate(transform, -M_PI_2, 0, 1, 0); 97 | [self addFace:4 withTransform:transform]; 98 | //add cube face 6 99 | transform = CATransform3DMakeTranslation(0, 0, -100); 100 | transform = CATransform3DRotate(transform, M_PI, 0, 1, 0); 101 | [self addFace:5 withTransform:transform]; 102 | } 103 | 104 | @end 105 | ``` 106 | 107 | 图5.22 108 | 109 | 图5.22 动态计算光线效果之后的立方体 -------------------------------------------------------------------------------- /chapter15/dirty-rectangles.md: -------------------------------------------------------------------------------- 1 | ## 脏矩形 2 | 3 | 4 | 5 |     有时候用`CAShapeLayer`或者其他矢量图形图层替代Core Graphics并不是那么切实可行。比如我们的绘图应用:我们用线条完美地完成了矢量绘制。但是设想一下如果我们能进一步提高应用的性能,让它就像一个黑板一样工作,然后用『粉笔』来绘制线条。模拟粉笔最简单的方法就是用一个『线刷』图片然后将它粘贴到用户手指碰触的地方,但是这个方法用`CAShapeLayer`没办法实现。 6 | 7 |     我们可以给每个『线刷』创建一个独立的图层,但是实现起来有很大的问题。屏幕上允许同时出现图层上线数量大约是几百,那样我们很快就会超出的。这种情况下我们没什么办法,就用Core Graphics吧(除非你想用OpenGL做一些更复杂的事情)。 8 | 9 |     我们的『黑板』应用的最初实现见清单13.3,我们更改了之前版本的`DrawingView`,用一个画刷位置的数组代替`UIBezierPath`。图13.2是运行结果 10 | 11 | 清单13.3 简单的类似黑板的应用 12 | 13 | ```objective-c 14 | #import "DrawingView.h" 15 | #import 16 | #define BRUSH_SIZE 32 17 | 18 | @interface DrawingView () 19 | 20 | @property (nonatomic, strong) NSMutableArray *strokes; 21 | 22 | @end 23 | 24 | @implementation DrawingView 25 | 26 | - (void)awakeFromNib 27 | { 28 | //create array 29 | self.strokes = [NSMutableArray array]; 30 | } 31 | 32 | - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 33 | { 34 | //get the starting point 35 | CGPoint point = [[touches anyObject] locationInView:self]; 36 | 37 | //add brush stroke 38 | [self addBrushStrokeAtPoint:point]; 39 | } 40 | 41 | - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 42 | { 43 | //get the touch point 44 | CGPoint point = [[touches anyObject] locationInView:self]; 45 | 46 | //add brush stroke 47 | [self addBrushStrokeAtPoint:point]; 48 | } 49 | 50 | - (void)addBrushStrokeAtPoint:(CGPoint)point 51 | { 52 | //add brush stroke to array 53 | [self.strokes addObject:[NSValue valueWithCGPoint:point]]; 54 | 55 | //needs redraw 56 | [self setNeedsDisplay]; 57 | } 58 | 59 | - (void)drawRect:(CGRect)rect 60 | { 61 | //redraw strokes 62 | for (NSValue *value in self.strokes) { 63 | //get point 64 | CGPoint point = [value CGPointValue]; 65 | 66 | //get brush rect 67 | CGRect brushRect = CGRectMake(point.x - BRUSH_SIZE/2, point.y - BRUSH_SIZE/2, BRUSH_SIZE, BRUSH_SIZE); 68 | 69 | //draw brush stroke  70 | [[UIImage imageNamed:@"Chalk.png"] drawInRect:brushRect]; 71 | } 72 | } 73 | @end 74 | ``` 75 | 76 | ![图13.2](./13.2.png) 77 | 78 | 图13.2 用程序绘制一个简单的『素描』 79 | 80 |     这个实现在模拟器上表现还不错,但是在真实设备上就没那么好了。问题在于每次手指移动的时候我们就会重绘之前的线刷,即使场景的大部分并没有改变。我们绘制地越多,就会越慢。随着时间的增加每次重绘需要更多的时间,帧数也会下降(见图13.3),如何提高性能呢? 81 | 82 | ![图13.3](./13.3.png) 83 | 84 | 图13.3 帧率和线条质量会随时间下降。 85 | 86 |     为了减少不必要的绘制,Mac OS和iOS设备将会把屏幕区分为需要重绘的区域和不需要重绘的区域。那些需要重绘的部分被称作『脏区域』。在实际应用中,鉴于非矩形区域边界裁剪和混合的复杂性,通常会区分出包含指定视图的矩形位置,而这个位置就是『脏矩形』。 87 | 88 |     当一个视图被改动过了,TA可能需要重绘。但是很多情况下,只是这个视图的一部分被改变了,所以重绘整个寄宿图就太浪费了。但是Core Animation通常并不了解你的自定义绘图代码,它也不能自己计算出脏区域的位置。然而,你的确可以提供这些信息。 89 | 90 |     当你检测到指定视图或图层的指定部分需要被重绘,你直接调用`-setNeedsDisplayInRect:`来标记它,然后将影响到的矩形作为参数传入。这样就会在一次视图刷新时调用视图的`-drawRect:`(或图层代理的`-drawLayer:inContext:`方法)。 91 | 92 |     传入`-drawLayer:inContext:`的`CGContext`参数会自动被裁切以适应对应的矩形。为了确定矩形的尺寸大小,你可以用`CGContextGetClipBoundingBox()`方法来从上下文获得大小。调用`-drawRect()`会更简单,因为`CGRect`会作为参数直接传入。 93 | 94 |     你应该将你的绘制工作限制在这个矩形中。任何在此区域之外的绘制都将被自动无视,但是这样CPU花在计算和抛弃上的时间就浪费了,实在是太不值得了。 95 | 96 |     相比依赖于Core Graphics为你重绘,裁剪出自己的绘制区域可能会让你避免不必要的操作。那就是说,如果你的裁剪逻辑相当复杂,那还是让Core Graphics来代劳吧,记住:当你能高效完成的时候才这样做。 97 | 98 |     清单13.4 展示了一个`-addBrushStrokeAtPoint:`方法的升级版,它只重绘当前线刷的附近区域。另外也会刷新之前线刷的附近区域,我们也可以用`CGRectIntersectsRect()`来避免重绘任何旧的线刷以不至于覆盖已更新过的区域。这样做会显著地提高绘制效率(见图13.4) 99 | 100 |     清单13.4 用`-setNeedsDisplayInRect:`来减少不必要的绘制 101 | ```objective-c 102 | - (void)addBrushStrokeAtPoint:(CGPoint)point 103 | { 104 | //add brush stroke to array 105 | [self.strokes addObject:[NSValue valueWithCGPoint:point]]; 106 | 107 | //set dirty rect 108 | [self setNeedsDisplayInRect:[self brushRectForPoint:point]]; 109 | } 110 | 111 | - (CGRect)brushRectForPoint:(CGPoint)point 112 | { 113 | return CGRectMake(point.x - BRUSH_SIZE/2, point.y - BRUSH_SIZE/2, BRUSH_SIZE, BRUSH_SIZE); 114 | } 115 | 116 | - (void)drawRect:(CGRect)rect 117 | { 118 | //redraw strokes 119 | for (NSValue *value in self.strokes) { 120 | //get point 121 | CGPoint point = [value CGPointValue]; 122 | 123 | //get brush rect 124 | CGRect brushRect = [self brushRectForPoint:point]; 125 |  126 | //only draw brush stroke if it intersects dirty rect 127 | if (CGRectIntersectsRect(rect, brushRect)) { 128 | //draw brush stroke 129 | [[UIImage imageNamed:@"Chalk.png"] drawInRect:brushRect]; 130 | } 131 | } 132 | } 133 | ``` 134 | 135 | ![图13.4](./13.4.png) 136 | 137 | 图13.4 更好的帧率和顺滑线条 138 | -------------------------------------------------------------------------------- /chapter4/drop-shadows.md: -------------------------------------------------------------------------------- 1 | ## 阴影 2 | 3 |     iOS的另一个常见特性呢,就是阴影。阴影往往可以达到图层深度暗示的效果。也能够用来强调正在显示的图层和优先级(比如说一个在其他视图之前的弹出框),不过有时候他们只是单纯的装饰目的。 4 | 5 |     给`shadowOpacity`属性一个大于默认值(也就是0)的值,阴影就可以显示在任意图层之下。`shadowOpacity`是一个必须在0.0(不可见)和1.0(完全不透明)之间的浮点数。如果设置为1.0,将会显示一个有轻微模糊的黑色阴影稍微在图层之上。若要改动阴影的表现,你可以使用CALayer的另外三个属性:`shadowColor`,`shadowOffset`和`shadowRadius`。 6 | 7 |     显而易见,`shadowColor`属性控制着阴影的颜色,和`borderColor`和`backgroundColor`一样,它的类型也是`CGColorRef`。阴影默认是黑色,大多数时候你需要的阴影也是黑色的(其他颜色的阴影看起来是不是有一点点奇怪。。)。 8 | 9 |     `shadowOffset`属性控制着阴影的方向和距离。它是一个`CGSize`的值,宽度控制这阴影横向的位移,高度控制着纵向的位移。`shadowOffset`的默认值是 {0, -3},意即阴影相对于Y轴有3个点的向上位移。 10 | 11 |     为什么要默认向上的阴影呢?尽管Core Animation是从图层套装演变而来(可以认为是为iOS创建的私有动画框架),但是呢,它却是在Mac OS上面世的,前面有提到,二者的Y轴是颠倒的。这就导致了默认的3个点位移的阴影是向上的。在Mac上,`shadowOffset`的默认值是阴影向下的,这样你就能理解为什么iOS上的阴影方向是向上的了(如图4.5). 12 | 13 | ![图4.5](./4.5.png) 14 | 15 | 图4.5 在iOS(左)和Mac OS(右)上`shadowOffset`的表现。 16 | 17 |     苹果更倾向于用户界面的阴影应该是垂直向下的,所以在iOS把阴影宽度设为0,然后高度设为一个正值不失为一个做法。 18 | 19 |     `shadowRadius`属性控制着阴影的*模糊度*,当它的值是0的时候,阴影就和视图一样有一个非常确定的边界线。当值越来越大的时候,边界线看上去就会越来越模糊和自然。苹果自家的应用设计更偏向于自然的阴影,所以一个非零值再合适不过了。 20 | 21 |     通常来讲,如果你想让视图或控件非常醒目独立于背景之外(比如弹出框遮罩层),你就应该给`shadowRadius`设置一个稍大的值。阴影越模糊,图层的深度看上去就会更明显(如图4.6). 22 | 23 | ![图4.6](./4.6.png) 24 | 25 | ### 阴影裁剪 26 | 27 | 28 | &nbps;   和图层边框不同,图层的阴影继承自内容的外形,而不是根据边界和角半径来确定。为了计算出阴影的形状,Core Animation会将寄宿图(包括子视图,如果有的话)考虑在内,然后通过这些来完美搭配图层形状从而创建一个阴影(见图4.7)。 29 | 30 | ![图4.7](./4.7.png) 31 | 32 | 图4.7 阴影是根据寄宿图的轮廓来确定的 33 | 34 | &nbps;   当阴影和裁剪扯上关系的时候就有一个头疼的限制:阴影通常就是在Layer的边界之外,如果你开启了`masksToBounds`属性,所有从图层中突出来的内容都会被才剪掉。如果我们在我们之前的边框示例项目中增加图层的阴影属性时,你就会发现问题所在(见图4.8). 35 | 36 | ![图4.8](./4.8.png) 37 | 38 | 图4.8 `maskToBounds`属性裁剪掉了阴影和内容 39 | 40 | &nbps;   从技术角度来说,这个结果是可以是可以理解的,但确实又不是我们想要的效果。如果你想沿着内容裁切,你需要用到两个图层:一个只画阴影的空的外图层,和一个用`masksToBounds`裁剪内容的内图层。 41 | 42 | &nbps;   如果我们把之前项目的右边用单独的视图把裁剪的视图包起来,我们就可以解决这个问题(如图4.9). 43 | 44 | ![图4.9](./4.9.png) 45 | 46 | 图4.9 右边,用额外的阴影转换视图包裹被裁剪的视图 47 | 48 | &nbps;   我们只把阴影用在最外层的视图上,内层视图进行裁剪。清单4.3是代码实现,图4.10是运行结果。 49 | 50 | 清单4.3 用一个额外的视图来解决阴影裁切的问题 51 | 52 | ```objective-c 53 | @interface ViewController () 54 | 55 | @property (nonatomic, weak) IBOutlet UIView *layerView1; 56 | @property (nonatomic, weak) IBOutlet UIView *layerView2; 57 | @property (nonatomic, weak) IBOutlet UIView *shadowView; 58 | 59 | @end 60 | 61 | @implementation ViewController 62 |  63 | - (void)viewDidLoad 64 | { 65 | [super viewDidLoad]; 66 | 67 | //set the corner radius on our layers 68 | self.layerView1.layer.cornerRadius = 20.0f; 69 | self.layerView2.layer.cornerRadius = 20.0f; 70 | 71 | //add a border to our layers 72 | self.layerView1.layer.borderWidth = 5.0f; 73 | self.layerView2.layer.borderWidth = 5.0f; 74 | 75 | //add a shadow to layerView1 76 | self.layerView1.layer.shadowOpacity = 0.5f; 77 | self.layerView1.layer.shadowOffset = CGSizeMake(0.0f, 5.0f); 78 | self.layerView1.layer.shadowRadius = 5.0f; 79 | 80 | //add same shadow to shadowView (not layerView2) 81 | self.shadowView.layer.shadowOpacity = 0.5f; 82 | self.shadowView.layer.shadowOffset = CGSizeMake(0.0f, 5.0f); 83 | self.shadowView.layer.shadowRadius = 5.0f; 84 | 85 | //enable clipping on the second layer 86 | self.layerView2.layer.masksToBounds = YES; 87 | } 88 | 89 | @end 90 | ``` 91 | 92 | ![图4.10](./4.10.png) 93 | 94 | 图4.10 右边视图,不受裁切阴影的阴影视图。 95 | ### shadowPath属性 96 | 97 | 98 |     我们已经知道图层阴影并不总是方的,而是从图层内容的形状继承而来。这看上去不错,但是实时计算阴影也是一个非常消耗资源的,尤其是图层有多个子图层,每个图层还有一个有透明效果的寄宿图的时候。 99 | 100 |     如果你事先知道你的阴影形状会是什么样子的,你可以通过指定一个`shadowPath`来提高性能。`shadowPath`是一个`CGPathRef`类型(一个指向`CGPath`的指针)。`CGPath`是一个Core Graphics对象,用来指定任意的一个矢量图形。我们可以通过这个属性单独于图层形状之外指定阴影的形状。 101 | 102 | 图4.11 展示了同一寄宿图的不同阴影设定。如你所见,我们使用的图形很简单,但是它的阴影可以是你想要的任何形状。清单4.4是代码实现。 103 | 104 | ![图4.11](./4.11.png) 105 | 106 | 图4.11 用`shadowPath`指定任意阴影形状 107 | 108 | 清单4.4 创建简单的阴影形状 109 | 110 | ```objective-c 111 | @interface ViewController () 112 | 113 | @property (nonatomic, weak) IBOutlet UIView *layerView1; 114 | @property (nonatomic, weak) IBOutlet UIView *layerView2; 115 | @end 116 | 117 | @implementation ViewController 118 | 119 | - (void)viewDidLoad 120 | { 121 | [super viewDidLoad]; 122 | 123 | //enable layer shadows 124 | self.layerView1.layer.shadowOpacity = 0.5f; 125 | self.layerView2.layer.shadowOpacity = 0.5f; 126 | 127 | //create a square shadow 128 | CGMutablePathRef squarePath = CGPathCreateMutable(); 129 | CGPathAddRect(squarePath, NULL, self.layerView1.bounds); 130 | self.layerView1.layer.shadowPath = squarePath; CGPathRelease(squarePath); 131 | 132 | //create a circular shadow 133 | CGMutablePathRef circlePath = CGPathCreateMutable(); 134 | CGPathAddEllipseInRect(circlePath, NULL, self.layerView2.bounds); 135 | self.layerView2.layer.shadowPath = circlePath; CGPathRelease(circlePath); 136 | } 137 | @end 138 | ``` 139 | 140 |     如果是一个矩形或者是圆,用`CGPath`会相当简单明了。但是如果是更加复杂一点的图形,`UIBezierPath`类会更合适,它是一个由UIKit提供的在CGPath基础上的Objective-C包装类。 141 | 142 | 143 | 图4.6 大一些的阴影位移和角半径会增加图层的深度即视感 -------------------------------------------------------------------------------- /目录.md: -------------------------------------------------------------------------------- 1 | [变换](chapter5/transforms.md) 2 | [仿射变换](chapter5/affine-fransforms.md) 3 | [创建一个`CGAffineTransform`](chapter5/creating-a-cgaffinetransform.md) 4 | [混合变换](chapter5/combining-transforms.md) 5 | [剪切变换](chapter5/the-shear-transform.md) 6 | [3D变换](chapter5/3d-transform.md) 7 | [透视投影](chapter5/perspective-projection.md) 8 | [灭点](chapter5/the-vanishing-point.md) 9 | [`sublayerTransform`属性](chapter5/the-sublayertransfrom-property.md) 10 | [背面](chapter5/backfaces.md) 11 | [扁平化图层](chapter5/layering-flattening.md) 12 | [固体对象](chapter5/solid-objects.md) 13 | [光亮和阴影](chapter5/light-and-shadow.md) 14 | [点击事件](chapter5/touch-events.md) 15 | [总结](chapter5/summary.md) 16 | 17 | 18 | 19 | [专用图层](chapter6/specialized-layers.md) 20 | [CAShapeLayer](chapter6/cashapelayer.md) 21 | [创建一个`CGPath`](chapter6/creating-a-cgpath.md) 22 | [圆角](chapter6/rounded-corners.md) 23 | [CATextLayer](chapter6/CATextLayer.md) 24 | [富文本](chapter6/rich-text.md) 25 | [行距和字距](chapter6/leading-and-kerning.md) 26 | [`UILabel`的替代品](chapter6/a-uilabel-replacement.md) 27 | [CATransformLayer](chapter6/catransformlayer.md) 28 | [##CAGradientLayer 29 | ](chapter6/cagradientLayer.md) 30 | [###基础渐变 31 | ](chapter6/) 32 | [###多重渐变 33 | ](chapter6/) 34 | [##CAReplicatorLayer 35 | ](chapter6/) 36 | [###重复图层(Repeating Layers) 37 | ](chapter6/) 38 | [###反射 39 | ](chapter6/) 40 | [##CAScrollLayer 41 | ](chapter6/) 42 | [##CATiledLayer 43 | ](chapter6/) 44 | [###小片裁剪 45 | ](chapter6/) 46 | [###Retina小图 47 | ](chapter6/) 48 | [CAEmitterLayer](chapter6/caemitterlayer.md) 49 | [CAEAGLLayer](chapter6/caeagllayer.md) 50 | [AVPlayerLayer](chapter6/avplayerlayer.md) 51 | [总结](chapter6/summarry.md) 52 | 53 | 54 | [隐式动画](chapter7/implicit-animations.md) 55 | [事务](chapter7/) 56 | [完成块](chapter7/completion-blocks.md) 57 | [图层行为](chapter7/layer-actions.md) 58 | [呈现与模型](chapter7/presentation-versus-model.md) 59 | [总结](chapter7/summary.md) 60 | 61 | [显式动画](chapter8/explicit-animations.md) 62 | [属性动画](chapter8/property-animations.md) 63 | [基础动画](chapter8/baasic-animations.md) 64 | [CAAnimationDelegate](chapter8/caanimationdelegate.md) 65 | [关键帧动画](chapter8/keyframe.md) 66 | [虚拟属性](chapter8/virtual-properties.md) 67 | [动画组](chapter8/animation-groups.md) 68 | [过渡](chapter8/transitions.md) 69 | [隐式过渡](chapter8/implicit-transitions.md) 70 | [对图层树的动画](chapter8/animating-layer-tree.md) 71 | [自定义动画](chapter8/) 72 | [在动画过程中取消动画](chapter8/changes-custom-transitions.md) 73 | [总结](chapter8/summary.md) 74 | 75 | [图层时间](chapter9/layer-time.md) 76 | [`CAMediaTiming`协议 持续和重复相对时间](chapter9/the-cAMediaTiming-protocol-duration-and-repetition-relative-time.md) 77 | [`fillMode`](chapter9/fillMode.md) 78 | [层级关系时间](chapter9/hierarchical-time.md) 79 | [全局时间和本地时间](chapter9/global-versus-local-time.md) 80 | [暂停,倒回和快进手动动画](chapter9/pause-rewind-and-fast-forward-manual-animation.md) 81 | [总结](chapter9/summary.md) 82 | 83 | 84 | 9 Layer Time 85 | The CAMediaTiming Protocol Duration and Repetition Relative Time 86 | fillMode 87 | Hierarchical Time 88 |  89 | Global Versus Local Time 90 | Pause, Rewind, and Fast-Forward Manual Animation 91 | Summary 92 | [缓冲](chapter10/easing.md) 93 | [动画速度](chapter10/animation-velocity.md) 94 | [`CAMediaTimingFunction`](chapter10/) 95 | [###`UIView`的动画缓冲 96 | ](chapter10/) 97 | [###缓冲和关键帧动画 98 | ](chapter10/) 99 | [##自定义缓冲函数 100 | ](chapter10/) 101 | [###三次贝塞尔曲线 102 | ](chapter10/) 103 | [###更加复杂的动画曲线 104 | ](chapter10/) 105 | [###基于关键帧的缓冲 106 | ](chapter10/) 107 | [###流程自动化 108 | ](chapter10/) 109 | [总结](chapter10/Summary.md) 110 | 111 | [基于定时器的动画](chapter11/timer-based-animation.md) 112 | [定时帧](chapter11/frame-timing.md) 113 | [总结](chapter11/summary.md) 114 | 115 | 116 | [性能调优](chapter12/tuning-for-speed.md) 117 | [CPU VS GPU](chapter12/cpu-versus-gpu.md) 118 | [真机测试,而不是模拟器](chapter12/test-reality-not-a-simulation.md) 119 | [保持一致的帧率 Instruments](chapter12/maintaining-a-consistent-frame-rate-instruments.md) 120 | [时间分析器](chapter12/time-profiler,md) 121 | [Core Animation OpenGL ES驱动](chapter12/core-animation-opengl-es-driver.md) 122 | [一个可用的案例](chapter12/a-working-example.md) 123 | [总结](chapter12/summary.md) 124 | 125 | 126 | [高效绘图](chapter13/efficient-drawing.md) 127 | [软件绘图](chapter13/software-drawing-vector-graphics.md) 128 | [矢量图形](chapter13/catiledLayer.md) 129 | [异步绘制](chapter13/drawsasynchronously.md) 130 | [总结](chapter13/summary.md) 131 | 132 | [图像IO](chapter14/image-ioi.md) 133 | [加载和潜伏](chapter14/loading-and-latency.md) 134 | [线程加载](chapter14/threaded-loading.md) 135 | [###GCD和`NSOperationQueue`延迟解压`CATiledLayer`](chapter14/gcd-and-nsoperationqueue-deferred-decompression-catiledLayer.md) 136 | [分辨率交换](chapter14/resolution-swapping.md) 137 | [缓存](chapter14/caching.md) 138 | [`+imageNamed:`方法](chapter14/the-imageNamed-nethod-custom-caching.md) 139 | [NSCache](chapter14/nscache.md) 140 | [文件格式-混合图片-JPEG-2000](chapter14/file-format-hybrid-images-jpeg-2000-pvrtc.md) 141 | [总结](chapter14/summary.md) 142 | 143 | 144 | [图层性能](chapter15/layer-performance.md) 145 | [隐式绘制](chapter15/inexplicit-drawing-text.md) 146 | [文本](chapter15/text.md) 147 | [光栅化](chapter15/rasterization.md) 148 | [离屏渲染](chapter15/offscreen-rendering.md) 149 | [CAShapeLayer](chapter15/cashapelayer.md) 150 | [可伸缩图片](chapter15/stretchable-images.md) 151 | [shadowPath](chapter15/shadowpath.md) 152 | [混合和过度绘制](chapter15/shadowpath.md) 153 | [减少图层数量](chapter15/blending-and-overdraw-reducing-layer-count.md) 154 | [裁切](chapter15/clipping.md) 155 | [对象回收](chapter15/object-recycling.md) 156 | [Core Graphics绘制](chapter15/core-graphics-drawing.md) 157 | [renderInContext: 方法](chapter15/the-renderInContext-method.md) 158 | [总结](chapter15/summary.md) 159 | 160 | -------------------------------------------------------------------------------- /chapter7/layer-actions.md: -------------------------------------------------------------------------------- 1 | # 图层行为 2 | 3 | 现在来做个实验,试着直接对UIView关联的图层做动画而不是一个单独的图层。清单7.4是对清单7.2代码的一点修改,移除了`colorLayer`,并且直接设置`layerView`关联图层的背景色。 4 | 5 | 清单7.4 直接设置图层的属性 6 | 7 | ```objective-c 8 | @interface ViewController () 9 | 10 | @property (nonatomic, weak) IBOutlet UIView *layerView; 11 | 12 | @end 13 | 14 | @implementation ViewController 15 | 16 | - (void)viewDidLoad 17 | { 18 | [super viewDidLoad]; 19 | //set the color of our layerView backing layer directly 20 | self.layerView.layer.backgroundColor = [UIColor blueColor].CGColor; 21 | } 22 | 23 | - (IBAction)changeColor 24 | { 25 | //begin a new transaction 26 | [CATransaction begin]; 27 | //set the animation duration to 1 second 28 | [CATransaction setAnimationDuration:1.0]; 29 | //randomize the layer background color 30 | CGFloat red = arc4random() / (CGFloat)INT_MAX; 31 | CGFloat green = arc4random() / (CGFloat)INT_MAX; 32 | CGFloat blue = arc4random() / (CGFloat)INT_MAX; 33 | self.layerView.layer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor; 34 | //commit the transaction 35 | [CATransaction commit]; 36 | } 37 | ``` 38 | 39 | 运行程序,你会发现当按下按钮,图层颜色瞬间切换到新的值,而不是之前平滑过渡的动画。发生了什么呢?隐式动画好像被`UIView`关联图层给禁用了。 40 | 41 | 试想一下,如果`UIView`的属性都有动画特性的话,那么无论在什么时候修改它,我们都应该能注意到的。所以,如果说UIKit建立在Core Animation(默认对所有东西都做动画)之上,那么隐式动画是如何被UIKit禁用掉呢? 42 | 43 | 我们知道Core Animation通常对`CALayer`的所有属性(可动画的属性)做动画,但是`UIView`把它关联的图层的这个特性关闭了。为了更好说明这一点,我们需要知道隐式动画是如何实现的。 44 | 45 | 我们把改变属性时`CALayer`自动应用的动画称作*行为*,当`CALayer`的属性被修改时候,它会调用`-actionForKey:`方法,传递属性的名称。剩下的操作都在`CALayer`的头文件中有详细的说明,实质上是如下几步: 46 | 47 | * 图层首先检测它是否有委托,并且是否实现`CALayerDelegate`协议指定的`-actionForLayer:forKey`方法。如果有,直接调用并返回结果。 48 | * 如果没有委托,或者委托没有实现`-actionForLayer:forKey`方法,图层接着检查包含属性名称对应行为映射的`actions`字典。 49 | * 如果`actions字典`没有包含对应的属性,那么图层接着在它的`style`字典接着搜索属性名。 50 | * 最后,如果在`style`里面也找不到对应的行为,那么图层将会直接调用定义了每个属性的标准行为的`-defaultActionForKey:`方法。 51 | 52 | 所以一轮完整的搜索结束之后,`-actionForKey:`要么返回空(这种情况下将不会有动画发生),要么是`CAAction`协议对应的对象,最后`CALayer`拿这个结果去对先前和当前的值做动画。 53 | 54 | 于是这就解释了UIKit是如何禁用隐式动画的:每个`UIView`对它关联的图层都扮演了一个委托,并且提供了`-actionForLayer:forKey`的实现方法。当不在一个动画块的实现中,`UIView`对所有图层行为返回`nil`,但是在动画block范围之内,它就返回了一个非空值。我们可以用一个demo做个简单的实验(清单7.5) 55 | 56 | 清单7.5 测试UIView的`actionForLayer:forKey:`实现 57 | 58 | ```objective-c 59 | @interface ViewController () 60 | 61 | @property (nonatomic, weak) IBOutlet UIView *layerView; 62 | 63 | @end 64 | 65 | @implementation ViewController 66 | 67 | - (void)viewDidLoad 68 | { 69 | [super viewDidLoad]; 70 | //test layer action when outside of animation block 71 | NSLog(@"Outside: %@", [self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]); 72 | //begin animation block 73 | [UIView beginAnimations:nil context:nil]; 74 | //test layer action when inside of animation block 75 | NSLog(@"Inside: %@", [self.layerView actionForLayer:self.layerView.layer forKey:@"backgroundColor"]); 76 | //end animation block 77 | [UIView commitAnimations]; 78 | } 79 | 80 | @end 81 | ``` 82 | 83 | 运行程序,控制台显示结果如下: 84 | 85 | $ LayerTest[21215:c07] Outside: 86 | $ LayerTest[21215:c07] Inside: 87 | 88 | 于是我们可以预言,当属性在动画块之外发生改变,`UIView`直接通过返回`nil`来禁用隐式动画。但如果在动画块范围之内,根据动画具体类型返回相应的属性,在这个例子就是`CABasicAnimation`(第八章“显式动画”将会提到)。 89 | 90 | 当然返回`nil`并不是禁用隐式动画唯一的办法,`CATransacition`有个方法叫做`+setDisableActions:`,可以用来对所有属性打开或者关闭隐式动画。如果在清单7.2的`[CATransaction begin]`之后添加下面的代码,同样也会阻止动画的发生: 91 | 92 | [CATransaction setDisableActions:YES]; 93 | 94 | 总结一下,我们知道了如下几点 95 | 96 | * `UIView`关联的图层禁用了隐式动画,对这种图层做动画的唯一办法就是使用`UIView`的动画函数(而不是依赖`CATransaction`),或者继承`UIView`,并覆盖`-actionForLayer:forKey:`方法,或者直接创建一个显式动画(具体细节见第八章)。 97 | * 对于单独存在的图层,我们可以通过实现图层的`-actionForLayer:forKey:`委托方法,或者提供一个`actions`字典来控制隐式动画。 98 | 99 | 我们来对颜色渐变的例子使用一个不同的行为,通过给`colorLayer`设置一个自定义的`actions`字典。我们也可以使用委托来实现,但是`actions`字典可以写更少的代码。那么到底改如何创建一个合适的行为对象呢? 100 | 101 | 行为通常是一个被Core Animation*隐式*调用的*显式*动画对象。这里我们使用的是一个实现了`CATransaction`的实例,叫做*推进过渡*。 102 | 103 | 第八章中将会详细解释过渡,不过对于现在,知道`CATransition`响应`CAAction`协议,并且可以当做一个图层行为就足够了。结果很赞,不论在什么时候改变背景颜色,新的色块都是从左侧滑入,而不是默认的渐变效果。 104 | 105 | 清单7.6 实现自定义行为 106 | 107 | ```objective-c 108 | @interface ViewController () 109 | 110 | @property (nonatomic, weak) IBOutlet UIView *layerView; 111 | @property (nonatomic, weak) IBOutlet CALayer *colorLayer;/*热心人发现这里应该改为@property (nonatomic, strong) CALayer *colorLayer;否则运行结果不正确。 112 | */ 113 | 114 | @end 115 | 116 | @implementation ViewController 117 | 118 | - (void)viewDidLoad 119 | { 120 | [super viewDidLoad]; 121 | 122 | //create sublayer 123 | self.colorLayer = [CALayer layer]; 124 | self.colorLayer.frame = CGRectMake(50.0f, 50.0f, 100.0f, 100.0f); 125 | self.colorLayer.backgroundColor = [UIColor blueColor].CGColor; 126 | //add a custom action 127 | CATransition *transition = [CATransition animation]; 128 | transition.type = kCATransitionPush; 129 | transition.subtype = kCATransitionFromLeft; 130 | self.colorLayer.actions = @{@"backgroundColor": transition}; 131 | //add it to our view 132 | [self.layerView.layer addSublayer:self.colorLayer]; 133 | } 134 | 135 | - (IBAction)changeColor 136 | { 137 | //randomize the layer background color 138 | CGFloat red = arc4random() / (CGFloat)INT_MAX; 139 | CGFloat green = arc4random() / (CGFloat)INT_MAX; 140 | CGFloat blue = arc4random() / (CGFloat)INT_MAX; 141 | self.colorLayer.backgroundColor = [UIColor colorWithRed:red green:green blue:blue alpha:1.0].CGColor; 142 | } 143 | 144 | @end 145 | ``` 146 | 147 | 图7.3 148 | 149 | 图7.3 使用推进过渡的色值动画 150 | -------------------------------------------------------------------------------- /chapter6/caeagllayer.md: -------------------------------------------------------------------------------- 1 | # CAEAGLLayer 2 | 3 | 当iOS要处理高性能图形绘制,必要时就是OpenGL。应该说它应该是最后的杀手锏,至少对于非游戏的应用来说是的。因为相比Core Animation和UIkit框架,它不可思议地复杂。 4 | 5 | OpenGL提供了Core Animation的基础,它是底层的C接口,直接和iPhone,iPad的硬件通信,极少地抽象出来的方法。OpenGL没有对象或是图层的继承概念。它只是简单地处理三角形。OpenGL中所有东西都是3D空间中有颜色和纹理的三角形。用起来非常复杂和强大,但是用OpenGL绘制iOS用户界面就需要很多很多的工作了。 6 | 7 | 为了能够以高性能使用Core Animation,你需要判断你需要绘制哪种内容(矢量图形,例子,文本,等等),但后选择合适的图层去呈现这些内容,Core Animation中只有一些类型的内容是被高度优化的;所以如果你想绘制的东西并不能找到标准的图层类,想要得到高性能就比较费事情了。 8 | 9 | 因为OpenGL根本不会对你的内容进行假设,它能够绘制得相当快。利用OpenGL,你可以绘制任何你知道必要的集合信息和形状逻辑的内容。所以很多游戏都喜欢用OpenGL(这些情况下,Core Animation的限制就明显了:它优化过的内容类型并不一定能满足需求),但是这样依赖,方便的高度抽象接口就没了。 10 | 11 | 在iOS 5中,苹果引入了一个新的框架叫做GLKit,它去掉了一些设置OpenGL的复杂性,提供了一个叫做`CLKView`的`UIView`的子类,帮你处理大部分的设置和绘制工作。前提是各种各样的OpenGL绘图缓冲的底层可配置项仍然需要你用`CAEAGLLayer`完成,它是`CALayer`的一个子类,用来显示任意的OpenGL图形。 12 | 13 | 大部分情况下你都不需要手动设置`CAEAGLLayer`(假设用GLKView),过去的日子就不要再提了。特别的,我们将设置一个OpenGL ES 2.0的上下文,它是现代的iOS设备的标准做法。 14 | 15 | 尽管不需要GLKit也可以做到这一切,但是GLKit囊括了很多额外的工作,比如设置顶点和片段着色器,这些都以类C语言叫做GLSL自包含在程序中,同时在运行时载入到图形硬件中。编写GLSL代码和设置`EAGLayer`没有什么关系,所以我们将用`GLKBaseEffect`类将着色逻辑抽象出来。其他的事情,我们还是会有以往的方式。 16 | 17 | 在开始之前,你需要将GLKit和OpenGLES框架加入到你的项目中,然后就可以实现清单6.14中的代码,里面是设置一个`GAEAGLLayer`的最少工作,它使用了OpenGL ES 2.0 的绘图上下文,并渲染了一个有色三角(见图6.15). 18 | 19 | 清单6.14 用`CAEAGLLayer`绘制一个三角形 20 | 21 | ```objective-c 22 | #import "ViewController.h" 23 | #import 24 | #import 25 | 26 | @interface ViewController () 27 | 28 | @property (nonatomic, weak) IBOutlet UIView *glView; 29 | @property (nonatomic, strong) EAGLContext *glContext; 30 | @property (nonatomic, strong) CAEAGLLayer *glLayer; 31 | @property (nonatomic, assign) GLuint framebuffer; 32 | @property (nonatomic, assign) GLuint colorRenderbuffer; 33 | @property (nonatomic, assign) GLint framebufferWidth; 34 | @property (nonatomic, assign) GLint framebufferHeight; 35 | @property (nonatomic, strong) GLKBaseEffect *effect; 36 |  37 | @end 38 | 39 | @implementation ViewController 40 | 41 | - (void)setUpBuffers 42 | { 43 | //set up frame buffer 44 | glGenFramebuffers(1, &_framebuffer); 45 | glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer); 46 | 47 | //set up color render buffer 48 | glGenRenderbuffers(1, &_colorRenderbuffer); 49 | glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer); 50 | glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _colorRenderbuffer); 51 | [self.glContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.glLayer]; 52 | glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &_framebufferWidth); 53 | glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &_framebufferHeight); 54 | 55 | //check success 56 | if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { 57 | NSLog(@"Failed to make complete framebuffer object: %i", glCheckFramebufferStatus(GL_FRAMEBUFFER)); 58 | } 59 | } 60 | 61 | - (void)tearDownBuffers 62 | { 63 | if (_framebuffer) { 64 | //delete framebuffer 65 | glDeleteFramebuffers(1, &_framebuffer); 66 | _framebuffer = 0; 67 | } 68 | 69 | if (_colorRenderbuffer) { 70 | //delete color render buffer 71 | glDeleteRenderbuffers(1, &_colorRenderbuffer); 72 | _colorRenderbuffer = 0; 73 | } 74 | } 75 | 76 | - (void)drawFrame { 77 | //bind framebuffer & set viewport 78 | glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer); 79 | glViewport(0, 0, _framebufferWidth, _framebufferHeight); 80 | 81 | //bind shader program 82 | [self.effect prepareToDraw]; 83 | 84 | //clear the screen 85 | glClear(GL_COLOR_BUFFER_BIT); glClearColor(0.0, 0.0, 0.0, 1.0); 86 | 87 | //set up vertices 88 | GLfloat vertices[] = { 89 | -0.5f, -0.5f, -1.0f, 0.0f, 0.5f, -1.0f, 0.5f, -0.5f, -1.0f, 90 | }; 91 | 92 | //set up colors 93 | GLfloat colors[] = { 94 | 0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 95 | }; 96 | 97 | //draw triangle 98 | glEnableVertexAttribArray(GLKVertexAttribPosition); 99 | glEnableVertexAttribArray(GLKVertexAttribColor); 100 | glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE, 0, vertices); 101 | glVertexAttribPointer(GLKVertexAttribColor,4, GL_FLOAT, GL_FALSE, 0, colors); 102 | glDrawArrays(GL_TRIANGLES, 0, 3); 103 | 104 | //present render buffer 105 | glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderbuffer); 106 | [self.glContext presentRenderbuffer:GL_RENDERBUFFER]; 107 | } 108 | 109 | - (void)viewDidLoad 110 | { 111 | [super viewDidLoad]; 112 | //set up context 113 | self.glContext = [[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES2]; 114 | [EAGLContext setCurrentContext:self.glContext]; 115 | 116 | //set up layer 117 | self.glLayer = [CAEAGLLayer layer]; 118 | self.glLayer.frame = self.glView.bounds; 119 | [self.glView.layer addSublayer:self.glLayer]; 120 | self.glLayer.drawableProperties = @{kEAGLDrawablePropertyRetainedBacking:@NO, kEAGLDrawablePropertyColorFormat: kEAGLColorFormatRGBA8}; 121 | 122 | //set up base effect 123 | self.effect = [[GLKBaseEffect alloc] init]; 124 | 125 | //set up buffers 126 | [self setUpBuffers]; 127 | 128 | //draw frame 129 | [self drawFrame]; 130 | } 131 | 132 | - (void)viewDidUnload 133 | { 134 | [self tearDownBuffers]; 135 | [super viewDidUnload]; 136 | } 137 | 138 | - (void)dealloc 139 | { 140 | [self tearDownBuffers]; 141 | [EAGLContext setCurrentContext:nil]; 142 | } 143 | @end 144 | ``` 145 | 146 | ![图6.15](./6.15.png) 147 | 148 | 图6.15 用OpenGL渲染的`CAEAGLLayer`图层 149 | 150 | 在一个真正的OpenGL应用中,我们可能会用`NSTimer`或`CADisplayLink`周期性地每秒钟调用`-drawRrame`方法60次,同时会将几何图形生成和绘制分开以便不会每次都重新生成三角形的顶点(这样也可以让我们绘制其他的一些东西而不是一个三角形而已),不过上面这个例子已经足够演示了绘图原则了。 -------------------------------------------------------------------------------- /chapter14/caching.md: -------------------------------------------------------------------------------- 1 | ## 缓存 2 | 3 | 4 |     如果有很多张图片要显示,最好不要提前把所有都加载进来,而是应该当移出屏幕之后立刻销毁。通过选择性的缓存,你就可以避免来回滚动时图片重复性的加载了。 5 | 6 |     缓存其实很简单:就是存储昂贵计算后的结果(或者是从闪存或者网络加载的文件)在内存中,以便后续使用,这样访问起来很快。问题在于缓存本质上是一个权衡过程 - 为了提升性能而消耗了内存,但是由于内存是一个非常宝贵的资源,所以不能把所有东西都做缓存。 7 | 8 |     何时将何物做缓存(做多久)并不总是很明显。幸运的是,大多情况下,iOS都为我们做好了图片的缓存。 9 | 10 | ###`+imageNamed:`方法 11 | 12 |     之前我们提到使用`[UIImage imageNamed:]`加载图片有个好处在于可以立刻解压图片而不用等到绘制的时候。但是`[UIImage imageNamed:]`方法有另一个非常显著的好处:它在内存中自动缓存了解压后的图片,即使你自己没有保留对它的任何引用。 13 | 14 |     对于iOS应用那些主要的图片(例如图标,按钮和背景图片),使用`[UIImage imageNamed:]`加载图片是最简单最有效的方式。在nib文件中引用的图片同样也是这个机制,所以你很多时候都在隐式的使用它。 15 | 16 |     但是`[UIImage imageNamed:]`并不适用任何情况。它为用户界面做了优化,但是并不是对应用程序需要显示的所有类型的图片都适用。有些时候你还是要实现自己的缓存机制,原因如下: 17 | 18 | * `[UIImage imageNamed:]`方法仅仅适用于在应用程序资源束目录下的图片,但是大多数应用的许多图片都要从网络或者是用户的相机中获取,所以`[UIImage imageNamed:]`就没法用了。 19 | 20 | * `[UIImage imageNamed:]`缓存用来存储应用界面的图片(按钮,背景等等)。如果对照片这种大图也用这种缓存,那么iOS系统就很可能会移除这些图片来节省内存。那么在切换页面时性能就会下降,因为这些图片都需要重新加载。对传送器的图片使用一个单独的缓存机制就可以把它和应用图片的生命周期解耦。 21 | 22 | * `[UIImage imageNamed:]`缓存机制并不是公开的,所以你不能很好地控制它。例如,你没法做到检测图片是否在加载之前就做了缓存,不能够设置缓存大小,当图片没用的时候也不能把它从缓存中移除。 23 | 24 | ###自定义缓存 25 | 26 |     构建一个所谓的缓存系统非常困难。菲尔 卡尔顿曾经说过:“在计算机科学中只有两件难事:缓存和命名”。 27 | 28 |     如果要写自己的图片缓存的话,那该如何实现呢?让我们来看看要涉及哪些方面: 29 | 30 | * 选择一个合适的缓存键 - 缓存键用来做图片的唯一标识。如果实时创建图片,通常不太好生成一个字符串来区分别的图片。在我们的图片传送带例子中就很简单,我们可以用图片的文件名或者表格索引。 31 | 32 | * 提前缓存 - 如果生成和加载数据的代价很大,你可能想当第一次需要用到的时候再去加载和缓存。提前加载的逻辑是应用内在就有的,但是在我们的例子中,这也非常好实现,因为对于一个给定的位置和滚动方向,我们就可以精确地判断出哪一张图片将会出现。 33 | 34 | * 缓存失效 - 如果图片文件发生了变化,怎样才能通知到缓存更新呢?这是个非常困难的问题(就像菲尔 卡尔顿提到的),但是幸运的是当从程序资源加载静态图片的时候并不需要考虑这些。对用户提供的图片来说(可能会被修改或者覆盖),一个比较好的方式就是当图片缓存的时候打上一个时间戳以便当文件更新的时候作比较。 35 | 36 | * 缓存回收 - 当内存不够的时候,如何判断哪些缓存需要清空呢?这就需要到你写一个合适的算法了。幸运的是,对缓存回收的问题,苹果提供了一个叫做`NSCache`通用的解决方案 37 | 38 | ###NSCache 39 | 40 |     `NSCache`和`NSDictionary`类似。你可以通过`-setObject:forKey:`和`-object:forKey:`方法分别来插入,检索。和字典不同的是,`NSCache`在系统低内存的时候自动丢弃存储的对象。 41 | 42 |     `NSCache`用来判断何时丢弃对象的算法并没有在文档中给出,但是你可以使用`-setCountLimit:`方法设置缓存大小,以及`-setObject:forKey:cost:`来对每个存储的对象指定消耗的值来提供一些暗示。 43 | 44 |     指定消耗数值可以用来指定相对的重建成本。如果对大图指定一个大的消耗值,那么缓存就知道这些物体的存储更加昂贵,于是当有大的性能问题的时候才会丢弃这些物体。你也可以用`-setTotalCostLimit:`方法来指定全体缓存的尺寸。 45 | 46 |     `NSCache`是一个普遍的缓存解决方案,我们创建一个比传送器案例更好的自定义的缓存类。(例如,我们可以基于不同的缓存图片索引和当前中间索引来判断哪些图片需要首先被释放)。但是`NSCache`对我们当前的缓存需求来说已经足够了;没必要过早做优化。 47 | 48 |     使用图片缓存和提前加载的实现来扩展之前的传送器案例,然后来看看是否效果更好(见清单14.5)。 49 | 50 | 清单14.5 添加缓存 51 | 52 | ```objective-c 53 | #import "ViewController.h" 54 | 55 | @interface ViewController() 56 | 57 | @property (nonatomic, copy) NSArray *imagePaths; 58 | @property (nonatomic, weak) IBOutlet UICollectionView *collectionView; 59 | 60 | @end 61 | 62 | @implementation ViewController 63 | 64 | - (void)viewDidLoad 65 | { 66 | //set up data 67 | self.imagePaths = [[NSBundle mainBundle] pathsForResourcesOfType:@"png" inDirectory:@"Vacation Photos"]; 68 | //register cell class 69 | [self.collectionView registerClass:[UICollectionViewCell class] forCellWithReuseIdentifier:@"Cell"]; 70 | } 71 | 72 | - (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section 73 | { 74 | return [self.imagePaths count]; 75 | } 76 | 77 | - (UIImage *)loadImageAtIndex:(NSUInteger)index 78 | { 79 | //set up cache 80 | static NSCache *cache = nil; 81 | if (!cache) { 82 | cache = [[NSCache alloc] init]; 83 | } 84 | //if already cached, return immediately 85 | UIImage *image = [cache objectForKey:@(index)]; 86 | if (image) { 87 | return [image isKindOfClass:[NSNull class]]? nil: image; 88 | } 89 | //set placeholder to avoid reloading image multiple times 90 | [cache setObject:[NSNull null] forKey:@(index)]; 91 | //switch to background thread 92 | dispatch_async( dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{ 93 | //load image 94 | NSString *imagePath = self.imagePaths[index]; 95 | UIImage *image = [UIImage imageWithContentsOfFile:imagePath]; 96 | //redraw image using device context 97 | UIGraphicsBeginImageContextWithOptions(image.size, YES, 0); 98 | [image drawAtPoint:CGPointZero]; 99 | image = UIGraphicsGetImageFromCurrentImageContext(); 100 | UIGraphicsEndImageContext(); 101 | //set image for correct image view 102 | dispatch_async(dispatch_get_main_queue(), ^{ //cache the image 103 | [cache setObject:image forKey:@(index)]; 104 | //display the image 105 | NSIndexPath *indexPath = [NSIndexPath indexPathForItem: index inSection:0]; UICollectionViewCell *cell = [self.collectionView cellForItemAtIndexPath:indexPath]; 106 | UIImageView *imageView = [cell.contentView.subviews lastObject]; 107 | imageView.image = image; 108 | }); 109 | }); 110 | //not loaded yet 111 | return nil; 112 | } 113 | 114 | - (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath 115 | { 116 | //dequeue cell 117 | UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath]; 118 | //add image view 119 | UIImageView *imageView = [cell.contentView.subviews lastObject]; 120 | if (!imageView) { 121 | imageView = [[UIImageView alloc] initWithFrame:cell.contentView.bounds]; 122 | imageView.contentMode = UIViewContentModeScaleAspectFit; 123 | [cell.contentView addSubview:imageView]; 124 | } 125 | //set or load image for this index 126 | imageView.image = [self loadImageAtIndex:indexPath.item]; 127 | //preload image for previous and next index 128 | if (indexPath.item < [self.imagePaths count] - 1) { 129 | [self loadImageAtIndex:indexPath.item + 1]; } 130 | if (indexPath.item > 0) { 131 | [self loadImageAtIndex:indexPath.item - 1]; } 132 | return cell; 133 | } 134 | 135 | @end 136 | ``` 137 | 138 |     果然效果更好了!当滚动的时候虽然还有一些图片进入的延迟,但是已经非常罕见了。缓存意味着我们做了更少的加载。这里提前加载逻辑非常粗暴,其实可以把滑动速度和方向也考虑进来,但这已经比之前没做缓存的版本好很多了。 139 | --------------------------------------------------------------------------------