版本记录
版本号 | 时间 |
---|---|
V1.0 | 2020.07.10 星期五 |
前言
iOS中有关视图控件用户能看到的都在UIKit框架里面,用户交互也是通过UIKit进行的。感兴趣的参考上面几篇文章。
1. UIKit框架(一) —— UIKit动力学和移动效果(一)
2. UIKit框架(二) —— UIKit动力学和移动效果(二)
3. UIKit框架(三) —— UICollectionViewCell的扩张效果的实现(一)
4. UIKit框架(四) —— UICollectionViewCell的扩张效果的实现(二)
5. UIKit框架(五) —— 自定义控件:可重复使用的滑块(一)
6. UIKit框架(六) —— 自定义控件:可重复使用的滑块(二)
7. UIKit框架(七) —— 动态尺寸UITableViewCell的实现(一)
8. UIKit框架(八) —— 动态尺寸UITableViewCell的实现(二)
9. UIKit框架(九) —— UICollectionView的数据异步预加载(一)
10. UIKit框架(十) —— UICollectionView的数据异步预加载(二)
11. UIKit框架(十一) —— UICollectionView的重用、选择和重排序(一)
12. UIKit框架(十二) —— UICollectionView的重用、选择和重排序(二)
13. UIKit框架(十三) —— 如何创建自己的侧滑式面板导航(一)
14. UIKit框架(十四) —— 如何创建自己的侧滑式面板导航(二)
15. UIKit框架(十五) —— 基于自定义UICollectionViewLayout布局的简单示例(一)
16. UIKit框架(十六) —— 基于自定义UICollectionViewLayout布局的简单示例(二)
17. UIKit框架(十七) —— 基于自定义UICollectionViewLayout布局的简单示例(三)
18. UIKit框架(十八) —— 基于CALayer属性的一种3D边栏动画的实现(一)
19. UIKit框架(十九) —— 基于CALayer属性的一种3D边栏动画的实现(二)
20. UIKit框架(二十) —— 基于UILabel跑马灯类似效果的实现(一)
21. UIKit框架(二十一) —— UIStackView的使用(一)
22. UIKit框架(二十二) —— 基于UIPresentationController的自定义viewController的转场和展示(一)
23. UIKit框架(二十三) —— 基于UIPresentationController的自定义viewController的转场和展示(二)
24. UIKit框架(二十四) —— 基于UICollectionViews和Drag-Drop在两个APP间的使用示例 (一)
25. UIKit框架(二十五) —— 基于UICollectionViews和Drag-Drop在两个APP间的使用示例 (二)
26. UIKit框架(二十六) —— UICollectionView的自定义布局 (一)
27. UIKit框架(二十七) —— UICollectionView的自定义布局 (二)
28. UIKit框架(二十八) —— 一个UISplitViewController的简单实用示例 (一)
29. UIKit框架(二十九) —— 一个UISplitViewController的简单实用示例 (二)
30. UIKit框架(三十) —— 基于UICollectionViewCompositionalLayout API的UICollectionViews布局的简单示例(一)
31. UIKit框架(三十一) —— 基于UICollectionViewCompositionalLayout API的UICollectionViews布局的简单示例(二)
32. UIKit框架(三十二) —— 替换Peek and Pop交互的基于iOS13的Context Menus(一)
33. UIKit框架(三十三) —— 替换Peek and Pop交互的基于iOS13的Context Menus(二)
34. UIKit框架(三十四) —— Accessibility的使用(一)
35. UIKit框架(三十五) —— Accessibility的使用(二)
36. UIKit框架(三十六) —— UICollectionView UICollectionViewDiffableDataSource的使用(一)
37. UIKit框架(三十七) —— UICollectionView UICollectionViewDiffableDataSource的使用(二)
38. UIKit框架(三十八) —— 基于CollectionView转盘效果的实现(一)
39. UIKit框架(三十九) —— iOS 13中UISearchController 和 UISearchBar的新更改(一)
40. UIKit框架(四十) —— iOS 13中UISearchController 和 UISearchBar的新更改(二)
41. UIKit框架(四十一) —— 使用协议构建自定义Collection(一)
42. UIKit框架(四十二) —— 使用协议构建自定义Collection(二)
43. UIKit框架(四十三) —— CALayer的简单实用示例(一)
源码
1. Swift
首先看下工程组织结构
接着看下sb中的内容:
接着就是源码了
1. TrackBall.swift
import UIKit
func pow2(_ lhs: CGFloat) -> CGFloat {
return pow(lhs, 2)
}
class TrackBall {
let tolerance = 0.001
var baseTransform = CATransform3DIdentity
let trackBallRadius: CGFloat
let trackBallCenter: CGPoint
var trackBallStartPoint = (x: CGFloat(0.0), y: CGFloat(0.0), z: CGFloat(0.0))
init(location: CGPoint, inRect bounds: CGRect) {
if bounds.width > bounds.height {
trackBallRadius = bounds.height * 0.5
} else {
trackBallRadius = bounds.width * 0.5
}
trackBallCenter = CGPoint(x: bounds.midX, y: bounds.midY)
setStartPointFromLocation(location)
}
func setStartPointFromLocation(_ location: CGPoint) {
trackBallStartPoint.x = location.x - trackBallCenter.x
trackBallStartPoint.y = location.y - trackBallCenter.y
let distance = pow2(trackBallStartPoint.x) + pow2(trackBallStartPoint.y)
trackBallStartPoint.z = distance > pow2(trackBallRadius) ? CGFloat(0.0) : sqrt(pow2(trackBallRadius) - distance)
}
func finalizeTrackBallForLocation(_ location: CGPoint) {
baseTransform = rotationTransformForLocation(location)
}
func rotationTransformForLocation(_ location: CGPoint) -> CATransform3D {
var trackBallCurrentPoint = (x: location.x - trackBallCenter.x, y: location.y - trackBallCenter.y, z: CGFloat(0.0))
let withinTolerance = fabs(Double(trackBallCurrentPoint.x - trackBallStartPoint.x)) < tolerance &&
fabs(Double(trackBallCurrentPoint.y - trackBallStartPoint.y)) < tolerance
if withinTolerance {
return CATransform3DIdentity
}
let distance = pow2(trackBallCurrentPoint.x) + pow2(trackBallCurrentPoint.y)
if distance > pow2(trackBallRadius) {
trackBallCurrentPoint.z = 0.0
} else {
trackBallCurrentPoint.z = sqrt(pow2(trackBallRadius) - distance)
}
let startPoint = trackBallStartPoint
let currentPoint = trackBallCurrentPoint
let x = startPoint.y * currentPoint.z - startPoint.z * currentPoint.y
let y = -startPoint.x * currentPoint.z + trackBallStartPoint.z * currentPoint.x
let z = startPoint.x * currentPoint.y - startPoint.y * currentPoint.x
var rotationVector = (x: x, y: y, z: z)
let startLength = sqrt(Double(pow2(startPoint.x) + pow2(startPoint.y) + pow2(startPoint.z)))
let currentLength = sqrt(Double(pow2(currentPoint.x) + pow2(currentPoint.y) + pow2(currentPoint.z)))
let startDotCurrent = Double(
startPoint.x * currentPoint.x +
startPoint.y + currentPoint.y +
startPoint.z + currentPoint.z)
let rotationLength = sqrt(Double(pow2(rotationVector.x) + pow2(rotationVector.y) + pow2(rotationVector.z)))
let angle = CGFloat(atan2(
rotationLength / (startLength * currentLength),
startDotCurrent / (startLength * currentLength)))
let normalizer = CGFloat(rotationLength)
rotationVector.x /= normalizer
rotationVector.y /= normalizer
rotationVector.z /= normalizer
let rotationTransform = CATransform3DMakeRotation(angle, rotationVector.x, rotationVector.y, rotationVector.z)
return CATransform3DConcat(baseTransform, rotationTransform)
}
}
2. ClassDescription.swift
import Foundation
struct ClassDescription {
let title: String
let description: String
}
3. ScrollingView.swift
import UIKit
import QuartzCore
class ScrollingView: UIView {
override class var layerClass: AnyClass {
return CAScrollLayer.self
}
}
4. AppDelegate.swift
import UIKit
let swiftOrangeColor = UIColor(red: 248 / 255.0, green: 96 / 255.0, blue: 47 / 255.0, alpha: 1.0)
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
UILabel.appearance().font = UIFont(name: "Avenir-Light", size: 17.0)
UILabel.appearance(whenContainedInInstancesOf: [UITableViewCell.self]).font =
UIFont(name: "Avenir-light", size: 14.0)
UINavigationBar.appearance().tintColor = UIColor.white
UINavigationBar.appearance().barTintColor = swiftOrangeColor
UINavigationBar.appearance().titleTextAttributes = [
NSAttributedString.Key.foregroundColor: UIColor.white,
// swiftlint:disable:next force_unwrapping
NSAttributedString.Key.font: UIFont(name: "Avenir-light", size: 17.0)!
]
UITableView.appearance().separatorColor = swiftOrangeColor
UITableViewCell.appearance().separatorInset = UIEdgeInsets.zero
UISwitch.appearance().tintColor = swiftOrangeColor
UISlider.appearance().tintColor = swiftOrangeColor
UISegmentedControl.appearance().tintColor = swiftOrangeColor
return true
}
}
5. ClassListViewController.swift
import UIKit
class ClassListViewController: UITableViewController {
let classes: [ClassDescription] = [
ClassDescription(title: "CALayer", description: "Manage and animate visual content"),
ClassDescription(title: "CAScrollLayer", description: "Display portion of a scrollable layer"),
ClassDescription(title: "CATextLayer", description: "Render plain text or attributed strings"),
ClassDescription(title: "AVPlayerLayer", description: "Display an AV player"),
ClassDescription(title: "CAGradientLayer", description: "Apply a color gradient over the background"),
ClassDescription(title: "CAReplicatorLayer", description: "Duplicate a source layer"),
ClassDescription(title: "CAShapeLayer", description: "Draw using scalable vector paths"),
ClassDescription(title: "CATransformLayer", description: "Draw 3D structures"),
ClassDescription(title: "CAEmitterLayer", description: "Render animated particles")
]
override func viewDidLoad() {
super.viewDidLoad()
tableView.tableFooterView = UIView(frame: .zero)
}
}
// MARK: - UITableViewDataSource
extension ClassListViewController {
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return classes.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ClassCell", for: indexPath)
let classDescription = classes[indexPath.row]
cell.textLabel?.text = classDescription.title
cell.detailTextLabel?.text = classDescription.description
cell.imageView?.image = UIImage(named: classDescription.title)
return cell
}
}
// MARK: - UITableViewDelegate
extension ClassListViewController {
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let identifier = classes[indexPath.row].title
performSegue(withIdentifier: identifier, sender: nil)
}
}
6. CALayerViewController.swift
import UIKit
class CALayerViewController: UIViewController {
@IBOutlet weak var viewForLayer: UIView!
let layer = CALayer()
// MARK: - View life cycle
override func viewDidLoad() {
super.viewDidLoad()
setUpLayer()
viewForLayer.layer.addSublayer(layer)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "DisplayLayerControls" {
(segue.destination as? CALayerControlsViewController)?.layerViewController = self
}
}
}
// MARK: - Layer
extension CALayerViewController {
func setUpLayer() {
//1
layer.frame = viewForLayer.bounds
layer.contents = UIImage(named: "star")?.cgImage
// 2
layer.contentsGravity = .center
layer.magnificationFilter = .linear
// 3
layer.cornerRadius = 100.0
layer.borderWidth = 12.0
layer.borderColor = UIColor.white.cgColor
layer.backgroundColor = swiftOrangeColor.cgColor
//4
layer.shadowOpacity = 0.75
layer.shadowOffset = CGSize(width: 0, height: 3)
layer.shadowRadius = 3.0
layer.isGeometryFlipped = false
}
}
7. CALayerControlsViewController.swift
import UIKit
class CALayerControlsViewController: UITableViewController {
@IBOutlet weak var contentsGravityPickerValueLabel: UILabel!
@IBOutlet weak var contentsGravityPicker: UIPickerView!
@IBOutlet var switches: [UISwitch]!
@IBOutlet var sliderValueLabels: [UILabel]!
@IBOutlet var sliders: [UISlider]!
@IBOutlet weak var borderColorSlidersValueLabel: UILabel!
@IBOutlet var borderColorSliders: [UISlider]!
@IBOutlet weak var backgroundColorSlidersValueLabel: UILabel!
@IBOutlet var backgroundColorSliders: [UISlider]!
@IBOutlet weak var shadowOffsetSlidersValueLabel: UILabel!
@IBOutlet var shadowOffsetSliders: [UISlider]!
@IBOutlet weak var shadowColorSlidersValueLabel: UILabel!
@IBOutlet var shadowColorSliders: [UISlider]!
@IBOutlet weak var magnificationFilterSegmentedControl: UISegmentedControl!
enum Row: Int {
case contentsGravity,
contentsGravityPicker,
displayContents,
geometryFlipped,
hidden,
opacity,
cornerRadius,
borderWidth,
borderColor,
backgroundColor,
shadowOpacity,
shadowOffset,
shadowRadius,
shadowColor,
magnificationFilter
}
enum Switch: Int {
case displayContents, geometryFlipped, hidden
}
enum Slider: Int {
case opacity, cornerRadius, borderWidth, shadowOpacity, shadowRadius
}
enum ColorSlider: Int {
case red, green, blue
}
enum MagnificationFilter: Int {
case linear, nearest, trilinear
}
// swiftlint:disable:next implicitly_unwrapped_optional
weak var layerViewController: CALayerViewController!
let contentsGravityValues: [CALayerContentsGravity] = [
.center, .top, .bottom, .left, .right, .topLeft, .topRight,
.bottomLeft, .bottomRight, .resize, .resizeAspect, .resizeAspectFill
]
var contentsGravityPickerVisible = false
override func viewDidLoad() {
super.viewDidLoad()
updateSliderValueLabels()
}
}
// MARK: - IBActions
extension CALayerControlsViewController {
@IBAction func switchChanged(_ sender: UISwitch) {
let switchesArray = switches as NSArray
// swiftlint:disable:next force_unwrapping
let theSwitch = Switch(rawValue: switchesArray.index(of: sender))!
switch theSwitch {
case .displayContents:
layerViewController.layer.contents = sender.isOn ? UIImage(named: "star")?.cgImage : nil
case .geometryFlipped:
layerViewController.layer.isGeometryFlipped = sender.isOn
case .hidden:
layerViewController.layer.isHidden = sender.isOn
}
}
@IBAction func sliderChanged(_ sender: UISlider) {
let slidersArray = sliders as NSArray
// swiftlint:disable:next force_unwrapping
let slider = Slider(rawValue: slidersArray.index(of: sender))!
switch slider {
case .opacity:
layerViewController.layer.opacity = sender.value
case .cornerRadius:
layerViewController.layer.cornerRadius = CGFloat(sender.value)
case .borderWidth:
layerViewController.layer.borderWidth = CGFloat(sender.value)
case .shadowOpacity:
layerViewController.layer.shadowOpacity = sender.value
case .shadowRadius:
layerViewController.layer.shadowRadius = CGFloat(sender.value)
}
updateSliderValueLabel(slider)
}
@IBAction func borderColorSliderChanged(_ sender: UISlider) {
let colorLabel = colorAndLabel(forSliders: borderColorSliders)
layerViewController.layer.borderColor = colorLabel.color
borderColorSlidersValueLabel.text = colorLabel.label
}
@IBAction func backgroundColorSliderChanged(_ sender: UISlider) {
let colorLabel = colorAndLabel(forSliders: backgroundColorSliders)
layerViewController.layer.backgroundColor = colorLabel.color
backgroundColorSlidersValueLabel.text = colorLabel.label
}
@IBAction func shadowOffsetSliderChanged(_ sender: UISlider) {
let width = CGFloat(shadowOffsetSliders[0].value)
let height = CGFloat(shadowOffsetSliders[1].value)
layerViewController.layer.shadowOffset = CGSize(width: width, height: height)
shadowOffsetSlidersValueLabel.text = "Width: \(Int(width)), Height: \(Int(height))"
}
@IBAction func shadowColorSliderChanged(_ sender: UISlider) {
let colorLabel = colorAndLabel(forSliders: shadowColorSliders)
layerViewController.layer.shadowColor = colorLabel.color
shadowColorSlidersValueLabel.text = colorLabel.label
}
@IBAction func magnificationFilterSegmentedControlChanged(_ sender: UISegmentedControl) {
// swiftlint:disable:next force_unwrapping
let filter = MagnificationFilter(rawValue: sender.selectedSegmentIndex)!
let filterValue: CALayerContentsFilter
switch filter {
case .linear:
filterValue = .linear
case .nearest:
filterValue = .nearest
case .trilinear:
filterValue = .trilinear
}
layerViewController.layer.magnificationFilter = filterValue
}
}
// MARK: - Triggered actions
extension CALayerControlsViewController {
func showContentsGravityPicker() {
contentsGravityPickerVisible = true
relayoutTableViewCells()
let index = contentsGravityValues.firstIndex(of: layerViewController.layer.contentsGravity) ?? 0
contentsGravityPicker.selectRow(index, inComponent: 0, animated: false)
contentsGravityPicker.isHidden = false
contentsGravityPicker.alpha = 0.0
UIView.animate(withDuration: 0.25) {
self.contentsGravityPicker.alpha = 1.0
}
}
func hideContentsGravityPicker() {
if contentsGravityPickerVisible {
tableView.isUserInteractionEnabled = false
contentsGravityPickerVisible = false
relayoutTableViewCells()
UIView.animate(
withDuration: 0.25,
animations: {
self.contentsGravityPicker.alpha = 0.0
}, completion: { _ in
self.contentsGravityPicker.isHidden = true
self.tableView.isUserInteractionEnabled = true
})
}
}
}
// MARK: - Helpers
extension CALayerControlsViewController {
func updateContentsGravityPickerValueLabel() {
contentsGravityPickerValueLabel.text = layerViewController.layer.contentsGravity.rawValue
}
func updateSliderValueLabels() {
for slider in Slider.opacity.rawValue...Slider.shadowRadius.rawValue {
// swiftlint:disable:next force_unwrapping
updateSliderValueLabel(Slider(rawValue: slider)!)
}
}
func updateSliderValueLabel(_ sliderEnum: Slider) {
let index = sliderEnum.rawValue
let label = sliderValueLabels[index]
let slider = sliders[index]
switch sliderEnum {
case .opacity, .shadowOpacity:
label.text = String(format: "%.1f", slider.value)
case .cornerRadius, .borderWidth, .shadowRadius:
label.text = "\(Int(slider.value))"
}
}
func colorAndLabel(forSliders sliders: [UISlider]) -> (color: CGColor, label: String) {
let red = CGFloat(sliders[0].value)
let green = CGFloat(sliders[1].value)
let blue = CGFloat(sliders[2].value)
let color = UIColor(red: red / 255.0, green: green / 255.0, blue: blue / 255.0, alpha: 1.0).cgColor
let label = "RGB: \(Int(red)), \(Int(green)), \(Int(blue))"
return (color: color, label: label)
}
func relayoutTableViewCells() {
tableView.beginUpdates()
tableView.endUpdates()
}
}
// MARK: - UITableViewDelegate
extension CALayerControlsViewController {
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// swiftlint:disable:next force_unwrapping
let row = Row(rawValue: indexPath.row)!
if row == .contentsGravityPicker {
return contentsGravityPickerVisible ? 162.0 : 0.0
} else {
return 44.0
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// swiftlint:disable:next force_unwrapping
let row = Row(rawValue: indexPath.row)!
switch row {
case .contentsGravity where !contentsGravityPickerVisible:
showContentsGravityPicker()
default:
hideContentsGravityPicker()
}
}
}
// MARK: - UIPickerViewDataSource
extension CALayerControlsViewController: UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return contentsGravityValues.count
}
}
// MARK: - UIPickerViewDelegate
extension CALayerControlsViewController: UIPickerViewDelegate {
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return contentsGravityValues[row].rawValue
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
layerViewController.layer.contentsGravity = CALayerContentsGravity(rawValue: contentsGravityValues[row].rawValue)
updateContentsGravityPickerValueLabel()
}
}
8. CAScrollLayerViewController.swift
import UIKit
class CAScrollLayerViewController: UIViewController {
@IBOutlet weak var scrollingView: ScrollingView!
@IBOutlet weak var horizontalScrollingSwitch: UISwitch!
@IBOutlet weak var verticalScrollingSwitch: UISwitch!
var scrollingViewLayer: CAScrollLayer {
// swiftlint:disable:next force_cast
return scrollingView.layer as! CAScrollLayer
}
override func viewDidLoad() {
super.viewDidLoad()
scrollingViewLayer.scrollMode = .both
}
}
// MARK: - IBActions
extension CAScrollLayerViewController {
@IBAction func panRecognized(_ sender: UIPanGestureRecognizer) {
var newPoint = scrollingView.bounds.origin
newPoint.x -= sender.translation(in: scrollingView).x
newPoint.y -= sender.translation(in: scrollingView).y
sender.setTranslation(.zero, in: scrollingView)
scrollingViewLayer.scroll(to: newPoint)
if sender.state == .ended {
UIView.animate(withDuration: 0.3) {
self.scrollingViewLayer.scroll(to: CGPoint.zero)
}
}
}
@IBAction func scrollingSwitchChanged(_ sender: UISwitch) {
switch (horizontalScrollingSwitch.isOn, verticalScrollingSwitch.isOn) {
case (true, true):
scrollingViewLayer.scrollMode = .both
case (true, false):
scrollingViewLayer.scrollMode = .horizontally
case (false, true):
scrollingViewLayer.scrollMode = .vertically
default:
scrollingViewLayer.scrollMode = .none
}
}
}
9. CATextLayerViewController.swift
import UIKit
class CATextLayerViewController: UIViewController {
@IBOutlet weak var viewForTextLayer: UIView!
@IBOutlet weak var fontSizeSliderValueLabel: UILabel!
@IBOutlet weak var fontSizeSlider: UISlider!
@IBOutlet weak var wrapTextSwitch: UISwitch!
@IBOutlet weak var alignmentModeSegmentedControl: UISegmentedControl!
@IBOutlet weak var truncationModeSegmentedControl: UISegmentedControl!
enum Font: Int {
case helvetica, noteworthyLight
}
enum AlignmentMode: Int {
case left, center, justified, right
}
enum TruncationMode: Int {
case start, middle, end
}
private enum Constants {
static let baseFontSize: CGFloat = 24.0
}
let noteworthyLightFont = CTFontCreateWithName("Noteworthy-Light" as CFString, Constants.baseFontSize, nil)
let helveticaFont = CTFontCreateWithName("Helvetica" as CFString, Constants.baseFontSize, nil)
let textLayer = CATextLayer()
var previouslySelectedTruncationMode = TruncationMode.end
override func viewDidLoad() {
super.viewDidLoad()
setUpTextLayer()
viewForTextLayer.layer.addSublayer(textLayer)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
textLayer.frame = viewForTextLayer.bounds
}
}
// MARK: - Layer setup
extension CATextLayerViewController {
func setUpTextLayer() {
textLayer.frame = viewForTextLayer.bounds
let string = (1...20)
.map { _ in
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce auctor arcu quis velit congue dictum."
}
.joined(separator: " ")
textLayer.string = string
// 1
textLayer.font = helveticaFont
textLayer.fontSize = Constants.baseFontSize
// 2
textLayer.foregroundColor = UIColor.darkGray.cgColor
textLayer.isWrapped = true
textLayer.alignmentMode = .left
textLayer.truncationMode = .end
// 3
textLayer.contentsScale = UIScreen.main.scale
}
}
// MARK: - IBActions
extension CATextLayerViewController {
@IBAction func fontSegmentedControlChanged(_ sender: UISegmentedControl) {
switch sender.selectedSegmentIndex {
case Font.helvetica.rawValue:
textLayer.font = helveticaFont
case Font.noteworthyLight.rawValue:
textLayer.font = noteworthyLightFont
default:
break
}
}
@IBAction func fontSizeSliderChanged(_ sender: UISlider) {
fontSizeSliderValueLabel.text = "\(Int(sender.value * 100.0))%"
textLayer.fontSize = Constants.baseFontSize * CGFloat(sender.value)
}
@IBAction func wrapTextSwitchChanged(_ sender: UISwitch) {
alignmentModeSegmentedControl.selectedSegmentIndex = AlignmentMode.left.rawValue
textLayer.alignmentMode = CATextLayerAlignmentMode.left
if sender.isOn {
if let truncationMode = TruncationMode(rawValue: truncationModeSegmentedControl.selectedSegmentIndex) {
previouslySelectedTruncationMode = truncationMode
}
truncationModeSegmentedControl.selectedSegmentIndex = UISegmentedControl.noSegment
textLayer.isWrapped = true
} else {
textLayer.isWrapped = false
truncationModeSegmentedControl.selectedSegmentIndex = previouslySelectedTruncationMode.rawValue
}
}
@IBAction func alignmentModeSegmentedControlChanged(_ sender: UISegmentedControl) {
wrapTextSwitch.isOn = true
textLayer.isWrapped = true
truncationModeSegmentedControl.selectedSegmentIndex = UISegmentedControl.noSegment
textLayer.truncationMode = CATextLayerTruncationMode.none
switch sender.selectedSegmentIndex {
case AlignmentMode.left.rawValue:
textLayer.alignmentMode = .left
case AlignmentMode.center.rawValue:
textLayer.alignmentMode = .center
case AlignmentMode.justified.rawValue:
textLayer.alignmentMode = .justified
case AlignmentMode.right.rawValue:
textLayer.alignmentMode = .right
default:
textLayer.alignmentMode = .left
}
}
@IBAction func truncationModeSegmentedControlChanged(_ sender: UISegmentedControl) {
wrapTextSwitch.isOn = false
textLayer.isWrapped = false
alignmentModeSegmentedControl.selectedSegmentIndex = UISegmentedControl.noSegment
textLayer.alignmentMode = .left
switch sender.selectedSegmentIndex {
case TruncationMode.start.rawValue:
textLayer.truncationMode = .start
case TruncationMode.middle.rawValue:
textLayer.truncationMode = .middle
case TruncationMode.end.rawValue:
textLayer.truncationMode = .end
default:
textLayer.truncationMode = .none
}
}
}
10. AVPlayerLayerViewController.swift
import UIKit
import AVFoundation
class AVPlayerLayerViewController: UIViewController {
@IBOutlet weak var viewForPlayerLayer: UIView!
@IBOutlet weak var playButton: UIButton!
@IBOutlet weak var rateSegmentedControl: UISegmentedControl!
@IBOutlet weak var loopSwitch: UISwitch!
@IBOutlet weak var volumeSlider: UISlider!
enum Rate: Int {
case slowForward, normal, fastForward
}
let playerLayer = AVPlayerLayer()
var player: AVPlayer? {
return playerLayer.player
}
var rate: Float {
switch rateSegmentedControl.selectedSegmentIndex {
case 0:
return 0.5
case 2:
return 2.0
default:
return 1.0
}
}
override func viewDidLoad() {
super.viewDidLoad()
rateSegmentedControl.selectedSegmentIndex = 1
setUpPlayerLayer()
viewForPlayerLayer.layer.addSublayer(playerLayer)
NotificationCenter.default.addObserver(
self,
selector: #selector(AVPlayerLayerViewController.playerDidReachEndNotificationHandler(_:)),
name: NSNotification.Name(rawValue: "AVPlayerItemDidPlayToEndTimeNotification"),
object: player?.currentItem)
playButton.setTitle("Pause", for: .normal)
}
}
// MARK: - Layer setup
extension AVPlayerLayerViewController {
func setUpPlayerLayer() {
// 1
playerLayer.frame = viewForPlayerLayer.bounds
// 2
// swiftlint:disable:next force_unwrapping
let url = Bundle.main.url(forResource: "colorfulStreak", withExtension: "m4v")!
let item = AVPlayerItem(asset: AVAsset(url: url))
let player = AVPlayer(playerItem: item)
// 3
player.actionAtItemEnd = .none
// 4
player.volume = 1.0
player.rate = 1.0
playerLayer.player = player
}
}
// MARK: - IBActions
extension AVPlayerLayerViewController {
@IBAction func playButtonTapped(_ sender: UIButton) {
if player?.rate == 0 {
player?.rate = rate
updatePlayButtonTitle(isPlaying: true)
} else {
player?.pause()
updatePlayButtonTitle(isPlaying: false)
}
}
@IBAction func rateSegmentedControlChanged(_ sender: UISegmentedControl) {
player?.rate = rate
updatePlayButtonTitle(isPlaying: true)
}
@IBAction func loopSwitchChanged(_ sender: UISwitch) {
if sender.isOn {
player?.actionAtItemEnd = .none
} else {
player?.actionAtItemEnd = .pause
}
}
@IBAction func volumeSliderChanged(_ sender: UISlider) {
player?.volume = sender.value
}
}
// MARK: - Triggered actions
extension AVPlayerLayerViewController {
@objc func playerDidReachEndNotificationHandler(_ notification: Notification) {
// 1
guard let playerItem = notification.object as? AVPlayerItem else { return }
// 2
playerItem.seek(to: .zero, completionHandler: nil)
// 3
if player?.actionAtItemEnd == .pause {
player?.pause()
updatePlayButtonTitle(isPlaying: false)
}
}
}
// MARK: - Helpers
extension AVPlayerLayerViewController {
func updatePlayButtonTitle(isPlaying: Bool) {
if isPlaying {
playButton.setTitle("Pause", for: .normal)
} else {
playButton.setTitle("Play", for: .normal)
}
}
}
11. CAGradientLayerViewController.swift
import UIKit
class CAGradientLayerViewController: UIViewController {
@IBOutlet weak var viewForGradientLayer: UIView!
@IBOutlet weak var startPointSlider: UISlider!
@IBOutlet weak var startPointSliderValueLabel: UILabel!
@IBOutlet weak var endPointSlider: UISlider!
@IBOutlet weak var endPointSliderValueLabel: UILabel!
@IBOutlet var colorSwitches: [UISwitch]!
@IBOutlet var locationSliders: [UISlider]!
@IBOutlet var locationSliderValueLabels: [UILabel]!
let gradientLayer = CAGradientLayer()
let colors: [CGColor] = [
UIColor(red: 209, green: 0, blue: 0),
UIColor(red: 255, green: 102, blue: 34),
UIColor(red: 255, green: 218, blue: 33),
UIColor(red: 51, green: 221, blue: 0),
UIColor(red: 17, green: 51, blue: 204),
UIColor(red: 34, green: 0, blue: 102),
UIColor(red: 51, green: 0, blue: 68)
].map { $0.cgColor }
let locations: [Float] = [0, 1 / 6.0, 1 / 3.0, 0.5, 2 / 3.0, 5 / 6.0, 1.0]
override func viewDidLoad() {
super.viewDidLoad()
sortOutletCollections()
setUpGradientLayer()
viewForGradientLayer.layer.addSublayer(gradientLayer)
setUpLocationSliders()
updateLocationSliderValueLabels()
}
}
// MARK: - Setup layer
extension CAGradientLayerViewController {
func sortOutletCollections() {
colorSwitches.sortUIViewsInPlaceByTag()
locationSliders.sortUIViewsInPlaceByTag()
locationSliderValueLabels.sortUIViewsInPlaceByTag()
}
func setUpGradientLayer() {
gradientLayer.frame = viewForGradientLayer.bounds
gradientLayer.colors = colors
gradientLayer.locations = locations.map { NSNumber(value: $0) }
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
}
func setUpLocationSliders() {
guard let sliders = locationSliders else {
return
}
for (index, slider) in sliders.enumerated() {
slider.value = locations[index]
}
}
}
// MARK: - @IBActions
extension CAGradientLayerViewController {
@IBAction func startPointSliderChanged(_ sender: UISlider) {
gradientLayer.startPoint = CGPoint(x: CGFloat(sender.value), y: 0.0)
updateStartAndEndPointValueLabels()
}
@IBAction func endPointSliderChanged(_ sender: UISlider) {
gradientLayer.endPoint = CGPoint(x: CGFloat(sender.value), y: 1.0)
updateStartAndEndPointValueLabels()
}
@IBAction func colorSwitchChanged(_ sender: UISwitch) {
var gradientLayerColors: [AnyObject] = []
var locations: [NSNumber] = []
for (index, colorSwitch) in colorSwitches.enumerated() {
let slider = locationSliders[index]
if colorSwitch.isOn {
gradientLayerColors.append(colors[index])
locations.append(NSNumber(value: slider.value as Float))
slider.isEnabled = true
} else {
slider.isEnabled = false
}
}
if gradientLayerColors.count == 1 {
gradientLayerColors.append(gradientLayerColors[0])
}
gradientLayer.colors = gradientLayerColors
gradientLayer.locations = locations.count > 1 ? locations : nil
updateLocationSliderValueLabels()
}
@IBAction func locationSliderChanged(_ sender: UISlider) {
var gradientLayerLocations: [NSNumber] = []
for (index, slider) in locationSliders.enumerated() {
let colorSwitch = colorSwitches[index]
if colorSwitch.isOn {
gradientLayerLocations.append(NSNumber(value: slider.value as Float))
}
}
gradientLayer.locations = gradientLayerLocations
updateLocationSliderValueLabels()
}
}
// MARK: - Triggered actions
extension CAGradientLayerViewController {
func updateStartAndEndPointValueLabels() {
startPointSliderValueLabel.text = String(format: "(%.1f, 0.0)", startPointSlider.value)
endPointSliderValueLabel.text = String(format: "(%.1f, 1.0)", endPointSlider.value)
}
func updateLocationSliderValueLabels() {
for (index, label) in locationSliderValueLabels.enumerated() {
let colorSwitch = colorSwitches[index]
if colorSwitch.isOn {
let slider = locationSliders[index]
label.text = String(format: "%.2f", slider.value)
label.isEnabled = true
} else {
label.isEnabled = false
}
}
}
}
// MARK: - Helpers
private extension UIColor {
convenience init(red: Int, green: Int, blue: Int) {
self.init(red: CGFloat(red) / 255.0, green: CGFloat(green) / 255.0, blue: CGFloat(blue) / 255.0, alpha: 1)
}
}
12. CAReplicatorLayerViewController.swift
import UIKit
class CAReplicatorLayerViewController: UIViewController {
@IBOutlet weak var viewForReplicatorLayer: UIView!
@IBOutlet weak var layerSizeSlider: UISlider!
@IBOutlet weak var layerSizeSliderValueLabel: UILabel!
@IBOutlet weak var instanceCountSlider: UISlider!
@IBOutlet weak var instanceCountSliderValueLabel: UILabel!
@IBOutlet weak var instanceDelaySlider: UISlider!
@IBOutlet weak var instanceDelaySliderValueLabel: UILabel!
@IBOutlet weak var offsetRedSwitch: UISwitch!
@IBOutlet weak var offsetGreenSwitch: UISwitch!
@IBOutlet weak var offsetBlueSwitch: UISwitch!
@IBOutlet weak var offsetAlphaSwitch: UISwitch!
let lengthMultiplier: CGFloat = 3.0
let replicatorLayer = CAReplicatorLayer()
let instanceLayer = CALayer()
let fadeAnimation = CABasicAnimation(keyPath: "opacity")
override func viewDidLoad() {
super.viewDidLoad()
setUpReplicatorLayer()
setUpInstanceLayer()
setUpLayerFadeAnimation()
instanceDelaySliderChanged(instanceDelaySlider)
updateLayerSizeSliderValueLabel()
updateInstanceCountSliderValueLabel()
updateInstanceDelaySliderValueLabel()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
setUpReplicatorLayer()
setUpInstanceLayer()
}
}
// MARK: - Layer setup
extension CAReplicatorLayerViewController {
func setUpReplicatorLayer() {
// 1
replicatorLayer.frame = viewForReplicatorLayer.bounds
// 2
let count = instanceCountSlider.value
replicatorLayer.instanceCount = Int(count)
replicatorLayer.instanceDelay = CFTimeInterval(instanceDelaySlider.value / count)
// 3
replicatorLayer.instanceColor = UIColor.white.cgColor
replicatorLayer.instanceRedOffset = offsetValueForSwitch(offsetRedSwitch)
replicatorLayer.instanceGreenOffset = offsetValueForSwitch(offsetGreenSwitch)
replicatorLayer.instanceBlueOffset = offsetValueForSwitch(offsetBlueSwitch)
replicatorLayer.instanceAlphaOffset = offsetValueForSwitch(offsetAlphaSwitch)
//4
let angle = Float.pi * 2.0 / count
replicatorLayer.instanceTransform = CATransform3DMakeRotation(CGFloat(angle), 0.0, 0.0, 1.0)
//5
viewForReplicatorLayer.layer.addSublayer(replicatorLayer)
}
func setUpInstanceLayer() {
let layerWidth = CGFloat(layerSizeSlider.value)
let midX = viewForReplicatorLayer.bounds.midX - layerWidth / 2.0
instanceLayer.frame = CGRect(
x: midX,
y: 0.0,
width: layerWidth,
height: layerWidth * lengthMultiplier)
instanceLayer.backgroundColor = UIColor.white.cgColor
replicatorLayer.addSublayer(instanceLayer)
}
func setUpLayerFadeAnimation() {
fadeAnimation.fromValue = 1.0
fadeAnimation.toValue = 0.0
fadeAnimation.repeatCount = Float(Int.max)
}
}
// MARK: - IBActions
extension CAReplicatorLayerViewController {
@IBAction func layerSizeSliderChanged(_ sender: UISlider) {
let value = CGFloat(sender.value)
instanceLayer.bounds = CGRect(origin: .zero, size: CGSize(width: value, height: value * lengthMultiplier))
updateLayerSizeSliderValueLabel()
}
@IBAction func instanceCountSliderChanged(_ sender: UISlider) {
replicatorLayer.instanceCount = Int(sender.value)
replicatorLayer.instanceAlphaOffset = offsetValueForSwitch(offsetAlphaSwitch)
updateInstanceCountSliderValueLabel()
}
@IBAction func instanceDelaySliderChanged(_ sender: UISlider) {
if sender.value > 0.0 {
replicatorLayer.instanceDelay = CFTimeInterval(sender.value / Float(replicatorLayer.instanceCount))
setLayerFadeAnimation()
} else {
replicatorLayer.instanceDelay = 0.0
instanceLayer.opacity = 1.0
instanceLayer.removeAllAnimations()
}
updateInstanceDelaySliderValueLabel()
}
@IBAction func offsetSwitchChanged(_ sender: UISwitch) {
switch sender {
case offsetRedSwitch:
replicatorLayer.instanceRedOffset = offsetValueForSwitch(sender)
case offsetGreenSwitch:
replicatorLayer.instanceGreenOffset = offsetValueForSwitch(sender)
case offsetBlueSwitch:
replicatorLayer.instanceBlueOffset = offsetValueForSwitch(sender)
case offsetAlphaSwitch:
replicatorLayer.instanceAlphaOffset = offsetValueForSwitch(sender)
default:
break
}
}
}
// MARK: - Triggered actions
extension CAReplicatorLayerViewController {
func setLayerFadeAnimation() {
instanceLayer.opacity = 0.0
fadeAnimation.duration = CFTimeInterval(instanceDelaySlider.value)
instanceLayer.add(fadeAnimation, forKey: "FadeAnimation")
}
}
// MARK: - Helpers
extension CAReplicatorLayerViewController {
func offsetValueForSwitch(_ offsetSwitch: UISwitch) -> Float {
if offsetSwitch == offsetAlphaSwitch {
let count = Float(replicatorLayer.instanceCount)
return offsetSwitch.isOn ? -1.0 / count : 0.0
} else {
return offsetSwitch.isOn ? 0.0 : -0.05
}
}
func updateLayerSizeSliderValueLabel() {
let value = layerSizeSlider.value
layerSizeSliderValueLabel.text = String(format: "%.0f x %.0f", value, value * Float(lengthMultiplier))
}
func updateInstanceCountSliderValueLabel() {
instanceCountSliderValueLabel.text = String(format: "%.0f", instanceCountSlider.value)
}
func updateInstanceDelaySliderValueLabel() {
instanceDelaySliderValueLabel.text = String(format: "%.0f", instanceDelaySlider.value)
}
}
13. CAShapeLayerViewController.swift
import UIKit
class CAShapeLayerViewController: UIViewController {
@IBOutlet weak var viewForShapeLayer: UIView!
@IBOutlet weak var hueSlider: UISlider!
@IBOutlet weak var lineWidthSlider: UISlider!
@IBOutlet weak var lineDashSwitch: UISwitch!
@IBOutlet weak var lineCapSegmentedControl: UISegmentedControl!
@IBOutlet weak var lineJoinSegmentedControl: UISegmentedControl!
enum LineCap: Int {
case butt, round, square, cap
}
enum LineJoin: Int {
case miter, round, bevel
}
let shapeLayer = CAShapeLayer()
var color = swiftOrangeColor
let openPath = UIBezierPath()
let closedPath = UIBezierPath()
override func viewDidLoad() {
super.viewDidLoad()
setUpPath()
setUpShapeLayer()
}
}
// MARK: - Layer setup
extension CAShapeLayerViewController {
func setUpPath() {
openPath.move(to: CGPoint(x: 30, y: 196))
openPath.addCurve(
to: CGPoint(x: 112.0, y: 12.5),
controlPoint1: CGPoint(x: 110.56, y: 13.79),
controlPoint2: CGPoint(x: 112.07, y: 13.01))
openPath.addCurve(
to: CGPoint(x: 194, y: 196),
controlPoint1: CGPoint(x: 111.9, y: 11.81),
controlPoint2: CGPoint(x: 194, y: 196))
openPath.addLine(to: CGPoint(x: 30.0, y: 85.68))
openPath.addLine(to: CGPoint(x: 194.0, y: 48.91))
openPath.addLine(to: CGPoint(x: 30, y: 196))
}
func setUpShapeLayer() {
// 1
shapeLayer.path = openPath.cgPath
// 2
shapeLayer.lineCap = .butt
shapeLayer.lineJoin = .miter
shapeLayer.miterLimit = 4.0
// 3
shapeLayer.lineWidth = CGFloat(lineWidthSlider.value)
shapeLayer.strokeColor = swiftOrangeColor.cgColor
shapeLayer.fillColor = UIColor.white.cgColor
// 4
shapeLayer.lineDashPattern = nil
shapeLayer.lineDashPhase = 0.0
viewForShapeLayer.layer.addSublayer(shapeLayer)
}
}
// MARK: - IBActions
extension CAShapeLayerViewController {
@IBAction func hueSliderChanged(_ sender: UISlider) {
let hue = CGFloat(sender.value / 359.0)
let color = UIColor(hue: hue, saturation: 0.81, brightness: 0.97, alpha: 1.0)
shapeLayer.strokeColor = color.cgColor
self.color = color
}
@IBAction func lineWidthSliderChanged(_ sender: UISlider) {
shapeLayer.lineWidth = CGFloat(sender.value)
}
@IBAction func lineDashSwitchChanged(_ sender: UISwitch) {
if sender.isOn {
shapeLayer.lineDashPattern = [50, 50]
shapeLayer.lineDashPhase = 25.0
} else {
shapeLayer.lineDashPattern = nil
shapeLayer.lineDashPhase = 0
}
}
@IBAction func lineCapSegmentedControlChanged(_ sender: UISegmentedControl) {
shapeLayer.path = openPath.cgPath
let lineCap: CAShapeLayerLineCap
switch sender.selectedSegmentIndex {
case LineCap.round.rawValue:
lineCap = .round
case LineCap.square.rawValue:
lineCap = .square
default:
lineCap = .butt
}
shapeLayer.lineCap = lineCap
}
@IBAction func lineJoinSegmentedControlChanged(_ sender: UISegmentedControl) {
let lineJoin: CAShapeLayerLineJoin
switch sender.selectedSegmentIndex {
case LineJoin.round.rawValue:
lineJoin = .round
case LineJoin.bevel.rawValue:
lineJoin = .bevel
default:
lineJoin = .miter
}
shapeLayer.lineJoin = lineJoin
}
}
14. CATransformLayerViewController.swift
import UIKit
func degreesToRadians(_ degrees: Double) -> CGFloat {
return CGFloat(degrees * Double.pi / 180.0)
}
func radiansToDegrees(_ radians: Double) -> CGFloat {
return CGFloat(radians / Double.pi * 180.0)
}
class CATransformLayerViewController: UIViewController {
@IBOutlet weak var boxTappedLabel: UILabel!
@IBOutlet weak var viewForTransformLayer: UIView!
@IBOutlet var colorAlphaSwitches: [UISwitch]!
enum Color: Int {
case red, orange, yellow, green, blue, purple
}
let sideLength = CGFloat(160.0)
let reducedAlpha = CGFloat(0.8)
// swiftlint:disable:next implicitly_unwrapped_optional
var transformLayer: CATransformLayer!
let swipeMeTextLayer = CATextLayer()
var redColor = UIColor.red
var orangeColor = UIColor.orange
var yellowColor = UIColor.yellow
var greenColor = UIColor.green
var blueColor = UIColor.blue
var purpleColor = UIColor.purple
var trackBall: TrackBall?
override func viewDidLoad() {
super.viewDidLoad()
setUpSwipeMeTextLayer()
buildCube()
sortOutletCollections()
}
}
// MARK: - Layer setup
extension CATransformLayerViewController {
// swiftlint:disable:next function_body_length
func buildCube() {
// 1
transformLayer = CATransformLayer()
// 2
let redLayer = sideLayer(color: redColor)
redLayer.addSublayer(swipeMeTextLayer)
transformLayer.addSublayer(redLayer)
// 3
let orangeLayer = sideLayer(color: orangeColor)
var orangeTransform = CATransform3DMakeTranslation(
sideLength / 2.0,
0.0,
sideLength / -2.0)
orangeTransform = CATransform3DRotate(
orangeTransform,
degreesToRadians(90.0),
0.0,
1.0,
0.0)
orangeLayer.transform = orangeTransform
transformLayer.addSublayer(orangeLayer)
let yellowLayer = sideLayer(color: yellowColor)
yellowLayer.transform = CATransform3DMakeTranslation(0.0, 0.0, -sideLength)
transformLayer.addSublayer(yellowLayer)
let greenLayer = sideLayer(color: greenColor)
var greenTransform = CATransform3DMakeTranslation(
sideLength / -2.0,
0.0,
sideLength / -2.0)
greenTransform = CATransform3DRotate(
greenTransform,
degreesToRadians(90.0),
0.0,
1.0,
0.0)
greenLayer.transform = greenTransform
transformLayer.addSublayer(greenLayer)
let blueLayer = sideLayer(color: blueColor)
var blueTransform = CATransform3DMakeTranslation(
0.0,
sideLength / -2.0,
sideLength / -2.0)
blueTransform = CATransform3DRotate(
blueTransform,
degreesToRadians(90.0),
1.0,
0.0,
0.0)
blueLayer.transform = blueTransform
transformLayer.addSublayer(blueLayer)
let purpleLayer = sideLayer(color: purpleColor)
var purpleTransform = CATransform3DMakeTranslation(
0.0,
sideLength / 2.0,
sideLength / -2.0)
purpleTransform = CATransform3DRotate(
purpleTransform,
degreesToRadians(90.0),
1.0,
0.0,
0.0)
purpleLayer.transform = purpleTransform
transformLayer.addSublayer(purpleLayer)
transformLayer.anchorPointZ = sideLength / -2.0
viewForTransformLayer.layer.addSublayer(transformLayer)
}
func setUpSwipeMeTextLayer() {
swipeMeTextLayer.frame = CGRect(x: 0.0, y: sideLength / 4.0, width: sideLength, height: sideLength / 2.0)
swipeMeTextLayer.string = "Swipe Me"
swipeMeTextLayer.alignmentMode = CATextLayerAlignmentMode.center
swipeMeTextLayer.foregroundColor = UIColor.white.cgColor
let fontName = "Noteworthy-Light" as CFString
let fontRef = CTFontCreateWithName(fontName, 24.0, nil)
swipeMeTextLayer.font = fontRef
swipeMeTextLayer.contentsScale = UIScreen.main.scale
}
}
// MARK: - IBActions
extension CATransformLayerViewController {
@IBAction func colorAlphaSwitchChanged(_ sender: UISwitch) {
let alpha = sender.isOn ? reducedAlpha : 1.0
switch (colorAlphaSwitches as NSArray).index(of: sender) {
case Color.red.rawValue:
redColor = colorForColor(redColor, withAlpha: alpha)
case Color.orange.rawValue:
orangeColor = colorForColor(orangeColor, withAlpha: alpha)
case Color.yellow.rawValue:
yellowColor = colorForColor(yellowColor, withAlpha: alpha)
case Color.green.rawValue:
greenColor = colorForColor(greenColor, withAlpha: alpha)
case Color.blue.rawValue:
blueColor = colorForColor(blueColor, withAlpha: alpha)
case Color.purple.rawValue:
purpleColor = colorForColor(purpleColor, withAlpha: alpha)
default:
break
}
transformLayer.removeFromSuperlayer()
buildCube()
}
}
// MARK: - Touch Handling
extension CATransformLayerViewController {
override func touchesBegan(_ touches: Set, with event: UIEvent?) {
if let location = touches.first?.location(in: viewForTransformLayer) {
if trackBall != nil {
trackBall?.setStartPointFromLocation(location)
} else {
trackBall = TrackBall(location: location, inRect: viewForTransformLayer.bounds)
}
guard let layers = transformLayer.sublayers else {
return
}
for layer in layers {
if layer.hitTest(location) != nil {
showBoxTappedLabel()
break
}
}
}
}
override func touchesMoved(_ touches: Set, with event: UIEvent?) {
if let location = touches.first?.location(in: viewForTransformLayer) {
if let transform = trackBall?.rotationTransformForLocation(location) {
viewForTransformLayer.layer.sublayerTransform = transform
}
}
}
override func touchesEnded(_ touches: Set, with event: UIEvent?) {
if let location = touches.first?.location(in: viewForTransformLayer) {
trackBall?.finalizeTrackBallForLocation(location)
}
}
func showBoxTappedLabel() {
boxTappedLabel.alpha = 1.0
boxTappedLabel.isHidden = false
UIView.animate(
withDuration: 0.5,
animations: {
self.boxTappedLabel.alpha = 0.0
}, completion: { _ in
self.boxTappedLabel.isHidden = true
})
}
}
// MARK: - Helpers
extension CATransformLayerViewController {
func sideLayer(color: UIColor) -> CALayer {
let layer = CALayer()
layer.frame = CGRect(origin: .zero, size: CGSize(width: sideLength, height: sideLength))
layer.position = CGPoint(x: viewForTransformLayer.bounds.midX, y: viewForTransformLayer.bounds.midY)
layer.backgroundColor = color.cgColor
return layer
}
func colorForColor(_ color: UIColor, withAlpha newAlpha: CGFloat) -> UIColor {
var color = color
var red = CGFloat()
var green = red, blue = red, alpha = red
if color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) {
color = UIColor(red: red, green: green, blue: blue, alpha: newAlpha)
}
return color
}
func sortOutletCollections() {
colorAlphaSwitches.sortUIViewsInPlaceByTag()
}
}
15. CAEmitterLayerViewController.swift
import UIKit
class CAEmitterLayerViewController: UIViewController {
@IBOutlet weak var viewForEmitterLayer: UIView!
@objc var emitterLayer = CAEmitterLayer()
@objc var emitterCell = CAEmitterCell()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
setUpEmitterCell()
setUpEmitterLayer()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "DisplayEmitterControls" {
(segue.destination as? CAEmitterLayerControlsViewController)?.emitterLayerViewController = self
}
}
}
// MARK: - Layer setup
extension CAEmitterLayerViewController {
func setUpEmitterLayer() {
// 1
resetEmitterCells()
emitterLayer.frame = viewForEmitterLayer.bounds
viewForEmitterLayer.layer.addSublayer(emitterLayer)
// 2
emitterLayer.seed = UInt32(Date().timeIntervalSince1970)
// 3
emitterLayer.emitterPosition = CGPoint(x: viewForEmitterLayer.bounds.midX * 1.5, y: viewForEmitterLayer.bounds.midY)
// 4
emitterLayer.renderMode = .additive
}
func setUpEmitterCell() {
// 1
emitterCell.contents = UIImage(named: "smallStar")?.cgImage
// 2
emitterCell.velocity = 50.0
emitterCell.velocityRange = 500.0
// 3
emitterCell.color = UIColor.black.cgColor
// 4
emitterCell.redRange = 1.0
emitterCell.greenRange = 1.0
emitterCell.blueRange = 1.0
emitterCell.alphaRange = 0.0
emitterCell.redSpeed = 0.0
emitterCell.greenSpeed = 0.0
emitterCell.blueSpeed = 0.0
emitterCell.alphaSpeed = -0.5
emitterCell.scaleSpeed = 0.1
// 5
let zeroDegreesInRadians = degreesToRadians(0.0)
emitterCell.spin = degreesToRadians(130.0)
emitterCell.spinRange = zeroDegreesInRadians
emitterCell.emissionLatitude = zeroDegreesInRadians
emitterCell.emissionLongitude = zeroDegreesInRadians
emitterCell.emissionRange = degreesToRadians(360.0)
// 6
emitterCell.lifetime = 1.0
emitterCell.birthRate = 250.0
// 7
emitterCell.xAcceleration = -800
emitterCell.yAcceleration = 1000
}
func resetEmitterCells() {
emitterLayer.emitterCells = nil
emitterLayer.emitterCells = [emitterCell]
}
}
// MARK: - Triggered actions
extension CAEmitterLayerViewController {
override func touchesBegan(_ touches: Set, with event: UIEvent?) {
if let location = touches.first?.location(in: viewForEmitterLayer) {
emitterLayer.emitterPosition = location
}
}
override func touchesMoved(_ touches: Set, with event: UIEvent?) {
if let location = touches.first?.location(in: viewForEmitterLayer) {
emitterLayer.emitterPosition = location
}
}
}
16. CAEmitterLayerControlsViewController.swift
import UIKit
class CAEmitterLayerControlsViewController: UITableViewController {
@IBOutlet weak var renderModePickerValueLabel: UILabel!
@IBOutlet weak var renderModePicker: UIPickerView!
@IBOutlet var sliderValueLabels: [UILabel]!
@IBOutlet weak var enabledSwitch: UISwitch!
@IBOutlet var sliders: [UISlider]!
enum Section: Int {
case emitterLayer, emitterCell
}
enum Slider: Int {
case color,
redRange,
greenRange,
blueRange,
alphaRange,
redSpeed,
greenSpeed,
blueSpeed,
alphaSpeed,
scale,
scaleRange,
spin,
spinRange,
emissionLatitude,
emissionLongitude,
emissionRange,
lifetime,
lifetimeRange,
birthRate,
velocity,
velocityRange,
xAcceleration,
yAcceleration
}
// swiftlint:disable:next implicitly_unwrapped_optional
weak var emitterLayerViewController: CAEmitterLayerViewController!
var emitterLayer: CAEmitterLayer {
return emitterLayerViewController.emitterLayer
}
var emitterCell: CAEmitterCell {
return emitterLayerViewController.emitterCell
}
let emitterLayerRenderModes: [CAEmitterLayerRenderMode] = [
.unordered,
.oldestFirst,
.oldestLast,
.backToFront,
.additive
]
var renderModePickerVisible = false
override func viewDidLoad() {
super.viewDidLoad()
updateSliderValueLabels()
}
}
// MARK: - IBActions
extension CAEmitterLayerControlsViewController {
@IBAction func enabledSwitchChanged(_ sender: UISwitch) {
emitterLayerViewController.emitterCell.isEnabled = sender.isOn
emitterLayerViewController.resetEmitterCells()
}
// swiftlint:disable:next cyclomatic_complexity
@IBAction func sliderChanged(_ sender: UISlider) {
let slidersArray = sliders as NSArray
// swiftlint:disable:next force_unwrapping
let slider = Slider(rawValue: slidersArray.index(of: sender))!
var keyPath = "emitterCell."
switch slider {
case .color: keyPath += "color"
case .redRange: keyPath += "redRange"
case .greenRange: keyPath += "greenRange"
case .blueRange: keyPath += "blueRange"
case .alphaRange: keyPath += "alphaRange"
case .redSpeed: keyPath += "redSpeed"
case .greenSpeed: keyPath += "greenSpeed"
case .blueSpeed: keyPath += "blueSpeed"
case .alphaSpeed: keyPath += "alphaSpeed"
case .scale: keyPath += "scale"
case .scaleRange: keyPath += "scaleRange"
case .spin: keyPath += "spin"
case .spinRange: keyPath += "spinRange"
case .emissionLatitude: keyPath += "emissionLatitude"
case .emissionLongitude: keyPath += "emissionLongitude"
case .emissionRange: keyPath += "emissionRange"
case .lifetime: keyPath += "lifetime"
case .lifetimeRange: keyPath += "lifetimeRange"
case .birthRate: keyPath += "birthRate"
case .velocity: keyPath += "velocity"
case .velocityRange: keyPath += "velocityRange"
case .xAcceleration: keyPath += "xAcceleration"
case .yAcceleration: keyPath += "yAcceleration"
}
if keyPath == "emitterCell.color" {
let color = UIColor(hue: 0.0, saturation: 0.0, brightness: CGFloat(sender.value), alpha: 1.0)
emitterLayerViewController.emitterCell.color = color.cgColor
emitterLayerViewController.resetEmitterCells()
} else {
emitterLayerViewController.setValue(NSNumber(value: sender.value as Float), forKeyPath: keyPath)
emitterLayerViewController.resetEmitterCells()
}
updateSliderValueLabel(slider)
}
}
// MARK: - Triggered actions
extension CAEmitterLayerControlsViewController {
func showEmitterLayerRenderModePicker() {
renderModePickerVisible = true
relayoutTableViewCells()
let index = emitterLayerRenderModes.firstIndex(of: emitterLayer.renderMode) ?? 0
renderModePicker.selectRow(index, inComponent: 0, animated: false)
renderModePicker.isHidden = false
renderModePicker.alpha = 0.0
UIView.animate(withDuration: 0.25) {
self.renderModePicker.alpha = 1.0
}
}
func hideEmitterLayerRenderModePicker() {
if renderModePickerVisible {
view.isUserInteractionEnabled = false
renderModePickerVisible = false
relayoutTableViewCells()
UIView.animate(
withDuration: 0.25,
animations: {
self.renderModePicker.alpha = 0.0
},
completion: { _ in
self.renderModePicker.isHidden = true
self.view.isUserInteractionEnabled = true
})
}
}
}
// MARK: - Helpers
extension CAEmitterLayerControlsViewController {
func updateSliderValueLabels() {
for slider in Slider.color.rawValue...Slider.yAcceleration.rawValue {
// swiftlint:disable:next force_unwrapping
updateSliderValueLabel(Slider(rawValue: slider)!)
}
}
func updateSliderValueLabel(_ sliderEnum: Slider) {
let index = sliderEnum.rawValue
let label = sliderValueLabels[index]
let slider = sliders[index]
switch sliderEnum {
case
.redRange,
.greenRange,
.blueRange,
.alphaRange,
.redSpeed,
.greenSpeed,
.blueSpeed,
.alphaSpeed,
.scale,
.scaleRange,
.lifetime,
.lifetimeRange:
label.text = String(format: "%.1f", slider.value)
case .color:
label.text = String(format: "%.0f", slider.value * 100.0)
case .spin, .spinRange, .emissionLatitude, .emissionLongitude, .emissionRange:
let formatter = NumberFormatter()
formatter.minimumFractionDigits = 0
label.text = "\(Int(radiansToDegrees(Double(slider.value))))"
case .birthRate, .velocity, .velocityRange, .xAcceleration, .yAcceleration:
label.text = String(format: "%.0f", slider.value)
}
}
func relayoutTableViewCells() {
tableView.beginUpdates()
tableView.endUpdates()
}
}
// MARK: - UITableViewDelegate
extension CAEmitterLayerControlsViewController {
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// swiftlint:disable:next force_unwrapping
let section = Section(rawValue: indexPath.section)!
if section == .emitterLayer && indexPath.row == 1 {
return renderModePickerVisible ? 162.0 : 0.0
} else {
return 44.0
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// swiftlint:disable:next force_unwrapping
let section = Section(rawValue: indexPath.section)!
switch section {
case .emitterLayer where !renderModePickerVisible:
showEmitterLayerRenderModePicker()
default:
hideEmitterLayerRenderModePicker()
}
}
}
// MARK: - UIPickerViewDataSource
extension CAEmitterLayerControlsViewController: UIPickerViewDataSource {
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return emitterLayerRenderModes.count
}
}
// MARK: - UIPickerViewDelegate
extension CAEmitterLayerControlsViewController: UIPickerViewDelegate {
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
return emitterLayerRenderModes[row].rawValue
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
emitterLayerViewController.emitterLayer.renderMode = emitterLayerRenderModes[row]
renderModePickerValueLabel.text = emitterLayer.renderMode.rawValue
}
}
17. Array+SortUIViewsInPlaceByTag.swift
import UIKit
extension Array where Element: UIView {
/// Sorts an array of UIViews or subclasses by tag. For example, this is useful when working with `IBOutletCollection`s, whose order of elements can be changed when manipulating the view objects in Interface Builder. Just tag your views in Interface Builder and then call this method on your `IBOutletCollection`s in `viewDidLoad()`.
/// - author: Scott Gardner
/// - seealso:
/// * [Source on GitHub](http://bit.ly/SortUIViewsInPlaceByTag)
mutating func sortUIViewsInPlaceByTag() {
sort { (left: Element, right: Element) in
left.tag < right.tag
}
}
}
18. UITableViewCell+ZeroLayoutMargins.swift
import UIKit
extension UITableViewCell {
override open var layoutMargins: UIEdgeInsets {
get { return UIEdgeInsets.zero }
// swiftlint:disable:next unused_setter_value
set { }
}
}
后记
本篇主要讲述了
CALayer
的简单实用示例,感兴趣的给个赞或者关注~~~