版本记录
| 版本号 | 时间 |
|---|---|
| V1.0 | 2019.05.16 星期四 |
前言
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边栏动画的实现(一)
源码
1. Swift
首先看下代码组织结构

接着看下sb中的内容

下面就是源码了
1. UIColor+ArrayInstantiation.swift
import UIKit
extension UIColor {
convenience init(colorArray array: [CGFloat]) {
var source = array
if array.count != 3 {
source = [255, 255, 255]
}
let red = source[0]/255
let green = source[1]/255
let blue = source[2]/255
self.init(red: red, green: green, blue: blue, alpha: 1.0)
}
}
2. CGFloat+Clamp.swift
import UIKit
extension CGFloat {
func unitClamp() -> CGFloat {
return Swift.min(Swift.max(0, self), 1.0)
}
}
3. UINavigationBar+StatusBar.swift
import UIKit
extension UINavigationController {
open override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
}
4. RootViewController.swift
import UIKit
extension UIView {
func embedInsideSafeArea(_ subview: UIView) {
addSubview(subview)
subview.translatesAutoresizingMaskIntoConstraints = false
subview.leadingAnchor.constraint(equalTo: safeAreaLayoutGuide.leadingAnchor)
.isActive = true
subview.trailingAnchor.constraint(equalTo: safeAreaLayoutGuide.trailingAnchor)
.isActive = true
subview.topAnchor.constraint(equalTo: safeAreaLayoutGuide.topAnchor)
.isActive = true
subview.bottomAnchor.constraint(equalTo: safeAreaLayoutGuide.bottomAnchor)
.isActive = true
}
}
class RootViewController: UIViewController {
let menuWidth: CGFloat = 80.0
lazy var threshold = menuWidth/2.0
var menuContainer = UIView(frame: .zero)
var detailContainer = UIView(frame: .zero)
var menuViewController: MenuViewController?
var detailViewController: DetailViewController?
var hamburgerView: HamburgerView?
// 1
lazy var scroller: UIScrollView = {
let scroller = UIScrollView(frame: .zero)
scroller.isPagingEnabled = true
scroller.delaysContentTouches = false
scroller.bounces = false
scroller.showsHorizontalScrollIndicator = false
scroller.delegate = self
return scroller
}()
// 2
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(named: "rw-dark")
view.embedInsideSafeArea(scroller)
installMenuContainer()
installDetailContainer()
menuViewController
= installFromStoryboard("MenuViewController",
into: menuContainer) as? MenuViewController
detailViewController
= installFromStoryboard("DetailViewController",
into: detailContainer) as? DetailViewController
menuViewController?.delegate = self
if let detailViewController = detailViewController {
installBurger(in: detailViewController)
}
hamburgerView?.setFractionOpen(1.0)
}
// 3
override var preferredStatusBarStyle: UIStatusBarStyle {
return .lightContent
}
func installMenuContainer() {
// 1
scroller.addSubview(menuContainer)
menuContainer.translatesAutoresizingMaskIntoConstraints = false
menuContainer.backgroundColor = .orange
// 2
menuContainer.leadingAnchor.constraint(equalTo: scroller.leadingAnchor)
.isActive = true
menuContainer.topAnchor.constraint(equalTo: scroller.topAnchor)
.isActive = true
menuContainer.bottomAnchor.constraint(equalTo: scroller.bottomAnchor)
.isActive = true
// 3
menuContainer.widthAnchor.constraint(equalToConstant: menuWidth)
.isActive = true
menuContainer.heightAnchor.constraint(equalTo: scroller.heightAnchor)
.isActive = true
}
func installDetailContainer() {
//1
scroller.addSubview(detailContainer)
detailContainer.translatesAutoresizingMaskIntoConstraints = false
detailContainer.backgroundColor = .red
//2
detailContainer.trailingAnchor.constraint(equalTo: scroller.trailingAnchor)
.isActive = true
detailContainer.topAnchor.constraint(equalTo: scroller.topAnchor)
.isActive = true
detailContainer.bottomAnchor.constraint(equalTo: scroller.bottomAnchor)
.isActive = true
//3
detailContainer.leadingAnchor
.constraint(equalTo: menuContainer.trailingAnchor)
.isActive = true
detailContainer.widthAnchor.constraint(equalTo: scroller.widthAnchor)
.isActive = true
}
}
extension RootViewController: UIScrollViewDelegate {
//1
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let offset = scrollView.contentOffset
scrollView.isPagingEnabled = offset.x < threshold
let fraction = calculateMenuDisplayFraction(scrollView)
updateViewVisibility(menuContainer, fraction: fraction)
hamburgerView?.setFractionOpen(1.0 - fraction)
}
//2
func scrollViewDidEndDragging(_ scrollView: UIScrollView,
willDecelerate decelerate: Bool) {
let offset = scrollView.contentOffset
if offset.x > threshold {
hideMenu()
}
}
//3
func moveMenu(nextPosition: CGFloat) {
let nextOffset = CGPoint(x: nextPosition, y: 0)
scroller.setContentOffset(nextOffset, animated: true)
}
//4
func hideMenu() {
moveMenu(nextPosition: menuWidth)
}
func showMenu() {
moveMenu(nextPosition: 0)
}
func toggleMenu() {
let menuIsHidden = scroller.contentOffset.x > threshold
if menuIsHidden {
showMenu()
} else {
hideMenu()
}
}
}
extension RootViewController {
func installInNavigationController(_ rootController: UIViewController)
-> UINavigationController {
let nav = UINavigationController(rootViewController: rootController)
//1
nav.navigationBar.barTintColor = UIColor(named: "rw-dark")
nav.navigationBar.tintColor = UIColor(named: "rw-light")
nav.navigationBar.isTranslucent = false
nav.navigationBar.clipsToBounds = true
//2
addChild(nav)
return nav
}
func installFromStoryboard(_ identifier: String,
into container: UIView)
-> UIViewController {
guard let viewController = storyboard?
.instantiateViewController(withIdentifier: identifier) else {
fatalError(" broken storyboard expected \(identifier) to be available")
}
let nav = installInNavigationController(viewController)
container
.embedInsideSafeArea(nav.view)
return viewController
}
}
extension RootViewController: MenuDelegate {
func didSelectMenuItem(_ item: MenuItem) {
detailViewController?.menuItem = item
}
}
extension RootViewController {
func installBurger(in viewController: UIViewController) {
let action = #selector(burgerTapped(_:))
let tapGestureRecognizer = UITapGestureRecognizer(target: self,
action: action)
let burger = HamburgerView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
burger.addGestureRecognizer(tapGestureRecognizer)
viewController.navigationItem.leftBarButtonItem
= UIBarButtonItem(customView: burger)
hamburgerView = burger
}
@objc func burgerTapped(_ sender: Any) {
toggleMenu()
}
}
extension RootViewController {
func transformForFraction(_ fraction: CGFloat, ofWidth width: CGFloat)
-> CATransform3D {
//1
var identity = CATransform3DIdentity
identity.m34 = -1.0 / 1000.0
//2
let angle = -fraction * .pi/2.0
let xOffset = width/2.0 + width * fraction/4.0
//3
let rotateTransform = CATransform3DRotate(identity, angle, 0.0, 1.0, 0.0)
let translateTransform = CATransform3DMakeTranslation(xOffset, 0.0, 0.0)
return CATransform3DConcat(rotateTransform, translateTransform)
}
}
extension RootViewController {
//1
func calculateMenuDisplayFraction(_ scrollview: UIScrollView) -> CGFloat {
let fraction = scrollview.contentOffset.x/menuWidth
let clamped = Swift.min(Swift.max(0, fraction), 1.0)
return clamped
}
//2
func updateViewVisibility(_ container: UIView, fraction: CGFloat) {
container.layer.anchorPoint = CGPoint(x: 1.0, y: 0.5)
container.layer.transform = transformForFraction(fraction,
ofWidth: menuWidth)
container.alpha = 1.0 - fraction
}
}
5. HamburgerView.swift
import UIKit
class HamburgerView: UIView {
//1
let imageView: UIImageView = {
let view = UIImageView(image: UIImage(imageLiteralResourceName: "Hamburger"))
view.contentMode = .center
return view
}()
//2
required override init(frame: CGRect) {
super.init(frame: frame)
configure()
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
configure()
}
private func configure() {
addSubview(imageView)
}
func setFractionOpen(_ fraction: CGFloat) {
let angle = fraction * .pi/2.0
imageView.transform = CGAffineTransform(rotationAngle: angle)
}
}
6. DetailViewController.swift
import UIKit
class DetailViewController: UIViewController {
@IBOutlet weak var backgroundImageView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor(named: "rw-dark")
}
var menuItem: MenuItem? {
didSet {
prepare(menuItem)
}
}
func prepare(_ menuItem: MenuItem?) {
if let newMenuItem = menuItem {
view.backgroundColor = newMenuItem.color
backgroundImageView?.image = newMenuItem.bigImage
}
}
}
7. MenuViewController.swift
import UIKit
protocol MenuDelegate: class {
func didSelectMenuItem(_ item: MenuItem)
}
class MenuViewController: UITableViewController {
let maxCellHeight: CGFloat = 100
private var datasource: MenuDataSource = MenuDataSource()
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = datasource
}
override func tableView(_ tableView: UITableView,
heightForRowAt indexPath: IndexPath) -> CGFloat {
let proposedHeight = tableView.safeAreaLayoutGuide.layoutFrame.height/CGFloat(datasource.menuItems.count)
return min(maxCellHeight, proposedHeight)
}
//1
weak var delegate: MenuDelegate?
override func tableView(_ tableView: UITableView,
didSelectRowAt indexPath: IndexPath) {
//2
let item = datasource.menuItems[indexPath.row]
delegate?.didSelectMenuItem(item)
//3
DispatchQueue.main.async {
tableView.deselectRow(at: indexPath, animated: true)
}
}
}
8. MenuItemCell.swift
import UIKit
class MenuItemCell: UITableViewCell {
@IBOutlet weak var menuItemImageView: UIImageView!
func configureForMenuItem(_ menuItem: MenuItem) {
menuItemImageView.image = menuItem.image
backgroundColor = menuItem.color
}
}
9. MenuDataSource.swift
import UIKit
class MenuDataSource: NSObject, UITableViewDataSource {
private(set) var menuItems: [MenuItem] = []
override init() {
super.init()
prepare()
}
func prepare() {
guard let url = Bundle.main.url(forResource: "MenuItems", withExtension: "json") else {
assertionFailure("project config error - unable to find MenuItems.json in bundle")
return
}
do {
let data = try Data(contentsOf: url)
menuItems = try JSONDecoder().decode([MenuItem].self, from: data)
} catch {
assertionFailure("config error - unable to decode json file - \(error)")
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int)
-> Int {
return menuItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
-> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "MenuItemCell",
for: indexPath)
as? MenuItemCell else {
fatalError("expected to dequeue MenuItemCell - check storyboard")
}
let menuItem = menuItems[indexPath.row]
cell.configureForMenuItem(menuItem)
return cell
}
}
10. MenuItem.swift
import UIKit
struct MenuItem: Decodable {
var colorArray: [CGFloat]
var bigImageName: String
var imageName: String
}
extension MenuItem {
var image: UIImage {
return UIImage(imageLiteralResourceName: imageName)
}
var bigImage: UIImage {
return UIImage(imageLiteralResourceName: bigImageName)
}
var color: UIColor {
return UIColor(colorArray: colorArray)
}
}
后记
本篇主要讲述了基于CALayer属性的一种3D边栏动画的实现,感兴趣的给个赞或者关注~~~
